根据个人习惯,我更愿意从一个实例开始某种语言的学习。
这里以一个 16 位汇编程序为例:
我们在 xp 虚拟机中新建文件 hello.asm,用记事本编辑:
1 data segment
2 abc db "hello, world!", 0Dh, 0Ah, "$"
3 data ends
4 ;这是一条注释
5 code segment
6 assume cs:code, ds:data
7 main:
8 mov ax, data
9 mov ds, ax
10 mov ah, 9
11 mov dx, offset abc
12 int 21h
13 mov ah, 4Ch
14 int 21h
15 code ends
16 end main
将其放在 \Masm 目录中。该目录中同时包含了 LINK.EXE 以及 MASM.EXE。我们在 command 中进入对应目录,输入指令:
masm hello;
link hello;
hello
我们通过运行 masm.exe 来编译 hello[.asm],然后通过 link.exe 来连接 hello[.obj],最后运行 hello[.exe]。结果显示: hello, world! 。
下面,我们对代码进行逐句解析:
段
对于 8086PC 机,在编程时可以根据需要将一组内存单元定义为一个段(机器语言代码也存储在内存中)。例如第 1 行和第 5 行就分别定义了名为 data 的段和名为 code 的段。第 3 行和第 15 行分别是这两个段的结束。
8086CPU 要求每个段的容量不能超过 64KB。这与计算机的寻址方式有关:
伪指令
定义数组
第 2 行 abc db "hello, world!", 0Dh, 0Ah, "$"
定义了一个字节类型的变量(db: define byte,byte 类型实际上等价于 C 语言中的 char 类型),名为 abc,内容为 "hello, world!", 0Dh, 0Ah, "$"
,相当于 C 语言中的 char abc[] = "hello, world!\x0D\x0A$";
,即逗号隔开的内容会被连接成一个变量。其中 0Dh, 0Ah 分别是回车(光标回到行首)和换行(光标向下移动一行)的 ASCII 码。在汇编语言中,$ 是字符串结束的标志。
我们可以通过 ans db 100 dup(0)
定义一个定长的数组,相当于 C 语言中的 char ans[100] = {0};
。dup 是 duplicate 的简写,表示重复。我们可以通过这种方法来取得内存空间存放数据。
汇编语言将所有的变量定义放在一起,即 data segment 区域中。
类似地,我们可以用 dw(define word) 定义字型数据(16位)。
注释?
寄存器
CPU 本身只负责运算,不负责存储数据。数据一般存放在存储器中,CPU 需要使用数据时就会去存储器中调用数据。然而,CPU 的运算速度远高于内存的读写速度,因此为了提高效率,CPU 自带缓存和 寄存器?(register)。缓存可以看做读写速度较快的内存,而寄存器是 "fastest, smallest and most expensive" 的,用来存储最常用的数据。
寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称。
8086CPU 有 14 个寄存器,分别为 AX, BX, CX, DX, SI, DI, SP, BP, IP, CS, SS, DS, ES, PSW,它们都是 16 位的。
本文中,我们用加括号的寄存器名称来表示寄存器中存储的数据。例如, (ax) 表示 ax 寄存器中存储的数据。
AX, BX, CX, DX 这 4 个寄存器通常用来存放一般性数据,称为 通用寄存器?。为了与上一代 CPU 兼容,每个通用寄存器都可以拆成两个 8 位寄存器独立使用,如 AX 可拆分为 AH 和 AL,BX 拆分为 BH 和 BL 等。H 和 L 分别表示高 8 位和低 8 位。
计算机存储信息的基本单位是一个 二进制位(bit),一位可存储一个二进制数 0 或 1。每 8 位组成一个 字节(Byte)。每两个字节组成一个 字(word),这两个字节分别称为高位字节和低位字节。
代码段寄存器?CS(code segment) 和指令指针寄存器 IP(Instruction Pointer) 是 8086CPU 中最关键的两个寄存器。它们分别用来提供当前指令的段地址和偏移地址。即任意时刻,8086CPU 将 CS:IP 指向的内容当做命令执行。每条指令进入指令缓冲器后、执行前,IP += 所读取指令的长度,从而指向下一条指令。
其余寄存器将在用到时再做记录。
伪指令 assume
assume cs:code, ds:data
将段寄存器和段名建立了关系。即 assume 使得段寄存器储存了对应段的段地址。标号?
main:
是一个标号。标号在程序中的主要用途是方便跳转语句的执行。跳转语句将在后面再做学习。传送指令 mov?
offset?
mov dx, offset abc
? 的作用就是将 abc 的偏移地址赋给 dx。?中断?
int 21h
调用了中断。中断在本文文末专门记录。mov ah, 9
;mov dx, offset abc
;int 21h
调用了中断 21h 的 09h 号功能,实现了对字符串 abc 的输出。中断 21h 的 09h 号功能实现的是:将自 ds:dx 开始、到 ‘$‘ 为止的字符串输出到标准输出设备上。mov ah, 4Ch
? ?int 21h
?完成的就是这个过程。end?
指令 mov ax, 4E20h
?表示将 4E20h 送入寄存器 AX。等价于高级语言中的 AX = 4E20h;
?(此后文中会大量使用高级语言的语法描述汇编指令)。
类似地, mov ax, bx
?表示 AX = BX;
?。
之前我们提到,8086CPU 中的地址由段地址和偏移地址组成。8086CPU 中有一个 DS 寄存器(段寄存器),用来存放要访问数据的段地址。
例如,我们要读取 10000h 单元的内容,可以用以下的程序段进行:
mov bx, 1000h
mov ds, bx
mov al, [0]
上面的三条指令将 10000h (1000:0) 中的数据读到了 al 中。可见,我们可以通过 mov register, [ address ] 的方式来将内存中 DS:address 的数据读到合法的寄存器 register 中。
值得注意的是,我们通过 1,2 两行将 1000h 放入了 DS,这是因为 8086CPU 不支持将数据直接送入段寄存器(ds, ss, cs, es)的操作。
我们还可以显式地规定我们调用的内存地址的段地址,如我们可以用 ds:[0] 来表示我们调用的内存单元为 ds:0。这允许了我们使用 ds 以外的段地址。这样的 "ds:" "cs:" 等被称为 段前缀。逻辑地址中的偏移地址可以用常数表示,但是段地址必须用段寄存器表示。
另外,我们可以通过 [bx] 表示内存单元 ds:bx,即段地址由 ds 提供,偏移地址由 bx 提供。
需要注意的是,8086CPU 中只有 bx, si, di, bp 这四个寄存器可以用来在 [] 中进行内存单元的寻址,其他寄存器进行这样的操作都是非法的。
同时,在 [] 中,这四个寄存器只能单独出现或以 bx, si / bx, di / bp, si / bp, di 的组合出现,如 [bx+si+1] 是合法的,而 [bx+bp] 就是非法的。两个寄存器只能相加,不能相减
只要在 [] 中用到寄存器 bp,而指令中没有显式给出段地址,那么段地址就默认在 ss 中而不是 ds 中。
由于 8086CPU 是 16 位结构,因此可以一次性传送 16 位数据。比如:
内存情况:
10000H 11
10001H 22
指令:
mov ax, 1000h
mov ds, ax
mov ax, [0]
结果:
ax = 2211H
这是因为,我们将 1000:0 处存放的字数据(由两个字节组成)送入 ax 时,1000:0 处存放的是字数据的低 8 位,即 11;1000:1 处存放的数字数据的高 8 位,即 22。(小端规则:对于 8 位以上的变量,先存放低位,再存放高位,即低位的内存地址低于高位的内存地址。)执行 mov 时,字数据的低 8 位送入 al,高 8 位送入 ah,因此 ax = 2211H。
这也说明,mov 操作的内存单元的长度由其他操作对象(寄存器)指出。但是,两个不同长度的寄存器之间的传递是非法的,如 al 和 bx。
另外,下面的代码反映了一种常见的错误:
data segment
xyz dw 1234h, 0ABCDh
data ends
...... ;略去
code segment
...... ;略去
mov ax, xyz[1]
...... ;略去
在 C 语言的理解中,short int xyz[2] = {1234h, 0ABCDh};
定义出的数组,xyz[1] 的值应该为 0ABCDh。而实际上在汇编语言中,xyz[1](即 [xyz + 1])指向的就是 xyz 物理地址 +1 的地址。假设 xyz 地址为 10000H,那么内存情况为(小端规则):
10000h 10001h 10002h 10003h
34 12 CD AB
实际上 xyz + 1 即 10001h,那么实际上程序认为 mov ax, xyz[1]
调用了以 10001h 为低八位的 16 个字节,根据小端规则,ax 被赋值为 0CD12h。
因此,如果希望引用 0ABCDh,实际上要写的是?mov ax, xyz[2]
?。这是需要特别注意的。
原文:https://www.cnblogs.com/xianyuxuan/p/12702512.html