汇编语言从入门到放弃,来自 B站
解读的 王爽
老师的 《汇编语言第3版》
,传送门
本篇文章是趁着周末,双倍速加持边听边写的,有点乱,凑合看😂
# 基础知识
汇编语言由 3 类组成,汇编语言的核心是汇编指令
汇编指令(机器码的助记符)
伪指令(由编译器执行)
其他符号(由编译器识别)
# 寄存器
寄存器是
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:IP
,CS
作为段地址,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
段;且
bx
、bp
只能作为基址,si
、di
只能作为变址,只能[基址+变址]
标志寄存器
PSW
:按位起作用,它的每一位都有专门的含义,记录特定的信息;主要用来存储相关指令的某些执行结果,为CPU
执行相关指令提供行为依据,控制CPU
的相关工作方式8086CPU
中 1、3、5、12、13、14、15 没有含义flag
位所具有的含义如下:
- 为兼容
# 汇编指令
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
ret
和retf
:返回指令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
相等 / 结果为 0 ZF=1
jne/jnz
不等 / 结果不为 0 ZF=0
js
结果为负 SF=1
jns
结果非负 SF=0
jo
结果溢出 OF=1
jno
结果未溢出 OF=0
jp
奇偶位为 1 PF=1
jnp
奇偶位不为 1 PF=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
标志位操作指令:cld
,std
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 # 带进位循环右移
# 伪指令
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
# 寻址方式
[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]
# 汇编中的数据位置表达
立即数:直接包含在机器指令中的数据
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 位
# 两重循环寻址问题
方法一:预存
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
# 转移操作
转移指令:可以控制
CPU
执行内存中的某处代码;可以修改IP
(指令指针),或同时修改CS
和IP
转移指令分类:
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
指令对相关标志位的检测,可以直接考虑cmp
和jxxx
指令配合时表现的逻辑含义。# 如果 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
# 标号:表示指令的地址
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
表示数据标号,标记了存储数据的单元地址和长度;其他标号仅仅使表示地址的地址标号,只有数据标号可以省略冒号
- 上述代码中
# 中断:
CPU
不在接着向下执行,而是转去处理中断信息中断分为内中断和外中断
内中断:由
CPU
内部发生的事件引起的中断外中断:由外部设备发生的事件引起的中断
8086
中断类型码①除法错误:0
②单步执行:1
③执行
into
指令:4④执行
int n
指令,立即数n
为中断类型码int n
:cpu
内部产生的中断信息int 21h # 之前程序结束时使用的
BIOS
和DOS
中断,参考文章:https://www.cnblogs.com/onroad/archive/2009/07/13/1522662.html