首页 > 系统服务 > 详细

Linux内核分析第一周作业

时间:2017-02-19 12:11:39      阅读:318      评论:0      收藏:0      [点我收藏+]

通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

1. C语言的代码非常简单

 1 int g(int x)
 2 {
 3   return x + 4;
 4 }
 5  
 6 int f(int x)
 7 {
 8   return g(x);
 9 }
10  
11 int main(void)
12 {
13   return f(6) + 2;
14 }

 

2. 直接利用实验楼的Linux环境编译。因为环境是64位的,所以加上了-m32参数指定生成32位程序。

gcc main.c -m32

可以看到生成了一个a.out文件。用objdump看一下格式,果然是32位的

技术分享

3. 用gcc生成汇编代码,直接加上参数-S就可以得到汇编代码,-o可以指定生成文件的文件名

gcc –S –o main.s main.c -m32

技术分享

4. 整理一下main.s文件,只保留汇编指令

 1 g:
 2     pushl    %ebp
 3     movl    %esp, %ebp
 4     movl    8(%ebp), %eax
 5     addl    $4, %eax
 6     popl    %ebp
 7     ret
 8 f:
 9     pushl    %ebp
10     movl    %esp, %ebp
11     subl    $4, %esp
12     movl    8(%ebp), %eax
13     movl    %eax, (%esp)
14     call    g
15     leave
16     ret
17 main:
18     pushl    %ebp
19     movl    %esp, %ebp
20     subl    $4, %esp
21     movl    $6, (%esp)
22     call    f
23     addl    $2, %eax
24     leave
25     ret

 5. 分析代码的运行。纸上谈兵不如实际运行,直接用gdb来单步调试刚才编译的a.out

gdb ./a.out

 

看一下main函数的前3条汇编

技术分享

和main.s里面的一样。

 

但是因为main并不是程序真正的入口(linux真正的入口是_start),为了观察main函数的运行,首先在main函数第一条汇编的地址处加断点

技术分享

然后打开disassemble-next-line开关,每次停下的时候就会自动显示当前的汇编指令。

set disassemble-next-line on

 

运行程序,就会停在断点处

技术分享

 

 察看一下当前的寄存器值

 技术分享

重点关注esp = 0xffffd48c,ebp=0,eip=0x804840b,后续步骤也只列出这几个寄存器。

 

然后单步运行一条汇编指令

技术分享

esp = 0xffffd488,ebp=0,eip=0x804840c

看到因为上一条指令将ebp压栈,esp向下增长了4字节,eip跳到下一条指令。看一下esp指向的内容

技术分享

确实是ebp的值。

 

 继续单步

技术分享

esp = 0xffffd488,ebp=0xffffd488,eip=0x804840e(%esp)=0

 技术分享

esp = 0xffffd484,ebp=0xffffd488,eip=0x8048411,(%esp)=0

这两步将调用f的参数压栈

 技术分享

esp = 0xffffd484,ebp=0xffffd488,eip=0x8048418,(%esp)=6

接下来不能再用ni了,ni(next instruction)的作用类似于高级语言调试的step over,继续ni会直接把f的调用运行完。这里要用si(step instruction),即高级语言的step into。

技术分享

esp = 0xffffd480,ebp=0xffffd488,eip=0x80483f8,(%esp)=0x0804841d

 可以看出call指令把下一条指令的地址压入栈内

技术分享

esp = 0xffffd47c,ebp=0xffffd488,eip=0x80483f9,(%esp)=0xffffd488

技术分享

esp = 0xffffd47c,ebp=0xffffd47c,eip=0x80483fb,(%esp)=0xffffd488

这两步保存并移动栈顶,从而保护上一个栈帧

 

技术分享

技术分享

技术分享

 esp = 0xffffd478,ebp=0xffffd47c,eip=0x8048404,eax=6,(%esp)=6

这三步取出了在上一个栈帧(main)中保存的函数入参6,并存在eax中,接下来把该值压栈,作为下一个调用的参数。

 

 技术分享

esp = 0xffffd474,ebp=0xffffd47c,eip=0x80483ed,(%esp)=0x08048409

 技术分享

esp = 0xffffd470,ebp=0xffffd470,eip=0x80483f0,(%esp)=0xffffd47c

进入g函数,保存返回地址、保护f的栈帧,都是套路

 

技术分享

esp = 0xffffd470,ebp=0xffffd470,eip=0x80483f3,eax=6,(%esp)=0xffffd47c

 从前一个栈帧中取出入参,并存入eax。接下来进行g函数中的运算a+4,结果仍然在eax中

 技术分享

esp = 0xffffd470,ebp=0xffffd470,eip=0x80483f6,eax=10,(%esp)=0xffffd47c

 下面开始一层一层返回,先恢复f的栈顶

技术分享

esp = 0xffffd474,ebp=0xffffd47c,eip=0x80483f7,eax=10,(%esp)=0x08048409

 再恢复eip返回f

技术分享

esp = 0xffffd478,ebp=0xffffd47c,eip=0x8048409,eax=10,(%esp)=6

 继续恢复main的栈顶

技术分享

esp = 0xffffd480,ebp=0xffffd488,eip=0x804840a,eax=10,(%esp)=0x0804841d

  再恢复eip返回main

技术分享

esp = 0xffffd484,ebp=0xffffd488,eip=0x804841d,eax=10,(%esp)=6

 接下来进行main中的计算

技术分享

esp = 0xffffd484,ebp=0xffffd488,eip=0x8048420,eax=12,(%esp)=6

 最后退出main

技术分享

 

6. 总结

从这个小程序的运行过程可以看出函数调用、堆栈保护的机制。和c语言对比一下,可以看出编译器替我们做了很多工作。

另外也练习了GDB的使用,以前没有用GDB调试过程序,都是套着IDE用的。作为一个开发调试器的人感到很惭愧~

顺便给个彩蛋,这是编译器-O2优化后的汇编代码  _(:з」∠)_

技术分享

 

 

王岩

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

Linux内核分析第一周作业

原文:http://www.cnblogs.com/cscat/p/6414785.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!