• 汇编语言设计


    一 概述

    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
    • 2
    • 3
    • 4
    • 5

    1) 4个通用寄存器

    四个通用寄存器可以也可以分为两个可独立使用的8位寄存器。

    16位    高8位     低8位
    AX      AH        AL
    BX      BH        BL
    CX      CH        CL
    DX      DH        DL           
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2) 4个专用寄存器

    SP:堆栈指针寄存器
    BP:基址变址寄存器
    SI:源变址寄存器
    DI:目的变址寄存器
    
    • 1
    • 2
    • 3
    • 4

    3) 4个段地址寄存器

    CS:代码段寄存器
    DS:数据段寄存器
    SS:堆栈段寄存器,存放段地址,SP存放的是段内偏移。SS:SP指向栈顶元素!
    DS:附加段寄存器
    
    • 1
    • 2
    • 3
    • 4

    4) 指令指针寄存器IP

    这个寄存器的主要的存在意义就是和CS寄存器一起,确定下一条指令的存储位置。(CS:IP就是指向下一条指令位置的指针,相当于简化模型中的那个PC)
    
    • 1

    5)标志寄存器PSW

    同样是16位的一个寄存器。这是用于反应CPU内部状态以及用于控制CPU某些不见的行为而专门设置的。里面一个位就代表一个状态。常使用到的状态有下面几种:

    CF:进位标志
    PF:奇偶标志
    AF:辅助进位标志
    ZF:零标志
    SF:符号溢出
    OF:溢出标志
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这些寄存器是和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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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就能找到下一条指令的存放位置。
    
    • 1

    2.3 理清几种寄存器之间的角色分类和搭配

    1)访问栈中元素的时候

    SS+EBP:可以访问整个栈
    SS+ESP:只能访问栈顶元素
    
    • 1
    • 2

    2)下一条指令

    CS+EIP
    
    • 1

    3)存储器指针

    EBX+ESI
    EBX+EDI 
    
    • 1
    • 2

    2.4 IA32体系下的系统级体系结构资源

    之前说到的都是基本执行环境下的一些基本的寄存器,实际上除此之外,还有一些其他的系统级别的资源,这些资源都是进行底层的一些控制的。

    主要有如下的几种:

    IO端口
    控制寄存器
    存储管理寄存器
    调试寄存器
    机器检查寄存器
    
    • 1
    • 2
    • 3
    • 4
    • 5

    下面主要展开讲一下存储管理寄存器,其余的那些可以不用看。

    在保护模式下通过分段和分页机制进行逻辑地址到物理地址的转换,从而实现虚拟存储。存储管理寄存器就是用于实现这种机制。存储管理寄存器包括:

    • 全局描述符表寄存器GDTR
    • 中断描述符表寄存器IDTR
    • 局部描述符表寄存器LDTR
    • 任务寄存器TR

    寄存器的表示:

    GDTR和IDTR都是48位的寄存器,高32位分别存放全局描述符表和中断描述符表的线性基地址,低16位存放的是两种表的限长。
    
    LDTR和TR是16位的寄存器。
    
    • 1
    • 2
    • 3

    指令系统

    寻址方式主要有:

    立即数寻址
    寄存器寻址
    直接寻址
    寄存器间接寻址
    寄存器相对寻址
    基址变址寻址
    相对变址寻址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.1 立即数寻址

    MOV AL,10H  # 将10H放到AL寄存器中
    MOV EAX,12003400H  # 将12003400放到EAX寄存器中
    
    • 1
    • 2

    3.2 寄存器寻址

    MOV CL,AL  # 将AL寄存器中的内容放到CL中
    MOV AX,BX
    
    • 1
    • 2

    3.3 直接寻址方式

    直接寻址方式,默认是在数据段上的,除非指定某一个段,

    MOV AX,[1200H] # 将数据段中偏移地址为1200H和1201H两个单元的内容放到AX中。(因为AX是16位的,而一个内存单元只是存放8位,也就是一个字节)
    
    MOV BX,ES:[2000H] # 将ES段(附加段)中偏移地址为2000H和2001两单元的内容放到BX中。
    
    • 1
    • 2
    • 3

    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寄存器中。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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中。
    
    • 1
    • 2
    • 3

    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都是存放的地址值
    
    • 1
    • 2
    • 3

    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中。
    
    • 1

    3.8 比例变址寻址方式

    默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。

    MOV EAX,COUNT[ESI*4] 
    
    • 1

    3.9 基址比例变址寻址方式

    默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。

    MOV ECX,[EAX][EDX*8]
    
    • 1

    3.10 相对基址比例变址寻址方式

    默认的段,和寄存器间接寻址是一样的,即使用的寄存器是ESP、EBP、SP、BP的时候,默认的段是在栈段,其他的时候都是在DS段。

    MOV,EAX TABLE[EBP][EDI*4]
    
    • 1

    四 IA32的基本指令集

    4.1 数据传送指令

    几个示例:

    MOV AX,1200H
    MOV BX,[BP+SI]
    MOV [DI],AX  # DI寄存器中存放的是地址,将AX寄存器中的内容,放到数据段中偏移地址为[DI]的存储单元中
    
    • 1
    • 2
    • 3

    需要注意的一个示例是,源操作数和目的操作数类型必须匹配,如果目的操作数为内存单元,而源操作数为立即数,则必须使用PTR运算符说明目的操作数的属性:

    MOV BYTE PTR [SI],24H
    MOV WORD PTR BUF[BX][SI],1234H  # word说明是字操作,那么这是将1234H放到指定地址后面的两个连续的存储单元中
    
    • 1
    • 2

    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这是个存储单元中。
    
    • 1
    • 2
    • 3

    出栈指令:POP DST

    当DST是32位寄存器的时候,执行的操作是,先将ESP、ESP+1、ESP+2、ESP+3指向的存储单元放到DST中,然后调节栈顶指针,也就是ESP=ESP+4
    
    • 1

    对于POP的使用,如果内存的操作数不是采用直接寻址,则必须用PTR运算符说明其属性:

    POP AX  # 正常的使用
    POP DWORD PTR[SI]  # 将ESP指向的地址的开始的的四个单元中的内容放到数据段中有效地址为SI的4个存储单元中,然后将ESP=ESP+4
    
    • 1
    • 2

    4.3 数据交换指令

    1、交换指令:XCHG DST,SRC
    将源操作数与目的操作数内容进行交换。

    XCHG AL,BL
    XCHG [2350],CX  # 将CX中的内容和数据段偏移地址为2350和2351中的内容进行交换
    
    • 1
    • 2

    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端口之间的数据传送功能。
    
    • 1

    使用的注意的地方:

    如果IO端口地址为一个字节,即在0~255之间,那么可以采用直接寻址方式,即端口地址直接在输入\输出中直接给出,最多访问256个端口。
    如果IO端口地址为大于两个字节,即端口地址>=256,那么要进行间接寻址的方式,即先把端口的地址放在寄存器DX中,然后再进行IN或OUT。
    IN和OUT一次传送多少字节,受外设端口宽度的影响,比如,如果端口宽度为8位,那么一次只能传送一个字节!!
    
    • 1
    • 2
    • 3

    示例:

    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代表的是地址)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.5 地址传送指令

    主要用于将存储器操作数地址(段地址、偏移地址)传送给指定的寄存器,他包含6条指令:LEA、LDS、LES、LFS、LGS、LSS

    这些指令就是用法简单,这里不再展开

    4.6 标志寄存器传送指令

    标志寄存器传送指令用来传送标志寄存器FLAGS的内容,方便对各个标志位的直接操作。标志位传送指令有四条指令:LAHG、SAHF、PUSHF、POPF

    1)读取标志指令LAHF

    将标志位寄存器的低8位送入AH寄存器。该指令的执行对标志位无影响。
    
    • 1

    2)设置标志指令SAHF

    将AH寄存器的内容送到标志寄存器的低8位,高8位不变。这是用来设置或恢复SF、ZF、AF、PF、CF五个标志位。
    
    • 1

    3) 标志寄存器进栈指令PUSHF/PUSHFD

    功能是将标志寄存器中的内容进栈。
    PUSHF是将SP中的内容进栈,也就是先将SP=SP-2,然后再把FLAGS的内容放到SP和SP+1指向的内存单元上。
    PUSHFD是将ESP中的内容进栈,也就是先将ESP=ESP-4,然后再把FLAGS中的内容放到那四个内存单元上。
    
    • 1
    • 2
    • 3

    4)标志寄存器出栈指令POPF/POPFD

    将栈顶内容放到标志寄存器中。
    POPF是先将SP和SP+1的内容放到FLAGS中,然后再把SP=SP-2
    POPFD和POPF相同,只不过操作的是ESP
    
    • 1
    • 2
    • 3

    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相加,结果送回这两个单元
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2)ADC DST,SRC

    将目的操作数加源操作数再加进位标志CF,结果送到目的操作数中,并设置相应的标志位。
    
    • 1

    3)INC OPR

    将目的操作数加1,送到目的操作数中。
    主要是用于对计数器加1或者是对地址指针加1,所以不影响进位标志CF!
    INC BX
    INC BYTE PTR [BX+DI+500]
    
    • 1
    • 2
    • 3
    • 4

    4)交换并相加指令 XADD DST,SRC

    先交换,然后再相加
    XADD BL,CL # 如果BL=12H,CL=02H,那么操作后BL=14H,CL=12H
    
    • 1
    • 2

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2)SBB DST,SRC

    将DST减去SRC再减去CF,结果放到DST中。并会设置相应的标志位。
    
    • 1

    3)DEC OPR

    将目的操作数减去1。和INC的用途是一样的,所以也不会设置相应的标志位。
    
    • 1

    4) 求补指令:NEG OPR

    对目的操作数进行求补运算。
    
    • 1

    5) 比较指令:CMP OPR1,OPR2

    将目的操作数减去源操作数,但是结果不会送回目的操作数,而是只会影响标志位。
    所以可以根据标志位判断两个操作数的大小。
    
    • 1
    • 2

    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
    
    • 1
    • 2
    • 3

    值得注意的是:

    - MUL和IMUL的不同就是对SRC中的数据的解释方式不一样,MUL将其解释成无符号的,IMUL将其解释成无符号的。
    - 乘法指令对CP和OF有影响,对其他标志位无定义(也就是说影响任意,不可预测,可以理解为无影响)。
    
    • 1
    • 2

    4.8.4 除法指令

    执行两个二进制的除法运算。

    1)无符号除法指令DIV SRC

    2)有符合除法指令IDIV SRC

    4.9 逻辑指令

    1)逻辑与指令:AND DST,SRC

    将目的操作数和源操作数进行按位与,结果送到目的操作数中。
    示例:
    AND AL,77H
    AND AX,BX
    
    • 1
    • 2
    • 3
    • 4

    2) 逻辑或指令:OR DST,SRC

    按位或
    
    • 1

    3) 逻辑非运算 NOT OPR

    对目的操作数进行按位取反。
    
    • 1

    4)异或指令:XOR DST,SRC

    将目的操作数和源操作数进行按位异或,结果放在目的操作数中
    
    • 1

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7)移位指令

    4.10 串处理指令

    先略

    4.11 控制转移指令

    控制转移指令就是通过修改CS和IP或EIP中的值来控制程序的执行流程的。
    控制转移指令包含5类指令:无条件转移指令、条件转移指令、循环指令、子程序调用指令、返回指令、中断和中断返回指令。

    五 总结

    上面的只是简单的进行了片面的总结,后序还需要对其中的很多东西进行补充、更新。

  • 相关阅读:
    K8s之CRD
    【C++基础入门】43.C++中多态的概念和意义
    【校招VIP】前端JS语言考点之px rem等单位
    Springboot如何整合Kafka
    二叉树层级遍历(深度优先、广度优先算法)
    Flask 的 ORM 模型 - 概述
    【星海随笔】redis 解析
    HCNP Routing&Switching之RSTP保护
    ARM Day8
    `算法竞赛题解` Monitor
  • 原文地址:https://blog.csdn.net/luseysd/article/details/127967135