第二十一课_堆栈图1
前言
难得端午放假,今天起来写写文章吧。今天画堆栈图,这堆栈图可能会写好几篇文章,这个很重要,这可以把之前的进制、汇编来个简单的复习,这堆栈图画好了,也为后面讲C语言、C++语言的基础。会画堆栈图,后面的一些概念就容易讲解了,不然用概念给大家讲解概念,浪费时间不说,还不能理解本质的东西。
今天画个简单的:就画一个1 + 2的函数的堆栈的变化过程。该函数是用C写的,源码和exe可执行文件都放网盘了,地址在文末,需要的可以下载。(编译器用的是vc6.0,exe是Debug版的)
说明:利用OD进行测试,入口函数的位置可以用本id的,当然你也可以自己找,有点基础的,也很容易找到main函数的那个Call。
具体步骤
1、把从本id网盘中下载的exe拖到OD中打开,按Ctrl + G快捷键,在弹出的对话框中输入“0x401068”,然后点OK。
2、在定位到的行设置断点,(按F2,或者双击第二列就可以设置)。
3、设置好断点后,按下图中蓝色这个按钮,让程序运行到刚设置的断点处停下来。
4、程序运行到“0x401068”时,当前OD中的显示。
我们就可以一边运行,一边画堆栈图,之前说过,栈是一个连续的内存块。根据当前ESP,EBP寄存器中的值,我们可以画出当前的栈顶、栈底。
5、下一条指令是,PUSH 2,这是一条入栈的指令,我们指令2会被压入栈顶,ESP的值也会减4,那么可以画出如下的堆栈图:
按F8,执行PUSH 2指令。OD上的显示跟我们预想的结果一样。
6、下一条指令是PUSH 1,跟上一条指令一样,1被压入栈顶,DSP的值减4.
按F8运行的结果如我们所想。
7、下一条指令是:CALL 00401005,之前说过,call指令会把Call的下一条指令的地址压入栈顶,当前call的地址是0x40106C,因为第二列中有5个字节,我们可以算出call指令的下一行地址是0x401071,所以指令该条指令后,0x401071会被压入栈顶,EIP寄存器中的值被改为0x401005。
在OD中这次就不能按F8了,要按F7。
运行后下一条指令是JMP指令,只是跳转,不会修改栈中的内容,直接按F8运行,或者按回车键。
8、下一条指令是PUSH EBP,入栈操作,EBP寄存器中的值被压入栈顶,ESP减4:
按F8运行,结果和预想的一样。
9、下一条指令是MOV EBP,ESP,该指令只是修改栈底EBP的值。
按F8运行结果如下。
10、下一条指令是SUB ESP,40,这是一个减法指令,修改的是ESP,也就是栈顶,栈顶被提升了。注意这里的40是十六进制的,也就是十进制的64,因为地址是4个字节的,所以64除以4,得到16,也就是向上画16个格。
按F8运行后的结果如下图,跟我们画的一样。
11、下一条指令是PUSH EBX,把寄存器EBX中的值压入栈顶,ESP减4.
按F8运行,跟我们画的一样。
12、下一条指令是PUSH ESI,把ESI中的值压入栈,ESP再减4.
按F8运行
13、下一条指令是PUSH EDI,把EDI中的值压入栈顶,ESP减4.
按F8运行
14、下一条指令LEA EDI,DWORD PTR SS:[EBP-40],这是一条取地址的指令,取EBP减0x40所把的内存地址,保存到EDI中。
按F8运行,可以看到只修改了EDI中的值。栈并没有发生改变。
15、下一条指令是MOV ECX,10,只是修改ECX中的值,栈没有发生改变。
16、下一条指令是MOV EAX,CCCCCCCC,只是把EAX中的值修改为CCCCCCCC,栈没有发生改变。
17、下一条指令是REP STOS DWORD PTR ES:[EDI],这里有两个指令:STOS指令是把EAX中的值写入到EDI所指的内存中,然后EDI中值根据标志寄存器DF位进行加或者减一个数,因为这里的DF是0,所以STOS执行一次,EDI中的值就加4。REP是重复指令指令,重复执行的次数是EAX中保存的值。执行这条指令后,前面步骤10提升的栈的16个内存都被填入CCCCCCCC。
按F8运行的结果如下,可以看到确实是被填充了CCCCCCCC。其实这一步是填的是INT3,是为了防止访问越界了。
18、下一条指令是MOV EAX,DWORD PTR SS:[EBP+8],是把EBP+8所指的内存中的值(即步骤6压入栈的1)保存到EAX中。没有改变栈中的内容。
19、下一条指令:ADD EAX,DWORD PTR SS:[EBP+C],加法指令,把EBP所指内存的值(即步骤5压入栈的2)与EAX中的值相加,保存到EAX中。
20、下一条指令:POP EDI,出栈,栈顶的值复制到EDI中,栈顶加4.
按F8 运行的结果。
21、下一条指令:POP ESI,出栈,栈顶的值复制到ESI中,栈顶加4.
按F8运行的结果。
22、下一条指令:POP EBX,出栈,栈顶的值复制到EBX中,栈顶加4.
按F8运行结果。
23、下一条指令:MOV ESP,EBP,把EBP中的值复制到ESP中,即修改栈顶到现在的栈底。
按F8运行,可以看到ESP、EBP中的值相等。
24、下一条指令:POP EBP,出栈,把现在栈顶的值复制到EBP中,结合步骤8,可以知道栈底的返回到调用前的状态。
按F8运行的结果如下:
25、下一条指令:RETN,RETN指令会把栈顶的值弹出到EIP中,即程序运行到调用Call指令(步骤7)的下一条指令。栈顶被修改。
按F8运行的结果如下:
26、下一条指令:ADD ESP,8,是加法指令,ESP的值加了8,这是为了平衡堆栈,即调用函数前后,堆栈的结构要一样。像这种在调用函数外面平衡堆栈的叫外平栈,如果在调用函数里面平衡堆栈的叫内平栈。
按F8运行的结果如下图。
到此,一个函数的调用的堆栈图就画好了。
下面对这个过程简单说明一下,步骤5、6的两个PUSH,把两个数放到栈中,这样在call的函数中,就可以用ESP或者EBP通过加、减来获取到PUSH进栈的值。这样就实现了函数调用时参数的传递。
而那个call就是本id之前说的函数调用。
写了很多,感觉很多都是废话,考虑到刚接触的人可能不理解只好一句一句地讲解。事实上等我们熟悉后,我们看汇编就是一大块一大块地看了。
例如这样看:
整个过程的堆栈图:
源码和exe执行程序的网盘地址:
21课相关资料网盘链接:
https://pan.baidu.com/s/1BQ1-TFMhUH4S4YlXlMDujQ
提取码:
el5g
写于2020.6.26 16:48