了解在机器级别执行功能
我正在学习基于语言的安全性课程,并且我必须逐步了解当一个函数正确执行时堆栈中发生了什么,以便以后我可以学习如何防范漏洞。 到目前为止,我对堆栈中推送和弹出的内容以及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