计算机系统基础(一)笔记——Week6 IA-32指令类型

本文最后更新于:2024年5月25日 晚上

Week6 IA-32指令类型

无需记忆,能看懂即可

前置知识:条件码

除了整数寄存器,CPU还维护了一组单个位的条件码寄存器,描述了最近的算术或逻辑操作的属性。

CF:进位标志。最近的操作使最高位产生了进位,用来检测无符号操作的溢出

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

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

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

6.1 传送指令

常用传送指令

  • 通用数据传送指令

    MOV:一般传送,包括movb、movw、movl等

    MOVS:符号拓展传送(高位补符号),如movsbw、movswl等

    MOVZ:零拓展传送(高位补0),movzwl、movzbl

    XCHG:数据交换

    PUSH/POP:入栈、出栈,如pushl、pushw、popl、popw等

  • 地址传送指令

    LEA:加载有效地址,如leal(%edx,%eax),%eax的功能为R[eax]=R[edx]+R[eax]

  • 输入输出指令

    IN/OUT:I/O端口和寄存器之间的交换

  • 标志传送指令

    PUSHF、POPF:将EFLAG压栈,或将栈顶内容送到EFLAG

入栈(pushw %ax)

此条语句是将寄存器ax的内容压栈

  • 栈不等于“堆栈”

执行过程:

首先R[sp]=R[sp]-2

然后M[R[sp]]=R[ax]

image-20240521200454512

出栈(popw %ax)

执行过程:

首先R[ax]=M[R[sp]]

然后R[sp]=R[sp]+2

对一段代码的简单分析

1
2
3
4
5
6
#include <stdio.h>
int add(int i,int j)
{
int x=i+j;
return x;
}

执行反汇编objdump -d test结果

1
2
3
4
5
6
7
8
9
10
11
12
080483d4 <add>:
80483d4: 55 push %ebp R[esp]=R[esp]-4;M[R[esp]]=R[ebp]
80483d5: 89 e5 mov %esp, %ebp R[ebp]=R[esp]
80483d7: 83 ec 10 sub $0x10,%esp
80483da: 8b 45 0c mov 0xc(%ebp),%eax R[eax]=M[R[ebp]+12]
80483dd: 8b 55 08 mov 0x8(%ebp),%edx R[edx]=M[R[ebp]+8]
80483e0: 8d 04 02 lea (%edx,%eax,1),%eax R[eax]=R[edx]+R[eax]
80483e3: 89 45 fc mov %eax,-0x4(%ebp) M[R[ebp]-4]=R[eax]
80483e6: 8b 45 fc mov -0x4(%ebp), %eax R[eax]=M[R[ebp]-4]
80483e9: c9 leave
80483ea: c3 ret
// op

add函数从80483d4开始

6.2 定点算术运算指令

  • 定点算术运算指令

    • 加/减运算(影响标志,不区分无/带符号)

      ADD:addb、addw、addl

      SUB:subb、subw、subl

    • 加1/减1运算(影响除CF以外的标志,不区分带/无符号)

      INC/DEC:incb incw incl(decb decw decl)

    • 取负运算(影响标志,对0取负,则结果为0且CF清0,否则CF置1)

      NEG:取负(negb negw negl)

    • 比较运算(做减法得到标志、不区分无/带符号)

      CMP:比较,包括cmpb、cmpw、cmpl

    • 乘/除运算(==不影响==标志、区分带/无符号)

      MUL/IMUL(无符号乘/带符号乘)

      DIV/IDIV(无符号除/带符号除)

整数乘除指令

  • 乘法指令:可给出一个、两个或三个操作数

    一个:SRC,另一个在AL/AX/EAX中,将SRC和累加器内容相乘,结果存放在AX(16)/DX-AX(32)/EDX-EAX(64)中。

    DX-AX表示32位的乘积高16位和低16位分别在DX、AX。==n位×n位=2n位==

    两个:DST和SRC,将DST和SRC相乘,结果在DST中。==n位×n位=n位==

    三个:REG、SRC和IMM,将SRC和立即数IMM相乘结果在REG中。==n位×n位=n位==

  • 除法指令:只给出除数。EDX-EAX中内容除以指定除数

    8位:16位被除数在AX寄存器中,商给AL,余数在AH

    16位:32位被除数在DX-AX寄存器中,商给AX,余数在DX

    32位:被除数在EDX-EAX寄存器中,商送EAX,余数在EDX

同样的例子

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int add(int i,int j)
{
int x=i+j;
return x;
}
int main(){
int t1=2147483647;
int t2=2;
int sum=add(t1,t2);
printf("sum=%d",sum);
}
image-20240521221938317 image-20240521221959617

ALU有什么?

  • 补码加/减器

    带符号整数加、减,无符号整数加、减

  • 各种与或非、前置0/1运算

没有乘除法器,因为可以通过加减法+移位实现

举个例子:定点加法指令

假设R[ax]=FFFAH,R[bx]=FFF0H,则执行指令“addw %bx, %ax”后,AX、BX中的内容各是什么?标志CF、OF、ZF、SF各是什么?要求分别将操作数作为无符号数带符号整数解释并验证指令执行结果

功能R[ax]=R[ax]+R[bx],即R[ax]=FFFAH+FFF0H=FFEAH,BX中内容不变

CF=1(有进位),OF=0(无溢出),ZF=0(最终结果不为0),SF=1(最高位为1)

  1. 无符号整数运算,则CF=1说明结果溢出

    验证:FFFA=65535-5=65530,FFEA=65535-21=65514

  2. 带符号运算,OF=0说明没有溢出

    验证:FFFA=-6,FFF0=-16,FFEA=-22=-6+(-16),结果吻合

举个例子:定点乘法指令

假设R[eax]=000000B4H,R[ebx]=00000011H,M[000000F8H]=000000A0H,请问:

执行指令“mulb %bl”后,哪些寄存器的内容会发生变化?是否与"imulb %bl"指令所发生的变化一样?请用该例子给出的数据验证结论。

  1. "mulb %bl"指的是R[ax]=R[al]×R[bl],mulb中的b指出乘数是8位(byte),%bl指出寄存器类型是用的低八位。也就是R[ax]=R[eax]的低八位×R[ebx]的低八位=B4H×11H=1011 0100×0001 0001=0000 1011 1111 0100=0BF4H。R[ax]=0BH,R[bx]=F4H
  2. "imulb %bl"功能是带符号相乘,R[ax]=R[al]×R[bl]=B4H×11H=0BF4H,结果显然不对

==对于带符号乘,若乘积取低n位,和无符号一致;取2n位,要采用布斯乘法==

假设R[eax]=000000B4H,R[ebx]=00000011H,M[000000F8H]=000000A0H,请问:

执行指令"imull $-16, (%eax,%ebx,4), %eax"后,哪些寄存器和存储单元发生了变化?乘积的机器数和真值是多少?

该指令有三个参数,功能为R[eax]=(-16)×M[R[eax]+R[ebx]×4]

R[eax]+R[ebx]×4=000000B4H+00000011H<<2=000000F8HR[eax]+R[ebx]×4=000000B4H+00000011H<<2=000000F8H

R[eax]=(16)×M[000000F8H]=16×(000000A0H)R[eax]=(-16)×M[000000F8H]=16×(-000000A0H)(带符号整数乘法)=FFFFFF60H<<4=FFFFF600H=2560=FFFFFF60H<<4=FFFFF600H=-2560

6.3 按位运算指令

逻辑运算

NOT:notb、notw、notl

AND:andb、andw、andl

OR:orb、orw、orl

XOR:xorb、xorw、xorl

TEST:做”与“操作测试,仅影响标志,不改变寄存器和存储单元的内容

  • 仅NOT不影响标志,其他指令OF=CF=0,而ZF和SF根据结果设置:若全为0,则ZF=1;若最高位为1,则SF=1

逻辑运算举例

M[0x1000]=00000F89H

M[0x1004]=00001270H

R[eax]=FF000001H

R[ecx]=00001000H

执行下列指令后如何变化?

notw %ax:R[ax]=not(0001H)=FFFEH

andl %eax, (%ecx):M[R[ecx]]=M[1000H]=M[0x1000]=00000F89H^FF000001H=0000001H

orb 4(%ecx), %al:R[al]=R[al] or M[4+R[ecx]]=01H or 70H=71H

xorw %ax, 4(%ecx):M[R[ecx]+4]=M[0x1004]=1270H$\oplus$0001H=1271H

testl %eax,%ecx:00001000H ^ FF000001H=0,ZF=1,SF=0,CF=OF=0

移位运算

SHL/SHR:逻辑左/右移动,包括shlb、shrw、shrl等

SAL/SAR:算术左/右移动,左移判溢出,右移高位补符。包括salb、sarw、sarl等

ROL/ROR:循环左/右移,包括rolb、rorw、roll等

RCL/RCR:带进位循环左/右移,即将CF作为操作数一部分循环移位,包括rclb、rcrw、rcll等

移位运算举例

假设short型变量x分配在AX中,R[ax]=FF80H,则以下汇编代码段执行后变量x的机器数和真值是什么?

movw %ax,%dx

salw $2,%ax

addl %dx,%ax

sarw $1,%ax

  1. R[dx]=R[ax]
  2. 1111 1111 1000 0000 << 2,算术左移,OF=0(移出的符号和剩下的符号相等,无溢出)
  3. R[ax]=1111 1110 0000 0000+1111 1111 1000 0000=1111 1101 1000 0000
  4. R[ax]>>1,1111 1110 1100 0000

程序执行的操作是((x<<2)+x)>>1

6.4 控制转移指令

指令执行可以==按顺序==或==跳转到转移目标指令处==执行

  • 无条件转移指令

    JMP DST:无条件转移到目标指令DST处执行

  • 条件转移

    Jcc DST:cc为条件码,根据条件码判断是否满足条件从而转移到目标指令DST

  • 条件设置

    SETcc DST:按条件码cc判断的结果保存到DST中(8位寄存器)

  • 调用和返回指令(用于过程调用)

    CALL DST:返回地址RA入栈,转移到DST处执行

    RET:从栈中取出返回地址RA,转到RA处执行

  • 中断指令(见Week7/8)

IA-32的标志寄存器

  • 6个条件标志

    OF/SF/ZF/CF

    AF:辅助进位标志(BCD码运算下才有意义)

    PF:奇偶标志

  • 3个控制标志

    DF(Direction Flag):方向标志(自动变址方向是增还是减)

    IF(Interrupt Flag):中断允许标志(仅对外部可屏蔽中断有用)

    TF(Trap Flag):陷阱标志(是否是单步跟踪状态)

根据标志进行跳转

序号 指令 转移条件 说明
1\color{green}1 jc label CF=1 有进位/借位
2\color{green}2 jnc label CF=0 无进位/借位
3\color{green}3 je/jz label ZF=1 相等/等于零
4\color{green}4 jne/jnz label ZF=0 不相等/不等于零
5\color{green}5 js label SF=1 是负数
6\color{green}6 jns label SF=0 是非负数
7\color{green}7 jo label OF=1 有溢出
8\color{green}8 jno label OF=0 无溢出
9\color{red}9 ja/jnbe label CF=0 AND ZF=0 无符号整数A>B
10\color{red}10 jae/jnb label CF=0 OR ZF=1 无符号整数A>=B
11\color{red}11 jb/jnae label CF=1 AND ZF=0 无符号整数A<B
12\color{red}12 jbe/jna label CF=1 OR ZF=1 无符号整数A<=B
13\color{blue}13 jg/jnle label SF=OF AND ZF=0 带符号整数A>B
14\color{blue}14 jge/jnl label SF=OF OR ZF=1 带符号整数A>=B
15\color{blue}15 jl/jnge label SF≠OF AND ZF≠0 带符号整数A<B
16\color{blue}16 jle/jng label SF≠OF OR ZF=1 带符号整数A<=B

18\color{green}1-8:根据单个标志的值进行转移

912\color{red}9-12:按无符号整数比较转移

1316\color{blue}13-16:按带符号整数比较转移

举个例子

1
2
3
4
5
6
7
int sum(int a[],unsigned len)
{
int i,sum=0;
for(i=0;i<=len-1;i++)
sum+=a[i];
return sum;
}

len=0时应该返回0,为什么是存储器访问异常

1
2
3
4
5
6
7
8
9
10
sum:
...
.L3:
...
movl -4(%ebp),%eax
movl 12(%ebp),%edx
subl $1,%edx
cmpl %edx,%eax
jbe .L3
...

解答:i在%eax中,len在%edx中,第一次循环eax=edx=0000 0000H

  1. subl $1,%edx执行结果:在运算器中,A=0000 0000H,B=0000 0001H,sub=1即AB=A+([B]+1)=0000 0000+FFFF FFFF=FFFF FFFFA-B=A+([B]_{反}+1)=0000\ 0000+FFFF\ FFFF=FFFF\ FFFF,即R[edx]=FFFF FFFFHR[edx]=FFFF\ FFFFH

  2. cmpl %edx,%eax执行结果:R[eax]R[edx]=0000 0001HCF=Cosub=01=1ZF=0,OF=0SF=0R[eax]-R[edx]=0000\ 0001H,CF=Co\oplus sub=0\oplus 1=1,ZF=0,OF=0,SF=0

  3. jbe .L3执行结果:

    CF=1,条件满足,因此转到.L3执行

进入下次循环后,i即使增加也会一直小于len-1=FFFF FFFF,因此是死循环。当i增加到非常大时,a[i]超出了访问界限,发生存储器访问异常

len如果声明为int类型,指令jbe .L3改为了jle .L3,按带符号整数比较

此时ZF=0且SF=OF,因此不满足条件,不会进入循环

C表达式类型转换顺序

两个数比较时,(unsigned)short/char——>int——>unsigned——>long long ——>unsigned long long

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
unsigned int a=1;//movl $0x1, 0x1c(%esp)
unsigned short b=1;//movw $0x1, 0x1a(%esp)
char c=-1;//movb $0xff,0x19(%esp)
int d;
d=(a>c)?1:0;
/*
movsbl 0x19(%esp),%eax
cmp 0x1c(%esp),%eax
setb %al 无符号比
*/
cout<<d;
d=(b>c)?1:0;
/*
movzwl 0x1a(%esp),%edx
movsbl 0x19(%esp),%eax
cmp %eax,%edx
setg %al 带符号比
*/
cout<<d;
}

6.5 x87浮点处理指令

IA-32的浮点处理架构

  • x87FPU指令集(gcc默认)
  • SSE指令集(x86-64架构所用)

IA-32中处理浮点数有三种:

  1. float:32位IEEE 754单精度

  2. double:64位IEEE 754双精度

  3. long double:80位双精度拓展格式

    1符号位s,15位阶码e,偏置常数16383,1位显式首位有效位j和63位尾数f

x87FPU指令概述

最初早期的浮点处理器是作为CPU的外置协处理器出现的

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

  • 浮点寄存器采用栈结构

    深度为8,宽度为80位,8个80位寄存器

    名称为ST(0)-ST(7),栈顶ST(0)

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

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

float、double、long double型变量在内存分别用IEEE 754单精度、双精度和扩展精度表示,分别占32位(4B)、64位(8B)和96位(12B,其中高16位无意义)

float、double、long double类型变量在浮点寄存器中都用80位扩展精度表示

*从浮点寄存器到内存:*80位拓展精度转为32位或64位

*从内存到浮点寄存器:*32位或64位转为80位拓展精度

x87FPU指令

  1. 数据传送类

    • 装入

      FLD:将数据从存储单元装入浮点寄存器栈顶 ST(0)

      FILD:将数据从int型转换为浮点格式后,装入浮点寄存器栈顶

    • 存储

      FSTx:x为 s/l时,将栈顶ST(0)转换为单/双精度格式,然后存入存储单元、

      FSTPx:弹出栈顶元素,并完成与FSTx相同的功能

      FISTx:将栈顶数据从int型转换为浮点格式后,存入存储单元

      FISTP:弹出栈顶元素,并完成与FISTx相同的功能

      出栈:ST(1)变为ST(0)

    • 交换

      FXCH:交换ST(0)和ST(1)

    • 常数装载到栈顶

      FLD1 :装入常数1.0

      FLDZ :装入常数0.0

      FLDPI :装入常数pi (=3.1415926…)

      FLDL2E :装入常数log(2)e

      FLDL2T :装入常数log(2)10

      FLDLG2 :装入常数log(10)2

      FLDLN2 :装入常数Log(e)2

  2. 算术运算类

    • 加法

      FADD/FADDP: 相加/相加后弹出栈

      FIADD:按int型转换后相加

    • 减法

      FSUB/FSUBP : 相减/相减后弹出栈

      FSUBR/FSUBRP:调换次序相减/相减后弹出栈

      FISUB:按int型转换后相减

      FISUBR:按int型转换并调换次序相减

      指令未带操作数则默认ST(0)和ST(1)

      带r指令就是相减顺序反转

    • 乘法

      FMUL/FMULP: 相乘/相乘后弹出栈

      FIMUL:按int型转换后相乘

    • 除法

      FDIV/FDIVP : 相除/相除后弹出栈

      FIDIV:按int型转换后相除

      FDIVR/FDIVRP:调换次序相除/相减后弹出栈

      FIDIVR:按int型转换并调换次序相除

IA-32浮点运算举例

例1

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
double f(int x){
return 1.0/x;
}
void main() {
double a, b;
int i;
a=f(10);
b=f(10);
i=a==b;
printf( "%d" , i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
double f(int x){
return 1.0/x;
}
void main() {
double a,b,c;
int i;
a=f(10);
b=f(10);
c=f(10);
i=a==b;
printf( "%d" , i);
}

Question:老版本gcc -O2编译第一个输出0,第二个输出1为什么?

Answer:

1
2
3
4
5
6
7
8
9
10
11
double f(int x){
return 1.0/x;
}
/*
8048328: 55 push %ebp
8048329: 89 e5 mov %esp,%ebp
804832b: d9 e8 fld1 将常数1.0压入栈顶
804832d: da 75 08 fidivl 0x8(%ebp) 将M[R[ebp+8]]的int转换为double,再用ST(0)除以该数,结果存入ST(0)
8048330: c9 leave
8048331: c3 ret
*/

f(10)=1.0(80位扩展精度)/10(转换为double)=0.1

0.1=0.00011[0011]B= 0.00011 0011 0011 0011 0011 0011 0011…B,无法精确表示0.1

1
2
3
4
5
6
7
8
9
push $0xa
call 8048328<f> a=f(10)
fstpl 0xfffffff8(%ebp) a存入内存 80位转换为64
movl $0xa,(%eap,1)
call 8048328 <f> b=f(10)
fldl 0xfffffff8(%ebp) a入栈顶 64位转为80
pop %eax
fucompp 比较ST(0)a和ST(1)b
fnstsw %ax 把FPU状态字送到AX

a舍入过而b没有舍入,因此a≠b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
call 8048328<f>			a=f(10)
fstpl 0xfffffff8(%ebp) a存入内存 80位转换为64
movl $0xa,(%eap,1)
call 8048328 <f> b=f(10)
fstpl 0xfffffff0(%ebp) b存入内存 80位转换为64
movl $0xa,(%esp,1)
call 8048328 <f> c=f(10)
fstp %st(0)
fldl 0xfffffff8(%ebp) a入栈顶 64位转为80
fldl 0xfffffff0(%ebp) b入栈顶 64位转为80
fxch %st(1)
pop %eax
fucompp 比较ST(0)a和ST(1)b
fnstsw %ax 把FPU状态字送到AX

a、b都舍入过,因此a==b

例2

1
2
3
4
5
6
7
8
int main()
{
int a=10;
double *p=(double*)&a;
printf("%f\n",*p); //结果为0.000000
printf("%f\n",(double(a))); //结果为10.000000
return 0;
}

**Question:**为什么?

**Answer:**区别在于指令fldlfildl


计算机系统基础(一)笔记——Week6 IA-32指令类型
http://kcollision.github.io/2024/05f3758fe4.html
作者
collision
更新于
2024年5月25日
许可协议