汇编语言设计
一 概述
1.1 汇编代码和目标代码
汇编代码非常接近与计算器机执行的实际机器代码。与目标代码的二进制格式相比,它的主要特色在于它采用的是更加易读的文本格式。通过阅读这些汇编代码,我们能够理解编译器的优化能力,并分析出代码中潜在的低效率。
1.2 反汇编
使用命令:objdump -d 文件,可以查看改文件的汇编代码。
这个文件可以是.o文件(目标代码文件),也可以是可执行文件。
1.3 几个常用的术语
字节:8位
字:16位,也就是两个字节
双字:32位,也就四个字节
二 寄存器
2.1 16位微处理器(8086为例)
16位处理器的寄存器都是16位的。8086是小端模式,高地址存放高位字节,低地址存放地位字节。
寄存器主要有14个,分为下面几类:
1、4个通用寄存器:AX、BX、CX、DX
2、4个专用寄存器:SP、BP、SI、DI
3、4个段地址寄存器:CS、DS、SS、ES
4、指令指针寄存器:IP
5、状态寄存器:PSW
1) 4个通用寄存器
四个通用寄存器可以也可以分为两个可独立使用的8位寄存器。
16位 高8位 低8位
AX AH AL
BX BH BL
CX CH CL
DX DH DL
2) 4个专用寄存器
SP:堆栈指针寄存器
BP:基址变址寄存器
SI:源变址寄存器
DI:目的变址寄存器
3) 4个段地址寄存器
CS:代码段寄存器
DS:数据段寄存器
SS:堆栈段寄存器,存放段地址,SP存放的是段内偏移。SS:SP指向栈顶元素!
DS:附加段寄存器
4) 指令指针寄存器IP
这个寄存器的主要的存在意义就是和CS寄存器一起,确定下一条指令的存储位置。(CS:IP就是指向下一条指令位置的指针,相当于简化模型中的那个PC)
5)标志寄存器PSW
同样是16位的一个寄存器。这是用于反应CPU内部状态以及用于控制CPU某些不见的行为而专门设置的。里面一个位就代表一个状态。常使用到的状态有下面几种:
CF:进位标志
PF:奇偶标志
AF:辅助进位标志
ZF:零标志
SF:符号溢出
OF:溢出标志
2.2 32位处理器中的寄存器(IA32架构处理器)
IA32架构的处理器提供三种基本的模式,实模式、保护模式、系统管理模式。其中实模式是为了兼容16位处理器而提供的,这个模式没有MMU。保护模式是IA32的原生模式,支持MMU。
寄存器主要有:
4个32位通用寄存器- EAX、EBX、ECX、EDX
4个32位专用寄存器- ESP、EBP、ESI、EDI
一个32位指令指针寄存器-EIP
6个32位段寄存器- CS、DS、ES、SS、FS、GS
一个标志寄存器-EFLAGS
这些寄存器是和8086CPU的功能作用是一样的,只不过拓展成了32位。但是某些寄存器仍然可以访问他的高16位或者是低16位或者是低16位中的高8位或者是低8位。
EAX,EAX的低16位AX,AX的高8位AH和低8位AL。
EBX,同EAX
ECX,同EAX
EDX,同EAX
ESI,ESI的低16位SI。
EDI,EDI的低16位DI。
EBP,EBP的低16位BP。
ESP,ESP的低16位SP。
1)通用寄存器(数据寄存器)
寄存器 | 用途 |
---|---|
EAX | 累加器,使用频率较高 |
EBX | 基地址寄存器,可以作为存储器指针来使用 |
ECX | 计数寄存器 |
EDX | 数据寄存器 |
2) 专用寄存器
两个指针寄存器ESP和EBP,主要存放的是在栈段的偏移量,都是用于访问栈内的存储单元:
指针寄存器 | 用途 |
---|---|
EBP | 基址指针寄存器, |
ESP | 堆栈指针寄存器,用它只可以访问栈顶 |
两个变址寄存器ESI和EDI,它们主要用于存放存储单元在段内的偏移量, 它们可作一般的存储器指针使用:
变址寄存器 | 用途 |
---|---|
ESI | 源变址寄存器 |
EDI | 目的变址寄存器 |
3)段寄存器(32机器下也是16位的)
需要说明的是,在32位机器下,只有在实地址模式下段寄存器才是存放内存段的段基址,在保护模式下,段寄存器存放的是内存中描述符表的位置。
段寄存器 | 用途 |
---|---|
CS | 代码段寄存器 |
DS | 数据段寄存器 |
SS | 堆栈段寄存器 |
ES、FS、GS | 附加段寄存器 |
4) 指令指针寄存器-EIP
存放的是下次要执行的指令在代码段的偏移量!使用CS和EIP就能找到下一条指令的存放位置。
2.3 理清几种寄存器之间的角色分类和搭配
1)访问栈中元素的时候
SS+EBP:可以访问整个栈
SS+ESP:只能访问栈顶元素
2)下一条指令
CS+EIP
3)存储器指针
EBX+ESI
EBX+EDI
2.4 IA32体系下的系统级体系结构资源
之前说到的都是基本执行环境下的一些基本的寄存器,实际上除此之外,还有一些其他的系统级别的资源,这些资源都是进行底层的一些控制的。
主要有如下的几种:
IO端口
控制寄存器
存储管理寄存器
调试寄存器
机器检查寄存器
下面主要展开讲一下存储管理寄存器,其余的那些可以不用看。
在保护模式下通过分段和分页机制进行逻辑地址到物理地址的转换,从而实现虚拟存储。存储管理寄存器就是用于实现这种机制。存储管理寄存器包括:
- 全局描述符表寄存器GDTR
- 中断描述符表寄存器IDTR
- 局部描述符表寄存器LDTR
- 任务寄存器TR
寄存器的表示:
GDTR和IDTR都是48位的寄存器,高32位分别存放全局描述符表和中断描述符表的线性基地址,低16位存放的是两种表的限长。
LDTR和TR是16位的寄存器。
三 指令系统
寻址方式主要有:
立即数寻址
寄存器寻址
直接寻址
寄存器间接寻址
寄存器相对寻址
基址变址寻址
相对变址寻址
3.1 立即数寻址
MOV AL,10H # 将10H放到AL寄存器中
MOV EAX,12003400H # 将12003400放到EAX寄存器中
3.2 寄存器寻址
MOV CL,AL # 将AL寄存器中的内容放到CL中
MOV AX,BX
3.3 直接寻址方式
直接寻址方式,默认是在数据段上的,除非指定某一个段,
MOV AX,[1200H] # 将数据段中偏移地址为1200H和1201H两个单元的内容放到AX中。(因为AX是16位的,而一个内存单元只是存放8位,也就是一个字节)
MOV BX,ES:[2000H] # 将ES段(附加段)中偏移地址为2000H和2001两单元的内容放到BX中。
3.4 寄存器间接寻址
寄存器中放的是地址的值。当使用寄存器间接寻址的时候,如果没有指定特定的段,那么在以ESP、EBP进行间接寻址的时候,默认的段寄存器为SS。以其他寄存器进行间接寻址的时候,默认的段寄存器为DS。
(注:两个指针寄存器ESP和EBP,主要存放的是在栈段的偏移量,都是用于访问栈内的存储单元,见前)
MOV AX,[BX] # 设BX=2000H,那么将在DS段(数据段)上偏移地址为2000H和2001H两单元中的内容放到AX中。
MOV AX,[BP] # 设BP=2000H,那么将栈段上偏移地址为2000H和2001H两单元中的内容放到AX中。
MOV ECX,[EDX] # 将DS段(数据段)上偏移地址为EDX中存放的内容的的后序4字节的存储单元中的内容放到ECX寄存器中。
3.5 寄存器相对寻址方式(直接变址寻址方式)
往后的寻址方式,需要熟悉ESI和EDI寄存器,一个是源变址寄存器,另一个是目的变址寄存器。在16位CPU的时代,直接变址寻址方式、间接变址寻址方式等的一些变址寻址方式中,只能使用SI或者DI,但是在32位的时代,不再仅限于ESI和EDI了。但是要知道的是SI、DI的出现就是在变址寻址这个场景下出现的。
在直接变址寻址方式中,默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。
MOV BX,100H[SI] (也可表示成MOV BX,[SI+100H]) # 假设SI=2000H,那么将数据段中,偏移地址为2100H和2101H两个单元的内容放到BX中。
MOV DL,ES:BUF[SI] # 将ES段中,偏移地址为[SI]+BUF中的内容放到DL中。
3.6 基址变址寻址方式
直接变址寻址方式中,默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。
MOV AX,[BX][SI] # 假设BX=100H,SI=2000H,那么将数据段中偏移地址为2100H和2101H中的内容放到AX中。
MOV EAX,[EBX][EBP] # 将堆栈段上偏移地址为[EBP+EBX]之后4字节的内容放到EAX上。EBX和EBP都是存放的地址值
3.7 相对基址变址寻址方式
默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。
MOV AX,MASK[BX][SI] (也可写成 MOV AX,MASK[SI+BX]或MOV AX,[SI+BX+MASK] )# BX和SI中都是地址值,将数据段中偏移地址为SI+BX+MASK的之后两个单元的位置放到AX中。
3.8 比例变址寻址方式
默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。
MOV EAX,COUNT[ESI*4]
3.9 基址比例变址寻址方式
默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。
MOV ECX,[EAX][EDX*8]
3.10 相对基址比例变址寻址方式
默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。
MOV,EAX TABLE[EBP][EDI*4]
四 IA32的基本指令集
4.1 数据传送指令
几个示例:
MOV AX,1200H
MOV BX,[BP+SI]
MOV [DI],AX # DI寄存器中存放的是地址,将AX寄存器中的内容,放到数据段中偏移地址为[DI]的存储单元中
需要注意的一个示例是,源操作数和目的操作数类型必须匹配,如果目的操作数为内存单元,而源操作数为立即数,则必须使用PTR运算符说明目的操作数的属性:
MOV BYTE PTR [SI],24H
MOV WORD PTR BUF[BX][SI],1234H # word说明是字操作,那么这是将1234H放到指定地址后面的两个连续的存储单元中
4.2 堆栈操作指令
栈是从高地址从低地址增长的。
一个16位数或者是32位的数据进栈规律是,高位字节存入高地址单元,低位字节存入低地址单元;
一个16位数或者是32位的数据出栈规律是,低地址字节存入目的操作数低位,高地址字节存入目的操作数高位;
堆栈的段地址放在SS寄存器中,并且栈顶指针寄存器ESP或SP存放栈顶的有效地址(偏移地址)。
进栈指令:PUSH SRC
当SRC是16位的时候,执行的操作是,先将SP-2,然后将SRC低位字节放到SP指向的存储单元中,将SRC的高位字节放到SP+1指向的存储单元中。
当SRC是32位的时候,执行的操作是,先将ESP-4,然后将SRC放到ESP、ESP+1、ESP+2、ESP+3这是个存储单元中。
出栈指令:POP DST
当DST是32位寄存器的时候,执行的操作是,先将ESP、ESP+1、ESP+2、ESP+3指向的存储单元放到DST中,然后调节栈顶指针,也就是ESP=ESP+4
对于POP的使用,如果内存的操作数不是采用直接寻址,则必须用PTR运算符说明其属性:
POP AX # 正常的使用
POP DWORD PTR[SI] # 将ESP指向的地址的开始的的四个单元中的内容放到数据段中有效地址为SI的4个存储单元中,然后将ESP=ESP+4
4.3 数据交换指令
1、交换指令:XCHG DST,SRC
将源操作数与目的操作数内容进行交换。
XCHG AL,BL
XCHG [2350],CX # 将CX中的内容和数据段偏移地址为2350和2351中的内容进行交换
2、字节交换指令 :BSWAP DST
该指令的DST必须要是32位的寄存器,实现的功能是将DST中3124与70位交换,2316与158位交换
4.4 累加器专用传送指令
这组指令只限于使用累加器EAX、AX、或AL传送信息。包括输出、输出、换码指令.(换码指令先不看了,下面只讲IN、OUT)
使用IN、OUT的场景:
IO端口的地址和内存单元是相互独立的,访问IO端口不能用普通的访问内存的指令来访问其信息。所以就有了输入输出指令(IN、OUT)来完成累加器EAX与IO端口之间的数据传送功能。
使用的注意的地方:
如果IO端口地址为一个字节,即在0~255之间,那么可以采用直接寻址方式,即端口地址直接在输入\输出中直接给出,最多访问256个端口。
如果IO端口地址为大于两个字节,即端口地址>=256,那么要进行间接寻址的方式,即先把端口的地址放在寄存器DX中,然后再进行IN或OUT。
IN和OUT一次传送多少字节,受外设端口宽度的影响,比如,如果端口宽度为8位,那么一次只能传送一个字节!!
示例:
IN AX,20H # 将端口20H和21H的内容送到AX。(是假设端口宽度是8位的前提下的,也就是一个端口上只有8位的数据,放满AX需要两个端口上的数据)
MOV [20H],AX # 也就是说,要使用IO端口中的数据,必须要使用EAX、AX和IN、OUT作为中介,不能直接从端口MOV到内存中。
MOV DX,180H
IN EAX,DX # 从180端口读一个双字到EAX中!端口号大于255,要使用DX作为中介存放端口号!(DX代表的是地址)
OUT 70H,AX # 如果端口宽度是8位,那么将AX中的内容输出到端口70H和71H上
MOV DX 1A8H
MOV AL,40H
OUT DX,AL # 这三条指令是将40H输出到端口1A8H上去。(DX代表的是地址)
4.5 地址传送指令
主要用于将存储器操作数地址(段地址、偏移地址)传送给指定的寄存器,他包含6条指令:LEA、LDS、LES、LFS、LGS、LSS
这些指令就是用法简单,这里不再展开
4.6 标志寄存器传送指令
标志寄存器传送指令用来传送标志寄存器FLAGS的内容,方便对各个标志位的直接操作。标志位传送指令有四条指令:LAHG、SAHF、PUSHF、POPF
1)读取标志指令LAHF
将标志位寄存器的低8位送入AH寄存器。该指令的执行对标志位无影响。
2)设置标志指令SAHF
将AH寄存器的内容送到标志寄存器的低8位,高8位不变。这是用来设置或恢复SF、ZF、AF、PF、CF五个标志位。
3) 标志寄存器进栈指令PUSHF/PUSHFD
功能是将标志寄存器中的内容进栈。
PUSHF是将SP中的内容进栈,也就是先将SP=SP-2,然后再把FLAGS的内容放到SP和SP+1指向的内存单元上。
PUSHFD是将ESP中的内容进栈,也就是先将ESP=ESP-4,然后再把FLAGS中的内容放到那四个内存单元上。
4)标志寄存器出栈指令POPF/POPFD
将栈顶内容放到标志寄存器中。
POPF是先将SP和SP+1的内容放到FLAGS中,然后再把SP=SP-2
POPFD和POPF相同,只不过操作的是ESP
4.7 类型转换指令
1)字节拓展到字CBW
2)字拓展到双字CWD
3)字拓展到双字CWDE
4)双字拓展到四字CDQ
4.8 算数运算指令
4.8.1 加法指令
1)ADD DST,SRC
将目的操作数与源操作数相加,送到目的操作数,源操作数不变。并设置响应的标志位(如进位标志和就标志)
ADD AL,20H
ADD EAX,ECX # 将ECX中的内容和EAX中的内容放在EAX中
ADD BYTE PTR [BX],12H # 将数据段中偏移地址为[BX]的存储单元中的字节和12H相加,结果送回该单元
ADD WORD PTR [BX],12H # 将数据段中偏移地址为[BX]和[BX+1]的内容和12H相加,结果送回这两个单元
2)ADC DST,SRC
将目的操作数加源操作数再加进位标志CF,结果送到目的操作数中,并设置相应的标志位。
3)INC OPR
将目的操作数加1,送到目的操作数中。
主要是用于对计数器加1或者是对地址指针加1,所以不影响进位标志CF!
INC BX
INC BYTE PTR [BX+DI+500]
4)交换并相加指令 XADD DST,SRC
先交换,然后再相加
XADD BL,CL # 如果BL=12H,CL=02H,那么操作后BL=14H,CL=12H
4.8.2 减法指令
1)SUB DST,SRC
将DST中的内容减去SRC,结果放到DST中。并设置相应的标志位。(产生进位或者借位的时候,CF都会置1)
示例:
SUB BX,3440H
SUB [BP+2],CL
SUB WORD PTR [DI],1000H # 将数据段中有效地址为DI的存储单元之后的两个字节减去1000H
2)SBB DST,SRC
将DST减去SRC再减去CF,结果放到DST中。并会设置相应的标志位。
3)DEC OPR
将目的操作数减去1。和INC的用途是一样的,所以也不会设置相应的标志位。
4) 求补指令:NEG OPR
对目的操作数进行求补运算。
5) 比较指令:CMP OPR1,OPR2
将目的操作数减去源操作数,但是结果不会送回目的操作数,而是只会影响标志位。
所以可以根据标志位判断两个操作数的大小。
4.8.3 乘法指令
1)无符号乘法指令:MUL SRC
2)有符号数乘法指令:IMUL SRC
两个指令的使用都是相同的:
如果SRC是8位的寄存器,那么执行的是字节乘法:AX=AL*SRC
如果SRC是16的位寄存器,那么执行的是字乘法:(DX,AX)=AX*SRC ,相乘的高位放在DX,地位放在AX
如果SRC是32位的寄存器,那么执行的是双字乘法:(EDX,EAX)= EAX*SRC ,结果的高位放在EDX,低位放在EAX
值得注意的是:
- MUL和IMUL的不同就是对SRC中的数据的解释方式不一样,MUL将其解释成无符号的,IMUL将其解释成无符号的。
- 乘法指令对CP和OF有影响,对其他标志位无定义(也就是说影响任意,不可预测,可以理解为无影响)。
4.8.4 除法指令
执行两个二进制的除法运算。
1)无符号除法指令DIV SRC
2)有符合除法指令IDIV SRC
4.9 逻辑指令
1)逻辑与指令:AND DST,SRC
将目的操作数和源操作数进行按位与,结果送到目的操作数中。
示例:
AND AL,77H
AND AX,BX
2) 逻辑或指令:OR DST,SRC
按位或
3) 逻辑非运算 NOT OPR
对目的操作数进行按位取反。
4)异或指令:XOR DST,SRC
将目的操作数和源操作数进行按位异或,结果放在目的操作数中
5)位测试并修改指令
测试目的操作数中由源操作指定的那一位,将测试位的值送CF。
- 位测试指令:BT DST,SRC #目的操作数不变
- 位测试并置1指令: BTS DST,SRC # 将测试结果送CF后,将测试位置1
- 位测试并置0指令:BTR DST,SRC # 将测试结果送CF后,将测试位置0
- 位测试并变反指令:BTC DST,SRC # 将测试结果送CF后,将测试位置取反
示例:
BT AX,0 # 将AX的第0位送CF
BTR EAX,31 #将EAX的31位送CF,并将其第31位置1
6)位扫描指令
- 正向位扫描指令:BSF REG,SRC ,从低位往高位开始扫描,将遇到的第一个1的位序号存入目的寄存器中,并将ZF置0。若源操作数为0,则将ZF置1,目的寄存器内容不变。
- 反向位扫描指令:BSR REG,SRC ,和BSF一样,只是扫描顺序不一样。
示例:
MOV EAX,23456780H
BSF EBX,EAX # (EBX)=7,EAX不变,ZF=0
7)移位指令
4.10 串处理指令
先略
4.11 控制转移指令
控制转移指令就是通过修改CS和IP或EIP中的值来控制程序的执行流程的。
控制转移指令包含5类指令:无条件转移指令、条件转移指令、循环指令、子程序调用指令、返回指令、中断和中断返回指令。
五 总结
上面的只是简单的进行了片面的总结,后序还需要对其中的很多东西进行补充、更新。