第三章 程序的转换与机器级表示 程序转换概述 IA-32 /x86-64指令系统 C语言程序的机器级表示 复杂数据类型的分配和访问 越界访问和缓冲区溢出、x86-64架构
程序的转换与机器级表示 主要教学目标 了解高级语言与汇编语言、汇编语言与机器语言之间的关系 掌握有关指令格式、操作数类型、寻址方式、操作类型等内容 了解高级语言源程序中的语句与机器级代码之间的对应关系 了解复杂数据类型(数组、结构等)的机器级实现 主要教学内容 介绍C语言程序与IA-32机器级指令之间的对应关系。 主要包括:程序转换概述、IA-32指令系统、C语言中控制语 句和过程调用等机器级实现、复杂数据类型(数组、结构等) 的机器级实现等。 本章所用的机器级表示主要以汇编语言形式表示为主。
程序的机器级表示 分以下五个部分介绍 第一讲:程序转换概述 机器指令和汇编指令 机器级程序员感觉到的属性和功能特性 高级语言程序转换为机器代码的过程 第二讲:IA-32 /x86-64指令系统 第三讲: C语言程序的机器级表示 过程调用的机器级表示 选择语句的机器级表示 循环结构的机器级表示 第四讲:复杂数据类型的分配和访问 数组的分配和访问 结构体数据的分配和访问 联合体数据的分配和访问 数据的对齐 第五讲:越界访问和缓冲区溢出 从高级语言程序出发,用其对应的机器级代码以及内存(栈)中信息的变化来说明底层实现 围绕C语言中的语句和复杂数据类型,解释其在底层机器级的实现方法
“指令”的概念 计算机中的指令有微指令、机器指令和伪(宏)指令之分 微指令是微程序级命令,属于硬件范畴 (将在”计算机组成与设计“课程中学习) 伪指令是由若干机器指令组成的指令序列,属于软件范畴 机器指令介于二者之间,处于硬件和软件的交界面 本章中提及的指令都指机器指令 汇编指令是机器指令的汇编表示形式,即符号表示 机器指令和汇编指令一一对应,它们都与具体机器结构有关,都属于机器级指令
回顾:Hardware/Software Interface temp = v[k]; v[k] = v[k+1]; v[k+1] = temp; 汇编指令 软件 lw $15, 0($2) lw $16, 4($2) sw $16, 0($2) sw $15, 4($2) 机器指令 1000 1100 0100 1111 0000 0000 0000 0000 1000 1100 0101 0000 0000 0000 0000 0100 1010 1100 0101 0000 0000 0000 0000 0000 1010 1100 0100 1111 0000 0000 0000 0100 硬件 … , EXTop=1,ALUSelA=1,ALUSelB=11,ALUop=add, IorD=1,Read,MemtoReg=1,RegWr=1,...... 微指令 … 1 1 11 100 1 0 1 1 …
机器级指令 mov [bx+di-6], cl 或 movb %cl, -6(%bx,%di) 机器指令和汇编指令一一对应,都是机器级指令 机器指令是一个0/1序列,由若干字段组成 汇编指令是机器指令的符号表示(可能有不同的格式) mov、movb、bx、%bx等都是助记符 指令的功能为:M[R[bx]+R[di]-6]←R[cl] 补码11111010的真值为多少? 操作码 寻址方式 寄存器编号 立即数(位移量) mov [bx+di-6], cl movb %cl, -6(%bx,%di) 或 Intel格式 AT&T 格式 R:寄存器内容 M:存储单元内容 寄存器传送语言 RLT(Register Transfer Language)
计算机中数据的存储 计算机中的数据存放在哪里? 指令中需给出的信息: 操作性质(操作码) 相当于宿舍书架 相当于图书馆书架 寄存器文件 通用寄存器组GPRs 存储器 指令中需给出的信息: 操作性质(操作码) 源操作数1 或/和 源操作数2 (立即数、寄存器编号、存储地址) 目的操作数地址 (寄存器编号、存储地址) 存储地址的描述与操作数的数据结构有关!
指令集体系结构ISA ISA(Instruction Set Architecture)位于软件和硬件之间 硬件的功能通过ISA提供出来 可执行的指令的集合,包括指令格式、操作种类以及每种操作对应的操作数的相应规定; 指令可以接受的操作数的类型; 操作数所能存放的寄存器组的结构,包括每个寄存器的名称、编号、长度和用途; 操作数所能存放的存储空间的大小和编址方式; 操作数在存储空间存放时按照大端还是小端方式存放; 指令获取操作数的方式,即寻址方式; 指令执行过程的控制方式,包括程序计数器、条件码定义等。 I/O CPU Compiler Operating System Application Digital Design Circuit Design Instruction Set Architecture MM Assembler
高级语言程序转换为机器代码的过程 用GCC编译器套件进行转换的过程 预处理:在高级语言源程序中插入所有用#include命令指定的文件和用#define声明指定的宏。 编译:将预处理后的源程序文件编译生成相应的汇编语言程序。 汇编:由汇编程序将汇编语言源程序文件转换为可重定位的机器语言目标代码文件。 链接:由链接器将多个可重定位的机器语言目标文件以及库例程(如printf()库函数)链接起来,生成最终的可执行目标文件。
GCC使用举例 两个源程序文件test1.c和test2.c,最终生成可执行文件为test gcc -O1 test1.c test2.c -o test 选项-O1表示一级优化,-O2为二级优化,选项-o指出输出文件名 目标文件可用“objdump -d test.o” 反汇编为汇编语言程序 00000000 <add>: 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 gcc -E test.c -o test.i gcc -S test.i -o test.s 位移量 机器指令 汇编指令 test.s gcc –S test.c –o test.s add: pushl %ebp movl %esp, %ebp subl $16, %esp movl 12(%ebp), %eax movl 8(%ebp), %edx leal (%edx, %eax), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret 编译得到的与反汇编得到的汇编指令形式稍有差异
两种目标文件 test.o:可重定位目标文件 test:可执行目标文件 “objdump -d test” 结果 “objdump -d test.o”结果 00000000 <add>: 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 080483d4 <add>: 80483d4: 55 push ... 80483d5: 89 e5 … 80483d7: 83 ec 10 … 80483da: 8b 45 0c … 80483dd: 8b 55 08 … 80483e0: 8d 04 02 … 80483e3: 89 45 fc … 80483e6: 8b 45 fc … 80483e9: c9 … 80483ea: c3 ret test.o中的代码从地址0开始,test中的代码从80483d4开始!
程序的机器级表示 分以下五个部分介绍 第一讲:程序转换概述 机器指令和汇编指令 机器级程序员感觉到的属性和功能特性 高级语言程序转换为机器代码的过程 第二讲:IA-32 /x86-64指令系统 第三讲: C语言程序的机器级表示 过程调用的机器级表示 选择语句的机器级表示 循环结构的机器级表示 第四讲:复杂数据类型的分配和访问 数组的分配和访问 结构体数据的分配和访问 联合体数据的分配和访问 数据的对齐 第五讲:越界访问和缓冲区溢出 从高级语言程序出发,用其对应的机器级代码以及内存(栈)中信息的变化来说明底层实现 围绕C语言中的语句和复杂数据类型,解释其在底层机器级的实现方法
IA-32/x64指令系统概述 x86是Intel开发的一类处理器体系结构的泛称 由AMD首先提出了一个兼容IA-32指令集的64位版本 包括 Intel 8086、80286、i386和i486等,因此其架构被称为“x86” 由于数字并不能作为注册商标,因此,后来使用了可注册的名称,如Pentium、PentiumPro、Core 2、Core i7等 现在Intel把32位x86架构的名称x86-32改称为IA-32 由AMD首先提出了一个兼容IA-32指令集的64位版本 扩充了指令及寄存器长度和个数等,更新了参数传送方式 AMD称其为AMD64,Intel称其为Intl64(不同于IA-64) 命名为“x86-64” ,有时也简称为x64
IA-32支持的数据类型及格式
IA-32的寄存器组织
IA-32的标志寄存器 …… 6个条件标志 3个控制标志 OF、SF、ZF、CF各是什么标志(条件码)? 8086 80286/386 6个条件标志 OF、SF、ZF、CF各是什么标志(条件码)? AF:辅助进位标志(BCD码运算时才有意义) PF:奇偶标志 3个控制标志 DF(Direction Flag):方向标志(自动变址方向是增还是减) IF(Interrupt Flag):中断允许标志 (仅对外部可屏蔽中断有用) TF(Trap Flag):陷阱标志(是否是单步跟踪状态) ……
IA-32的寻址方式 寻址方式 根据指令给定信息得到操作数或操作数地址 操作数所在的位置 指令中:立即寻址 寄存器中:寄存器寻址 存储单元中(属于存储器操作数,按字节编址):其他寻址方式 存储器操作数的寻址方式与微处理器的工作模式有关 两种工作模式:实地址模式和保护模式 实地址模式(基本用不到) 为与8086/8088兼容而设,加电或复位时 寻址空间为1MB,20位地址:(CS)<<4+(IP) 保护模式(需要掌握) 加电后进入,采用虚拟存储管理,多任务情况下隔离、保护 80286以上高档微处理器最常用的工作模式 寻址空间为232B,32位线性地址分段(段基址+段内偏移量)
保护模式下的寻址方式 存储器操作数 SR段寄存器(间接)确定操作数所在段的段基址 有效地址给出操作数在所在段的偏移地址 跳转目标指令地址 SR段寄存器(间接)确定操作数所在段的段基址 有效地址给出操作数在所在段的偏移地址 寻址过程涉及到“分段虚拟管理方式”,将在第6章讨论
存储器操作数的寻址方式 int x; float a[100]; short b[4][4]; char c; double d[10]; b31 b0 x a[0] a[99] b[0][1] 100 104 b[0][0] b[3][3] b[3][2] c 500 504 532 536 544 d[0] d[9] 616 a[i]的地址如何计算? 104+i×4 i=99时,104+99×4=500 b[i][j]的地址如何计算? 504+i×8+j×2 i=3、j=2时,504+24+4=532 d[i]的地址如何计算? 544+i×8 i=9时,544+9×8=616
存储器操作数的寻址方式 int x; float a[100]; short b[4][4]; char c; double d[10]; b31 b0 x a[0] a[99] b[0][1] 100 104 b[0][0] b[3][3] b[3][2] c 500 504 532 536 544 d[0] d[9] 616 各变量应采用什么寻址方式? x、c:位移 / 基址 a[i]:104+i×4,比例变址+位移 d[i]:544+i×8,比例变址+位移 b[i][j]: 504+i×8+j×2, 基址+比例变址+位移 将b[i][j]取到AX中的指令可以是: “movw 504(%ebp,%esi,2), %ax” 其中, i×8在EBP中,j在ESI中, 2为比例因子
浮点寄存器栈和多媒体扩展寄存器组 IA-32的浮点处理架构有两种 : 浮点协处理器x87架构(x87 FPU) 8个80位寄存器ST(0) ~ ST(7) (采用栈结构),栈顶为ST(0) 由MMX发展而来的SSE架构 MMX指令使用8个64位寄存器MM0~MM7,借用8个80位寄存器ST(0)~ST(7)中64位尾数所占的位,可同时处理8个字节,或4个字,或2个双字,或一个64位的数据 MMX指令并没带来3D游戏性能的显著提升,故推出SSE指令,并陆续推出SSE2、SSE3、SSSE3和SSE4等采用SIMD技术的指令集,这些统称为SSE指令集 SSE指令集将80位浮点寄存器扩充到128位多媒体扩展通用寄存器XMM0~XMM7,可同时处理16个字节,或8个字,或4个双字(32位整数或单精度浮点数),或两个四字的数据,而且从SSE2开始,还支持128位整数运算或同时并行处理两个64位双精度浮点数
IA-32中通用寄存器中的编号 反映了体系结构发展的轨迹,字长不断扩充,指令保持兼容 ST(0)~ ST(7)是80位,MM0 ~MM7使用其低64位
IA-32常用指令类型 (1)传送指令 通用数据传送指令 MOV:一般传送,包括movb、movw和movl等 MOVS:符号扩展传送,如movsbw、movswl等 MOVZ:零扩展传送,如movzwl、movzbl等 XCHG:数据交换 PUSH/POP:入栈/出栈,如pushl,pushw,popl,popw等 地址传送指令 LEA:加载有效地址,如leal (%edx,%eax), %eax”的功能为R[eax]←R[edx]+R[eax],执行前,若R[edx]=i,R[eax]=j,则指令执行后,R[eax]=i+j 输入输出指令 IN和OUT:I/O端口与寄存器之间的交换 标志传送指令 PUSHF、POPF:将EFLAG压栈,或将栈顶内容送EFLAG
“入栈”和“出栈”操作 栈(Stack)是一种采用“先进后出”方式进行访问的一块存储区,用于嵌套过程调用。从高地址向低地址增长 小班讨论 栈(Stack)是一种采用“先进后出”方式进行访问的一块存储区,用于嵌套过程调用。从高地址向低地址增长 “栈”不等于“堆栈”(由“堆”和“栈”组成) 栈底 栈底 为什么AL的内容在栈顶? 小端方式! R[sp]←R[sp]-2、M[R[sp]]←R[ax] R[ax]←M[R[sp]]、[sp]←R[sp]+2
传送指令举例 将以下Intel格式指令转换为AT&T格式指令,并说明功能。 push ebp mov ebp, esp mov edx, DWORD PTR [ebp+8] mov bl, 255 mov ax, WORD PTR [ebp+edx*4+8] mov WORD PTR [ebp+20], dx lea eax, [ecx+edx*4+8] pushl %ebp //R[esp]←R[esp]-4,M[R[esp]] ←R[ebp],双字 movl %esp, %ebp //R[ebp] ←R[esp],双字 movl 8(%ebp), %edx //R[edx] ←M[R[ebp]+8],双字 movb $255, %bl //R[bl]←255,字节 movw 8(%ebp,%edx,4), %ax //R[ax]←M[R[ebp]+R[edx]×4+8],字 movw %dx, 20(%ebp) //M[R[ebp]+20]←R[dx],字 leal 8(%ecx,%edx,4), %eax //R[eax]←R[ecx]+R[edx]×4+8,双字
IA-32常用指令类型 (2)定点算术运算指令 加 / 减运算(影响标志、不区分无/带符号) ADD:加,包括addb、addw、addl等 SUB:减,包括subb、subw、subl等 增1 / 减1运算(影响除CF以外的标志、不区分无/带符号) INC:加,包括incb、incw、incl等 DEC:减,包括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位分别在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 以上内容不要死记硬背,遇到具体指令时能查阅到并理解即可。
定点算术运算指令汇总
定点加法指令举例 假设R[ax]=FFFAH,R[bx]=FFF0H,则执行Intel格式指令: “add ax, bx” AX、BX中的内容各是什么?标志CF、OF、ZF、SF各是什么?要求分别将操作数作为无符号数和带符号整数解释并验证指令执行结果。 解:功能:R[ax]←R[ax]+R[bx],指令执行后的结果如下 R[ax]=FFFAH+FFF0H=FFEAH ,BX中内容不变 CF=1,OF=0,ZF=0,SF=1 若是无符号整数运算,则CF=1说明结果溢出 验证:FFFA的真值为65535-5=65530,FFF0的真值为65515 FFEA的真值为65535-21=65514≠65530+65515,即溢出 若是带符号整数运算,则OF=0说明结果没有溢出 验证:FFFA的真值为-6,FFF0的真值为-16 FFEA的真值为-22=-6+(-16),结果正确,无溢出
定点乘法指令举例 假设R[eax]=000000B4H,R[ebx]=00000011H,M[000000F8H]=000000A0H,请问: (1) 执行指令“mulb %bl”后,哪些寄存器的内容会发生变化?是否与执行“imulb %bl”指令所发生的变化一样?为什么?请用该例给出的数据验证你的结论。 解:“mulb %bl”功能为 R[ax]←R[al]×R[bl],执行结果如下 R[ax]=B4H × 11H(无符号整数180和17相乘) R[ax]=0BF4H,真值为3060=180 × 17 “imulb %bl”功能为 R[ax]←R[al]×R[bl] R[ax]=B4H × 11H(带符号整数-76和17相乘) 若R[ax]=0BF4H,则真值为3060≠-76 × 17 R[al]=F4H, R[ah]=? AH中的变化不一样! R[ax]=FAF4H, 真值为-1292=-76 × 17 无符号乘: 1011 0100 0001 0001 x 0000 1011 1111 0100 AH=? AL=? 对于带符号乘,若积只取低n位,则和无符号相同;若取2n位,则采用“布斯”乘法
定点乘法指令举例 布斯乘法: “imulb %bl” R[ax]=B4H × 11H 1 0 1 1 0 1 0 0 0 0 1-1 0 0 1-1 0001 0001 x 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 1 1 1 1 1 1 1 1 0 1 1 0 1 0 0 0 0 0 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 1 0 1 0 0 1 1 1 1 1 0 1 0 1 1 1 1 0 1 0 0 AH=? AL=? R[ax]=FAF4H, 真值为-1292=-76 × 17
定点乘法指令举例 假设R[eax]=000000B4H,R[ebx]=00000011H,M[000000F8H]=000000A0H,请问: (2) 执行指令“imull $-16, (%eax,%ebx,4), %eax”后哪些寄存器和存储单元发生了变化?乘积的机器数和真值各是多少? 解:“imull -16, (%eax,%ebx,4),%eax” 功能为 R[eax]←(-16)×M[R[eax]+R[ebx]×4] ,执行结果如下 R[eax]+R[ebx]×4=000000B4H+00000011H<<2=000000F8H R[eax]=(-16)×M[000000F8H] =(-16)× 000000A0H(带符号整数乘) =FFFFFF60H<<4 =FFFFF600H EAX中的真值为-2560 SKIP
整数乘除指令 BACK BACK 乘法指令:可给出一个、两个或三个操作数 若给出一个操作数SRC,则另一个源操作数隐含在AL/AX/EAX中,将SRC和累加器内容相乘,结果存放在AX(16位)或DX-AX(32位)或EDX-EAX(64位)中。DX-AX表示32位乘积的高、低16位分别在DX和AX中。 若指令中给出两个操作数DST和SRC,则将DST和SRC相乘,结果在DST中。 若指令中给出三个操作数REG、SRC和IMM,则将SRC和立即数IMM相乘,结果在REG中。 除法指令:只明显指出除数,用EDX-EAX中内容除以指定的除数 若为8位,则16位被除数在AX寄存器中,商送回AL,余数在AH 若为16位,则32位被除数在DX-AX寄存器中,商送回AX,余数在DX 若为32位,则被除数在EDX-EAX寄存器中,商送EAX,余数在EDX BACK 以上内容不要死记硬背,遇到具体指令时能查阅到并理解即可。 BACK
IA-32常用指令类型 (3)按位运算指令 逻辑运算(仅NOT不影响标志,其他指令OF=CF=0,而ZF和SF根据结果设置:若全0,则ZF=1;若最高位为1,则SF=1 ) NOT:非,包括 notb、notw、notl等 AND:与,包括 andb、andw、andl等 OR:或,包括 orb、orw、orl等 XOR:异或,包括 xorb、xorw、xorl等 TEST:做“与”操作测试,仅影响标志 移位运算(左/右移时,最高/最低位送CF) SHL/SHR:逻辑左/右移,包括 shlb、shrw、shrl等 SAL/SAR:算术左/右移,左移判溢出,右移高位补符 (移位前、后符号位发生变化,则OF=1 ) ROL/ROR: 循环左/右移,包括 rolb、rorw、roll等 RCL/RCR: 带循环左/右移,将CF作为操作数一部分循环移位 以上内容不要死记硬背,遇到具体指令时能查阅到并理解即可。
按位运算指令举例 小班讨论 假设short型变量x被编译器分配在寄存器AX中,R[ax]=FF80H,则以下汇编代码段执行后变量x的机器数和真值分别是多少? movw %ax, %dx salw $2, %ax addl %dx, %ax sarw $1, %ax 解:$2和$1分别表示立即数2和1 。 x是short型变量,故都是算术移位指令,并进行带符号整数加。 假设上述代码段执行前R[ax]=x,则执行((x<<2)+x)>>1后,R[ax]=5x/2。算术左移时,AX中的内容在移位前、后符号未发生变化,故OF=0,没有溢出。最终AX的内容为FEC0H,解释为short型整数时,其值为-320。验证:x=-128,5x/2=-320。经验证,结果正确。 1111 1111 1000 0000<<2 1111 1111 1000 0000+1111 1110 0000 0000 1111 1101 1000 0000>>1=1111 1110 1100 0000
移位指令举例 小班讨论 算术 逻辑
IA-32常用指令类型 (4)控制转移指令 指令执行可按顺序 或 跳转到转移目标指令处执行 无条件转移指令 JMP DST:无条件转移到目标指令DST处执行 条件转移 Jcc DST:cc为条件码,根据标志(条件码)判断是否满足条件,若满足,则转移到目标指令DST处执行,否则按顺序执行 条件设置 SETcc DST:将条件码cc保存到DST(通常是一个8位寄存器 ) 调用和返回指令 (用于过程调用) CALL DST:返回地址RA入栈,转DST处执行 RET:从栈中取出返回地址RA,转到RA处执行 中断指令 (详见第7、8章) 以上内容不要死记硬背,遇到具体指令时能查阅到并理解即可。
条件转移指令 分三类: (1)根据单个标志的值转移 (2)按无符号整数比较转移 (3)按带符号整数比较转移
例子:C表达式类型转换顺序 unsigned long long ↑ long long unsigned int 小班讨论 unsigned long long ↑ long long unsigned int (unsigned)char,short 猜测:各用哪种条件设置指令? 条件设置指令: SETcc DST:将条件码cc保存到DST(通常是一个8位寄存器 )
小班讨论 unsigned int a=1; unsigned short b=1; char c=-1; d=(a>c)?1:0 无符号 d=(b>c)?1:0 带符号
例子:程序的机器级表示与执行* int sum(int a[ ], unsigned len) sum: { … int i,sum = 0; for (i = 0; i <= len–1; i++) sum += a[i]; return sum; } sum: … .L3: movl -4(%ebp), %eax movl 12(%ebp), %edx subl $1, %edx cmpl %edx, %eax jbe .L3 小班讨论 当参数len为0时,返回值应该是0,但是在机器上执行时,却发生了存储器访问异常。 Why? i 在%eax中,len在%edx中 %eax: 0000 …… 0000 %edx: 0000 …… 0000 subl 指令的执行结果是什么? cmpl 指令的执行结果是什么? i 和 len 分别存放在哪个寄存器中? %eax? %edx?
subl $1, %edx指令的执行结果 加/减运算部件 小班讨论 Result 加法器 n A ZF Ci Co B 1 多路选择器 Sub OF 加/减运算部件 CF=CoSub SF 当Sub为1时,做减法 当Sub为0时,做加法 已知EDX中为 len=0000 0000H “subl $1, %edx”执行时:A=0000 0000H,B为0000 0001H,Sub=1,因此Result是32个1。
cpml %edx,%eax指令的执行结果 加/减运算部件 Sub Ci n A ZF SF 加法器 Result OF CF=CoSub 小班讨论 Result 加法器 n A ZF Ci Co B 1 多路选择器 Sub OF 加/减运算部件 CF=CoSub SF 当Sub为1时,做减法 当Sub为0时,做加法 已知EDX中为 len-1=FFFF FFFFH EAX中为 i=0000 0000H “cmpl %edx,%eax”执行时:A=0000 0000H,B为FFFF FFFFH,Sub=1,因此Result是0…01, CF=1, ZF=0, OF=0, SF=0
jbe .L3指令的执行结果 小班讨论 指令 转移条件 说明 JA/JNBE label CF=0 AND ZF=0 无符号数A>B JAE/JNB label CF=0 OR ZF=1 无符号数A≥B JB/JNAE label CF=1 AND ZF=0 无符号数A<B JBE/JNA label CF=1 OR ZF=1 无符号数A≤B JG/JNLE label SF=OF AND ZF=0 有符号数A>B JGE/JNL label SF=OF OR ZF=1 有符号数A≥B JL/JNGE label SF≠OF AND ZF=0 有符号数A<B JLE/JNG label SF≠OF OR ZF=1 有符号数A≤B “cmpl %edx,%eax”执行结果是 CF=1, ZF=0, OF=0, SF=0,说明满足条件,应转移到.L3执行! 显然,对于每个 i 都满足条件,因为任何无符号数都比32个1小,因此循环体被不断执行,最终导致数组访问越界而发生存储器访问异常。
例子:程序的机器级表示与执行 sum: … .L3: movl -4(%ebp), %eax movl 12(%ebp), %edx 小班讨论 sum: … .L3: movl -4(%ebp), %eax movl 12(%ebp), %edx subl $1, %edx cmpl %edx, %eax jle .L3 例: int sum(int a[ ], int len) { int i,sum = 0; for (i = 0; i <= len–1; i++) sum += a[i]; return sum; } 正确的做法是将参数len声明为int型。 Why? i 在%eax中,len在%edx中 %eax: 0000 …… 0000 %edx: 0000 …… 0000 subl 指令的执行结果是什么? cmpl 指令的执行结果是什么? i 和 len 分别存放在哪个寄存器中? %eax? %edx?
jle .L3指令的执行结果 小班讨论 指令 转移条件 说明 JA/JNBE label CF=0 AND ZF=0 无符号数A>B JAE/JNB label CF=0 OR ZF=1 无符号数A≥B JB/JNAE label CF=1 AND ZF=0 无符号数A<B JBE/JNA label CF=1 OR ZF=1 无符号数A≤B JG/JNLE label SF=OF AND ZF=0 有符号数A>B JGE/JNL label SF=OF OR ZF=1 有符号数A≥B JL/JNGE label SF≠OF AND ZF=0 有符号数A<B JLE/JNG label SF≠OF OR ZF=1 有符号数A≤B “cmpl %edx,%eax”执行结果是 CF=1, ZF=0, OF=0, SF=0, 说明不满足条件,应跳出循环执行,执行结果正常。
X87浮点指令、MMX和SSE指令 IA-32的浮点处理架构有两种 : 浮点协处理器x87架构(x87 FPU) 8个80位寄存器ST(0) ~ ST(7) (采用栈结构),栈顶为ST(0) 由MMX发展而来的SSE架构 MMX指令使用8个64位寄存器MM0~MM7,借用8个80位寄存器ST(0)~ST(7)中64位尾数所占的位,可同时处理8个字节,或4个字,或2个双字,或一个64位的数据 MMX指令并没带来3D游戏性能的显著提升,故相继推出SSE指令集 ,它们都采用SIMD(单指令多数据,也称数据级并行)技术 SSE指令集将80位浮点寄存器扩充到128位多媒体扩展通用寄存器XMM0~XMM7,可同时处理16个字节,或8个字,或4个双字(32位整数或单精度浮点数),或两个四字的数据,而且从SSE2开始,还支持128位整数运算或同时并行处理两个64位双精度浮点数
SSE指令(SIMD操作) 用简单的例子来比较普通指令与数据级并行指令的执行速度 为使比较结果不受访存操作影响,下例中的运算操作数在寄存器中 为使比较结果尽量准确,例中设置的循环次数较大: 0x4000000=226 例子只是为了说明指令执行速度的快慢,并没有考虑结果是否溢出 以下是普通指令写的程序 080484f0 <dummy_add>: 80484f0: 55 push %ebp 80484f1: 89 e5 mov %esp, %ebp 80484f3: b9 00 00 00 04 mov $0x4000000, %ecx 80484f8: b0 01 mov $0x1, %al 80484fa: b3 00 mov $0x0, %bl 80484fc: 00 c3 add %al, %bl 80484fe: e2 fc loop 80484fc <dummy_add+0xc> 8048500: 5d pop %ebp 8048501: c3 ret 所用时间约为22.643816s 循环400 0000H=226次,每次只有一个数(字节)相加
SSE指令(SIMD操作) 以下是SIMD指令写的程序 所用时间约为1.411588s 08048510 <dummy_add_sse>: 8048510: 55 push %ebp 8048511: b8 00 9d 04 10 mov $0x10049d00, %eax 8048516: 89 e5 mov %esp, %ebp 8048518: 53 push %ebx 8048519: bb 20 9d 04 14 mov $0x14049d20, %ebx 804851e: b9 00 00 40 00 mov $0x400000, %ecx 8048523: 66 0f 6f 00 movdqa (%eax), %xmm0 8048527: 66 0f 6f 0b movdqa (%ebx), %xmm1 804852b: 66 0f fc c8 paddb %xmm0, %xmm1 804852f: e2 fa loop 804852b <dummy_add_sse+0x1b> 8048531: 5b pop %ebx 8048532: 5d pop %ebp 8048533: c3 ret 22.643816s/ 1.411588s 16.041378,与预期结果一致! SIMD指令并行执行效率高! SIDM指令 循环400000H=222次,每次同时有128/8=16个数(字节)相加
总结 高级语言程序总是转换为机器代码才能在机器上执行 转换过程:预处理、编译、汇编、链接 机器代码是二进制代码,可DUMP为汇编代码表示 ISA规定了一台机器的指令系统涉及到的所有方面,例如: 所有指令的指令格式、功能 通用寄存器的个数、位数、编号和功能 存储地址空间大小、编址方式、大/小端 指令寻址方式 IA-32是典型的CISC(复杂指令集计算机)风格ISA Intel格式汇编、AT&T格式汇编(本课程使用) 指令类型(传送、算术、位操作、控制、浮点、…) 寻址方式 立即、寄存器、存储器(SR:[B]+[I]*s+A)