嘿嘿
我编写了这个非常基本的 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/