这是网易云课堂linux内核分析课程的实验报告

实验的内容是通过编写一段简单的c程序,并分析其汇编代码,以了解计算机是如何运行程序。

程序hello.c源代码:

  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下可以使用:

:%s/\s*\..*//g     #将以"."开头的行换为空行
:%g/^$/d           #删除空行

操作后得到以下代码:

  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函数开始:
18、19行的pushlmovl指令相当于enter指令,用于保存前一个栈的信息,同时为main函数开辟一个空栈。pushl将前一个栈的基地址保存,movlebp赋值为前一个栈的栈顶,同时也是main栈的基地址。esp作为main栈的栈顶。完成了上面两部,main栈就算建成了。接下来开始执行源代码里的东西了。

20、21行。subl为函数的栈开了空间,用来存放foo需要的参数。movl将参数放在该空间。然后执行call指令,跳到foo函数中,也就是eip要变成foo的地址。注意call指令要把当前eip压栈(pushl %eip)。然后把目光转到foo函数中,也就是第7行。和main函数开头一样,需要保存上一个栈的信息,同时为自己开创一个栈。pushlmovl指令做了这件事。接着放置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函数。程序结束。

注意1:函数的栈在内存中是从高地址向低地址增长的,所以pushlesp要减4,而poplesp要加4
注意2:leave指令相当于movl %esp %ebppopl %ebp
由于bar的代码没有使用到变量,所以在创建好函数的栈后,espebp是相等的,不需要leave指令。函数foo和main中创建了变量,并存放在栈中,修改了esp,因此退出函数时要还原esp的。所以在退出函数的时候要使用leave
注意3:call指令压栈的内容在调用函数的栈中。毕竟call指令执行在开辟新的函数栈指令之前。

理解:
程序就是由一条条汇编代码组成。这些汇编代码执行了运算,使用了内存空间。c语言中的函数,就是汇编中将当前寄存器保存到内存中,然后转跳到另外一处执行,执行完成后跳回原来地方,并恢复寄存器内容。而c语言屏蔽了这一过程,提供了抽象,我们只需要专注于函数要实现什么功能,不需要关注如何实现函数。

过程截图
源代码和汇编

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


shanyin
129 声望3 粉丝

写代码