程序的机器级表示

来源:互联网 发布:完美卸载软件评价 编辑:程序博客网 时间:2024/05/18 21:42

  之前一段时间一直在看《深入理解计算机系统》这本书,因为看的进度比较慢吧,到现在也只看完里第三章,也就是程序的机器级表示。之前有一点点的汇编基础,所以这章看起来不算特别困难。如题所示,程序的机器级表示,这章的重点就是教我们如何用计算机的视角来看待一段代码。

先上一段简单的代码:

int sum(int x, int y){    int t;    t = x+y;    return t;}

一个简单的加法,下面是它的汇编代码以及机器级代码
00000000 <sum>:
   0:    55                       push   %ebp
   1:    89 e5                    mov    %esp,%ebp
   3:    83 ec 10                 sub    $0x10,%esp
   6:    8b 45 0c                 mov    0xc(%ebp),%eax
   9:    8b 55 08                 mov    0x8(%ebp),%edx
   c:    8d 04 02                 lea    (%edx,%eax,1),%eax
   f:    89 45 fc                 mov    %eax,-0x4(%ebp)
  12:    8b 45 fc                 mov    -0x4(%ebp),%eax
  15:    c9                       leave  

  16:    c3                       ret   
左边的是机器指令,右边是汇编指令,第一个指令push,表示压栈。这段代码只是一个子函数,在主函数调用子函数的时候需要将后面的内容压栈,在结束子函数后再从栈中弹出之后的代码,以保证程序的顺序进行。之前的数字表示指令长度。

第二行定义栈帧指针,因为栈是向低地址方向生长,所以第一个SUB指令是定义栈的大小,之后两个mov指令分别表所x和y。在C语言中只有一句int就定义了两个变量,但是这两个变量定在什么位置我们不知道,通过这个汇编指令可以看出来,x定义在段寄存器bp+0xc上,y定义在bp+0x8上,中间差了四个字节,刚好是int类型的大小,如果定义的是long int那么中间就会差8个字节,其他的类型也一样,是相应的大小。x和y保存在ax和bx两个寄存器中,这里并没有给x和y赋值,那么x和y的值也就算ax和bx当前的值。这就很好理解了在变量不赋值直接使用的时候会出现一些奇怪的值。我觉得这种寻址方式很像指针,x和y都是一个指针,int型,指向数据存放的地址,也就是bp+0x8和bp+0x8这两个地址。

第三条lea指令,load effective address。实际上是mov指令的变形,加载储存器数据到寄存器,但是实际上它没有用到储存器。在这里是作普通的算数操作。 lea   N(寄存器A,寄存器B,S)是一种很巧的寻址方式,这里S表示比例寻址,这里结果是寄存器A的值+寄存器B的值*S+N,比例多重变址寻址方式,S必须为1,2,4,8这些数。假如,需要将寄存器A的值乘五怎么办?看这个指令 lea (寄存器A,寄存器A,4)这样不就是A+A*4=A*5了吗?这条指令也就是把寄存器ax和bx相加把值存入ax当中。

最后两个mov指令有些没看懂,我猜测-0x4(%ebp)是变量t的地址,第一个mov指令将x和y 的值赋给t,第二个mov指令是因为这个函数有个返回值,返回值不能是个地址,只能返回一个寄存器,将寄存器里的值返回。如果在sum函数里直接返回x+y那么应该就没有这两个mov指令,直接进行返回了。

现在是机器指令,在左边的那些指令就是机器指令,第一个push的指令的机器指令就是55。

那些机器指令在寄存器描述如下,这里采用Y86指令方式描述

字节                                        0      1      2     4     5                  

halt                                        00                                                    

nop                                        10

rrmovl  rA,rB                       20  rA rB

irmovl   V,rB                        30   F rB  【      V       】

rmmovl   rA,D(rB)                40   rA rB【       D      】

push      rA                             A0   rA F

数字       寄存器名

0                ax

1                cx

2                dx

3                bx

4                sp

5                bp

6                si

7                di

F               无寄存器

那第一个push指令用Y86指令描述则是:A05F

第二个mov指令:2045

第三个SUB指令我没写出来,SUB对应的机器指令为61,这条指令对应的指令为:30F410000000

这里操作指令为30F0,后面的那串数字是0x10的十六进制表示为0x00000010,以反向写的顺序为10 00 00 00,于是那个减法指令为30F410000000.

那第三个就很简单了,对应的指令为:50500C000000

lea指令我没在Y86指令集中看到,在我看到的例题中也没有这种比例变址寻址方式的Y86指令,这里不会。

最后就是那个负数的指令,负数的表示形式为F4FFFFFF,指令为:4005F4FFFFFF


通过这一章的学习,了解了从机器的角度来看程序,一个程序的执行,首先需要一个程序段,在段中执行代码。一般C语言中的主函数main函数,表示一个程序的入口,标识符为main,告诉机器是从这个程序执行,然后逐步往下执行程序。如果在程序中遇见了其他函数的调用,则将main函数剩下的部分压栈,然后调用函数,在调用函数的最后有个ret指令,这个表示将之前压栈的部分从栈中弹出,即使是在多重调用的时候,如递归函数,ret只返回前一个函数的栈。这样一层一层往上弹出,直到主函数,再继续往下执行。这里关于栈的规则还不是很明白,比如能够调用多少个栈,计算机能够调用的空间在哪里,这些还不是很明白。还从机器指令的角度看程序,也对机器码有了一些初步的了解,指令的执行也就是寄存器的调用,与功能的设置,我想再往下就是硬件设计了吧。

这是我的第一篇博客,本想第一篇应该写点感慨什么的,结果直接把这段时间的学习写了上来。也不知道会不会有人看,但是也还是要欢迎有朋友指正一下错误。