LINUX_book
456 pág.

LINUX_book


DisciplinaLinux716 materiais1.848 seguidores
Pré-visualização50 páginas
);
}
5.4 What Is a Stack Frame?
158 The Stack Chap. 5
int main( void )
{
 int stackVar = 3;
 function1( stackVar );
 return 0;
}
There\u2019s a lot going on in the example, but for now we\u2019re most interested in the
fact that running this program will cause main() to call function1(), which
calls function2(), which then calls function3(). function3() then displays its
PID and waits for the user to hit ENTER to continue. Also pay attention to the
local variables that are declared in each function. When we run this program
and let it pause in function3(), we can visualize the stack frames by what is
shown in Figure 5.3:
Fig. 5.3 Functions and stack frames.
This conceptual view can be viewed practically in gdb by compiling and
running stack.c and then running the stack program under gdb with a
breakpoint set in function3(). Once the breakpoint is hit, enter the command
backtrace (synonymous with bt and where) to display the stack frames. The
output will look like the following:
penguin> gdb stack3
GNU gdb 5.3.92
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and
you are welcome to change it and/or distribute copies of it under
certain conditions.
Type \u201cshow copying\u201d to see the conditions.
There is absolutely no warranty for GDB. Type \u201cshow warranty\u201d for
159
details.
This GDB was configured as \u201ci586-suse-linux\u201d...
(gdb) break function3
Breakpoint 1 at 0x80483d2: file stack3.c, line 5.
(gdb) run
Starting program: /home/dbehman/book/code/stack3
Breakpoint 1, function3 (passedByReference=0xbffff284) at stack3.c:5
5 int dummy = \u2018\0\u2019;
(gdb) backtrace
#0 function3 (passedByReference=0xbffff284) at stack3.c:5
#1 0x0804842d in function2 (paramString=0xbffff2a0 \u201cThis is a
string.\u201d)
 at stack3.c:16
#2 0x08048481 in function1 (paramInt=3) at stack3.c:24
#3 0x080484a8 in main () at stack3.c:31
(gdb)
For more information on the various GDB commands used to view and
manipulate stacks, see Chapter 6, \u201cThe GNU Debugger (GDB).\u201d
5.5 HOW DOES THE STACK WORK?
The stack\u2019s functionality is implemented at many different levels of a computer
including low-level processor instructions. On x86 for example, the pop and
push instructions are specifically for placing data on and removing data from
the stack respectively. Most architectures also supply dedicated registers to
use for manipulating and managing the stack. On x86 and x86-64, the bp and
sp registers (for \u201cbase pointer\u201d and \u201cstack pointer\u201d\u2014see following sections) are
used. They are named slightly differently for each architecture in that a prefix
is used to indicate the size of the register. For x86, the prefix letter \u201ce\u201d is used
to indicate a size of 32-bit, and for x86-64 the prefix letter of \u201cr\u201d is used to
indicate a size of 64-bit.
5.5.1 The BP and SP Registers
The bp, or base pointer (also referred to as frame pointer) register is used to
hold the address of the beginning or base of the current frame. The purpose of
this is so that a common reference point for all local stack variables can be
used. In other words, stack variables are referenced by the bp register plus an
offset. When working in a particular stack frame, the value of this register will
never change. Each stack frame has its own unique bp value.
The sp, or stack pointer register is used to hold the address of the end of
the stack. A program\u2019s assembly instructions will modify its value when new
space is needed in the current stack frame for local variables. Because the sp is
5.5 How Does the Stack Work?
160 The Stack Chap. 5
always the end of the stack, when a new frame is created, its value is used to
set the new frame\u2019s bp value. The best way to understand exactly how these
two registers work is to examine the assembly instructions involved in starting
a new function and allocating stack variables within it. Consider the following
source code:
#include <stdio.h>
void function1( int param )
{
 int localVar = 99;
}
int main( void )
{
 int stackVar = 3;
 function1( stackVar );
 return 0;
}
Compiling this code with the -S switch will produce the following assembly
listing:
 .file \u201cstack4.c\u201d
 .text
.globl function1
 .type function1, @function
function1:
 pushl %ebp
 movl %esp, %ebp
 subl $4, %esp
 movl $99, -4(%ebp)
 leave
 ret
 .size function1, .-function1
.globl main
 .type main, @function
main:
 pushl %ebp
 movl %esp, %ebp
 subl $8, %esp
 andl $-16, %esp
 movl $0, %eax
 subl %eax, %esp
 movl $3, -4(%ebp)
 subl $12, %esp
 pushl -4(%ebp)
 call function1
161
 addl $16, %esp
 movl $0, %eax
 leave
 ret
 .size main, .-main
 .ident \u201cGCC: (GNU) 3.3.1 (SuSE Linux)\u201d
Note: Because the source program was very simple, this assembly
listing is also quite simple. Without any prior knowledge of or experience
with assembly listings, you should be able to easily look at this listing
and pick out the beginning of the two functions, function1 and main.
In function1, the first instruction pushl %ebp saves the value of the base
pointer from the previous frame on the stack. The next instruction movl %esp,
%ebp copies the value of the stack pointer into the base pointer register. Recall
that the stack pointer, esp in this example, always points to the top of the
stack. The next instruction subl $4, %esp subtracts 4 from the current value
stored in the stack pointer register. This effectively opens up storage in the
newly created stack frame for 4 bytes. This is the space needed for the local
variable localVar, which is indeed 4 bytes in size (an int). These three
instructions combined form what\u2019s commonly referred to as the function
prologue. The function prologue is code added to the beginning of every function
that is compiled by gcc and most, if not all, compilers. It is responsible for
defining and preparing a new stack frame for upcoming function execution.
Along with a function prologue is an associated function epilogue. In the
assembly code shown for the preceding function1(), the epilogue consists of
the leave and ret instructions. The epilogue is effectively the reverse of the
prologue. It is hard to tell this because those unfamiliar with the x86 instruction
set will not know that the leave instruction is actually a high-level instruction
equivalent to these instructions:
movl %ebp, %esp
popl %ebp
Comparing these two instructions to the first two instructions in the prologue,
we can see that they are in fact the mirror image of each other. The function
epilogue code is completed by the ret instruction, which transfers program
control to the address located at the end of the stack.
The function prologue and epilogue are extremely important contributors
to the proper execution and isolation of individual function calls. They make
up what\u2019s commonly referred to as the function or procedure calling conventions.
We will discuss the remaining details of the calling conventions, but first a
special note is required regarding the prologue and epilogue.
5.5 How Does the Stack Work?
162 The Stack Chap. 5
5.5.1.1 Special Case: gcc\u2019s -fomit-frame-pointer Compile Option
Some architectures support gcc\u2019s -fomit-frame-pointer compile option, which
is used to avoid the need for the function prologue and epilogue, thus freeing
up the frame pointer register to be used for other purposes. This optimization
is done at the cost of the ability to debug the application because certain
debugging tools and techniques rely on the frame pointer being present. SUSE
9.0 Professional and SLES 8 on the x86-64 architecture have been compiled
with the