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

前言

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

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

环境配置

dosbox.conf文件拉到最下,[autoexec]部分添加:

Plain Text
[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

对应的名称及释义如下:

寄存器名英文解释
AXAccumulator累加器
BXBase基址寄存器
CXCounter计数寄存器
DXData数据寄存器
SISource Index源变址寄存器
DIDestination Index目的变址寄存器
SPStack Pointer栈指针寄存器
BPBase Pointer基址指针寄存器
IPInstruction Pointer指令指针寄存器
CSCode Segment代码段寄存器
DSData Segment数据段寄存器
SSStack Segment栈段寄存器
ESExtra Segment附加段寄存器
PSWProgram 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指令

ASM8086
mov ax,1
mov bx,ax

mov指令的功能是将后一个操作数(数据的来源位置)送入前一个(数据的目标位置),上述指令的作用是将1送入AX,然后将AX的内容送入BX

add指令

ASM8086
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,例如:

    ASM8086
    jmp 2000:3
    jmp 4:0b16

    仅修改IP的内容:jmp 某合法寄存器,将寄存器值赋给IP,例如:

    ASM8086
    jmp ax

内存中字的存储

低位字节存放在低地址,高位字节存放在高地址,也就是小端序。

例:在起始地址为0的单元中,存放4e20h;在起始地址为2的单元中,存放0012h;内存情况为:

Plain Text
0: 20h
1: 4eh
2: 12h
3: 00h

    0地址单元存放的字节型数据是:20h

    0地址单元存放的字型数据是:4e20h

    2地址单元存放的字节型数据是:12h

    2地址单元存放的字型数据是:0012h

8086 CPU从内存单元读取数据

DS寄存器存放要访问的数据的段地址,偏移地址用[...]的形式给出,例如:

ASM8086
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处的指令

image

练习

A+B

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

image

运行后AX的值为1245h

jmp指令

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

image

    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将内存单元赋值为:

Plain Text
10000h: 23h
10001h: 11h
10002h: 22h
10003h: 66h

然后运行以下指令,观察寄存器值的结果。

ASM8086
mov ax,1000
mov ds,ax
mov ax,[0]
mov bx,[2]
mov cx,[1]
add bx,[1]
add cx,[2]

写入内存数据和代码:

image

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

image

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

栈操作

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

ASM8086
mov ax,1000
mov ss,ax
mov sp,0010

mov ax,001a
mov bx,001b

push ax
push bx

pop ax
pop bx

执行完两次push后,内存数据应为:

Plain Text
1000bh: ...
1000ch: 1bh
1000dh: 00h
1000eh: 1ah
1000fh: 00h
10010h: ...

image

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

image