了解在机器级别执行功能

我正在学习基于语言的安全性课程,并且我必须逐步了解当一个函数正确执行时堆栈中发生了什么,以便以后我可以学习如何防范漏洞。 到目前为止,我对堆栈中推送和弹出的内容以及ESP,EBP如何跟踪帧进行了很好的理解。 另外,我知道EIP保存在堆栈中。

我不知道的是函数中的代码实际上被执行以获得结果的位置(我假设在内存中的其他位置,Heap?)如果我给出一个简单函数的演练,有人可以解释丢失的位(I会用问题来标记这些部分)。 设定一个简单的功能:

int add(int x, int y)
{
   int sum = x + y;
   return sum;
}

在main()中用add(3,4)调用;

在新函数初始化时,堆栈(从最低地址到最高地址)的ESP指向顶部,EBP指向新帧的底部。 下面是main()。

现在,参数从右到左被推入堆栈。 函数调用将EIP的内容保存在堆栈中。 [这是函数返回后要执行的下一条指令的地址吗?]

现在Prolog部分:将旧的EBP地址压入堆栈,并使EBP指向ESP。 最后,局部变量被压入堆栈[这些只是它们的值的存储地址?]

Epilog是当堆栈要解开当前帧的时候。 ESP移至EBP,因此局部变量不可访问(通常)。 旧的EBP弹出堆栈,并指向它的原始地址。 ESP移动指向保存的EIP,这是在添加(3,4)被调用之前的位置。

在我给出的解释中,最后一部分是返回指令将保存的EIP值弹回到EIP寄存器中。 [当然,这不是函数中的return语句,而是机器级的ret指令,对吗?]

最后一个问题,有人可以解释当函数中的代码正在执行时以及在所有调用,序言和epilog发生期间的什么时候发生了什么? 或者提供一个清晰的解释的好链接?

非常感谢堆(可以这么说:)


首先,我编译然后反汇编你的函数,这样你就可以在ASM级别看到实际发生了什么。 我禁用了优化,并编译为32位代码以保持简单:

Dump of assembler code for function add:
   0x080483cb <+0>:     push   %ebp
   0x080483cc <+1>:     mov    %esp,%ebp
   0x080483ce <+3>:     sub    $0x10,%esp
   0x080483d1 <+6>:     mov    0x8(%ebp),%edx
   0x080483d4 <+9>:     mov    0xc(%ebp),%eax
   0x080483d7 <+12>:    add    %edx,%eax
   0x080483d9 <+14>:    mov    %eax,-0x4(%ebp)
   0x080483dc <+17>:    mov    -0x4(%ebp),%eax
   0x080483df <+20>:    leave  
   0x080483e0 <+21>:    ret    
End of assembler dump.

试着看看上面的反汇编,并认识它在做什么以及它如何匹配你的C代码。 现在回答你的问题。

现在Prolog部分:将旧的EBP地址压入堆栈,并使EBP指向ESP。 最后,局部变量被压入堆栈[这些只是它们的值的存储地址?]

这里的序言从0x080483cb <+0>0x080483ce <+3>包括在内。 首先我们创建一个带push %ebp; mov %esp,%ebp的框架push %ebp; mov %esp,%ebp push %ebp; mov %esp,%ebp正如你所说的,然后我们为sub $0x10,%esp的堆栈上的局部变量分配0x10字节的空间。 该指令所做的只是将堆栈指针向下移动0x10个字节。 它不存储任何值,如果我们想要的话,它只留下一些我们可以用于局部变量的空间(我们将看到编译器甚至没有使用它)。

接下来我们有这个函数的实际逻辑。 首先,我们将堆栈中的两个参数x和y加载到寄存器中:

0x080483d1 <+6>:     mov    0x8(%ebp),%edx
0x080483d4 <+9>:     mov    0xc(%ebp),%eax

我们将它们加在一起:

0x080483d7 <+12>:    add    %edx,%eax

现在我们将结果存储在局部变量中。 该局部变量实际上只是我们在prolog中分配的堆栈空间。 我们为局部变量分配了0x10个字节,这里我们只使用前4个字节来存储加法的结果:

0x080483d9 <+14>:    mov    %eax,-0x4(%ebp)

由于没有任何优化,我们立即将该结果从本地变量加载回寄存器,以便我们可以返回它:

0x080483dc <+17>:    mov    -0x4(%ebp),%eax

正如你所看到的,代码的效率非常低,但至少它很容易阅读。 现在只剩下了epilog,这很简单:

0x080483df <+20>:    leave  
0x080483e0 <+21>:    ret   

leave破坏我们在序言中创建的框架,并且ret返回到调用函数的下一条指令。

Epilog是当堆栈要解开当前帧的时候。 ESP移至EBP,因此局部变量不可访问(通常)。 旧的EBP弹出堆栈,并指向它的原始地址。 ESP移动指向保存的EIP,这是在添加(3,4)被调用之前的位置。

在我给出的解释中,最后一部分是返回指令将保存的EIP值弹回到EIP寄存器中。 [当然,这不是函数中的return语句,而是机器级的ret指令,对吗?]

函数中的return语句对应于机器级别的ret指令。 这是直接翻译。 请记住,您的计算机不直接运行C代码,所有C都先编译为机器代码,这里的ret指令实际上是EIP的弹出窗口。

最后一个问题,有人可以解释当函数中的代码正在执行时以及在所有调用,序言和epilog发生期间的什么时候发生了什么? 或者提供一个清晰的解释的好链接?

您在上面看到的反汇编是计算机运行的粗略文本表示。 EIP包含计算机将运行的下一条指令的地址。 当程序运行时,它存储在内存中的某个地方,EIP直接指向内存中的指令。

因此,计算机将按照写入的顺序运行函数,并且prolog和epilog是函数的一部分。

序言和epilog是一个约定,但它们只是代码。 如果你愿意,你可以完全删除序言和写一个疯狂的结语,它也可以工作。

我建议你去使用反汇编和调试器,以熟悉它是如何工作的。 这并不困难,也很合理。

链接地址: http://www.djcxy.com/p/80385.html

上一篇: Understanding executing a function at the machine level

下一篇: Questions regarding base pointer and stack pointer