Linux内核分析(一)

来源:互联网 发布:soa java 编辑:程序博客网 时间:2024/05/17 07:30

Linux内核分析 —— 【实验一:栈与程序 】

栈,是一种数据结构,是一种有限制的一维线性表。它的特点是“先进后出”,就像是一条只有一人宽的死胡同,先进胡同的人必须等后进胡同的人全部离开后,才能离开。
但栈与程序之间又有什么联系呢?

首先,看一段简单的C程序代码:test1.c

    int g(int x)    {      return x + 2;    }    int f(int x)    {      return g(x);    }      int main(void)    {      return f(5) + 8;    }

1) 在Linux 64位系统环境下,用以下命令将上述代码进行反汇编:
gcc –S –o test1.s test.c -m32

如下图如示
1-1

2)用gedit软件打开test1.s文件:

1-4
1-3
1-2
可以看到三个函数g,f,main对应的汇编代码,下面分析这个程序在栈中是如何执行的。

3)我们都知道main函数是程序的入口,所以先看main函数。注意:test1.s文件中以 ’ . ‘开头的都是编译器所添加的,可以忽略。

程序在内存中栈的增长方式如下:
1-5
每一个函数在调用的时候都会在内存中(也就是栈中)开辟一块属于自己的临时空间,称为一个栈帧,它用于存储该函数运行过程中的临时变量,在函数调用结束后就会清除。程序在执行main函数时,栈的变化为:
1-6
上图(1)中没有标示出ebp的位置,但我们知道它在更高的内存位置上。push ebp 就是把这个位置保存起来,以便于在修改ebp指针后,仍然可以恢复原来的值。
(2)中将当前esp位置赋给了ebp,也就是说栈底指针往下内存地址方向移了一段距离,和栈顶指针在同一位置。
(3)相当于push 5。5 是 f 函数的参数,c 程序函数参数的传递是通过栈来保存的。
(4)调用 f 函数。call f 可以看作是两条指令:push eip和jmp f 。把将要执行的指令地址ret1入栈,然后跳转到 f 函数,实现函数的调用。

1-7
(5)保存ebp1,把参数传给将要调用的函数g。
(6)调用函数g 。
1-8
(7)中绿色代表main函数的栈帧,蓝色是f函数的栈帧,黄色是g函数的栈帧。
(8)函数返回时,通常eax寄存器中的值作为返回值。语句”movl 8(%ebp),%eax” 和”addl $2 , %eax” 这表示将参数的值加2赋给eax,即eax=7。

1-9
(9)(10)中ret 相当于pop eip 。将栈中保存的返回地址赋给eip,使得可以正常返回到原来的调用者f中,而返回值保存在eax寄存器中。
(11)同理,f函数在执行后,将结果保存到eax返回到main函数。在f函数汇编代码中有一条“leave”语句,它相当于“mov ebp,esp”和”pop ebp”两条语句。
(12)在main函数中,语句”addl $8,%eax“则表示将f函数的返回值加上8,结果为eax=15 。再执行一次”leave“栈就被清空了。main函数也是函数,它也有它的调用者,只是我们看不到它的调用者而已,但它也需要”ret“返回到调用者,到这我们的程序就算结束了。

通过以上的例子,我们可以知道栈在程序执行过程中发挥着至关重要的作用,栈这种特殊的数据结构为函数的调用提供了可能,使得程序的执行更加灵活、过程更加直观;也使得编程更加简单、方便;同时,只有理解栈的结构和性质,才能理解程序是如何执行的。


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


2 0
原创粉丝点击