这是网易云课堂linux内核分析课程的实验报告linux
实验的内容是经过编写一段简单的c程序,并分析其汇编代码,以了解计算机是如何运行程序。vim
程序hello.c源代码:bash
1 #include <stdio.h> 2 3 int bar(int a) 4 { 5 return a; 6 } 7 8 int foo(a) 9 { 10 return bar(a) + 1; 11 } 12 13 int main() 14 { 15 return foo(10) + 1; 16 }
在bash下输入以下指令,得到源代码的汇编hello.s:函数
gcc -S -o hello.s hello.c -m32
因为本次试验只须要用到源代码对应的汇编指令,因此能够把hello.s中以.
开头的行删除。在vim下可使用:spa
:%s/\s*\..*//g #将以"."开头的行换为空行 :%g/^$/d #删除空行
操做后获得如下代码:code
1 bar: 2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 popl %ebp 6 ret 7 foo: 8 pushl %ebp 9 movl %esp, %ebp 10 subl $4, %esp 11 movl 8(%ebp), %eax 12 movl %eax, (%esp) 13 call bar 14 addl $1, %eax 15 leave 16 ret 17 main: 18 pushl %ebp 19 movl %esp, %ebp 20 subl $4, %esp 21 movl $10, (%esp) 22 call foo 23 addl $1, %eax 24 leave 25 ret
下面对汇编代码进行分析,从main函数开始:
1八、19行的pushl
和movl
指令至关于enter
指令,用于保存前一个栈的信息,同时为main函数开辟一个空栈。pushl
将前一个栈的基地址保存,movl
将ebp
赋值为前一个栈的栈顶,同时也是main栈的基地址。esp
做为main栈的栈顶。完成了上面两部,main栈就算建成了。接下来开始执行源代码里的东西了。ip
20、21行。subl
为函数的栈开了空间,用来存放foo须要的参数。movl
将参数放在该空间。而后执行call
指令,跳到foo函数中,也就是eip
要变成foo的地址。注意call
指令要把当前eip
压栈(pushl %eip
)。而后把目光转到foo函数中,也就是第7行。和main函数开头同样,须要保存上一个栈的信息,同时为本身开创一个栈。pushl
和movl
指令作了这件事。接着放置bar须要的参数在本身的栈中(就是刚刚在mian函数时放的那个参数)。完成后执行call
指令跳到bar函数。内存
bar函数终于再也不调用别的函数,而是获取foo给的参数(第4行,这时候ebp就是foo栈的栈顶)。将参数赋值给eax
后进行结束函数的工做。第5行的popl
指令将esp
加4,变为前一个栈(foo)的栈顶;ebp
得到出栈的内容,也就是前一个栈的基址。foo的栈完成恢复。最后ret
指令跳回foo函数中(至关于popl %eip
),bar函数就正式结束了。回到foo函数后,根据源代码要给返回值加1(14行),紧接着要结束foo。和结束bar函数过程同样,结束foo函数就是执行leave
而后回跳到main中。main函数为返回值加1(23行),以一样的流程退出main函数。程序结束。get
注意1:函数的栈在内存中是从高地址向低地址增加的,因此pushl
后esp
要减4,而popl
后esp
要加4
注意2:leave
指令至关于movl %esp %ebp
,popl %ebp
。
因为bar的代码没有使用到变量,因此在建立好函数的栈后,esp
和ebp
是相等的,不须要leave指令。函数foo和main中建立了变量,并存放在栈中,修改了esp,所以退出函数时要还原esp的。因此在退出函数的时候要使用leave
。
注意3:call
指令压栈的内容在调用函数的栈中。毕竟call
指令执行在开辟新的函数栈指令以前。it
理解:
程序就是由一条条汇编代码组成。这些汇编代码执行了运算,使用了内存空间。c语言中的函数,就是汇编中将当前寄存器保存到内存中,而后转跳到另一处执行,执行完成后跳回原来地方,并恢复寄存器内容。而c语言屏蔽了这一过程,提供了抽象,咱们只须要专一于函数要实现什么功能,不须要关注如何实现函数。
过程截图
xxtsmooc
原创做品转载请注明出处
《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000