1、通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
欢迎访问我的博客
****:http://blog.****.net/u011301547
博客园:http://www.cnblogs.com/zyzyzy92/
姓名:周毅
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、基础知识
1、计算机一条一条的执行指令;
2、寄存器%EIP总是存着下一条指令所在内存的地址;
3、本课程用到的3个寄存器(32位):
%eax:默认“累加器”,也是一个通用寄存器,往往也是存储函数返回值的寄存器。
%esp:“基址指针”,一般作为一个函数的框架指针;
%ebp:堆栈指针,典型的用法是配合push和pop汇编指令间接改变自身,%ebp就是栈顶指针;
4、本课程用到的汇编指令:
movl A,B 将A赋值给B,其中A可为寄存器、立即数、内存地址、对应内存地址中的内存等,B可为寄存器、对应内存地址指向的空间;
addl A,B B=A+B
subl A,B B=B-A
push和pop指令:
push A 将A入栈,栈顶指针esp减4然后存入栈顶
pop A esp地址内容出栈赋值给A,esp加4
在函数开头处经常见到如下汇编指令:
其含义表明将上一个函数的框架指针%ebp入栈,然后将当前栈顶指针%esp作为当前函数的框架指针。
leave指令:
leave等价于:
movl %ebp,%esp
popl %ebp
在ret指令之前,作为恢复返回函数的基址和栈顶指针状态。
call和ret指令:
call A A为函数名或地址,表示执行A函数或执行A地址的命令;
ret 返回执行调用当前函数指令的下一条指令;
call和ret成套使用,如同C语言的调用和返回,图中右边可以看成左边call和ret的伪汇编执行(实际%eip是无法直接访问的)。
5、本课程需要用到的linux命令
得到C语言code.c的汇编代码code.s:
gcc -S -o code.s code.c
得到C语言code.c的可执行程序code,-g将代码信息编译到可执行文件中方便gdb调试:
gcc -o code code.c -g
gdb调试:
gdb code 调试可执行程序code
(gdb) r 运行到下一个断点处
(gdb) b 8 行号8设置断点
(gdb) b main main函数名设置断点
(gdb) b *0x8048333 在内存0x8048333处设置断点
(gdb) s 执行下一条语句(若调用函数则进入函数)
(gdb) n 执行下一条语句,调用函数当做一条语句
(gdb) p i 输出变量i的值
(gdb) i r 输出所有寄存器的值
(gdb) q 退出
二、实验
1、创建文件code.c,然后写入如下C代码:
2、得到对应汇编指令文件code.s:
3、打开code.s:
4、去掉.开头的行:
5、汇编代码分析:
6、结果验证(查看寄存器里的值):
odjdump -d code得到code的汇编代码(因为实验楼环境为64位,所以寄存器为%r开头)
使用gdb调试,我们在main函数的返回指令0x400525处设置断点,这时查看%rax的值发现为13,与分析结果一致。
6、结果验证(直接C语言调试):
修改源代码如下:
gdb调试,b 12在12行设置断点,r运行发现12行a=f(9)+1正要执行,n执行这条指令,p a输出a的值为13,符合分析结果。
三、总结
这次实验用到了gcc和gdb的常用命令,涉及到汇编及其指令执行时内存和寄存器的变化。
通过这次实验,了解了计算机执行程序时的内部原理:
1、每条指令执行时,先从eip取指令地址,然后从内存地址处取指令执行;
2、eip总是存下一条指令(取指后eip自动指向下条指令地址);
3、eip不能直接修改,只能通过转跳指令call,ret,jmp等间接修改eip,即下条指令的执行地址;
4、call指令执行时,计算机先将下条指令地址eip存入栈中,然后改变eip地址为需要执行的地址;
5、ret指令执行时,计算机出栈恢复执行call时的下条指令地址至eip,执行下条指令相当于返回执行,所以call,ret总是成对的;
6、ebp往往作为一个函数的基地址,esp作为栈顶地址,所以函数开始往往有push %ebp,mov %esp,%ebp两条指令;
7、leave与第6条相对,往往在ret前出现,相当于pop %ebp,mov %ebp,%esp两条指令,恢复调用前的基址和栈顶指针。