[关闭]
@gain 2016-02-28T12:47:06.000000Z 字数 1689 阅读 1492

实例剖析一个C程序的运作原理

汇编 Linux


文章注明:干宇航 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

实验环境:Windows10 下Virtual Box Linux 64bit(Ubuntu 15.10) + putty
实验环境截图:

C代码

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

然后创建main.c

然后通过如下选项编译,

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

得到main.s,并精简代码,得到。

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

下面来分析这段汇编代码的工作过程中堆栈的变化
在分析的过程中,用这张图描述堆栈,每个一个单位之间相差8字节。

C语言,当然是先执行main函数喵:

  1. main:
  2. pushl %ebp
  3. movl %esp, %ebp
  4. pushl $6
  5. call f
  6. addl $4, %esp
  7. addl $5, %eax
  8. leave
  9. ret

pushl %ebp 压栈操作,这里pushl %ebp相当于做了两步
subl $4,%esp(esp(栈底指向1))和movl %ebp,(%esp)(esp所指向的内存单元存入ebp的地址)
于是,栈变成了这个样子,

movl %esp, %ebp将ebp指向esp

pushl $6
esp压栈,并存储一个立即数6

call f 相当于 pushl %eip*movl f ,%eip*
eip是指令寄存器,则将指令寄存器eip压入下一个内存单元,并将eip指向f

我们来看向f

  1. f:
  2. pushl %ebp
  3. movl %esp, %ebp
  4. pushl 8(%ebp)
  5. call g
  6. addl $4, %esp
  7. leave
  8. ret

依然是压栈,并将ebp指向esp

  1. pushl %ebp
  2. movl %esp, %ebp


pushl 8(%ebp)变址寻址,将6存入esp中。这相当于函数的传值功能,将main函数内的6传到f函数中。

  1. call g

将指令寄存器eip压入下一个内存单元,并将eip指向g

  1. g:
  2. pushl %ebp
  3. movl %esp, %ebp
  4. movl 8(%ebp), %eax
  5. addl $12, %eax
  6. popl %ebp
  7. ret
  1. pushl %ebp
  2. movl %esp, %ebp

依然压栈,esp中存入ebp的地址,ebp再指向esp,

  1. movl 8(%ebp), %eax
  2. addl $12, %eax

将5所指向的内存单元中的数值6(变址寻址)赋值给变量eax(函数传值)

notes:函数值得返回值默认使用eax寄存器存储返回给上一级函数

然后eax中的值加上12。
eax = 12+6=18

然后popl %ebp 相当于 movl (%esp),%ebp(把esp中存储先前ebp的值传给ebp,意思是说ebp重新指向4,)和addl $4,%esp(esp指向上一个内存单元)

g函数的最后return了
ret
这步相当于pop %eip*,也就是movl (%esp),%eip(eip指向f了现在,就是call g之后的语句)和addl $4,%esp

addl $4,%esp出栈

leave相当于 movl %ebp,%esp(esp指向ebp)并popl %ebp(ebp 指向1,esp指向3)

ret
eip指向main函数中call f之后的语句,esp指向2

  1. addl $4, %esp
  2. addl $5, %eax
  3. leave
  4. ret

addl $4, %espesp 指向1,addl $5, %eaxeax的值加上5,现在是23.
leave,esp指向ebp,都是1,然后popl,都指向0

ret可能后面还有栈。

总结,看着这张动图,整个指令流过程了然于胸。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注