汇编语言从入门到放弃,来自 B站 解读的 王爽 老师的 《汇编语言第3版》传送门

本篇文章是趁着周末,双倍速加持边听边写的,有点乱,凑合看😂

  1. # 基础知识

    汇编语言由 3 类组成,汇编语言的核心是汇编指令

    汇编指令(机器码的助记符)
    伪指令(由编译器执行)
    其他符号(由编译器识别)
  2. # 寄存器

    寄存器是 CPU 内部的信息存储单元, 8086CPU 寄存器都是 16 位的

    8086CPU 有 14 个寄存器: AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

    通用寄存器:AX、BX、CX、DX
    变址寄存器:SI、DI
    指针寄存器:SP、BP
    指令指针寄存器:IP
    段寄存器:CS、SS、DS、ES
    标准寄存器:PSW
    • 为兼容 8 位寄存器,可以将通用寄存器细分:
    AX -> AH、AL
    BX -> BH、BL
    CX -> CH、CL
    DX -> DH、DL
    • 存储段地址的段寄存器

      物理地址 = 段地址 (十进制) x 16 - 偏移地址

    CS -代码段寄存器
    SS -栈段寄存器
    DS -数据段寄存器
    ES -附加段寄存器
    • 执行指令的地址,取决于 CS:IPCS 作为段地址, IP 作为偏移地址

    • 段前缀

    mov ds,ax	# 确定段地址
    mov dx,[bx]	# [bx] 默认是 ds:[bx] 内存段
    # 当同时处理两个段内存的时候,可以使用附加段寄存器 ES
    assume cs:code
    code segment
        mov ax,0fffh	
        mov ds,ax	# 第一个内存段地址
        mov ax,0020h
        mov es,ax	# 第二个内存段地址
        mov bx,0
        mov cx,12
      s:mov dl,[bx]
        mov es:[bx],dl
        inc bx
        loop s
        mov ax,4c00h
        int 21h
    code ends
    end
    • DS 数据段寄存器
    mov bx, 10000h		# 将 16 进制 10000 送入 bx
    mov ds, bx		# 指定内存数据段地址, 内存单元的地址 = 段地址 + 偏移地址
    mov al, ds:[0] 	# 表示将内存中数据段地址 ds:[0] 对应的一个字节的数据写入 al 通用寄存器中。
    • 栈段寄存器 SS 和栈顶指针寄存器 SP ,栈地址取决于 SS:SP
    SS -> 存放栈顶的段地址
    SP -> 存放栈顶的偏移地址
    • 变址寄存器 SI、DI ,通常执行与地址有关操作,与 BX 功能相近,但是不能分成两个 8 位寄存器
    SI -> 源变址寄存器
    DI -> 目标变址寄存器
    # bx 的程序都可以用 si 或 di 代替,当复制一段字符串时,使用 si,di 更优雅
    assume cs:codesg,ds:datasg
    datasg segment
    	db 'welcome to masm!'	# 长度 16 的字符串
    	db '................'
    datasg ends
    codesg segment
    start:
    	mov ax,datasg
    	mov ds,ax
    	mov si,0
    	mov di,16
    	mov cs,8	
      s:mov ax,[si]
    	mov [di],ax
    	add si,2
    	add di,2
    	loop s
    	
    	mov ax,4c00h
    	int 21h
    codesg ends
    end start
    • 指针寄存器 BP
    # 与 bx 一样,也可以表示基址
    mov ax,[bp]

    只有 bx,bp,si,di 可以用在 [] 对内存单元寻址;

    bx 默认指 ds 段, bp 默认指 ss 段;

    bxbp 只能作为基址, sidi 只能作为变址,只能 [基址+变址]

    • 标志寄存器 PSW :按位起作用,它的每一位都有专门的含义,记录特定的信息;主要用来存储相关指令的某些执行结果,为 CPU 执行相关指令提供行为依据,控制 CPU 的相关工作方式

      8086CPU 中 1、3、5、12、13、14、15 没有含义

      flag 位所具有的含义如下:

  3. # 汇编指令

    • mov 数据传送指令,不能用于直接将数据从一个内存位置传送到另一个内存位置。

      mov ax,18		# 将 18 添加到 AX 寄存器
      mov ah,78		# 将 78 添加到 AH
      mov ax,bx		# 将寄存器 BX 中的数据添加到寄存器 AX 中
    • add :加法

      add ax,18		# 将寄存器 AX 中的数值加 18
      add ax,bx		# 将 AX,BX 中的数据相加,结果存在 AX 中
      add al,bl		# 将 AX,BX 中的低位数据相加,结果存在 AL, 如果超过 8 位,进位将被丢弃
      add ax,ds:[1]		# 将 ds 段地址内存单元 1 处的数据相加,结果存在 AX
    • sub :减法,用法参考加法

    • div :除法

      # 被除数:默认放在 AX 或 DX 和 AX 中
      # 除数:8 位或 16 位,在寄存器或内存单元中 
      # 结果:如果除数是 8 位,则 AL 存储除法操作的商,AH 存储除法操作的余数;如果除数是 16 位,则 AX 存储除法操作的商,DX 存储除法操作的余数。

      示例指令:

    • mul :乘法

    • adc :带进位加法指令

      # adc 利用了 CF 位上记录的进位值
      mov al,98H	
      adc al,al	# 98h+98h = 130, 1 进位,CF=1; al = 30
      adc al,3	# 结果 = al+3+CF = 34H
      mov ax,2
      mov bx,1	
      sub bx,ax	# bx 比 ax 小,所以需要借位,借位使 CF=1
      adc ax,1	# ax = ax+1+CF = 4
    • sbb :带借位减法

      # 利用 CF 位上记录的借位值
      mov bx,1000H
      mov ax,003EH
      sub bx,2000H	# bx - 2000H 借位
      sbb ax,0020H	# ax - 0020H - CF
    • jmp :跳转指令,修改 CS、IP 的内容

      jmp 段地址:偏移地址	# jmp 2AE3:3
      jmp 某一合法寄存器			# jmp ax  跳转到 ax 寄存器内的地址
    • jcxz :有条件跳转指令

      jcxz 标号
    • push :入栈(栈操作)

      push ax
      push bx
    • pop :出栈(栈操作)

      pop ax
      pop bx
    • inc :加 1 操作

      inc bx
      inc bx		# BX 寄存器中的值 两次 + 1
    • loop :循环操作, loop 指令默认使用 cx 寄存器

      assume cs:code
      code segment
      	mov ax,2
      	mov cx,11
      s: add ax,ax		# cpu 执行 loop 指令时要进行的操作:cx = cx - 1
      	loop s			# 判断 cx 值不为零则转至标号处执行程序,为零则向下执行
      	
      	mov as, 4c00h
      	int 21h
      code ends
      end
    • and :逻辑与

      and al,11011111b	# 将 al 的值和 11011111 进行逻辑与,此二进制是把小写字母转大写字母
    • or :逻辑或

      or al,00100000b		# 将 al 的值和 00100000 进行逻辑或,此二进制是把大写字母转小写字母
    • call :调用子程序,使用 call 要先设置栈

      assume cs:code,ss:stack
      stack segment
      	db 8 dup (0)
      	db 8 dup (0)
      stack ends
      code segment
      start:
      	mov ax,stack
      	mov ss,ax
      	mov sp,16
      	mov a,1000
      	call s
      	mov ax,4c00h
      	int 21h
        s:add ax,ax
        	ret
      code ends
      end start
    • retretf :返回指令

    • cmp :比较指令,动能相当于减法指令,只是不保存结果

      # cmp 执行后,将对标志寄存器产生影响
      cmp ax,ax	# 结果为 0,但不保存在 ax 中,仅影响 flag 的相关值
      标志寄存器此时:ZF=1 PF=1 SF=0 CF=0 OF=0

      通过 cmp 指令执行后相关标志位的值,可以看出比较的结果

    • jxxx :条件转移指令

      j:jump	  e:equal    n:not      b:below	    a:above    l:less    g:greater   s:sign    c:carry    p:parity   o:overflow   z:zero
      指令:根据单个标志位转移含义测试条件
      je/jz相等 / 结果为 0ZF=1
      jne/jnz不等 / 结果不为 0ZF=0
      js结果为负SF=1
      jns结果非负SF=0
      jo结果溢出OF=1
      jno结果未溢出OF=0
      jp奇偶位为 1PF=1
      jnp奇偶位不为 1PF=0
      jb/jnae/jc低于 / 不高于等于 / 有错位CF=1
      jnb/jae/jnc不低于 / 高于等于 / 无错位CF=0
      指令:根据无符号数比较结果转移含义测试条件
      ------------------------------------------------------
      jb/jnae/jc低于则转移CF=1
      jnb/jae/jnc不低于则转移CF=0
      jna/jbe不高于则转移CF=1或ZF=1
      ja/jnbe高于则转移CF=0且ZF=0
      指令:根据有符号数比较结果转移含义测试条件
      ----------------------------------------------------------
      jl/jnge小于则转移SF=1且OF=0
      jnl/jge不小于则转移SF=0且OF=0
      jle/jng小于等于则转移SF=0或OF=1
      jnle/jg不小于等于则转移SF=1且OF=1
    • 串传送指令: movsb (以字节为单位传送), movsw (以字为单位传送)

      # 串传送指令,从 ds 的内存数据中,si 位置的值取出来放到 es:[di] 地址中
      # DF =0 时,si 和 di 都加 1;DF = 1 时,si 和 di 都减 1
      data segment
      	db 'welcome to masm!'
      	db 16 dup (0)
      data ends
      code segment
      start:
      	mov ax,data
      	mov ds,ax
      	mov si,0
      	mov es,ax
      	mov di,16
      	cld
      	mov cx,16
         s:movsb
         	loop s
       	mov ax,4c00h
       	int 21h
      code ends
      end start
    • DF 标志位操作指令: cldstd

      cld:将标志寄存器的DF位设为0(clear)
      std:将标志寄存器的DF位设为1(setup)
    • rep :根据 cx 的值,重复执行后面的指令

      # rep 通常和串传送指令搭配使用,上诉串传送指令可以改为
      ...
      cld
      mov cx,8	# rep 循环 cx 次
      rep movsw		# 把 loop s 改为 rep
      mov ax,4c00h
      ...
    • 移位指令

      # OPR 代表操作数,CNT 代表移的位数
      SHL OPR,CNT		# 逻辑左移  把最高位移到 CF 中
      SHR OPR,CNT		# 逻辑右移	
      SAL OPR,CNT		# 算术左移	 和逻辑左移一样
      SAR OPR,CNT		# 算术右移	 把最低为移到 CF,最高位保持不变
      ROL OPR,CNT		# 循环左移	把最高位移到 CF 中,且移动到最低位
      ROR OPR,CNT		# 循环右移
      RCL OPR,CNT		# 带进位循环左移 最高位移入 CF,最低位由原有的 CF 值移入
      RCR OPR,CNT		# 带进位循环右移
  4. # 伪指令

    • end :汇编程序结束标志

    • ends :段结束标志

    • assume :假设某一段寄存器和程序中用 segment...ends 定义的段相关联

      assume cs:codesg	# 指 CS 寄存器与 codesg 关联,将定义的 codesg 当作程序的代码段使用
      codesg segment	
      	mov ax,2
      	add ax,ax
      	add ax,ax
      	
      	mov ax,4c00h   
      	int 21h		   # 固定套路,程序返回值
      codesg ends
      end		# 结束标记
    • DB、DW、DD、DQ、DT :数据定义标记

    DB 		# 定义字节型变量,每变量分配 1 个存储单元
    DW 		# 定义字节型变量,每变量分配 2 个存储单元
    DD 		# 定义字节型变量,每变量分配 4 个存储单元
    DQ 		# 定义字节型变量,每变量分配 8 个存储单元
    DT 		# 定义字节型变量,每变量分配 10 个存储单元
    • 标号:当用标号定义数据时,需要指示代码开始位置

      assume cs:code,ds:data,ss:stack		# 指定代码段,数据段,栈段
      data ssegment		# 数据段
      	dw 0123H,0456H,0789H,0abcH,0defH,0fedH	
      data ends
      stack segment		# 栈段
      	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
      stack ends
      code segment		# 代码段
      start:		# 指示代码开始位置,标号不固定此处不一定是 start
      	# 初始化各段寄存器
      	mov ax,stack	
      	mov ss,ax	# 栈段地址
      	mov sp,20h	# 栈顶地址
      	mov ax,data
      	mov dx,ax
      	# 入栈
      	mov bx,0
      	mov cx,8
        s:push [bx]	# 默认段地址是 ds, 即 ds:[bx],s 标号
        	add bx,2
        	loop s
        	# 出栈
        	mov bx,0
        	mov cx,8
       s0:pop [bx]	# 默认段地址是 ds,s0 标号
       	add bx,2
       	loop s0
       	
      	mov ax,4c00h
      	int 21h
      code ends
      end start
    • dup :设置内存空间,和 db、dw、dd 等数据定义配合使用,用来进行数据重复

      db 3 dup(0)		# 定义了 3 个字节,它们值都是 0,相当于 db 0,0,0
      db 3 dup(0,1,2)	# 定义 9 个字节,由 0,1,2 重复 3 次构成,相当于 db 0,1,2,0,1,2,0,1,2
      db 3 dup('abc','ABC')	# 定义 18 个字节构成 'abcABCabcABCabcABC' 相当于 db 'abcABCabcABCabcABC'
    • equ :把一个符号名称与一个整数表达式或一个任意文本连接起来

    • offset :取得标号的偏移地址

      assume cs:codeseg
      codeseg segment
      start:
      	mov ax,offset start		# 相当于 mov ax,0
        s:mov ax, offset s		# 相当于 mov ax,3
      codeseg ends
      end start
  5. # 寻址方式

    • [idata] 直接寻址,用一个 常量/立即数 来表示

      mov ax,[200]
    • [bx] 寄存器间接寻址,用一个 变量 来表示

      mov bx,4
      mov ax,[bx]
    • [bx+idata] 寄存器相对寻址,用一个 变量常量 表示

      mov bx,4
      mov ax,[200+bx]
      mov ax,[bx+200]
      mov ax,200[bx]
      mov ax,[bx].200		# 几种写法效果相同
    • [bx+si] 基址变址寻址,用 两个变量 表示

      mov ax,[bx+si]		# bx 作为基址,si 作为可变地址
    • [bx+si+idata][bx+di+idata] 相对基址变址寻址,用 两个变量一个常量 表示

      mov ax,[bx+si+200]	# 写法可以参考 [bx+idata]
  6. # 汇编中的数据位置表达

    • 立即数:直接包含在机器指令中的数据

      mov ax,1		# 1
      add bx,200H		# 200h 
      or bx,00010000b		# 000100000b
      mov al,'a'		# 字符串 'a', 汇编字符串用单引号表示
    • 寄存器:指令要处理的数据

      mov ax,bx		# ax,bx
      mov ds,ax		# ds,ax
      push bx			# bx
      mov ds:[0],bx	# bx
    • 内存:段地址 (SA) 和偏移地址 (EA),指令要处理的数据再内存中

      mov ax,[0]		# 默认段地址 ds, 偏移 0
      mov ax,[di]		# 默认段地址 ds, 偏移 di
      mov ax,[bp]		# 默认段地址 ds, 偏移 bp
      mov ax,ds:[bp]	# 指定段地址 ds, 偏移 bp
      mov ax,es:[bx]	# 指定段地址 es, 偏移 bx
      mov ax,cs:[bx+8]	# 指定段地址 cs, 偏移 bx+8
    • 判断指令处理的数据长度

      mov ax,1		# 一个字,16 位
      mov bx,ds:[0]	# 一个字,16 位
      mov al,1		# 一个字节,8 位
      mov al,bl		# 一个字节,8 位
      # 在没有寄存器参与的内存单元访问指令中,用关键字 word 和 byte 显性指明内存单元的长度
      mov word ptr ds:[0],1	# word ptr 指明一个字,16 位
      inc word ptr [bx]	# word ptr 指明一个字,16 位
      mov byte ptr ds:[0],1	# byte ptr 指明一个字节,8 位
      inc byte ptr [bx]		# byte ptr 指明一个字节,8 位
  7. # 两重循环寻址问题

    • 方法一:预存 cx 的值到其他寄存器

      总共 14 个寄存器,用 1 个寄存器存 cx 太浪费

      # 编程将 datasg 段中的单词都改为大写
      assume cs:codesg,ds:datasg
      datasg segment
      	db 'ibm       '
      	db 'dec       '
      	db 'dos       '
      	db 'vax       '
      datasg ends
      codesg segment
      start:
      	mov ax,datasg
      	mov ds,ax
      	mov bx,0
      	mov cx,4
        s0:mov dx,cx	# 将外层循环的 cx 值存储到 dx 中
        	mov si,0
        	mov cx,3	# 设置内层 cx 值
        s:mov al,[bx+si]
        	and al,11011111b	
        	mov [bx+si],al
        	inc si
        	loop s
        	add bx,16
        	mov cx,dx	# 恢复外层 cx 值
        	loop s0
      codesg ends
      end start
    • 方法二:用固定的内存空间保存数据

      内存单元可能在其他地方使用,直接使用内存单元存储,存在篡改值的风险

      start:
      	mov ax,datasg
      	mov ds,ax
      	mov bx,0
      	mov cx,4
        s0:mov ds:[40H],cx	# 将外层循环的 cx 值存储到 datasg:40H 中
        	mov si,0
        	mov cx,3		# 设置内层 cx 值
        s:mov al,[bx+si]
        	and al,11011111b	
        	mov [bx+si],al
        	inc si
        	loop s
        	add bx,16
        	mov cx,ds:[40H]		# 还原外层 cx 值
        	loop s0
    • 方法三:用栈保存数据

      适用两层以上嵌套循环,更推荐这种方式

      stacksg segment
      	dw 0,0,0,0,0,0,0,0
      stacksg ends
      # 以下为代码段
      start:
      	mov ax,stacksg
      	mov ss,ax
      	mov sp,16
      	mov ax,datasg
      	mov ds,ax
      	mov bx,0
      	mov cx,4
        s0:push cx	# 将外层 cx 值压入栈
        	mov si,0
        	mov cx,3	# 设置内层 cx 值
        s:mov al,[bx+si]
        	and al,11011111b	
        	mov [bx+si],al
        	inc si
        	loop s
        	add bx,16
        	pop cx		# 从栈顶弹出原 cx 得值,恢复 cx
        	loop s0
  8. # 转移操作

    • 转移指令:可以控制 CPU 执行内存中的某处代码;可以修改 IP (指令指针),或同时修改 CSIP

    • 转移指令分类:

    • jmp 寄存器

      jmp bx		# 16 位的位移
    • jmp 标号:依据位移进行转移, jmp short 的机器指令中,包含的是跳转到指令的相对位置,而不是转移的目标地址

      assume cs:codesg
      codesg segment
      start:
      	mov ax,0
      	jmp short s		# 短转移
      	add ax,1
        s:inc ax
      codesg ends
      end start
      远转移: jmp far ptr 标号近转移: jmp near ptr 标号
      段间转移段内转移
      far ptr 指明了跳转的 目的地址near ptr 指明了相对于当前 IP转移位移地址
      assume cs:codesg
      codesg segment
      start:
      	mov ax,0
      	mov bx,0
      	jmp far ptr s		
      	db 256 dup(0)
        s:add ax,1
        	inc ax
      codesg ends
      end start
    • jmp 转移地址在内存中

      jmp word ptr 内存单元地址jmp dword ptr 内存单元地址
      段内转移段间转移
      从内存单元地址处存放一个字,是转移目的偏移地址从内存单元存放两个字,到地址处是目的段地址,低地址处是目的偏移地址
    • jcxz 转移指令:cx 为零时,则转移到标号处,否则,什么也不做

      # 对应机器码中包含转移的位移地址
      assume cs:codesg
      codesg segment
      start:
      	mov ax,2000H
      	mov ds,ax
      	mov bx,0
        s:mov cx,[bx]
        	jcxz ok
        	inc bx
        	inc bx
        	jmp short s
        ok:mov dx,bx
        	mov ax,4c00H
        	int 21H
      codesg ends
      end start
    • loop 转移:机器码包含转移的位移地址

      如果 loop s 的机器码包含的是 s 的地址,则对程序段在内存中的偏移地址有了严格限制,易引发错误;

      当机器码包含的时位移地址,无论 s 处的指令的实际地址是多少, loop 指令转移的相对位移时不变的。

    • call、ret 转移:转移地址在寄存器中

      mov ax,0
          call ax
          ....
          mov ax,4c00H
          int 21h
    • 转移地址在内存中的 call

      # word ptr 内存单元地址
      mov sp,10h
      mov ax,0123h
      mov ds:[0],ax		
      call word ptr ds:[0]	
      #dword ptr 内存单元地址
      mov sp,10h
      mov ax,0123h
      mov ds:[0],[ax]	# 低地址放偏移地址
      mov word ptr ds:[2],0	# 高地址放段地址
      call dword ptr ds:[0]
    • call 实现段间转移

      mov ax,0
          call far ptr s
          ....
          mov ax, 4c00h
          int 21h
        s:add ax,1
        	ret
    • jxxx 转移指令和 cmp 指令配合,构造条件转移指令:不必再考虑 cmp 指令对相关标志位的影响和 jxxx 指令对相关标志位的检测,可以直接考虑 cmpjxxx 指令配合时表现的逻辑含义。

      # 如果 ah=bh, 则 ah =ah+ah,否则 ah=ah+bh
      	cmp ah,bh
      	je s		# 相等则跳转 s
      	add ah,bh	# 不等则相加	
      	jmp short ok
         s:add ah,ah
        ok:ret
  9. # 标号:表示指令的地址

    assume cs:code			# 标号 code
    code segment			
    	a db 1,2,3,4,5,6,7,8  # 标号 a, 不加冒号就代表地址,加冒号需要用 offset 取址
    	b dw 0		# 标号 b
    start:			# 标号 start
    	mov si,0
    	mov cx,8
       s:mov al,a[si]	# 标号 s
       	mov ah,0
       	add b,ax
       	inc si
       	loop s
       	mov ax,4c00h
       	int 21h
    code ends
    end start
    • 上述代码中 a、b 表示数据标号,标记了存储数据的单元地址和长度;其他标号仅仅使表示地址的地址标号,只有数据标号可以省略冒号
  10. # 中断: CPU 不在接着向下执行,而是转去处理中断信息

    • 中断分为内中断和外中断

      内中断:由 CPU 内部发生的事件引起的中断

      外中断:由外部设备发生的事件引起的中断

    • 8086 中断类型码

      ①除法错误:0

      ②单步执行:1

      ③执行 into 指令:4

      ④执行 int n 指令,立即数 n 为中断类型码

    • int ncpu 内部产生的中断信息

      int 21h		# 之前程序结束时使用的
    • BIOSDOS 中断,参考文章:https://www.cnblogs.com/onroad/archive/2009/07/13/1522662.html

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

渣渣文 微信支付

微信支付

渣渣文 支付宝

支付宝