《Linux内核分析》课程第一次作业
(曹越+原创作品转载请注明出处+《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000)
程序的正确执行靠堆栈来维持,堆栈是一块保存数据的连续内存。一个名为堆栈指针(ESP)的寄存器指向堆栈的顶部,一个名为堆栈指针(EBP)的寄存器指向堆栈的底部。堆栈的底部在一个固定的地址。堆栈的大小在运行时由内核动态地调整。CPU实现指令PUSH和POP, 向堆栈中添加元素和从中移去元素,而且要注意的是堆栈是向低地址增长的。
下面我们通过一个简单的例子来说明程序是如何执行的:
main.c
为了理解程序在调用时都做了哪些事情, 我们使用gcc的-S选项编译, 以产生汇编代码整理输出:
main.s:
一些汇编代码简单说明 :
(main函数)
- pushl %ebp 将帧指针ebp压入栈中
- movl %esp,%ebp 然后把当前的esp复制到ebp, 使其成为新的帧指针
- subl $4,%esp 将栈顶指针esp的值减4
- movl $17,(%esp) 指将参数17压入栈
- call f 先把eip压入栈,在跳转到f处去执行
- addl $2,%eax 指函数返回值存在寄存器eax中,并对其值加2
- leave其实是一条宏汇编指令,相当于movl %ebp,%esp、popl %ebp两条指令回收栈空间,然后ebp出栈
- ret相当于popl %eip,指令寄存器eip出栈
(f函数)
- pushl %ebp 将帧指针ebp压入栈中(old-ebp)
- movl %esp,%ebp 然后把当前的esp复制到ebp, 使其成为新的帧指针
- subl $4,%esp 将f函数栈顶地址减4
- movl 8(%ebp),%eax 间接寻址,将ebp寄存器加8的位置里面存放的内容移到eax寄存器中
- movl %eax,(%esp) 将eax值写入堆栈
- call g 先把eip压入栈,在跳转到g处去执行
- leave 相当于movl %ebp,%esp、popl %ebp两条指令回收栈空间,然后ebp出栈
- ret 指令寄存器eip出栈
(g函数)
- pushl %ebp 将帧指针ebp压入栈中(old-ebp)
- movl %esp,%ebp 然后把当前的esp复制到ebp, 使其成为新的帧指针
- movl 0x8(%ebp),%eax 间接寻址,将ebp寄存器加8的位置里面存放的内容移到eax寄存器中
- addl $2,%eax 寄存器eax加2
- popl %ebp 退栈
- ret 返回
总结:存储程序计算机工作流程就是eip不停地取出指令让cpu去执行,函数的调用通过堆栈来实现。看似一个简单C程序也需要经过编译链接之后才能生成可执行程序,这其间的复杂程度远远超出我们的想象。