前言

感觉汇编挺有意思的,平时写代码的时候没考虑这么多,以下示例皆为8086CPU
8086 是 x86 架构的起点,后续的 80186、80286、80386、80486、Pentium 都是基于它扩展的

基础知识

存储器

8bit(位)=1Byte(字节)
2Byte=1word
1024Byte(字节)=1KB

存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号,大小为1byte,表现为2位的16进制数

为方便调用,8086CPU在内部用段地址和偏移地址最终形成物理地址即
段地址*16(左移4位)+偏移地址=物理地址

大小端

示例
同样 int x = 0x12345678
大端序(Big Endian)
地址: 0x100 0x101 0x102 0x103
值: 12 34 56 78

小端序(Little Endian)
地址: 0x100 0x101 0x102 0x103
值: 78 56 34 12

这样看来大端更加贴近常识,但是常见的系统,如windows.linux等都是小端存储,所以遇到大于一个字节的数据,并且有操作字节的操作时应注意

python实现小端数据的运算

1
2
3
4
5
cipher = bytearray(b"zer0pts")
value = int.from_bytes(cipher, 'little') #转换成小端序整数
print(hex(value))#0x7374703072657a,即 'ztpr0pts'的反转
value += key
s=value.to_bytes(8,'little').decode() #小端序整数转化成字符串

总线

CPU要对数据进行读写,必须和外部器件进行以下三类信息的交互:
1、存储单元的地址(地址信息);
2、器件的选择、读或写命令(控制信息);
3、读或写的数据(数据信息) 。
总线是连接CPU和其他芯片的导线,逻辑上分为地址总线、数据总线、控制总线。

CPU从内存单元中读写数据的过程:
1、CPU通过地址线将地址信息发出;
2、CPU通过控制线发出内存读命令,选中存储器芯片,并通知它将要从中读或写数据;
3、存储器将相应的地址单元中的数据通过数据线送入CPU或CPU通过数据线将数据送入相应的内存单元。

8086外部数据总线宽度为16位,它既能处理16位数据,也能处理8位数据。可寻址的内存空间为1mb。
CPU是通过地址总线指定存储单元,地址总线传送的能力决定了CPU对存储单元的寻址能力。
8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力,但8086CPU内部数据总线宽度是16位,表现出来的寻址能力只有64kb。8086CPU采用一种在内部用用两个16位地址合成的方法来形成一个20位的物理地址。

寄存器

寄存器是cpu完成操作的载体
8086 CPU 中寄存器总共为 14 个,且均为 16 位。
即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。而这 14 个寄存器按照一定方式又分为了通用寄存器,控制寄存器和段寄存器。

如果32位寄存器,就是在前面加E然后是32位,4字节

通用寄存器

通用寄存器的每一个16位寄存器就可以当做2个独立的8位寄存器来使用,如AX寄存器可以分为两个独立的8位的AH和AL寄存器;

  1. AX (Accumulator)——累加寄存器,也称之为累加器;
    AX寄存器还具有的特殊用途是在使用DIV和MUL指令时使用,
    在mul使用时al或ax作为被乘数,积保留在ax或积低16位保留在ax
    在div使用时ax作为被除数或被除数后16位,商保留在al或ax
  2. bx——based register——基地址寄存器
    BX主要还是用于其专属功能–寻址(寻址物理内存地址)上,BX寄存器中存放的数据一般是用来作为偏移地址使用的。
  3. cx——count register——计数器
    当在汇编指令中使用循环LOOP指令时,可以通过CX来指定需要循环的次数
  4. dx——data registered——数据寄存器
    dx可与ax配合使用,常作为ax的扩展
    在执行MUL指令时,如果两个相乘的数都是16位的话,那么相乘后产生的结果显然需要32位来保存,而这32位的结果的高16位就是存放在DX寄存器中。
    如除数为16位时,被除数将会是32位,而被除数的高16位就是存放在DX中,而且执行完DIV指令后,本次除法运算所产生的余数将会保存在DX中

    段寄存器

    cs——code segment——存储代码段的起始地址
    ds——data segment——存储数据段的起始地址
    ss——stack segment——存储栈段的起始地址
    es——extra segment——附加段寄存器

    变址寄存

    si——source index——源变址寄存器
    di——destination index——目的变址寄存器

    指针寄存器

    ip——instructor point——指令指针寄存器
    sp——stack point——堆栈指针寄存器,始终指向当前栈顶
    bp——base point——基础指针,在函数执行期间固定,作为当前函数栈帧的基准点

状态标志寄存器

名称 全称(英文) 中文解释 为 1 时的简写 为 0 时的简写 说明
11 OF Overflow Flag 溢出标志 OV (Overflow) NV (No Overflow) 有符号数溢出时置 1
10 DF Direction Flag 方向标志 DN (Down) UP (Up) 字符串操作方向控制
9 IF Interrupt Flag 中断允许标志 EI (Enable Interrupt) DI (Disable Interrupt) 控制外部中断开关
8 TF Trap Flag 陷阱标志(单步调试) ST (Single-step Trap) NT (No Trap) 若为 1 → 每执行一条指令触发调试中断
7 SF Sign Flag 符号标志 NG (Negative) PL (Plus) 结果最高位为 1(负)时置 1
6 ZF Zero Flag 零标志 ZR (Zero) NZ (Not Zero) 运算结果为 0 时置 1
4 AF Auxiliary Carry Flag 辅助进位标志 AC (Auxiliary Carry) NA (No Auxiliary Carry) 用于 BCD 半字节进位
2 PF Parity Flag 奇偶标志 PE (Parity Even) PO (Parity Odd) 结果最低字节中 1 的个数为偶数则置 1
0 CF Carry Flag 进位标志 CY (Carry) NC (No Carry) 无符号进位/借位产生时置 1

汇编指令

数据传送指令

数据传送指令可以利用立即数和寄存器来完成存储单元的数据传输,没有程序转移的功能,不能改变cs:ip的值

  1. 通用数据传送指令。
    MOV 传送指令 MOV DEST,SRC
    XCHG 交换指令 XCHG OPER1,OPER2

  2. 堆栈和堆栈操作指令。
    PUSH 进栈指令 PUSH SRC
    POP 出栈指令 POP DEST

  3. 目的地址传送指令。
    LEA 取偏移地址指令 LEA REC,OPRD
    LDS 取指针内容指令 例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
    LES 取指针内容指令 例: LES DI,string ;把段地址:偏移地址存到ES:DI.

  4. 符号拓展指令
    CBW 字节转化为字指令 把寄存器AL中的值符号拓展到寄存器AH
    CWD 字转化为双字指令 把寄存器AX中的值符号拓展到寄存器DX

    算术运算指令

  5. 加减
    ADD 加法指令 ADD DEST,SRC DEST<=DEST SRC 两数相加
    SUB 减法指令 SUB DEST,SRC DEST<=DEST-SRC 两数相减
    INC 加1指令 INC DEST DEST<=DEST 1
    DEC 减1指令 DEC DEST DEST<=DEST-1
    ADC 带进位加法指令 ADC DEST,SRC DEST<=DEST SRC CF 与add指令不同之处是要再加上进位标志cf的值
    SBB 带借位减法 SBB DEST,SRC DEST<=DEST-(SRC CF) 与sub指令不同之处是要再减上借位标志cf的值

  6. 乘除
    MUL 无符号数乘法指令
    IMUL 有符号数乘法指令
    DIV 无符号数除法指令
    IDIV 有符号数除法

  7. 比较
    CMP 比较指令 CMP DEST,SRC 根据dest-src的差影响各状态标志寄存器 不把dest-src的结果送入dest

  8. 相反数
    NEG 取补指令 NEG OPRD OPRD=0-OPRD 对操作数取补(相反数)

    逻辑运算指令

指令 名称 格式 功能描述 运算规则 / 特点
NOT 否运算指令 NOT OPRD 把操作数 OPRD 按位取反,然后送回 OPRD 每一位取反:1→0,0→1
AND 与运算指令 AND DEST, SRC 把两个操作数进行按位与运算,结果送回 DEST 同 1 得 1,否则得 0
OR 或运算指令 OR DEST, SRC 把两个操作数进行按位或运算,结果送回 DEST 同 0 得 0,否则得 1
XOR 异或运算指令 XOR DEST, SRC 把两个操作数进行按位异或运算,结果送回 DEST 相同得 0,不同得 1
TEST 测试指令 TEST DEST, SRC 将两个操作数按位 AND 运算,但结果不保存,仅影响标志位 若结果为 0 → ZF=1;结果非 0 → ZF=0;同时 CF=0,OF=0,ZF/PF/SF 根据结果设置
SAL 算术左移 SAL OPRD, count 把操作数 OPRD 左移 count 位,右边补 0 SHL 相同;移位数由 count 低 5 位决定(0~31)
SHL 逻辑左移 SHL OPRD, count 把操作数 OPRD 左移 count 位,右边补 0 SAL 相同;移位数取 count 低 5 位(0~31)
SAR 算术右移 SAR OPRD, count 把操作数 OPRD 右移 count 位,左边补符号位,移出的最低位进 CF 保持符号(正负不变);count 低 5 位为有效位数(0~31)
SHR 逻辑右移 SHR OPRD, count 把操作数 OPRD 右移 count 位,左边补 0,移出的最低位进 CF 无符号右移;count 低 5 位为有效位数(0~31)
ROL 左循环移位 ROL OPRD, count 操作数按位循环左移 移出高位循环补入低位;count 低 5 位为有效位数(0~31)
ROR 右循环移位 ROR OPRD, count 操作数按位循环右移 移出低位循环补入高位;count 低 5 位为有效位数(0~31)
RCL 带进位左循环移位 RCL OPRD, count 操作数与 CF 一起参与循环左移 CF 参与循环(扩展一位);count 低 5 位为有效位数(0~31)
RCR 带进位右循环移位 RCR OPRD, count 操作数与 CF 一起参与循环右移 CF 参与循环(扩展一位);count 低 5 位为有效位数(0~31)

补充说明

  • count 取值:CPU 实际只使用 count 的低 5 位(即最多 31 次移位)。
  • 标志位影响
    • AND, OR, XOR, TEST:影响 ZF, SF, PF,清除 CF, OF
    • SHL, SHR, SAL, SAR:影响 CFOF
    • ROL, ROR, RCL, RCR:影响 CFOF(依情况)

程序转移指令

  1. 转移指令

    JMP    无条件段内直接转移指令    JMP LABEL    使控制无条件地转移到标号为label的位置    无条件转移指令本身不影响标志
    
    JA/JNBE >
    JAE/JNB ≥
    JB/JNAE <
    JBE/JNA ≤
    以上四条,测试无符号整数运算的结果(标志C和Z)。
    
    JG/JNLE >
    JGE/JNL ≥
    JL/JNGE <
    JLE/JNG ≤
    以上四条,测试有符号整数运算的结果(标志S,O和Z)。
    
    JE/JZ    ZF=1
    JNE/JNZ  ZF=0
    JC       CF=1
    JNC 
    JO       OF=1
    JNO 
    JS       SF=1
    JNS
    
  1. 循环指令
    LOOP    计数循环指令    LOOP LABEL    使CX的值减1,当CX的值不为0的时候跳转至LABEL,否则执行LOOP之后的语句
    
  2. 子程序指令
    CALL    过程调用指令    CALL LABEL    段内直接调用LABEL    与jmp的区别在于call指令会在调用label之前保存返回地址(call 中return之后主程序还可以继续执行,jmp 当label执行完毕后不能返回主程序继续执行)
    RET    段内过程返回指令    RET    使子程序结束,继续执行主程序
    

    变量属性

    SEG 得到变量名或标号名的段基址
    OFFSET 得到变量名或标号名的偏移量
    TYPE 在变量名前,返回值为1(字节)、2(字)、4(双字)
    LENGTH 用在变量前面,对于变量使用DUP进行定义的情况,汇编程序将回送分配给该变量的单元数。对于其他情况则回送1
    SIZE 用在变量前面,汇编程序将回送分配给该变量的字节数,值为 LENGTH 与 TYPE 值的乘积
    PTR 指定类型

    汇编程序

    一个汇编语言程序从写出到最终执行的简要过程:
    编写 -> 编译连接 -> 执行
    在Win 64位系统下实现Debug汇编的方法。
1
2
3
4
mount c d:\                  回车 
c:                          回车
汇编连接源程序文件
ML <源文件名.ASM>

如果源程序没有错误,则自动生成.OBJ 文件和.EXE 可执行文件。

结构

1
2
3
4
5
6
7
8
9
10
11
assum cs:codesg
codesg segment
start: mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax

mov ax,4c00h
int 21h
codesg ends
end

segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。segment和ends的功能是定义一个段,segment说明一个段开始,ends 说明一个段结束。
assume对除了CS以外的其它段寄存器,仅仅只是关联了段名,以便在访问段内变量时程序可以知道用哪个段寄存器,并没有在程序加载时将段地址装入段寄存器。所以,将段地址装入段寄存器的工作,必须由用户在程序中自己编写代码,并在程序开始运行时执行代码完成装入工作。

内存并没有分段,段的划分来自于CPU。由于8086CPU用“基础地址(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。

数据段:存放数据的段。使用时候,用DS寄存器。
程序段:用来存放程序的段。使用的时候,用CS和IP寄存器。
栈段: 是一个栈。使用时,初始设置SS和SP寄存器。

输入

  1. 字符输入
    mov ah, 1;输入后,收到的值存放在AL中
    int 21h

  2. 字符串输入
    BUFFER DB 20 ;预定义20字节的空间

    DB  ?                    ;待输入完成后,自动获得输入的字符个数
    DB  20  DUP(0)  
    
    LEA DX,BUFFER        ;接收字符串
    MOV AH, 0AH
    INT 21H  
    

输出

  1. 字符输出
    1
    2
    3
    4
    5
    6
          mov ah,02h
    mov dl,0dh ;回车键
    int 21h
    mov ah,02h
    mov dl,0ah ;换行键
    int 21h
  2. 字符串输出
    1
    2
    3
    4
    5
    INPUT  DB  "Please input a string: ",'$'

    LEA DX, INPUT ;打印提示输入信息
    MOV AH, 09H
    INT 21H