我有以下程序:
void test_function(int a,int b, int c, int d){
int flag;
char buffer[10];
flag = 31337;
buffer[0]='A';
}
int main(){
test_function(1,2,3,4);
}
我用gcc -g
选项编译了它。
我在main内部和之后的test_function调用之前设置了2个断点。
(gdb) list
1 void test_function(int a,int b, int c, int d){
2 int flag;
3 char buffer[10];
4
5 flag = 31337;
6 buffer[0]='A';
7 }
8
9 int main(){
10 test_function(1,2,3,4);
(gdb) break 10
Breakpoint 1 at 0x804843c: file stackexample.c, line 10.
(gdb) break test_function
Breakpoint 2 at 0x804840a: file stackexample.c, line 1.
(gdb) run
Starting program: /root/tests/c-tests/./stackexample
Breakpoint 1, main () at stackexample.c:10
10 test_function(1,2,3,4);
(gdb) i r esp ebp eip
esp 0xbffff4d0 0xbffff4d0
ebp 0xbffff4e8 0xbffff4e8
eip 0x804843c 0x804843c <main+9>
根据我的知识,0xbffff4d0
这个地址是堆栈的当前底部(最高地址),这将用于在调用test_function之后创建(引用)新堆栈帧。
(gdb) x/5i $eip
=> 0x804843c <main+9>: mov DWORD PTR [esp+0xc],0x4
0x8048444 <main+17>: mov DWORD PTR [esp+0x8],0x3
0x804844c <main+25>: mov DWORD PTR [esp+0x4],0x2
0x8048454 <main+33>: mov DWORD PTR [esp],0x1
0x804845b <main+40>: call 0x8048404 <test_function>
在test_function调用之前,参数与这些mov
指令一起存储。
(gdb) info frame
Stack level 0, frame at 0xbffff4f0:
eip = 0x804843c in main (stackexample.c:10); saved eip 0xb7e8bbd6
source language c.
Arglist at 0xbffff4e8, args:
Locals at 0xbffff4e8, Previous frame's sp is 0xbffff4f0
Saved registers:
ebp at 0xbffff4e8, eip at 0xbffff4ec
(gdb) cont
Continuing.
Breakpoint 2, test_function (a=1, b=2, c=3, d=4) at stackexample.c:1
1 void test_function(int a,int b, int c, int d){
(gdb) info frame
Stack level 0, frame at 0xbffff4d0:
eip = 0x804840a in test_function (stackexample.c:1); saved eip 0x8048460
called by frame at 0xbffff4f0
source language c.
Arglist at 0xbffff4c8, args: a=1, b=2, c=3, d=4
Locals at 0xbffff4c8, Previous frame's sp is 0xbffff4d0
Saved registers:
ebp at 0xbffff4c8, eip at 0xbffff4cc
(gdb) i r esp ebp eip
esp 0xbffff4a0 0xbffff4a0
ebp 0xbffff4c8 0xbffff4c8
eip 0x804840a 0x804840a <test_function+6>
因此,显而易见的是,第一帧的esp成为该帧的当前起始地址。虽然我没有得到的是在哪个堆栈框架中的参数是???因为...
(gdb) info locals
flag = 134513420
buffer = "\377\267\364\237\004\b\350\364\377\277"
在这里我们看不到args。如果我们......
(gdb) info args
a = 1
b = 2
c = 3
d = 4
(gdb) print &a
$4 = (int *) 0xbffff4d0
(gdb) print &b
$5 = (int *) 0xbffff4d4
(gdb) print &c
$6 = (int *) 0xbffff4d8
(gdb) print &d
$7 = (int *) 0xbffff4dc
所以在这里我们看到参数是从当前堆栈帧的第一个地址开始的0xbffff4d0
根据此输出,另一个问题是以下
(gdb) x/16xw $esp
0xbffff4a0: 0xb7fc9ff4 0x08049ff4 0xbffff4b8 0x0804830c
0xbffff4b0: 0xb7ff1080 0x08049ff4 0xbffff4e8 0x08048499
0xbffff4c0: 0xb7fca324 0xb7fc9ff4 0xbffff4e8 0x08048460
0xbffff4d0: 0x00000001 0x00000002 0x00000003 0x00000004
地址0x08048460
是堆栈实例中的eip = 0x804840a in test_function (stackexample.c:1); saved eip 0x8048460
和主#()中的#1 0x08048460:10(来自回溯的输出)
为什么RET和main一起位于顶部(进入较低的地址)而不是参数?不应该将ret地址放在新堆栈帧的开头?对不起,但我想了解堆栈是如何工作的,我有点困惑:S另一件我不理解的事情是局部变量的引用是通过$ esp +(offset)发生的。 esp的值是否始终取决于&#34;当前&#34;堆栈帧执行是什么?
答案 0 :(得分:2)
你的反汇编程序在我的系统上看起来像这样:
gcc -m32 -c -o stackexample.o stackexample.c
objdump -d -M intel stackexample.o
test_function:
push ebp
mov ebp,esp
sub esp,0x10
mov DWORD PTR [ebp-0x4],0x7a69
mov BYTE PTR [ebp-0xe],0x41
leave
ret
main:
push ebp
mov ebp,esp
sub esp,0x10
mov DWORD PTR [esp+0xc],0x4
mov DWORD PTR [esp+0x8],0x3
mov DWORD PTR [esp+0x4],0x2
mov DWORD PTR [esp],0x1
call test_function
leave
ret
让我们从头开始。
堆栈在内存中从上到下排列。堆栈顶部的地址最低。
esp
是堆栈指针。它总是指向堆栈的顶部。
ebp
是基指针。它指向当前堆栈帧的底部。它用于引用当前函数的参数和局部变量。
这些说明
push ebp
mov ebp,esp
可以在每个函数的顶部找到。他们执行以下操作:
leave
相当于
mov esp, ebp
pop ebp
与上述说明完全相反:
现在回答你的问题
在哪个堆栈框架中参数是???
参数存储在调用者的堆栈帧中。但是,您可以使用Base Pointer访问它们。 info locals
不会将有关函数参数的信息显示为gdb规范的一部分:
http://visualgdb.com/gdbreference/commands/info_locals
为什么RET和main一起位于顶部(进入较低的地址)而不是参数?不应该将ret地址放在新堆栈帧的开头
那是因为参数存储在调用者的框架中。当调用test_function
时,堆栈已经存储了参数,因此返回的地址存储得比参数更高(也就是更低的地址)。
局部变量的引用通过$ esp +(offset)发生。
据我所知,使用Base Pointer和Stack Pointer都可以引用局部变量 - 无论哪种方便编译器(不太确定)。
esp的值是否始终取决于&#34;当前&#34;堆栈帧执行是什么?
是。堆栈指针是最重要的堆栈寄存器。它指向堆栈的顶部。