朱宇轲 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
/*-------------------------------------以下内容是课堂笔记,咿呀咿呀呦!------------------------------------------*/
本课主要对计算机的运行原理和汇编语言进行了简单的介绍。
冯若依曼体系结构即存储程序计算机,也就是将程序写在内存中,由CPU通过总线从内存中读取一条条程序,根据程序的内容执行具体的步骤。
如图所示
CPU在读取指令时,通过寄存器IP来指向下一条指令(如果是32位系统,则为EIP)
CPU的寄存器分为通用寄存器、段寄存器、状态寄存器
四种寻址方式:
movl %eax,%edx edx=eax 寄存器寻址
movl $0x123,%edx edx=0x123 立即寻址
movl 0x123,%edx edx=*(int32_t*)0x123 直接寻址
movl (%ebx),%edx edx=*(int32_t*)ebx 间接寻址
movl 4(%ebx),%edx edx=*(int32_t*)(ebx+4) 变址寻址
了解pushl、popl、call 0x12345、ret命令
注意:IP寄存器一般不能随便修改,只能通过call、ret等命令更改!
函数的返回值默认使用EAX寄存器存储返回给上一级函数
/*-------------------------以下内容是实验分析,咿呀咿呀呦!------------------------------------------*/
首先写下这么一段C程序:
1 //linux.c 2 int g(x) 3 { 4 return x+3; 5 } 6 int f(x) 7 { 8 return g(x); 9 } 10 int main() 11 { 12 return f(10)+1; 13 }
在Linux的环境中输入如下指令:
gcc –S –o linux.s linux.c -m32
然后打开linux.s,就可以看到我们汇编后的代码(直接上截图了)
将里面以“.”开头的行去掉(这是为链接用的),得到汇编后的代码:
1 g: 2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 addl $3, %eax 6 popl %ebp 7 ret 8 f: 9 pushl %ebp 10 movl %esp, %ebp 11 subl $4, %esp 12 movl 8(%ebp), %eax 13 movl %eax, (%esp) 14 call g 15 leave 16 ret 17 main: 18 pushl %ebp 19 movl %esp, %ebp 20 subl $4, %esp 21 movl $10, (%esp) 22 call f 23 addl $1, %eax 24 leave 25 ret
接下来我们来分析一下改程序具体的流程。
程序一开始,CPU的IP寄存器指向汇编代码的第18行,假设堆栈在内存中的地址分别为0,1,2,3……堆栈基指针寄存器(EBP)和堆栈顶指针寄存器(ESP)均指向堆栈段0处。
第18~21行首先为main函数开辟新的内存区域,之后将传的参数10入栈,此时堆栈段如下所示:
然后程序调用call 函数,将IP入栈,IP指向代码第9行f处。
在f函数的代码处,首先为f函数开辟新的内存区域,接着将传入的参数10赋值给EAX,并将EAX入栈,此时堆栈段内存如下图:
程序在此调用call进入g函数。在g函数中,同样先是开辟内存空间,然后将参数传给EAX,并将EAX的值加上3。
之后将EBP出栈,并调用ret命令。此时IP重新指向f函数call之后的命令,堆栈内存的情况如下:
之后就是不断的调用leave与ret命令,跳出当前的内存区域,回到上一级函数的内存区域中,并将EAX的值加3,直到跳出main函数,至此程序结束。
从上面的分析中,我觉得可以归纳出以下几点:
1.计算机的运行流程确是遵循冯诺依曼框架,CPU将内存中的代码和数据读取到自己的寄存器中,再根据一条条命令调用寄存器进行进一步的操作。
2.在进入每一个程序之前,CPU都会将上一级的EIP和EBP压栈,相当于为新的函数重新开辟了一段新的内存空间,直到退出函数的时候才将它们出栈。与此同时,将函数的返回值保存在EAX中。
3.CPU的各个寄存器都有不同的分工,如EIP指向要执行的代码,EAX存储返回值等。它们贯穿于整个程序执行流程,自己写程序时一般不要轻易改动。