【笔记】汇编语言

前言

汇编语言学习笔记

准备工作

  • edit.com:编辑器
  • masm.exe:汇编程序
  • link.exe:链接程序
  • debug.exe:调试程序

DOSBox

  • 虚拟的16位8086环境

挂载真实磁盘并跳转

  • 在DOSBox中直接操作

c:挂在后的虚拟盘符
<dir>:真实环境中的目录路径,不能包含中文

1
2
mount c <dir>
c:

将挂载和跳转加入到启动选项

  • Windows上配置文件在软件根目录下(C:\Program Files (x86)\DOSBox-0.74-3\),双击启动选项批处理文件(DOSBox 0.74-3 Options.bat)后,会自动使用记事本打开配置文件(C:\Users\xxx\AppData\Local\DOSBox\dosbox-0.74-3.conf),在文件末尾追加配置
C:\Users\xxx\AppData\Local\DOSBox\dosbox-0.74-3.conf
1
2
mount c <dir>
c:
  • MacOS上的配置文件路径:~/Library/Preferences/DOSBox 0.74-3 Preferences

全屏

  • Windows:alt+enter
  • MacOS:option+return

edit.com的使用

启动edit

1
edit

存盘

  • alt+f->save

启动edit时编辑文件

  • 如果问价存在,则直接编辑
  • 如果文件不存在,则创建新文件开始编辑

<name>:文件名

1
edit <name>

编辑汇编程序

  • 创建一个.asm汇编源文件

注释

  • ;后写注释
1
;注释

段定义

  • 创建汇编程序第一步是段定义,告诉操作系统程序需要几个段(最多只能定义4个段)

  • 段定义时需要指明段名,段名实际上就是段地址的符号地址

    • 段的类型是由段关联段地址传送决定的,而不是段名本身
  • 段名命名规则

    • 只能存在字母、数字、下划线
    • 不能以数字开头
    • 字符不能超过8个,超过8个末尾将被舍去
  • 段名命名习惯

    • 只要符合命名规则,段名可以任意定义
    • 通常将代码段命名为code
    • 通常将数据段命名为data
    • 通常将堆栈段命名为stack
1
2
3
段名 segment
...
段名 ends

段关联

  • 段关联语句必须打在段定义指令后的第一行
1
2
3
4
段名 segment
assume 寄存器:段名

段名 ends

段地址传送

  • 除了代码段,其他段都需要段传送后,才可以决定段的类型

  • 通过mov指令实现段地址传送,但是mov不允许将立即数存放到寄存器,所以需要借助通用寄存器

1
2
3
4
5
6
7
段名 segment
assume 寄存器:段名

mov ax,data
mov ds,ax

段名 ends

符号地址

  • 在指令之前写标号,标号是用于指向该指令所存放的地址的符号,被称作符号地址
  • 标号可以卸载指令正前方,也可以写在指令正上方
  • 不是每一个指令前都需要写标号,只有在需要获取其内存地址时才写标号,程序的第一个指令前一定要有标号
  • 代码执行之前需要书写开始标号,执行结束需要书写结束标号,开始标号通常使用start命名
1
2
3
4
5
6
7
8
9
10
11
12
段名 segment
assume 寄存器名:段名

标号1:
指令 目的操作数,源操作数

标号2:指令 目的操作数,源操作数

段名 ends

end 标号1
end 标号2

指令

  • 伪指令:除了具体操作数的指令
  • 指令:具体操作数的指令
    • 双操作指令
      • 指令 目的操作数,源操作数
    • 单操作指令
      • 指令 目的操作数
      • 指令 源操作数
    • 无操作数指令
      • 指令

代码段

  • 代码段通常放在所有段之后
  • 代码段不需要段地址传送
1
2
3
4
5
6
7
8
9
10
code segment
assume cs:code

start:

...

code ends

end start

数据段

  • 数据段需要在代码段中定义段关联段地址传送
  • 当需要存储数据时,可以定义数据段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data segment

...

data ends

code segment
assume cs:code,ds:data

start:
mov ax,data
mov ds,ax

...

code ends

end start

定义数据

  • 数据定义伪指令

    • 字节数据定义伪指令
      • 字节数据伪指令使用db关键字,每次开辟1个字节的空间
    • 字数据定义伪指令
      • 字数据定义伪指令使用dw关键字,每次开辟2个字节的空间
    • 双字数据定义定义伪指令
      • 字数据定义伪指令使用dd关键字,每次开辟4个字节的空间
  • 多字节数据存储原则

    • 高字节存放在高地址
    • 低字节存放在低地址
开辟空间并存入数据
1
2
3
db 0,0,0
dw 0,0,0
dd 0,0,0
存入字符数据
1
dw 'a'
只开辟空间不存入数据
  • 只开辟空间不存入数据,使用?作为占位符
1
2
3
db ?,?,?
dw ?,?,?
dd ?,?,?
利用重复伪指令开辟更多空间
1
2
3
db 重复次数 dup(?)
dw 重复次数 dup(?)
dd 重复次数 dup(?)

获取数据

  • 定义数据前加上符号地址,通过符号地址即可获取数据,符号地址可以自定义名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data segment

符号地址 db 重复次数 dup(?)
符号地址 dw 重复次数 dup(?)
符号地址 dd 重复次数 dup(?)

data ends

code segment
assume cs:code,ds:data

start:
mov ax,data
mov ds,ax

mov ax,符号地址

code ends

end start

编译的过程

  • 源文件 -汇编> 目标文件 -链接> 可执行文件

汇编

  • 把.asm源文件汇编成.obj目标文件

<file>:.asm汇编源文件,.asm可省略

1
2
3
4
5
masm <file>

Object filename:
Source listing:
Cross-reference:

Object filename::询问生成的.obj文件名,如果直接回车不指定,则使用默认文件名
Source listing::询问生成的列表文件名,如果直接回车不指定,则不会生成列表文件
Cross-reference::询问生成的调试文件名,如果直接回车不指定,则不会生成调试文件

链接

  • 把.obj目标文件链接成.exe可执行文件

<file>:.obj目标文件,.obj可省略

1
2
3
4
5
link <file>

Run File:
List File:
Libraries:

Run File::询问生成的.exe文件名,如果直接回车不指定,则使用默认文件名

执行

<file>:.exe可执行文件,.exe可省略

1
<file>

debug.exe的使用

使用debug将可执行程序载入内存

<file>:.exe可执行文件

1
debug <file>

debug的命令

退出

1
-q

查看寄存器的值

1
-r
得到的值
  • 得到的除了所有寄存器的值,还有CPU下一条将要执行的指令
1
段地址:偏移地址 代码的机器语言代码对应        指令 目的操作数,源操作数

从当前代码段开始向下反汇编

1
-u

执行一条语句

  • 从下一条将要执行的语句执行一条语句
1
-g
执行到断点地址
  • 从下一条将要执行的与执行到断点地址之前
1
-g偏移地址

查看内存空间

  • 多次输入d,每次只显示一段
当前数据段
1
-d
查看指定内存地址
1
2
3
-d偏移地址

-d物理地址:偏移地址

寻址方式

  • 寻找数据的方式

直接寻址

  • 直接通过内存中的地址,从内存中寻找数据

寄存器寻址

  • 直接通过寄存器名,从寄存器中寻找数据

间接寻址(寄存器间接寻址)

  • 将内存中地址值存放到寄存器,通过寄存器中存放的地址值间接寻找内存地址

  • 可以存放地址的寄存器有:BX、BP、SI、DI

立即数寻址

  • 直接通过立即数获取数据

汇编指令

  • 汇编指令实质上就是机器语言的助记符,与机器语言没有本质区别,只不过程序员编程不需要记住复杂的二进制代码
  • 汇编指令不区分大小写

存数

  • 将源操作数放到目录操作数

通过符号地址

1
mov 目的操作数,源操作数符号地址

通过符号地址+偏移量

1
mov 目的操作数,源操作数符号地址+偏移量

通过真实地址

源操作数真实地址:可以是真实地址值,也可以是寄存器中存储的内存真实地址

1
2
3
mov 目的操作数,[源操作数真实地址]

mov 目的操作数,[DS:0000]

重复伪指令

  • 将一部分代码重复执行
1
重复次数  dup (需要重复的代码)

加法指令

  • 把源操作数与目的操作数相加,得到的结果放到目的操作数中
  • 源操作数可以是立即数,目的操作数不可以是立即数
  • 目的操作数是寄存器的时候,最好给寄存器初始化(mov 寄存器,0
  • 不可以在两个存储器间直接操作,必须借助寄存器,将立即数存到内存除外
1
add 目的操作数,源操作数

减法指令

  • 目的操作数减源操作数,得到的结果放到目的操作数中
1
sub 目的操作数,源操作数
  • 目的操作数减源操作数,得到的结果不放到目的操作数中,结果只用作影响寄存器标识位
1
cmp 目的操作数,源操作数

将地址存到寄存器

1
2
3
4
lea 可以存放地址的寄存器,符号地址

;获取地址所指向的数
mov 寄存器,[可以存放地址的寄存器]

中断

  • 中断号只能向AH寄存器中存放
1
2
mov AH,中断号
int 中断地址

常见的DOS中断

21H:DOS功能中断

返回DOS
  • 在Windows平台下,写完8086汇编代码后,需要返回DOS,如果不写返回DOS的代码,DOS将中止
  • 仿真环境下不需要返回DOS
1
2
mov ah,4ch
int 21h
DOS键盘输入
  • 获取键盘输入的一个字符,将ASCII码值存到AL寄存器中
  • ASCII码转数字:-30H,数字转ASCII码:+30H
带回显的输入
1
2
mov ah,01h
int 21h
不带回显的输入
1
2
mov ah,07h
int 21h
DOS屏幕输出
  • 在屏幕上显示DL寄存器中存的数据
1
2
mov ah,02h
int 21h
按任意键退出程序
  • 无阻塞中断,不会打断程序运行
  • 如果按下任意键,al的值为FF,如果没按下,al的值为00
1
2
mov ah 0bh
int 21h

将数据保存在系统堆栈段

  • 想堆栈段存取,必须是1字节(16位)的数据

压栈

1
push 源操作数

弹栈

1
pop 目标作数

交换数据

1
XCHG 操作数1,操作数2
1
2
3
4
push 操作数1
push 操作数2
pop 操作数1
pop 操作数2

自增自减

自增1

1
inc 操作数

自减1

1
dec 操作数

逻辑运算指令

逻辑移位指令

循环左移
  • 最高位移到最低位,其他各位左移一位
    • 例如10000001左移后为00000011
  • 先将移动次数存储在cl寄存器,再根据次数向左移
1
2
mov cl,移动次数
rol 目的操作数,存储次数的寄存器
  • 如果只循环一次可以简写
1
rol 目的操作数,1
循环右移
  • 最低位移到最高位,其他各位右移一位
    • 例如10000001左移后为11000000
  • 先将移动次数存储在cl寄存器,再根据次数向右移
1
2
mov cl,移动次数
ror 目的操作数,存储次数的寄存器
  • 如果只循环一次可以简写
1
ror 目的操作数,1

控制转移指令

设置跳转点

1
2
3
4
符号地址:

代码
代码

无条件跳转

  • 直接跳转到符号地址处代码
1
jmp 跳转点符号地址

条件跳转(jump)

  • 通过判断标识位,实现比较数值,然后跳转
    • z:等于判断,通常用于有符号数,也可用于无符号数
    • g:有符号数的大于判断
    • l:有符号数的小于判断
    • e:等于判断,通常用于无符号数,也可用于有符号数
    • a:有符号数的大于判断
    • b:有符号数的小于判断
    • n:非判断
有符号数的判断

有符号数

jz:等于
jg:大于
jl:小于
jnz:不等于
jng:不大于
jnl:不小于
jgz:大于等于
jlz:小于等于
jngz:不大于等于、小于等于
jnlz:不小于等于、大于等于

无符号数

je:等于
ja:大于
jb:小于
jne:不等于
jna:不大于
jnb:不小于
jaz:大于等于
jbz:小于等于
jnaz:不大于等于、小于等于
jnbz:不小于等于、大于等于

举例:判断相等
  • cmp在这里的目的只是用于改变寄存器标识位
  • 判断标识位中
1
2
cmp 操作数1,操作数2
jz 跳转点符号地址

条件跳转(loop)

  • 在进入循环之前,在cx寄存器设置一个循环初值(只能用cx寄存器存循环初值),每次循环cx自减1,直到cx寄存器值为0时,跳出循环
1
2
mov cx,源操作数
loop 跳转点符号地址
loop循环与dec+jnz循环
  • loop循环完全等价于dec+jnz循环
1
2
dec cx
jnz 跳转点符号地址

按位逻辑运算

原理

任意数,和0相与,都变为0
任意数,和1相与,数字不变

任意数,和1相或,都变为1
任意数,和0相或,数字不变

按位相与

1
and 目标操作数,源操作数

按位相或

1
or 目标操作数,源操作数

按位相非

1
not 目标操作数,源操作数

按位相异或

1
xor 目标操作数,源操作数

接口编程

向接口芯片写入B口数据

  • 如果写入的数据大于FFH,那么需要用寄存器传递
1
2
3
mov al,数据
mov dx,接口地址
out dx,al

从接口芯片A口读取数据

  • 将读取的数据存储到dx寄存器
1
2
mov al,接口地址
in dx,al

无限循环

通过符号地址

1
符号地址: jmp 符号地址

简写

1
jmp $

软件方式实现减速

  • 利用空循环实现减速
  • 如果减速速度不够,可以使用多层嵌套循环
  • 这种方法只能实现不精确的减速
1
2
3
4
5
mov ax,100
a:
dcc ax
cmp ax,0
jnz a

子程序

  • 封装常用的代码块,如果需要反复使用相同的代码,可以直接调用子程序

定义子程序

  • 子程序定义需要在代码段内定义,可以放在代码段内的任意位置,通常放在代码段末尾(返回DOS之后)

属性

far:远属性,允许跨段调用
near:近属性,只允许在相同段内调用

1
2
3
4
5
6
子程序名 proc 属性

...

ret
子程序名 endp

调用子程序

1
call 子程序名

完成