c - 参数在 gdb 中的哪里以及 ret 在哪里?

标签 c debugging assembly gdb disassembly

我有以下程序:

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选项编译了它。

我设置了 2 个断点,一个位于 main 内的 test_function 调用之前,一个位于其之后。

(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"

这里我们看不到参数。如果我们..

(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是test_function(stackexample.c:1)中的eip = 0x804840a;在 stackexample.c:10 处的 main () 中保存了 eip 0x8048460 以及 `#1 0x08048460(来自回溯的输出)

为什么 main 的 RET 比参数位于顶部(进入较低的地址)? ret 地址不应该位于新堆栈帧的开头吗?抱歉,但我想了解堆栈的工作原理,但我有点困惑:S 我不明白的另一件事是局部变量的引用是通过 $esp+(offset) 发生的。 esp 的值是否始终取决于执行的“当前”堆栈帧?

最佳答案

你的反汇编程序在我的系统上看起来像这样:

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

可以在每个函数的顶部找到。他们执行以下操作:

  • 保存调用者的基址指针
  • 通过将当前函数的基指针分配给堆栈指针来设置它。此时堆栈指针指向当前堆栈帧的底部,因此通过将基指针分配给它,基指针将显示当前堆栈帧的底部。堆栈指针在函数执行过程中可以增加/减少,因此可以使用基指针来引用变量。基指针还用于保存/存储调用者的堆栈指针。

离开相当于

mov    esp, ebp
pop    ebp

这与上面的说明完全相反:

  • 恢复调用者的堆栈指针
  • 恢复调用者的基址指针

现在回答你的问题

in which stack frame the arguments are ???

参数存储在调用者的堆栈帧中。但是您可以使用基指针来访问它们。 info locals 不显示有关函数参数的信息作为 gdb 规范的一部分:

http://visualgdb.com/gdbreference/commands/info_locals

How come and RET to main is on top (into a lower address) than the arguments ? Shouldnt ret address be in the start of the new stack frame

这是因为参数存储在调用者的框架中。当调用 test_function 时,堆栈已经存储了参数,因此返回的地址存储得比参数更高(也称为更低地址)。

reference for the local variables is happening through $esp+(offset).

据我所知,引用局部变量可以使用基指针和堆栈指针 - 无论哪种对您的编译器方便(不太确定)。

Does the value of esp is always depending on the "current" stack frame that the execution is?

是的。堆栈指针是最重要的堆栈寄存器。它指向堆栈的顶部。

关于c - 参数在 gdb 中的哪里以及 ret 在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20733342/

相关文章:

c - 为什么这个函数序言中没有 "sub rsp"指令,为什么函数参数存储在负 rbp 偏移处?

c - 你可以在 double 中存储多大的数字,在 c 中用 float 存储?

c - 具有巨大深度的有根树 - DFS 遍历算法性能

c - 如何调试 x86 程序集

c++ - 如何遍历类指针的 vector

assembly - sin 和 cos 是如何在硬件上实现的?

c - 删除逗号之间的白色字符,但不删除逗号内的内容

java - 如何单步执行代码而不执行自动生成的代码?

c++ - 如果用两个变量或一个变量和一个数字文字进行加法,为什么从 C++ 调用的汇编函数会在标志中给出不同的结果?

c# - 为什么.NET Native 以相反的顺序编译循环?