linux - 帮助理解 GDB 中一个非常基本的 main() 反汇编

标签 linux assembly gdb x86 disassembly

嘿嘿

我编写了这个非常基本的 main 函数来尝试反汇编,并查看并希望了解较低级别发生的事情:

int main() {
  return 6;
}

使用 gdb 来 disas main 会产生这个:
0x08048374 <main+0>:    lea    0x4(%esp),%ecx
0x08048378 <main+4>:    and    $0xfffffff0,%esp
0x0804837b <main+7>:    pushl  -0x4(%ecx)
0x0804837e <main+10>:   push   %ebp
0x0804837f <main+11>:   mov    %esp,%ebp
0x08048381 <main+13>:   push   %ecx
0x08048382 <main+14>:   mov    $0x6,%eax
0x08048387 <main+19>:   pop    %ecx
0x08048388 <main+20>:   pop    %ebp
0x08048389 <main+21>:   lea    -0x4(%ecx),%esp
0x0804838c <main+24>:   ret  

这是我对正在发生的事情以及我需要逐行帮助的最佳猜测:
lea 0x4(%esp),%ecx
将 esp + 4 的地址加载到 ecx 中。 为什么要在 esp 上加 4?

我在某处读到这是命令行参数的地址。但是当我这样做时 x/d $ecx我得到了 argc 的值。 实际的命令行参数值存储在哪里?
and $0xfffffff0,%esp
对齐堆栈
pushl -0x4(%ecx)
将 esp 最初所在位置的地址压入堆栈。 这样做的目的是什么?
push %ebp
将基指针压入堆栈
mov %esp,%ebp
将当前堆栈指针移入基指针
push %ecx
将原始 esp + 4 的地址压入堆栈。 为什么?
mov $0x6,%eax
我想在这里返回 6 所以我猜返回值存储在 eax 中?
pop %ecx
将 ecx 恢复到堆栈上的值。 为什么我们返回时希望 ecx 为 esp + 4?
pop %ebp
将 ebp 恢复到堆栈上的值
lea -0x4(%ecx),%esp
将 esp 恢复到它的原始值
ret
在组装方面我是 n00b,所以任何帮助都会很棒!另外,如果您看到有关我认为正在发生的事情的任何虚假陈述,请纠正我。

谢谢一堆! :]

最佳答案

堆栈帧

函数体开头的代码:

push  %ebp
mov   %esp, %ebp

是创建所谓的堆栈框架,它是一个“坚实的基础”,用于引用程序本地的参数和对象。 %ebp register 用作(如其名称所示)作为基指针,它指向过程内本地堆栈的基(或底部)。

进入程序后,栈指针寄存器( %esp )指向调用指令存储在栈上的返回地址(即调用后的指令地址)。如果你只是调用 ret现在,这个地址将从堆栈中弹出到 %eip (指令指针)并且代码将从该地址(在 call 之后的下一条指令)进一步执行。但我们还没有回来,是吗? ;-)

然后你推 %ebp注册以将其先前的值保存在某处而不会丢失它,因为您很快就会将其用于某些用途。 (顺便说一句,它通常包含调用者函数的基指针,当您查看该值时,您会发现以前存储的 %ebp ,这将再次成为更高一级函数的基指针,因此您可以跟踪调用堆栈那样。)当您保存 %ebp 时,然后可以存储当前 %esp (堆栈指针)在那里,所以 %ebp将指向相同的地址:当前本地堆栈的基址。 %esp当您将在堆栈上推送和弹出值或保留和释放局部变量时,将在过程中来回移动。但是%ebp将保持固定,仍指向本地堆栈帧的底部。

访问参数

调用者传递给过程的参数“埋在地下”(也就是说,它们相对于基数具有正偏移量,因为堆栈向下增长)。您在 %ebp本地堆栈基址的地址,这里是 %ebp 的前一个值.在它下面(也就是说,在 4(%ebp) 是返回地址。所以第一个参数将在 8(%ebp) ,第二个在 12(%ebp) 等等。

局部变量

并且局部变量可以在基数上方的堆栈上分配(也就是说,它们相对于基数具有负偏移量)。只需将 N 减去 %esp你刚刚分配了 N局部变量堆栈上的字节数,通过将堆栈顶部移动到此区域上方(或准确地说,下方):-) 您可以通过相对于 %ebp 的负偏移量来引用此区域。 ,即 -4(%ebp)是第一个字,-8(%ebp)是第二个等等。请记住 (%ebp)指向本地堆栈的基址,其中前面的 %ebp值已保存。所以在尝试恢复 %ebp 之前记得把堆栈恢复到之前的位置。通过 pop %ebp在程序结束时。你可以通过两种方式做到这一点:
1. 您可以通过添加回 N 来仅释放局部变量。到 %esp (堆栈指针),即移动堆栈顶部,就好像这些局部变量从未存在过一样。 (好吧,它们的值将保留在堆栈中,但它们将被视为“已释放”并且可能会被后续推送覆盖,因此引用它们不再安全。它们是尸体 ;-J )
2. 您可以通过简单地恢复 %esp 将堆栈冲洗到地面并释放所有本地空间。来自 %ebp它已被修复到堆栈的底部。它会将堆栈指针恢复到进入过程并保存 %esp 后的状态。进入 %ebp .这就像当你搞砸了一些东西时加载以前保存的游戏;-)

关闭帧指针

可以从 gcc -S 获得一个不那么凌乱的组件通过添加一个开关 -fomit-frame-pointer .它告诉 GCC 在真正需要某些东西之前不要组装任何用于设置/重置堆栈帧的代码。请记住,它可能会混淆调试器,因为它们通常依赖于那里的堆栈帧能够跟踪调用堆栈。但是如果你不需要调试这个二进制文件,它不会破坏任何东西。这对于释放目标来说非常好,并且可以节省一些时空。

调用帧信息

有时你会遇到一些奇怪的汇编指令,从 .cfi 开始与函数头交错。这就是所谓的调用帧信息。调试器使用它来跟踪函数调用。但它也用于高级语言中的异常处理,这需要堆栈展开和其他基于调用堆栈的操作。您也可以在程序集中关闭它,方法是添加一个开关 -fno-dwarf2-cfi-asm .这告诉 GCC 使用普通的旧标签而不是那些奇怪的 .cfi指令,它在程序集的末尾添加了一个特殊的数据结构,引用这些标签。这不会关闭 CFI,只是将格式更改为更“透明”的格式:然后程序员可以看到 CFI 表。

关于linux - 帮助理解 GDB 中一个非常基本的 main() 反汇编,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4751502/

相关文章:

linux - Bluez-5.36 StartDiscovery() 方法

c - 如何检查字符串是否没有任何字母数字字符?

linux - 有没有办法改变 Unix 中另一个进程的环境变量?

linux - 截断的核心转储有什么用?

linux - 在 linux scheduler 中,它是否跟踪当前正在休眠的任务或已终止的任务?

linux - 如何获取 bash 中某些文件夹的总大小?

linux - 解压子目录到一个目录

assembly - MSP430 CMP运算符

c++ - 使用 GCC 进行内联汇编

linux - 在 x86 中将字符串定义为字节 (db) 和将字符串定义为字/双字 (dw/dd) 有什么区别