6.IA32指令

不需要记住,查手册

但是要理解手册在说什么

6.1 传送指令

6.1.1 常用传送指令

  • 通用数据传送指令

    • MOV,等长的传送movb,movw,movl

    • MOVS,不等长的传送8->16,高8位用符号位扩展movsbw,movswl

    • MOVZ,0扩展(无符号扩展)高位补0,movzwl, movzbl

    • XCHG,数据交换,类似于两条MOV实现的功能

    • PUSH/POP,出入栈,如pushl, pushw, popl, popw

  • 地址传送

    • LEA,加载有效地址

      • leal (%edx,%eax), %eax 实现的功能 R[eax] <- R[edx] + R[eax]

  • 输入输出指令

  • 标志传送指令

    • PUSHF,POPF,

用出入栈来研究指令

栈(Stack),不等于堆栈

pushw %ax

R[sp] <- R[sp] - 2

M[R[sp]] <- R[ax]

popw %ax

R[ax] <- M[R[sp]]

R[sp] <- R[sp] + 2

前面介绍过,程序由指令序列组成,一个简单的函数进行反汇编

#include <stdio.h>

int add(int i, int j)
{
  int x = i + j;
  return x;
}
0000000000000000 <add>:
   0:	f3 0f 1e fa          	endbr64 
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	89 7d ec             	mov    %edi,-0x14(%rbp)
   b:	89 75 e8             	mov    %esi,-0x18(%rbp)
   e:	8b 55 ec             	mov    -0x14(%rbp),%edx
  11:	8b 45 e8             	mov    -0x18(%rbp),%eax
  14:	01 d0                	add    %edx,%eax
  16:	89 45 fc             	mov    %eax,-0x4(%rbp)
  19:	8b 45 fc             	mov    -0x4(%rbp),%eax
  1c:	5d                   	pop    %rbp
  1d:	c3                   	ret  

理解了程序是指令序列。

函数从首地址开始执行,把函数地址送到EIP里面,就开始执行了。

根据EIP取指令,指令译码,一步一步往前走。

6.1.2 指令传送过程

第一条指令

   4:	55                   	push   %ebp

执行函数的第一件事情去取指令,4这个地址,EIP寄存器接通到地址总线,并且CPU发出读命令,控制信号也送给存储器,存储器收到信号把制定的存储单元接通到数据总线上,此时数据总线在CPU一侧是接通到指令寄存器里的。

指令寄存器里的各个字段又分别送到不同的控制器,解析op的内容,产生很多控制信号控制对应的数字电路动作。比如说这个指令就是接通ebp到数据总线,然后把栈指针寄存器接通到地址总线,给出存储器写信号,就写进去了,然后栈指针加4。

更具体的,就要参考时序图了,纯数字电路分析一条指令的执行过程。

执行完操作后,EIP寄存器会自动+1,重复上述过程,所有指令完成了,那么这个函数也就运行完成了。

如果是寄存器到寄存器之间的传送,那么就是CPU内部电路的事情,如果是访问外部内存,那么就会有总线上的操作。

6.2 定点运算指令

6.2.1 常用定点运算指令

  • 加减运算指令,影响标志位,不区分有无符号运算

    • ADD:加,addb, addw, addl

    • SUB:减,subb, subw, subl

  • 加1 减1运算,影响CF之外的标志,不区分符号

    • INC,incb, incw, incl

    • DEC, decb, decw, decl

  • 取负运算,影响标志

    • NEG,negb, negw, negl

  • 比较运算,作减法得到标志,不区分符号

    • CMP,cmpb, cmpw, cmpl

  • 乘除运算,不影响标志,区分符号

    • MUL/IMUL

    • DIV/IDIV

乘法指令格式有很多种,可以给出一个、两个、三个操作数。

  • 给一个,另一个操作数隐含在AL/AX/EAX里,结果存放在AX或者DX-AX或者EDX-EAX里

  • 两个操作数DST和SRC,则结果存放在DST里

  • 三个操作数,REG,SRC,IMM,src*imm,结果放寄存器里

除法指令只明显指出除数,用EDX-EAX中的内容处以指定除数

  • 8bit,16bit被除数在AX里,商AL,余数AH

  • 16bit,32bit被除数在DX-AX里,商AX,余数DX

  • 32bit,被除数在EDX-EAX里,商EAX,余数EDX

此外还有一个运算指令对标志位影响与否的表,对应的有C语言的运算符。

6.2.2 加法运算的底层实现

上面的例子,有一条指令

  14:	01 d0                	add    %edx,%eax

是做加法运算的,在看实现之前,先看看IA32的寄存器组织

  • 000 a

  • 001 c

  • 010 d

  • 011 b

机器里面执行指令,按照前面的思路,两个数据已经到了EAX和EDX寄存器里了,下一步就是取得这个指令

01 d0

00000001 11010000

两个寄存器里的数据要送到ALU里去运算,ALU的样子钱买年有过简单介绍,里面有补码加减器,可以对有无符号数进行加减运算,还有逻辑运算器。ALU里面没有乘法器,可以用加减移位来实现,当然为了快速实现乘法,也可以在ALU外面设计一个独立的乘法器。除法器也不在ALU里。

ALU像是一个多路选择器,两个输入端,一个输出端,控制信号选择输出的结果。

这里的可玩性就比较高了,可以用FPGA设计实现一个简单的CPU

6.2.3 加法指令和乘法指令举例

假设 R[ax]=FFFAH,R[bx]=FFF0H,执行指令 addw %bx,%ax 后,AX,BX中的内容?标志位?操作数作为无符号数和带符号数解释并验证。

R[ax] <- R[ax] + R[bx]

结果是FFEAH,进位位1

CF=1, OF=0, ZF=1, SF=1

无符号数来解释,那么必然溢出了。带符号数解释的话,结果正确OF=0

对于乘法,布斯乘法算法

6.3

6.3.1 逻辑运算

  • 逻辑运算,仅NOT不影响标志,OF=CF=0

    • NOT,非 notb, notw, notl

    • AND,与 andb, andw, andl

    • OR,或 orb, orw, orl

    • XOR,异或 xorb, xorw, xorl

    • TEST,与测试,只影响标志

  • 移位运算

    • SHL/SHR,逻辑左/右移

    • SAL/SAR,算术左右移

    • ROL/ROR,循环左/右移

6.3.2 运算举例

逆向工程,机器码,反汇编,从汇编程序语言推断高级语言代码。

#include <stdio.h>
int main()
{
  int a = 0x80000000;
  unsigned int b = 0x80000000;

  printf("a=0x%x\n",a>>1);
  printf("b=0x%x\n",b>>1);
}

可以看出,高级语言不区分算术移位和逻辑移位的指令

6.4 条件转移指令

6.4.1 常用指令

指令的执行一种是按顺序执行,还有一种是跳转到转移目标出执行

  • 无条件转移指令

    • JMP DST,无条件转移

  • 条件转移

    • Jcc DST,cc为条件码,满足才跳转到DST

  • 条件设置

    • SETcc DST,按条件码cc判断的结果保存到DST

  • 调用和返回指令,用于过程调用

    • CALL DST,返回地址入栈,转到DST执行

    • RET,栈内取出返回地址,继续返回点执行

  • 中断指令,也是一种跳转

在这些指令里,有非常重要的一个寄存器:条件寄存器。

条件码是执行一条指令后生成的。

具体的转移指令特别多,查手册就好。

举个小例子

int sum(int a[], unsigned int len)
{
  int i, sum=0;
  for (i=0; i<=0; i++)
    sum+=a[i];
  return sum;
}

当参数为0时,会出错。

按照无符号数比时,i已经变成了一个巨大的值。

正确的做法是把len声明为int型,机器指令会使用带符号指令来比较。

6.4.2 条件设置指令举例

C表达式类型转换顺序。不同类型的参与运算,会向上做一转换处理。

6.5 x87 FPU

IA32浮点处理架构有两种,

  • x87 FPU 指令集 , gcc默认

  • SSE指令集,x86-64架构

6.5.1

早期的FPU作为一个外置的浮点处理器使用的,协处理器。现在的FPU和CPU做在一起。

x87 FPU 特指与 x86 处理器配套的浮点协处理器架构。

  • 寄存器用栈结构

    • 深度为8,位数为80,8个80bit寄存器

    • ST(0)-ST(7),栈顶为ST(0)-

  • 所有浮点运算按80bit扩展精度进行

  • 浮点数在浮点寄存器和内存之间传送

因此会有个bit扩展或者阶段的操作。

  • 装入,转换为80bit扩展精度

    • FLG

    • FILD,int转换为浮点格式在装入

  • 存储,转换为IEEE754单精度或者双精度

    • FSTx,sl精度格式

    • FSTPx,弹出栈顶

6.6 MMX 及 SSE 指令集

最后更新于