计算机组成原理

要查看目标代码(.o文件),最经常使用的使用反汇编器。在Linux中是命令objdump -d file.o能够调用程序OBJDUMP充当这个角色。可是它产生的相似汇编代码格式的文本和由gcc生产的汇编代码的字节序列有细微差异,好比前者省略了表示大小的后缀。函数

数据格式oop

Intel一般用”字“来表示16位的数据类型,所以,32位数为”双字"。注意:虽然汇编代码中,都是使用后缀“l"表示4字节的整数和8字节的双精度浮点数,可是不存在歧义,由于浮点数使用的是彻底不一样的指令的寄存器。测试

在IA32的CPU中有8个32位的寄存器。大多数指令不以固定的寄存器做为源/目标寄存器。不过,%ebp(Extended Base Pointer)和%esp(Extended Stack Pointer,栈顶指针)保存着指向程序栈(在IA32中,程序栈被放在存储器的某一个区域中并向下增加)的重要位置的指针,按照惯例,%eax(Extended Accumulator Register)一般用来存储函数返回值和累加器。此外,咱们还能够单独地读取某些寄存器的低位字节。好比字操做指令能够只读写寄存器的低16位,其他的字节不会改变。编码

简单介绍MOV类指令spa

movl 传送双字
movwl 将作了符号扩展的字传送到双字
movwl 将作了零扩展的字传送到双字
pushl S

R[%esp] <--- R[%esp] + 4翻译

M[R[%esp] <---  S指针

popl D

D <--- M[R[%esp]];blog

R[%esp] <--- R[%esp] + 4get

 

 

 

 

 

 

 

 

算术和逻辑操做编译器

leal(load effective address)最后的”l"比较迷惑人,看起来像是处理双字大小的,然而实际上leal指令没有大小变种,不像add类,mov类指令。leal其实是movl的变种,指令形式是从存储器读数据到寄存器,如,可是并无真正引用存储器,它只是计算了一下地址,如"leal 7(%edx,%edx,4), %eax",若%edx存储的是x,则就将寄存器%eax设置为x+4x+7=5x+7。形式化能够写做:leal S, D意为D<---&S,加载有效地址。

SAL  k, D 左移
SHL  k, D 左移(等价于SAL)
SAR  k, D 算术右移
SHR  k, D 逻辑右移

 

 

 

 

控制

条件码

除了整数寄存器,CPU还维护了一组单个位的条件码寄存器,他们描述了最近的算术或逻辑运算操做的属性。咱们能够经过检测这些寄存器的值来执行条件分支指令。

CF:进位标志。最近的操做使最高位产生了进位。能够用来检查无符号操做数的溢出。

ZF:零标志。最近的操做得出的结果为0。

SF:符号标志。最近的操做获得的结果为负数。

OF:溢出标志。最近的操做致使一个补码的溢出——正溢出或负溢出。

假设执行了一个加法运算t=a+b(t,a,b都是整型),能够根据以下表达式来设置条件码:

CF (unsigned) t < (unsigned) a 无符号溢出
ZF (t == 0)
SF (t < 0) 负数
OF (a < 0 == b < 0) && (t < 0 != a < 0) 有符号溢出

 

 

 

 

注意:leal不改变任何条件码。对于XOR,进位标志和溢出标志会设置为0。对于移位操做,进位操做会被设置为最后一个被移出的位,溢出标志设置为0。这里强调一下CMP和TEST指令。CMP和SUB指令的行为是同样的(CMP S2, S1: S1 - S2),而CMP只改变条件码,不更新目标寄存器。当两个操做数相等时,ZF会被设置为0,其余标志则能够用来判断两个数的大小。TEST和AND(按位与&)指令的行为是同样的,而TEST只改变条件码。当两个操做数是同样的,如指令testl &eax, %eax一般被用来检查%eax是正数、负数仍是零。然而条件码一般不会直接读取。咱们经过SET类指令针对条件码的不一样组合而设置值。SET类指令的操做数只能是单字节寄存器或一个字节的存储器位置。

JMP类指令当执行于PC(programming counter)寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令自己的地址。好比

jb指令的跳转目标是0x8048340,其对应的目标编码为72 0xe7(72是jb指令的表示,0xe7是-25的补码表示)。所以0x8048359-25=0x8048340。

条件分支

C语言中的if-else语句的通用形式模板:

if(test-expr)

  then-statement

else

  else-statement

 对于这种通用形式,汇编实现一般会使用下面这种形式

t = test-expr

if(!t)

  goto false;

then-statement

goto done;

false:
  else-statement

done:

举例说明,如下是一段C语言对应的反汇编代码

x at %ebp+8, y at %ebp+12
1   movl  8(%ebp), %eax 将地址为%ebp+8的值转移到%eax(x)中
2   movl  12(%ebp), %edx 将地址为%ebp+12的值转移到%edx(y)中
3   cmpl  $-3, %eax 将x与-3进行比较
4   jge   .L2   若x大于或等于-3,则跳转到L2
5   cmpl  %edx, %eax 将x与y进行比较
6   jle   .L3 若x小于或等于y,则跳转到L3
7   imull  %edx, %eax %eax = %eax * % edx (x * y)
8   jmp   .L4 无条件跳转到L4
9 .L3:  
10   leal  (%edx, %eax), %eax %eax = %eax + %edx (x + y)
11   jmp  .L4 无条件跳转到L4
12 .L2:  
13   cmpl  $2, %eax 将x与2进行比较
14  jg    .L5 若x大于2,则跳转到L5
15  xorl   %edx, %eax %eax = %eax ^ %edx (x ^ y)
16  jmp   .L4 无条件跳转到L4
17 .L5      
18   subl  %edx, %eax %eax = %eax - %edx (x - y)
19 .L4  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 经过分析汇编代码,咱们能够很容易完成C代码的填空。C代码的第一个if对应于汇编语言的第3行,并注意将条件取反,所以为x<-3,此外,易得汇编语言的L2对应于C代码的八、9行。所以C语言的第8行应填写x<=2,第9行填写x^y,第2行填写x-y。同理,C代码的第4行应该填写y<x,第5行填写x*y,第7行填写x+y。注意:这里的初始化表达式(C代码的第2行)向下移了(移到了汇编代码15行),这样一来,只有当肯定它就是返回值的时候,才会计算它。

循环

C语言提供的多种循环结构,即do-while、while和for循环。由于汇编没有相应的指令存在,可是可使用条件测试和跳转组合结合起来实现循环的效果。大多数的编译器根据一个循环的do-while形式来产生循环代码。其余的循环会首先被转换成do-while形式,而后再翻译成机器代码。do-while循环的通用形式以下:

do

  body-statement

  while(test-expr);

能够看到,body-statement至少会被执行一次。上述的do-while的通用形式能够翻译为以下的条件和goto语句

loop:

  body-statement

  t = test-expr;

  if(t)

    goto loop;

 

while语句的通用形式以下

while(test-expr)

  body-statement

将while循环翻译成机器代码有不少方法,采用gcc的策略,使用条件分支,将其转换为do-while循环,以下

t = test-expr;

if(!t)

  goto done;

loop:

  body-statement

  t = test-expr;

  if(t)

    goto loop;

done:

for循环的通用形式以下

for (init-expr; test-expr; update-expr)

  body-statement

把它翻译为goto代码为

  init-expr;

  t = test-expr;

  if(!t)

    goto done;

loop:

  body-statement

  update-expr;

  t = test-expr;

  if(t)

    goto loop;

done:

相关文章
相关标签/搜索