asm基础——堆栈框架、调用惯例

来源:互联网 发布:编程培训机构推荐 编辑:程序博客网 时间:2024/06/05 20:22

通过反汇编了解函数的调用和堆栈结构。


得到的反汇编代码,其中main()里面代码的反汇编如下:


注意这里的_add、k等是我们在c代码中使用到的符号,可以通过在代码窗口右键唤出菜单来去掉这些符号:


去掉符号后的显示如下:


需要注意这里的call指令。在x86系列CPU的call指令的寻址方式为:用call指令相关的偏移量定位跳转到的地址。偏移量的计算方式是:偏移量=跳转到的地址-call指令后一条指令的起始地址。因此本例中的计算方式如下:

1. 通过机器码可以得到偏移量的负数表示FFFFF9FF(E8是jmp的机器码),对应的实际偏移量是-601h,注意是负数。

2. call指令的下一条指令的地址是3017D7,加上-601h后,就得到了3011D6,即call后面的值。

下面就要跳转到add函数中去了,使用工具栏中的按键使程序继续执行:


跳转进入add函数的汇编代码:


注意,前面的一大段代码,实际上是为了构造堆栈框架。

堆栈框架的建立主要有以下几步:

1. 如果有参数传递,则压入堆栈。(这一步在main函数中完成了,即由调用者完成)

2. 函数调用后的返回地址压入堆栈。(这一步在上面的代码中看不到,但实际上已经由call指令完成,实际上call指令相当于以下两句指令:push返回地址 jmp 函数入口地址)

3. 函数开始执行时,EBP被压入堆栈。(可以看到add函数的第一步操作就是这个)

4. EBP的值设置为ESP的值,从这里开始,EBP就是函数中参数寻址的基地址了。此时堆栈中的情形如下:


5. 如果有局部变量,ESP减去一个数值,以便在堆栈上为局部变量保留空间。

6. 如果寄存器需要保存,也可以压入堆栈。

注意在add函数前面mov ebp, esp之后有一段代码,主要是用来初始化堆栈的,VS中堆栈的初始化值是0xCC,它们会对应到中文的"烫“,这个在程序打印(出错时)中或者二进制中会看到。

实际的返回值被存放在了EAX中:


通过EBP,EBP+偏移量就是参数的地址,而EBP-偏移量就是变量的地址。

之后函数返回,但是在实际返回前,还需要做一些堆栈处理:


对于原先push到堆栈中的寄存器,需要pop出来,ESP和EBP的指针也需要还原。在返回到主函数时,堆栈变成了如下的样子:


在主函数中有:


这里的add esp, 8是为了清空堆栈中的无用数据,防止函数反复调用后堆栈溢出。

最后一句将add函数返回的变量放到了main函数中的局部变量k中。

关于函数调用还有两点说明,即参数传递和值返回。

1) 对于参数传递,实际上有两种方式,上述代码使用的是堆栈传递,也可以使用寄存器传递。将add函数申明为_fastcall,其它都不变:


得到的反汇编,在main函数中有:


不再是push。在add函数中有:


2) 对于值返回,实际上也有两种方式。在add函数前加关键字_stdcall:


得到的反汇编在main函数中不再需要add esp, 8:


而在add函数中有:


在函数调用中,下面三方面的内容构成了函数调用的习惯:

1) 是寄存器还是堆栈来传递参数;

2) 堆栈传递时,参数是从右往左还是从左往右压栈;

3) 谁来清理堆栈,调用方还是被调用方。

上述几点组合构成了调用惯例。C语言的调用惯例是堆栈传递参数,参数从右往左压栈,调用方清理堆栈。_stdcall是微软系统调用采用的惯例,除了清理堆栈是被调用方之外,其它同C调用惯例。

0 0
原创粉丝点击