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

mas*_*wok 12 linux x86 assembly gdb disassembly

Heyo,

我已经编写了这个非常基本的主要功能来试验反汇编,还可以看到并希望了解更低层次的情况:

int main() {
  return 6;
}
Run Code Online (Sandbox Code Playgroud)

使用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  
Run Code Online (Sandbox Code Playgroud)

这是我最好的猜测,我认为正在发生什么以及我需要逐行帮助:

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所以任何帮助都会很棒!此外,如果你看到任何关于我认为发生的事情的错误陈述,请纠正我.

谢谢你!:]

Sas*_*asQ 12

堆栈帧

函数体开头的代码:

push  %ebp
mov   %esp, %ebp
Run Code Online (Sandbox Code Playgroud)

是创建所谓的堆栈帧,这是一个"坚实的基础",用于引用过程本地的参数和对象.使用%ebp寄存器(如其名称所示)作为基指针,指向过程内本地堆栈的(或底).

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

然后按下%ebp寄存器以保存其先前的值并且不会丢失它,因为您将很快使用它.(顺便说一句,它通常包含调用函数的基指针,当你查看该值时,你会发现一个先前存储的%ebp,这将再次是一个更高级别的函数的基指针,因此你可以跟踪调用堆栈那样.)当你保存时%ebp,你可以在%esp那里存储当前(堆栈指针),这样%ebp就会指向同一个地址:当前本地堆栈的基础.该%esp会来回移动时,你会推栈上弹出值或保留和释放局部变量的过程中.但是%ebp会保持固定,仍然指向本地堆栈帧的基础.

访问参数

由调用者传递给过程的参数"只是在地面上埋葬"(也就是说,它们相对于基础具有偏移,因为堆栈向下增长).你有%ebp本地堆栈的基地址,其中的前一个值是%ebp.在它下面(也就是说,它4(%ebp)位于返回地址.所以第一个参数是at 8(%ebp),第二个是at 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表.