八叶一刀·无仞剑

万物流转,无中生有,有归于无

导航

从一段代码的汇编看计算机的工作原理

Posted on 2015-03-05 11:47  闪之剑圣  阅读(2472)  评论(0编辑  收藏  举报

朱宇轲 + 原创作品转载请注明出处 + 《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存储返回值等。它们贯穿于整个程序执行流程,自己写程序时一般不要轻易改动。