初窥汇编(使用工具Devc++)
Devc++添加调试信号。
工具——>编译选项,再编译器的上上方空白地方添加-g3
先来一个简单的C程序
#include<stdio.h>
int f(int x){
return x;
}
int main(){
f(1);
}
首先我们先了解一下简单的几个汇编指令
- push +寄存器或者内存:实现入栈操作,将寄存器内的值或者内存的值入栈;
- pop+寄存器:实现出栈操作,将栈顶元素放于该寄存器,栈顶元素弹出(但是栈顶元素并未送该内存释放,但是之后再压栈的时候会将其覆盖);
- mov A,B:将A寄存器的值或者A所值地址的值放在B中,即A->B(不同的系统会可能是B->A);
- sub A,B:B=A-B;
- call:将程序下一条指令的位置IP压入堆栈中; 转移到调用的子程序(IP里面存储的是每一条指令的起始地址,在执行一条指令之后,IP会自动跳转到下一条指令的地址)。
- ret:栈顶元素出栈,并将其置于IP中。call常在主函数中使用,达到调用子函数的目的,ret常在被调用的函数中用,从而在执行完子函数之后能够返回到主函数,
- jmp,com,je等指令网上有很多讲解,这里不再赘述
重头戏
将代码贴在devc++,编译运行。在第9行设置断点,点击左下角的调试,进入调试状态。
点击下面的查看CPU窗口
通过这个窗口,上面的函数名字代表这里面的汇编代码对应C代码中的main()段的代码,内部是具体的代码,代码左边是指令所在的起始地址,不同的指令可能占用的内存不同(地址后面的<+数字>表示指令占用的内存大小)。右边是CPU中的寄存器,主要是前8个寄存器,部分显示的是eax,ebx(前缀为e,r开头表示寄存器是64位的,e开头表示寄存器是32位的)。寄存器后面是对应的十六进制数值。
点击 “单步进入”,“单步进入”和“下一步”的区别可在网上查到
- 第1行是将本函数的地址入栈,
- 第2行是将rsp指向栈顶。(rbp,rsp是主要保存基站指针和栈顶指针)基本上每个函数的开头都有这两行。
- 第三行是给函数中的变量开辟指针,位置是在距离rbp所指向的地址,向高地址区偏移0x20的所有区域。但是主函数中我们并未设置变量,所以这里的空间应该是在之后调用子函数之后得到的值存储的地方。
- 第5行,将1放在ecx寄存器中,这里的这个1表示主函数中的f(1)的1。
- 第6行:通过call调用f函数
点击“单步进入”,进入f函数中
通过call调用f函数,此时进入f的汇编代码段。
- 第一二行不赘述
- 第三行,我们将ecx放在了据rbp指针偏移量为0x10的位置,所以现在rbp+0x10的位置也是主函数的参数1,
- 第四行:将rbp+0x10的位置(还是1)放在寄存器eax中
- 第五行,add指令,将eax的值加7后放在eax中,这句话对应的代码是return x+7;
- 第六行:pop,将主函数的call指令压栈的地址弹出放在IP中,返回主函数
- 此时IP指向的是g函数
- retq,返回到主函数。
- - 接下来的指令我也没不大懂,可能这是函数结束时编译器需要做的事情吧,一般情况下编译器汇编出来的代码都是相对按照你自己意图编写的汇编代码较冗余,因为编译器要做出大量工作防止出错。