Microsoft PowerPoint – 23_X86_Assembly_Language_Part4
O
SU
C
SE
2
42
1
J.E.Jones
calling functions, caller/callee saved registers
O
SU
C
SE
2
42
1
J. E. Jones
stdlinux is running hundreds of processes for hundreds
of different people
Everyone doesn’t get a private CPU, so how does this
work?
◦ When considering multiple processes (executables), the
operating system takes care of this
◦ Uses a construct called a Process Control Block (PCB) and a
boatload of code
◦ This is a topic that CSE 2431 (Systems 2) addresses
◦ For now, you can consider this part of the puzzle to be magic
(next semester, not so much)
O
SU
C
SE
2
42
1
J. E. Jones
%rsp
◦ Can reference low-order 4 bytes (also low-order 1 & 2 bytes)
• *See Figure 3.2, page 180 of Bryant/O’Halloran for 1-byte register names
%eax
%ebx
%ecx
%edx
%esi
%edi
%esp
%r8d
%r9d
%r10d
%r11d
%r12d
%r13d
%r14d
%r15d
%r8
%r9
%r10
%r11
%r12
%r13
%r14
%r15
%rax
%rbx
%rcx
%rdx
%rsi
%rdi
%rbp
%ax
%bx
%cx
%dx
%si
%di
%sp
%r8w
%r9w
%r10w
%r11w
%r12w
%r13w
%r14w
%r15w%ebp %bp
O
SU
C
SE
2
42
1
J. E. Jones
We’ve discussed how to separate space on the stack for
each function (within a specific process) using stack
frames
If main(), or some other function, fills many (all?) of the
14* integer registers with valid data, then calls another
function, what happens to that data?
Coordinating/Organizing this part of the problem is our
responsibility rather than the OS’s.
What registers can the called function use to perform its
work?
What to do? What to do?
*%rbp and %rsp are always used for the stack
O
SU
C
SE
2
42
1
J. E. Jones
1. The function that is performing the call has to save every, single register
it’s using to the stack prior to making the call, then pop them back into the
appropriate registers upon return.
2. The function that is called has to save every, single register it plans to use
to the stack prior to doing any “real” work, then pop the values back into
the correct registers before returning to the calling function.
Both seem a little harsh! Can’t we both just get along???
How about a little cooperation?
O
SU
C
SE
2
42
1
J. E. Jones
Although only one procedure can be active at a given time, the 16 registers
are “shared” by all functions.
Therefore, we need a way to ensure that when one function (the caller)
calls another (the callee), values that the caller needs after return will not
be overwritten.
To ensure this, conventions have been adopted as to which function, the
caller or callee, is responsible for preserving a given register (other than
%rsp).
We must use the register save conventions used by X86-64 in a C
programming environment.
O
SU
C
SE
2
42
1
J. E. Jones
Register Allocation
What can be used?
When do you save?
Linux uses what is call System V ABI to define this, in
addition to many other things…most of which we
won’t need to address this semester.
O
SU
C
SE
2
42
1
J. E. Jones
What is System V ABI? – a “bible” of sorts with respect to
how to interface to C standard libraries when not using C
code….X86-64, for example.
Here is the link to a reasonable “draft” copy from 2013:
https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
A bug occurs, sometimes, if you try to click on this link, it
doesn’t work. If you copy/paste the link in a browser window it
consistently comes up just fine.
There are many interface standards within the ABI, register usage
and caller/callee parameters are just a couple…
O
SU
C
SE
2
42
1
J. E. Jones
The first six integer or pointer arguments are passed in
registers %rdi, %rsi, %rdx, %rcx, %r8, %r9 – in this
order.
%rax is used for return values
%rsp must be restored when control is returned to the caller
function
If the callee wishes to use registers %rbx, %rbp or %r12-
%r15, the callee must save/then restore their original values
before returning control to the caller.
◦ %rbx, %rbp, and %r12 through %r15 are callee saved
registers
All other registers must be saved by the caller if the caller
wishes to preserve their values
◦ All other registers are caller saved registers
O
SU
C
SE
2
42
1
J. E. Jones
Caller Saved Registers: Registers for which the Caller function is responsible.
◦ IF the register contains data needed by the Caller after the Callee function returns, the Caller
function must preserve them by pushing them to the stack prior to calling the Callee function,.
Caller then pops them from the stack after Callee returns.
Callee Saved Registers: Registers for which the Callee function is responsible.
◦ IF the Callee function wishes to use these registers , the Callee function must push them to the
stack prior to using them. Callee must assume there is data in each of these registers that is
important to the Caller function. Callee function must pop these registers back prior to returning
to the Caller.
Passing Parameters: The first 6 parameters are passed from the Caller to the Callee in
registers %rdi, %rsi, %rdx, %rcx, %r8 and %r9, respectively. (We won’t address
passing more than 6 parameters in this class.)
◦ Caller must assume Callee will trash all values in these registers prior to return.
◦ So, if Caller needs any of the parameters after the call, must push registers to stack before call
and pop back after call
Return Value: If the Callee function returns a value to the Caller, it is returned in
register %rax.
Confused? Check out Figure 3.2, p 180 of Bryant/O’Halloran
O
SU
C
SE
2
42
1
J. E. Jones
%rsp
%r8
%r9
%r10
%r11
%r12
%r13
%r14
%rax
%rbx
%rcx
%rdx
%rsi
%rdi
%rbp %r15
Stack Pointer
Return value
Caller Saved
Callee Saved
4th parameter
Caller Saved
3rd parameter
Caller Saved
2nd parameter
Caller Saved
1st parameter
Caller Saved
Callee Saved
5th parameter
Caller Saved
6th parameter
Caller Saved
Caller Saved
Caller Saved
Callee Saved
Callee Saved
Callee Saved
Callee Saved
O
SU
C
SE
2
42
1
J. E. Jones
It depends!
◦ On what parameters our function is passed
◦ On what registers our function wants to use
◦ On what other functions our function might call
◦ How we can minimize save/restore activity
Efficiency is why we got here in the first place, remember?
O
SU
C
SE
2
42
1
J. E. Jones
Must save and restore any of these registers a function
plans to use:
◦ %rbp: used as part of the stack frame and planned to restore it
anyway
◦ %rsp: if we don’t restore it, the program will probably crash.
Assume all functions deal with %rsp correctly.
◦ %rbx and %r12-%r15: Must save these before we use them
O
SU
C
SE
2
42
1
J. E. Jones
Leaf functions make no calls to any other function
Can freely use %rdi, %rsi, %rdx, %rcx, %r8 and
%r9 even when passed fewer than 6 parameters.
Can freely use %r10 and %r11 since these are caller
saved registers
Can freely use %rax as long as function fills it with the
return value prior to returning to the caller.
O
SU
C
SE
2
42
1
J. E. Jones
Trade-offs to be made:
◦ If we make many calls to other functions, we must save and
restore any of the parameter registers as well as %r10/%r11
before and after each call if we still want to use the values
they contained prior to the call.
◦ If we use %rbx, %rbp, %r12-%r15 for our function’s work,
we only save them one time (at the beginning) and restore
them one time (at the end) of our function.
O
SU
C
SE
2
42
1
J. E. Jones
Because the stack frame for the procedure is set up at the beginning of its
code, a procedure which calls itself recursively will get a new stack frame
each time it is called.
The stack frame for the second call of the procedure will be above the stack
frame for the first (i.e., higher in the stack, but at a lower-numbered
address), and so on.
When each call returns, the frame pointer of the previous call will be
restored, and at that point, what is at the top of the stack will be the return
address from the previous call.
Therefore, when the ret instruction is executed at the end of the recursive
function’s assembly code, execution will return to the point in the code of
the function from which the call was made.
So, just how deep of a recursive procedure do you want to have in your
code given all the resources each call is going to use? Hmmm?
O
SU
C
SE
2
42
1
J. E. Jones
We can call any C-Library function that we used in our
C-language programs from any x86-64 program that we
write
◦ Seems kinda odd to call a C function from an assembler one
doesn’t it?
Parameters must be passed using the caller/callee/return
value paradigm described previously
Assume that all caller saved registers will be trashed
after return and plan accordingly
◦ Push values you want to keep to the stack
O
SU
C
SE
2
42
1
J. E. Jones
From section 3.5.7 Variable Argument Lists:
Some otherwise portable C programs depend on the argument passing scheme, implicitly
assuming that all arguments are passed on the stack, and arguments appear in increasing order
on the stack. Programs that make these assumptions never have been portable, but they have
worked on many implementations. However, they do not work on the AMD64 architecture
because some arguments are passed in registers. Portable C programs must use the header file
in order to handle variable argument lists. When a function taking variable-arguments is
called, %rax must be set to the total number of floating-point parameters passed to the
function in vector registers.
Since we won’t be passing any floating-point parameters
(we’re only using integers in this class), we will always have
to set %rax to zero before calling a function that allows a
variable argument list.
*the section above references AMD64 architecture, but x86-64 is equivalent
O
SU
C
SE
2
42
1
J. E. Jones
What functions did we use in C that had variable
argument lists?
◦ printf() family
◦ scanf() family
You have to make a point to set %rax to zero prior to
calling printf() or scanf(), because if you do not, expect
your program to seg fault.
No only that, but fully expect the information in all
“caller saved registers” to be totally trashed upon
return
O
SU
C
SE
2
42
1
J. E. Jones
Consider the following simple C program, with two functions. It illustrates
the X86 conventions for parameter passing, return value, and use of caller
and callee save registers.
First, main:
long sum(long count, long *array);
int main() {
static long array[4] = {10, 12, 15, 19};
long count= 4; /* number of array elements */
long result;
result = sum(count, array);
printf(“The sum of the array is %i\n”, result);
}
O
SU
C
SE
2
42
1
J. E. Jones
Now, sum():
long sum(long count, long *array) {
long result = 0;
long i;
for (i = 0; i < count; i++) {
result = result + array[i];
}
return(result);
}
O
SU
C
SE
2
42
1
J. E. Jones
The next slide shows X86 assembler directives to set
up space in memory for:
1. the static array,
2. output, and
3. defining main() as a function
All of the following code is in a single file called
sumprog.s.
O
SU
C
SE
2
42
1
J. E. Jones
.file “sumprog.s”
# Assembler directives to allocate storage for static array
.section
.rodata
printf_line:
.string “The sum of the array is %i\n”
.data
.align 8 # guarantee that we are starting on an 8-byte boundary
array: # this is a LABEL
.quad 10
.quad 12
.quad 15
.quad 19
.globl main
.type main, @function
O
SU
C
SE
2
42
1
J. E. Jones
.text
main:
pushq %rbp # save caller’s %rbp
movq %rsp, %rbp # copy %rsp to %rbp so our stack frame is ready to use
movq $array, %rsi # set %rsi (2nd parameter) to point to start of array
movq $4, %rdi # set %rdi (1st parameter) to count = 4
# (i.e. caller saved registers)
# since we aren’t using %rsi or %rdi values or the
# value in any other caller saved registers,
# we don’t have to push them
call sum
movq %rax, %rsi # Write return value to 2nd parameter
movq $printf_line, %rdi # Write string literal to 1st parameter
movq $0, %rax # Need 0 in %rax for System V ABI requirement
call printf
leave
ret
.size main, .-main
O
SU
C
SE
2
42
1
J. E. Jones
.globl sum
.type sum, @function
sum:
pushq %rbp #save caller’s rbp
movq %rsp, %rbp #set function’s frame pointer
# register %rdi contains count (1st parameter)
# register %rsi contains address to array (2nd parameter)
movq $0, %rax # initialize sum to 0, by putting 0 in %rax,
# it’s where return value
# needs to be when we return
loop: # loop to sum values in array
decq %rdi # decrement number of remaining elements by 1
jl exit # jump out of loop if no elements remaining
addq (%rsi,%rdi,8), %rax # add element to sum
jmp loop # jump to top of loop
exit: # sum already in register %rax so ready to return
leave
ret #return to caller’s code at return address
.size sum, .-sum
O
SU
C
SE
2
42
1
J. E. Jones
The following slides show what would change if sum()
wasn’t a “leaf function”.
Let’s see how the code would change if we called
printf()…
O
SU
C
SE
2
42
1
J. E. Jones
.file “sumprog.s”
# Assembler directives to allocate storage for static array
.section
.rodata
printf_line:
.string “The sum of the array is %i\n”
printf_literal1:
.string “We are in the sum() function.\n”
.data
.align 8 # guarantee that we are starting on an 8-byte boundary
array: # this is a LABEL
.quad 10
.quad 12
.quad 15
.quad 19
.globl main
.type main, @function
O
SU
C
SE
2
42
1
J. E. Jones
. sum:
pushq %rbp #save caller’s rbp
movq %rsp, %rbp #set function’s frame pointer
# register %rdi contains count (1st parameter)
# register %rsi contains address to array (2nd parameter)
movq $0, %rax # initialize sum to 0, by putting 0 in %rax,
# it’s where return value
# needs to be when we return
pushq %rax # %rax, %rdi, %rsi have values in them that our program needs
pushq %rdi
pushq %rsi
movq $printf_literal1, %rdi # some literal string with no % entries, so no other parameters
movq $0, %rax # no Mr. Resetti today!
call printf
popq %rsi # get values back so my loop still works
popq %rdi # Note that the pop instructions are in the opposite order of the pushes
popq %rax
loop: # loop to sum values in array
decq %rdi # decrement number of remaining elements by 1
jl exit # jump out of loop if no elements remaining
addq (%rsi,%rdi,8), %rax # add element to sum
jmp loop # jump to top of loop
exit: # sum already in register %rax so ready to return
leave
ret #return to caller’s code at return address
.size sum, .-sum