本页目录
【汇编语言】访问寄存器和内存
前言
汇编指令是机器指令的助记符,相当于把本是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。
