跳转至

汇编入门

模板:

.386
data segment use16
data ends
code segment use16
assume cs:code,ds:data
main:

   mov ah, 4Ch
   mov al, 0
   int 21h
code ends
end main

数据的表示与运算

  • 数据的表示

    • 有符号数与无符号数

      • 有符号数:具有符号位,用于表示正数和负数。用补码存储。

        表示范围:\([-2^{n-1},2^{n-1}-1]\),其中非负数范围为 \([0,2^{n-1}-1]\),负数范围为 \([-2^{n-1},-1]\)

        补码
        • 补码定义:\(x_{补码}=\begin{cases}x&(x\geq 0)\\2^n-|x|&(x<0)\end{cases}\)

          Info
          • 原码:最高位为符号位,其余为数值位。

          • 反码:正数反码 = 原码;负数反码 = 原码符号位不变、数值位取反。

            \(x_{反码}=\begin{cases}x&(x\geq 0)\\2^n-1-|x|&(x<0)\end{cases}\)

          • 补码:正数补码 = 原码;负数补码 = 反码 + 1。

            \(x_{补码}=\begin{cases}x&(x\geq 0)\\2^n-|x|&(x<0)\end{cases}\)

            性质:设 \(a,b>0\)\(a_{补码}+(-b)_{补码}=a+(2^n-b)\equiv a-b\pmod {2^n}\)

            因此补码不仅统一了 \(+0\)\(-0\),也实现了加法与减法的统一。

        • 由补码求真值:\(x=\begin{cases}x_{补码}&(0\leq x_{补码}<2^{n-1})\\x_{补码}-2^n&(2^{n-1}\leq x_{补码}<2^n)\end{cases}\)

      • 无符号数:无符号位,均为数值位。

        表示范围:\([0,2^n-1]\)

    • 十六进制常数:后缀 hH,且若十六进制常数恰好以字母开头,需要添加 0 前缀(如 0FFFFh 相当于 C 语言的 0xFFFF)。

      二进制常数:后缀 B

  • 运算

    • 算术运算:

      • +add ax,bx 表示 ax+=bx

      • -sub ax,bx

      • *imul(有符号),mul(无符号)

        单操作数(mulimul):

        数据宽度 被乘数(隐含) 乘数(显式操作) 结果
        8 位乘法 AL(8 位) 8 位 AX(16 位,AH:AL
        16 位乘法 AX(16 位) 16 位 DX:AX(32 位)
        32 位乘法 EAX(32 位) 32 位 EDX:EAX(64 位)
        64 位乘法(x86-64) RAX(64 位) 64 位 RDX:RAX(128 位)

        双操作数(imul):

        imul ax,bx

        数据宽度 被乘数 乘数 结果
        16 位 16 位寄存器 16 位 目标寄存器(16 位)
        32 位 32 位寄存器 32 位 目标寄存器(32 位)
        64 位(x86-64) 64 位寄存器 64 位 目标寄存器(64 位)
      • /idiv(有符号),div(无符号)

        数据宽度 被除数(隐含) 除数(显式操作) 商(结果) 余数(结果)
        8 位除法 AX(16 位) 8 位 AL(8 位) AH(8 位)
        16 位除法 DX:AX(32 位) 16 位 AX(16 位) DX(16 位)
        32 位除法 EDX:EAX(64 位) 32 位 EAX(32 位) EDX(32 位)
        64 位除法(x86-64) RDX:RAX(128 位) 64 位 RAX(64 位) RDX(64 位)
        示例
        mov dx, -1; 相当于mov dx, 0FFFFh
        mov ax, -6; 相当于mov ax, 0FFFAh
                  ; dx与ax组合起来就是0FFFFFFFAh, 它就是32位的-6  
        mov bx, 2 ; 当除数为16位的寄存器或变量时,被除数一定是=dx(高 16 位):ax(低 16 位)
        idiv bx   ; ax=商=-3=0FFFDh, dx=余数=0=0000h
        
    • 关系运算:

      • ==je
      • !=jne
      • >:有符号数 jg,无符号数 ja
      • <:有符号数 jl,无符号数 jb
      • >=:有符号数 jge,无符号数 jae
      • <=:有符号数 jle,无符号数 jbe
    • 逻辑运算:

      • &&:用两个嵌套的 if 实现。
      • ||:用两个并列的 if 实现。
      • !
    • 位运算:

      • &and al,bl
      • |or al,bl
      • ^xor al,bl
      • ~not ax
      • <<shl al,1。最后一个被移动的位会被放到 CF 标志中。

        (与 jc 配合:若 CF=1,jc 会跳转)

      • >>shr al,1(无符号),sar al,1(有符号)。最后一个被移动的位会被放到 CF 标志中。

      • 循环左移 rol,循环右移 ror。最后一个被移动的位会在被放到另一端的同时放到 CF 标志中。

int21h 中断

常见中断 int 21h:(中断大全

AH 功能 调用参数 返回参数
00 程序终止 CS=程序段前缀
01 键盘输入并回显 AL=输入字符
02 显示输出 DL=输出字符(\r → 0Dh,\n → 0Ah)
09 显示字符串 DS:DX=字符串地址
4C 带返回码结束 AL=返回码

堆栈

push ax:堆栈指针 -=2,再把 ax 存放到新指针指向的内存中

pop cx:把当前堆栈指针指向的内容取出保存到 cx,再堆栈指针 +=2

分支与循环

用跳转实现。

do-while
code segment
assume cs:code
main:
mov ax,[l1] 
outer_loop:
    mov bx,[l2]
    middle_loop:
        mov cx,[l3]
        inner_loop:
            ; 循环体操作
            add cx,1
            cmp cx,[r3]
            jle inner_loop     
        add bx,1
        cmp bx,[r2]
        jle middle_loop 
    add ax,1
    cmp ax,[r1]
    jle outer_loop
code ends
end main

函数

写在 main 前面,用 call 调用,用 ret 返回

CPU、内存和端口

  • 逻辑地址(段地址 : 偏移地址)

    • 在 8086 中,可以采用逻辑地址间接访问物理地址。物理地址 = 段地址 × 16 + 偏移地址。其中,段地址存储在 16 位段寄存器(如 csds)中,偏移地址为 16 位值。

      一个段可以表示的内存为 64KB,且段首地址的物理地址的 16 进制表示的个位必然是 0(段首地址 = 段地址 × 16)。

    • 引用变量:段寄存器:[偏移地址]段寄存器:[常数]段寄存器:[寄存器]段寄存器:[寄存器+常数])。段地址只能用段寄存器表示,不能用常数表示。

    • seg s 获取变量 s 所在段的段地址,offset s 获取 s 在段内的偏移地址。bxbpsidi 均可用作偏移地址。
  • 宽度修饰

    宽度修饰词 限定变量的宽度
    byte ptr 8 位
    word ptr 16 位
    dword ptr 32 位

数组

  • 数组的定义

    • 字节(byte)类型:db(8 位)

      char a[100] = {'\0'};a db 100 dup(0)(dup:duplicate 重复。dup 可嵌套使用)

      char a[3] = {1, 2, 3};a db 1, 2, 3

      char a[100];a db 100 dup(?)(编译时默认 0)

      char a[] = "Hello";a db "Hello", 0(需手动补 '\0')

      char a[] = "Hello\0World";a db "Hello", 0, "World", 0

      char a[] = "Hello\n";a db "Hello", 0Dh, 0Ah, 0a db "Hello", 13, 10, 0a db "Hello", 15Q, 12Q, 0(Q 表示八进制数)或 a db "Hello", 00001101B, 00001010B, 0

    • 字(word)类型:dw(16 位)

      short int a[3]={0x1234, 0x5678, 0xABCD};a dw 1234h, 5678h, 0ABCDh

    • 双字(double word)类型:dd(32 位)

      long int a[3] = {0x100, 0x200, 0x300};a dd 100h, 200h, 300h

  • 数组的引用

    • 地址计算规则

      汇编中指针 q 的 q+i 表示:(unsigned int)q + i(偏移量直接加 i,不乘元素大小)。

      b dw 1234h, 5678h, 0ABCDh,定义了 3 个字元素,每个元素占 2 字节。按小端序存储,每个元素的低字节在低地址、高字节在高地址。

      12h 34h 56h 78h 0ABh 0CDh
      地址 b+1 b b+3 b+2 b+5 b+4

      mov ax,b[2],b[2] 表示地址 b+2,又 ax 是 16 位寄存器,会访问 b+2(低字节),b+3(高字节),所以 ax 的值是 5678h。同理,mov ax,b[1],ax 的值是 7812h。

    • 段寄存器设置

      assume cs:code, ds:data

      mov ax, datamov ds, ax

    • 元素访问

      一条指令不能同时访问两个变量(如 add c[0], c[1] 非法,需先将 c[1] 存入寄存器)。

显卡内存

  • 文本模式下,显卡内存的段地址固定为 0B800h,整个屏幕的显示内容都存储在该段内。

    每个字符占 2 字节:低地址字节存储字符的 ASCII 码(8 位),高地址字节存储字符的颜色属性(8 位,其中低 4 位为前景色,高 4 位为背景色。4 位表示 3 位 RGB 编码和 1 位亮度位)。

    25 行 × 80 列,屏幕上的每个位置对应的显存偏移地址计算公式为:偏移地址 = (行号 × 80 + 列号) × 2。

    code segment
    assume cs:code
    main:
       mov ax, 0B800h; B800h是显卡内存的段地址
       mov ds, ax
       mov byte ptr ds:[0], 'A'; 待输出字符的ASCII码
       mov byte ptr ds:[1], 17h; 待输出字符的颜色
                               ; 高4位是背景色, 低4位是前景色
                               ; 1=蓝色背景, 7=白色前景
                               ; 1=0001      7=0111
                               ;    RGB         RGB
    
       mov ah, 4Ch
       int 21h
    code ends
    end main
    

int16h 中断

AH 功能 输入字符 返回参数
00 读取键盘输入 可打印字符 AL=按键的 ASCII 码、AH=按键扫描码
上方向键 AL=00h、AH=48h
下方向键 AL=00h、AH=50h
左方向键 AL=00h、AH=4Bh
右方向键 AL=00h、AH=4Dh