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

Heyo,

我已经写了这个非常基本的主要function来实验反汇编,也看到并希望了解底层的事情:

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中。 为什么我们加4到esp?

我在某处读到这是命令行参数的地址。 但是当我做了x/d $ecx我得到了argc的值。 在哪里存储实际的命令行参数值?

and $0xfffffff0,%esp

alignment堆栈

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

恢复到它的原始值

ret

我是一个n00b当谈到组装,所以任何帮助将是伟大的! 另外,如果您发现我认为正在进行的任何&#x865A;假陈述,请纠正我。

谢谢一堆! :]

Solutions Collecting From Web of "帮助理解GDB中一个非常基本的main()反汇编"

堆栈帧

函数体开头的代码:

 push %ebp mov %esp, %ebp 

就是创建所谓的堆栈框架 ,它是引用过程本地参数和对象的“坚实基础”。 使用%ebp寄存器(如其名称所示)作为基本指针 ,该指针指向过程内本地堆栈的底层(或底层)。

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

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

访问参数

调用者传递给过程的参数“只是埋在地下”(也就是说,它们相对于基础具有偏移,因为堆栈增长下来)。 你在%ebp中有本地堆栈的基地址,其中%ebp的前一个值在这里。 在它下面(也就是4(%ebp)是返回地址,所以第一个参数是8(%ebp) ,第二个参数是12(%ebp) ,依此类推。

局部变量

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

关闭帧指针

通过添加一个开关-fomit-frame-pointer可以减少来自gcc -S的混乱程序集。 它告诉GCC不要组装任何代码来设置/重置堆栈帧,直到真正需要某些东西。 只要记住,它可以混淆调试器,因为它们通常依赖于在那里的堆栈框架能够追踪调用堆栈。 但是如果你不需要调试这个二进制文件,它不会破坏任何东西。 这对于发布目标来说是非常好的,并且节省了一些时间。

呼叫帧信息

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

你的解释很不错。 当一个函数被调用时,返回地址被自动推送到堆栈,这就是为什么argc(第一个参数)被推回到4(%esp)的原因。 argv将从8(%esp)开始,每个参数都有一个指针,后跟一个空指针。 该函数将%esp的旧值压入堆栈,以便返回时可以包含原始的未对齐值。 返回时%ecx的值不重要,这就是为什么它被用作%esp引用的临时存储。 除此之外,你对所有事情都是正确的。

关于你的第一个问题(在哪里存储命令行参数),函数的参数是在ebp之前。 我必须说,你的“真正的”主要从< main + 10 > ,在那里推动ebp并把esp移到ebp 。 我认为gcc用所有的东西来处理所有的东西,只是为了在函数调用之前和之后的esp上代替通常的操作(成瘾和减法)。 通常一个例程看起来像这样(我作为一个例子简单的函数):

  0x080483b4 <+0>: push %ebp 0x080483b5 <+1>: mov %esp,%ebp 0x080483b7 <+3>: sub $0x10,%esp # room for local variables 0x080483ba <+6>: mov 0xc(%ebp),%eax # get arg2 0x080483bd <+9>: mov 0x8(%ebp),%edx # and arg1 0x080483c0 <+12>: lea (%edx,%eax,1),%eax # just add them 0x080483c3 <+15>: mov %eax,-0x4(%ebp) # store in local var 0x080483c6 <+18>: mov -0x4(%ebp),%eax # and return the sum 0x080483c9 <+21>: leave 0x080483ca <+22>: ret 

也许你已经启用了一些优化,这可能会使代码更棘手。 最后是的,返回值存储在eax 。 无论如何,你的解释是非常正确的。

我认为从原来的问题中唯一出色的是为什么在你的代码中存在以下语句:

 0x08048381 <main+13>: push %ecx 0x08048382 <main+14>: mov $0x6,%eax 0x08048387 <main+19>: pop %ecx 

%ecx在<main+13><main+19>的推送和弹出似乎没有什么意义 – 在这个例子中他们没有做任何事情,但是考虑一下你的代码调用函数调用的情况

系统无法保证对其他功能的调用(将设置自己的堆栈激活帧)不会重置寄存器值。 事实上,他们可能会。 因此,代码在堆栈中设置了一个保存的寄存器部分 ,在可能将控制交给函数调用之前,代码使用的任何寄存器(除了已经通过常规堆栈设置保存的%esp和%ebp)都存储在堆栈中在当前代码块的“肉”中。

当这些潜在的调用返回时 ,系统将弹出堆栈中的值以恢复预调用寄存器值。 如果您直接编写汇编程序而不是编译,那么您将自己负责存储和检索这些寄存器值。

然而,在您的示例代码的情况下,没有函数调用 – 只有一个指令在<main+14>设置返回值,但编译器不能知道这个,并像往常一样保留它的寄存器。


如果你在<main+14>之后添加了将其他值推入堆栈的C语句,看看会发生什么。 如果我正确地说这是一个已保存的堆栈寄存器部分 ,那么您希望编译器在<main+19>之前插入自动pop语句以清除这些值。