【汇编语言】访问寄存器和内存
前言
汇编指令是机器指令的助记符,相当于把本是01
串的机器指令,用便于人类理解的字符表示出来。汇编指令表达的语义与机器指令是一样的。汇编程序的主体是汇编指令。
对于不同的硬件平台,汇编指令集都是不同的。这里学习的是8086 CPU的汇编语言。
环境配置
DOSBox:https://www.dosbox.com/
dosbox.conf
文件拉到最下,[autoexec]
部分添加:
[autoexec]
# Lines in this section will be run at startup.
mount C: <path/to/masm>
C:
下次启动DOSBox时会自动挂载C
盘到masm
目录。
寄存器
8086 CPU有14
个寄存器,包括:
通用寄存器:AX
、BX
、CX
、DX
变址寄存器:SI
、DI
指针寄存器:SP
、BP
指令指针寄存器:IP
段寄存器:CS
、DS
、SS
、ES
标志寄存器:PSW
对应的名称及释义如下:
寄存器名 | 英文 | 解释 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8086 CPU所有寄存器都是16
位的,可以存放2
个字节。有时还会见到AH
、AL
的表示方法,它们分别表示AX
的高、低8
位。
8086 CPU物理地址表示
8086 CPU有20
位地址总线,最大寻址空间是1MB
,但由于字长是16
位,所以需要两个16
位地址(段地址、偏移地址)相加合成一个20
位地址。
段地址16
偏移地址物理地址
一个段的起始地址一定是16
的倍数
由于偏移地址是16
位,所以一个段的最大长度为64KB
例如数据在21f60h
内存单元中,段地址是2000h
,表示方法可以是:
数据在内存2000:1f60
单元中;
数据在内存的2000h
段的1f60h
单元中;
有四个专门存放段地址的寄存器:
CS
:代码段寄存器
DS
:数据段寄存器
SS
:栈段寄存器
ES
:附加段寄存器
mov指令
mov ax,1
mov bx,ax
mov
指令的功能是将后一个操作数(数据的来源位置)送入前一个(数据的目标位置),上述指令的作用是将1
送入AX
,然后将AX
的内容送入BX
。
add指令
add ax,1
add bx,ax
add
指令的功能是将后一个操作数的值加到前一个操作数上,上述指令的作用是将1
加到AX
上,然后将AX
的值加到BX
上。
CS、IP寄存器与jmp指令
8086 CPU执行程序时,CS
寄存器存放的是代码段的段地址,IP
寄存器存放的是代码段中的偏移地址。
CPU将内存中CS:IP
指向的内容当作指令执行。
既然执行何处的指令取决于CS:IP
,那么就需要有一种方法可以改变CS
和IP
的值,这就是jmp
指令。8086 CPU不支持用立即数修改CS
和IP
的值(例如mov cs,2000
),但是可以通过转移指令jmp
来实现。
同时修改CS
、IP
的内容:jmp 段地址:偏移地址
,用指令给出的段地址修改CS
,偏移地址修改IP
,例如:
jmp 2000:3
jmp 4:0b16
仅修改IP
的内容:jmp 某合法寄存器
,将寄存器值赋给IP
,例如:
jmp ax
内存中字的存储
低位字节存放在低地址,高位字节存放在高地址,也就是小端序。
例:在起始地址为0
的单元中,存放4e20h
;在起始地址为2
的单元中,存放0012h
;内存情况为:
0: 20h
1: 4eh
2: 12h
3: 00h
0
地址单元存放的字节型数据是:20h
0
地址字单元存放的字型数据是:4e20h
2
地址单元存放的字节型数据是:12h
2
地址字单元存放的字型数据是:0012h
8086 CPU从内存单元读取数据
用DS
寄存器存放要访问的数据的段地址,偏移地址用[...]
的形式给出,例如:
mov bx,1000
mov ds,bx
mov al,[0]
以上代码的作用是将1000:0
也就是10000h
中的数据读到AL
中。
DS
也不支持立即数赋值,需要将数据先存入通用寄存器,再送入段寄存器。
8086 CPU也可以一次性传输一个字,假如DS
寄存器的值为1000h
,则mov ax,[0]
就表示将1000:0
中的字送入AX
中;mov [0],cx
就表示将CX
中的字送入1000:0
中。
栈操作(push指令、pop指令)
基于8086 CPU编程,可以将一段内存当作栈来使用。8086 CPU有两个与栈相关的寄存器,SS
存放栈顶的段地址,SP
存放栈顶的偏移地址。在任意时刻,SS:SP
指向栈顶元素。
push
和pop
指令用于栈操作:
push 寄存器
:SP
自动减2
,将寄存器的内容送入SS:SP
,此时SS:SP
指向新栈顶。
pop 寄存器
:将SS:SP
指向的内容送入寄存器,SP
自动加2
。
8086 CPU只知道栈顶的位置,不知道栈的大小有多大,并不能保证栈操作不会越界。编程时需要程序员主动避免栈溢出风险。
DEBUG.EXE的使用
在终端输入debug
即可,在-
后输入参数。
r
:查看所有寄存器内容
r [reg]
:修改寄存器reg
内容
d [start addr]
:列出start addr
开始的128
字节内容
d [start addr] [end offset addr]
:列出地址范围内的内存内容,第二个参数表示结尾偏移地址
e [addr]
:修改内存addr
处的数据
u
:将内存中的数据视作机器码,反汇编为汇编指令,用法与d
命令类似,可以接地址范围
a [addr]
:在内存addr
处写入汇编指令的机器码
t
:单步执行CS:IP
处的指令
t [cnt]
:执行cnt
步CS:IP
处的指令(cnt
是16
进制数)
t [=addr] [cnt]
:执行cnt
步addr
处的指令
练习
A+B
使用debug
编写、运行一个程序,计算0789h + 0abch
的值。
运行后AX
的值为1245h
。
jmp指令
编程实现下图所示的程序,并分析从2000:0
开始的执行流程。
mov ax,6622
jmp 1000:3
(注意此时会把CS
的值设为1000h
)
mov ax,0000
mov bx,ax
jmp bx
(也就是jmp 0000
,相当于跳转到1000:0
)
mov ax,0123
mov ax,0000
(回到第三步,循环了)
从内存单元读取数据
使用debug
将内存单元赋值为:
10000h: 23h
10001h: 11h
10002h: 22h
10003h: 66h
然后运行以下指令,观察寄存器值的结果。
mov ax,1000
mov ds,ax
mov ax,[0]
mov bx,[2]
mov cx,[1]
add bx,[1]
add cx,[2]
写入内存数据和代码:
执行-t 7
后,使用-r
查看寄存器值,AX
为1123h
,BX
为8833h
,CX
为8833h
。
注意操作对象是字型数据,一次操作16
位数据,例如mov cx,[1]
是将2211h
送入寄存器CX
。
栈操作
使用debug
编写、运行如下程序,分析最终寄存器AX
、BX
的值。
mov ax,1000
mov ss,ax
mov sp,0010
mov ax,001a
mov bx,001b
push ax
push bx
pop ax
pop bx
执行完两次push
后,内存数据应为:
1000bh: ...
1000ch: 1bh
1000dh: 00h
1000eh: 1ah
1000fh: 00h
10010h: ...
此时SP
值为000ch
,执行两次pop
时,先将001bh
送入AX
,再将001ah
送入BX
。