linux进程堆栈分析,C程序运行堆栈分析

最近在上孟宁老师的《Linux内核分析》,本文是该课程的实验作业,通过分析汇编代码来理解C程序在计算机中是如何工作的。分析的实验代码如下:

3706444923.png

右边为通过gcc -S main.c -o main.s -m32命令转成的x86汇编代码,下文分析以右边代码为准

C代码

int g(int x) {

return x + 31;

}

int f(int x) {

return g(x);

}

int main(void) {

return f(52) + 33;

}

x86汇编代码

g:

pushl %ebp

movl %esp, %ebp

movl 8(%ebp), %eax

addl $31, %eax

popl %ebp

ret

f:

pushl %ebp

movl %esp, %ebp

subl $4, %esp

movl 8(%ebp), %eax

movl %eax, (%esp)

call g

leave

ret

main:

pushl %ebp

movl %esp, %ebp

subl $4, %esp

movl $52, (%esp)

call f

addl $33, %eax

leave

ret

由于C程序的入口为main函数,所以这段代码的起始点为第17行,eip(Extended Instruction Pointer, 指令寄存器)指向第18行(eip指向下一条指令)。程序在启动时,系统会为程序分配一个堆栈空间,此时程序的堆栈为空,ebp(Extended Base Pointer, 栈基指针寄存器)和esp(Extended Stack Pointer, 栈指针寄存器)都指向栈底。这里使用的内存堆栈模型为更常见的由下至上,而非课程视频中由上之下的结构。同时需要注意的是,右边的数字并非内存的实际地址,这里只是将内存做了简单的编号。

2744900993.png

18 pushl %ebp,将ebp寄存器中的值压栈,同时esp向上移一个单位

3622399494.png

19 movl %esp, %ebp 将esp中的至赋值给ebp。此时,esp和ebp都指向1

1798510657.png

20 subl $4, %esp 这条指令的直接作用是将esp中的值减去4,然后把结果存回esp中。这里需要说明两点:

这里的4指的是4个字节,也就是内存中真实地址移动4个单位(相当于本文模型中的1个单位)

因为栈是向低地址扩展的数据结构。对应本文内存模型就是,1的地址比0要小4个单位,2的地址比1要小4个单位,以此类推。这也是为什么这里用了减法指令

所以这条指令的执行结果就是将esp指向2

4001523847.png

21 movl $52, (%esp) 这条指令的含义是将52这个数传入esp指向的内存地址中,也就是内存2

876695750.png

22 call f call是一个宏指令,其对应的两个指令为pushl %eip和movl f, %eip。上面说过eip的指代表下一条指令的位置,这里也就是第23行代码(记作EIP23)。pushl %eip就是将EIP23压栈,然后通过movl f, %eip将f函数的地址(EIP8)传入eip,使得下一条指令从f函数开始,从而实现C函数的调用。

2475439254.png

9 pushl %ebp 将ebp的值入栈,也就是将EBP 1放入内存4中,同时esp上移一个单位

2800336634.png

10 movl %esp, %ebp 将esp的值传入ebp中,此时esp 和 ebp同时指向内存4

1042347267.png

11 subl $4, %esp 将esp上移一个单位,指向内存5

4283974771.png

12 movl 8(%ebp), %eax 8(%ebp) = (8 + %ebp) 也就是ebp指针下移两个单位,指向内存2,然后将内存2中的值(也就是52)传入eax(Extended Accumulator X,累加寄存器)。这条指令执行完后堆栈中并无变化,只是将52这个数传给了eax

13 movl %eax, (%esp) 将%eax中的数值传入%esp指向的内存位置(内存5)

1357720786.png

14 call g 同样的,call相当于pushl %eip和movl g, %eip,此时eip指向第15条指令(记作EIP15)

513153701.png

2 pushl %ebp 将ebp的值入栈

88037499.png

3 movl %esp, %ebp 将esp的值传入ebp,执行后ebp和esp都指向内存7

2505911286.png

4 movl 8(%ebp), %eax 将内存5中的数据(也就是52)传入eax。此时堆栈不变化

5 addl $31, %eax 将eax中的数据加上31,并把结果存入eax,所以此时eax中的值为83(52+31)

6 popl %ebp 将栈顶的数据弹出,并传入ebp,所以执行后ebp指向内存4。同时esp下移一个单位,指向内存6

2294328226.png

7 ret ret也是一个宏指令,实际执行的效果为popl %eip,就是将栈顶的数据传入eip,同时esp下移一个单位,此时eip指向第15行指令

2370156542.png

15 leave leave指令对应movl %ebp, %esp和popl %ebp,先将ebp的值传入esp,执行后ebp和esp都指向内存4,然后将内存4的数据弹出并传入ebp中。所以执行leave执行后ebp指向内存1,esp指向内存3

3644390037.png

16 ret 也就是popl %eip,执行后eip指向第23行代码,esp指向内存2

3333359282.png

23 addl $33, %eax 将33累加到eax中,结果为116(83+33)

24 leave 即movl %ebp, %esp和popl %ebp,执行后ebp和esp均指向内存0。至此,改程序的堆栈又重新变为空栈

2744900993.png

25 ret 该程序执行结束,通过popl %eip将eip指向上个程序的指令

总结:通过分析可以看出,C语言其实是对汇编语言做了一层抽象,以方便程序员编写和阅读代码。计算机在执行程序时,也只能按部就班的逐条执行,这中间其实多了很多看似繁琐的过程。比如每次进入一个函数,都要先保存ebp指针。同时系统分配给每个程序的栈空间是有限的,如果调用的函数过多,则会导致栈溢出,引发程序异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值