本页目录

【汇编语言】访问寄存器和内存

前言

汇编指令是机器指令的助记符,相当于把本是01串的机器指令,用便于人类理解的字符表示出来。汇编指令表达的语义与机器指令是一样的。汇编程序的主体是汇编指令。

对于不同的硬件平台,汇编指令集都是不同的。这里学习的是8086 CPU的汇编语言。

环境配置

MASM(B站教学版本):8086汇编环境

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个寄存器,包括:

通用寄存器:AXBXCXDX

变址寄存器:SIDI

指针寄存器:SPBP

指令指针寄存器:IP

段寄存器:CSDSSSES

标志寄存器:PSW

对应的名称及释义如下:

寄存器名

英文

解释

AX

Accumulator

累加器

BX

Base

基址寄存器

CX

Counter

计数寄存器

DX

Data

数据寄存器

SI

Source Index

源变址寄存器

DI

Destination Index

目的变址寄存器

SP

Stack Pointer

栈指针寄存器

BP

Base Pointer

基址指针寄存器

IP

Instruction Pointer

指令指针寄存器

CS

Code Segment

代码段寄存器

DS

Data Segment

数据段寄存器

SS

Stack Segment

栈段寄存器

ES

Extra Segment

附加段寄存器

PSW

Program Status Word

程序状态字寄存器

8086 CPU所有寄存器都是16位的,可以存放2个字节。有时还会见到AHAL的表示方法,它们分别表示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,那么就需要有一种方法可以改变CSIP的值,这就是jmp指令。8086 CPU不支持用立即数修改CSIP的值(例如mov cs,2000),但是可以通过转移指令jmp来实现。

同时修改CSIP的内容: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指向栈顶元素。

pushpop指令用于栈操作:

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]:执行cntCS:IP处的指令(cnt16进制数)

t [=addr] [cnt]:执行cntaddr处的指令

img

练习

A+B

使用debug编写、运行一个程序,计算0789h + 0abch的值。

img

运行后AX的值为1245h

jmp指令

编程实现下图所示的程序,并分析从2000:0开始的执行流程。

img
1.

mov ax,6622

2.

jmp 1000:3(注意此时会把CS的值设为1000h

3.

mov ax,0000

4.

mov bx,ax

5.

jmp bx(也就是jmp 0000,相当于跳转到1000:0

6.

mov ax,0123

7.

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]

写入内存数据和代码:

img

执行-t 7后,使用-r查看寄存器值,AX1123hBX8833hCX8833h

img

注意操作对象是字型数据,一次操作16位数据,例如mov cx,[1]是将2211h送入寄存器CX

栈操作

使用debug编写、运行如下程序,分析最终寄存器AXBX的值。

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: ...
img

此时SP值为000ch,执行两次pop时,先将001bh送入AX,再将001ah送入BX

img