456 pág.


DisciplinaLinux716 materiais1.856 seguidores
Pré-visualização50 páginas
return_value stack variable.
So now at this point, the %eax register contains the return value just
before the function returns to its caller, so let\u2019s now look at what happens back
in the calling function. In our example, that function is make_dinner:
call make_pizza
addl $16, %esp
movl %eax, -4(%ebp)
Immediately after the call to make_pizza we can see that the stack is shrunk by
16 bytes by adding 16 to the %esp register. We then see that the value from the
%eax register is moved to a stack variable specified by -4 (%ebp), which turns
out to be the return_value variable. x86-64 Architecture The calling conventions for x86-64 are a bit
more complex than for x86. The primary difference is that rather than all the
functions\u2019 arguments being pushed on the stack before a function call as is
done on x86, x86-64 makes use of some of the general purpose registers first.
The reason for this is that the x86-64 architecture provides a few more general
purpose registers than x86, and using them rather than pushing the arguments
onto the stack that resides on much slower RAM is a very large performance
Function parameters are also handled differently depending on their data
type classification. The main classification, referred to as INTEGER, is any
integral data type that can fit into one of the general purpose registers (GPR).
Because the GPRs on x86-64 are all 64-bit, this covers the majority of data
types passed as function arguments. The calling convention that is used for
this data classification is (arguments\u2014from left to right\u2014are assigned to the
following GPRs)
Remaining arguments are pushed onto the stack as on x86.
To illustrate this, consider a modified pizza.c program:
#define pizza 50
#define large 51
#define thin_crust 52
#define cheese 1
#define pepperoni 2
#define onions 3
#define peppers 4
#define mushrooms 5
#define sausage 6
#define pineapple 7
5.5 How Does the Stack Work?
168 The Stack Chap. 5
#define bacon 8
#define ham 9
int make_pizza( int size, int crust_type, int topping1, int
\u27a5topping2,int topping3, int topping4, int topping5,int topping6,
\u27a5int topping7, int topping8,int topping9 )
 int return_value = 0;
 /* Do stuff */
 return return_value;
int make_dinner( int meal_type )
 int return_value = 0;
 return_value = make_pizza( large, thin_crust, cheese,
\u27a5pepperoni,onions, peppers, mushrooms, sausage,pineapple, bacon,
\u27a5ham );
 return return_value;
int main( void )
 int return_value = 0;
 return_value = make_dinner( pizza );
 return return_value;
Again, we produce the assembly listing for this program with the command:
gcc -S pizza.c
The assembly listing produced is:
 .file \u201cpizza.c\u201d
.globl make_pizza
 .type make_pizza,@function
 pushq %rbp
 movq %rsp, %rbp
 movl %edi, -4(%rbp)
 movl %esi, -8(%rbp)
 movl %edx, -12(%rbp)
 movl %ecx, -16(%rbp)
 movl %r8d, -20(%rbp)
 movl %r9d, -24(%rbp)
 movl $0, -28(%rbp)
 movl -28(%rbp), %eax
 .size make_pizza,.Lfe1-make_pizza
.globl make_dinner
 .type make_dinner,@function
 pushq %rbp
 movq %rsp, %rbp
 subq $48, %rsp
 movl %edi, -4(%rbp)
 movl $0, -8(%rbp)
 movl $9, 32(%rsp)
 movl $8, 24(%rsp)
 movl $7, 16(%rsp)
 movl $6, 8(%rsp)
 movl $5, (%rsp)
 movl $4, %r9d
 movl $3, %r8d
 movl $2, %ecx
 movl $1, %edx
 movl $52, %esi
 movl $51, %edi
 call make_pizza
 movl %eax, -8(%rbp)
 movl -8(%rbp), %eax
 .size make_dinner,.Lfe2-make_dinner
.globl main
 .type main,@function
 pushq %rbp
 movq %rsp, %rbp
 subq $16, %rsp
5.5 How Does the Stack Work?
170 The Stack Chap. 5
 movl $0, -4(%rbp)
 movl $50, %edi
 call make_dinner
 movl %eax, -4(%rbp)
 movl -4(%rbp), %eax
 .size main,.Lfe3-main
 .section .eh_frame,\u201daw\u201d,@progbits
\u20148<\u2014 SNIPPED UNIMPORTANT INFO \u20148<\u2014
The instructions we\u2019re most interested in are the ones that come before the
call to make_pizza in the make_dinner function. Specifically, they are
movl $9, 32(%rsp)
movl $8, 24(%rsp)
movl $7, 16(%rsp)
movl $6, 8(%rsp)
movl $5, (%rsp)
movl $4, %r9d
movl $3, %r8d
movl $2, %ecx
movl $1, %edx
movl $52, %esi
movl $51, %edi
call make_pizza
We can look at this graphically in Figure 5.5.
As you can see, the six general purpose registers are used up with six left-
most function arguments. The remaining five function arguments are pushed
onto the stack. Note, however, that the last five arguments are not pushed onto
the stack as they are on x86; rather they are moved directly to the addresses in
memory referenced by %rsp. Return Value The convention used to handle the function return
value is very similar to x86. The data is first classified to determine the method
used to handle the return. For the INTEGER data classification, the %rax
register is first used. If it is unavailable at the time of return, the %rdx register
can be used instead. There are other possibilities for different return scenarios,
but the general idea remains the same. For all the details, it is recommended
to refer to the x86-64 ABI.
Fig. 5.5 Illustration of calling conventions on x86-64.
We\u2019ve seen by now that the stack is crucial for proper and flexible program
execution. We\u2019ve also seen that the stack really isn\u2019t as complex as it first may
seem to be. This section will explain how data is stored on the stack and how it
is manipulated.
Recall our simple C program from the earlier section, \u201cThe BP and SP
Registers,\u201d where we declare a simple stack variable like this:
int localVar = 99;
Recall further that the assembly produced for this area of the program consisted
of these three instructions, which make up the function prolog:
5.6 Referencing and Modifying Data on the Stack
172 The Stack Chap. 5
pushl %ebp
movl %esp, %ebp
subl $4, %esp
The subl instruction effectively increases the size of the stack by 4 bytes\u2013keep
in mind that the stack grows down toward lower addresses on x86. Because we
know that the function in question only declares one local variable, int localVar,
we know that this space is created for it. Therefore, at this point we could
define the memory location holding localVar\u2019s value as whatever the register
esp holds. This method does not work very well, however, because the value of
esp will change as more local variables are declared. The correct method is to
reference ebp (the base or frame pointer) instead. We can see that this is done,
in fact, by looking at the next instruction in the assembly listing from our
small program:
movl $99, -4(%ebp)
This instruction is taking care of assigning the value 99 to localVar, or as it\u2019s
referred to in assembly, -4 (%ebp) which essentially means \u201cthe value stored in
ebp offset by -4 bytes.\u201d Note that some assembly outputs, for example objdump
-d <object>, might show the values as hex instead, which would look like:
movl $0x63,0xfffffffc(%ebp)
When it is known in which offset relative to the frame pointer a particular
variable is stored,