跳至正文
汇编语言不会编?

汇编语言不会编?

上篇已经介绍了 CPU 的寄存器种类,知道了程序是由指令和数据组成的,以及 CPU 是如何通过寄存器运行程序的,那么现在可以真正了解下汇编指令集了。

前提须知

  • 因为不同 CPU 架构的指令集都不尽相同,汇编语言通常不具有可移植性,这里介绍的还是以 16 位的 8086CPU 为主。
  • 下面的描述,用 () 表示一个寄存器或内存单元中的内容,比如(ax)表示 ax 中的内容,(20000H)表示内存 2000H 单元的内容。
  • 下面的描述,用 idata 表示常量,比如 mov ax, [idata] 就代表 mov ax, [1]、mov ax, [2]、mov ax, [3]。
  • reg 表示一个寄存器,比如:ax、bx、cx、dx、ah、bh、bl、ch、cl、dh、dl、sp、bp、si、di。
  • sreg 表示一个段寄存器,比如:ds、ss、cs、es。

伪指令(宏)

用汇编语言写的源程序,包括伪指令和汇编指令。首先主要知道伪指令,它区别于常见的汇编指令。伪指令没有对应的机器指令,最终也不会被 CPU 执行,而是由编译器执行的指令,用于指示编译器如何汇编源程序。

segment

segment 和 ends 是一对成对使用的伪指令,它们的功能是定义一个段,segment 是段的开始,ends 说明一个段结束,同时一个段必须有一个名称来标识。下面的代码是定义一个名为 codesg 的段。

asm
assume cs:codesg

codesg segment
	mov ax, 0123H
	mov bx, 0456H
	add ax, bx
	add ax, ax
	
	mov ax, 4c00H
	int 21H
codesg ends

end

而这个 codesg 也会在编译器的编译、链接过程中,处理成一个段的段地址,从而成为真正可以被 CPU 执行的程序。

end

end 是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令 end,就结束对源程序的编译。end 和上面的 ends 不一样。ends 是和 segment 成对使用的。

end 除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。

asm
assume cs:code
code segment

dw 0123H,0456H,0789H,0abcH

start:	mov ax, 2
    mov cx, 11
  s:add ax,ax
    loop s
    mov ax, 4c00h
    int 21h
code ends
end start

在编译链接后,由 “end start” 指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。当程序被加载后,读到了程序的入口地址,设置 CS:IP(这里为 CS:A),这样 CPU 就从我们希望的地址处开始执行。

assume

assume 的含义为假设。它假设某一个段寄存器和程序中的某一个 segment…ends 定义的段相关联。例如在上面的程序中,assume 将代码段的 codesg 和 CPU 的段寄存器 CS 联系起来。

DB、DW 和 DD

db 定义字节类型变量,一个字节数据占 1 个字节单元,读完一个,偏移量加 1。

dw 定义字类型变量,一个字数据占 2 个字节单元,读完一个,偏移量加 2。

dd 定义双字类型变量,一个双字数据占 4 个字节单元,读完一个,偏移量加 4。

asm
dw 0123H,0456H,0789H,0abcH

程序在运行的时候 CS 中存放代码段的地址。dw 命令定义的数据一般处于代码段的最开始,所以偏移地址为 0。配合 loop 就可以循环 (bx) = (bx) + 2 得到所有的值。

ASCII 码

在汇编程序中,用 'xxx' 的方式指明数据是以字符的形式给出的,编译器将它们转化为相应的 ASCII 码。

asm
db 'UNIX'

相当于 db 75H,6EH,49H,58H,因为“U”、“N”、“I”、“X”的 ASCII 码分别为 75H、6EH、49H、58H。

将数据、代码、栈放入不同的段

在 8086 模式下,一个段最大的大小为 64 K(2 的 16 次方),如果数据、代码、栈加起来所需要的空间大于 64KB,就不能放在一个段中。另外,都放在一个段中会使程序变得混乱。这种情况可以定义多个段。

asm
assume cs:code

data segment
	dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H ; 0H ~ fH
data ends

stack segment
	dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0        ; 0H ~ 1fH
stack ends

code segment
start: mov ax, data
	     mov ds, ax ; 设置 ds 指向 data 段
	     mov ax, stack
	     mov ss, ax
	     mov sp, 20H
	     mov bx, 0
	     mov cx, 8
s0: push [bx]
		   add bx, 2
		   loop s0
	     mov bx, 0
	     mov cx, 8
s1: pop [bx]
		   add bx, 2
		   loop s1
       mov ax, 4c00H
       int 21H
code ends

end start

另外 8086CPU 不允许将一个数值直接送入段寄存器中,比如 mov ds, data 就是错误的。

传送指令

MOV

mov 指令可以改变大部分寄存器的值,还可以改变内存单元的值。

asm
mov ax, 8
mov ax, bx
mov ax, [0]
mov [0], ax
mov ds, ax

涉及内存单元的,一般和 DS 寄存器有关,即段地址为 DS,偏移地址为 [0] 的内存单元。

mov ax, [bx] 表示 bx 中存放的数据作为一个偏移地址 EA,段地址 SA 默认在 ds 中,将 SA:EA 处的数据放入 ax 中,即:

asm
(ax) = ((ds) * 16 + (bx))

PUSH 和 POP

栈顶的段地址存放在 SS 中,偏移地址存放在 SP 中。

执行 push 和 pop 指令时,CPU 从 SS:SP 寄存器中得到栈顶的地址。

push 和 pop 执行的时候只会修改 SP。

入栈时,栈顶从搞地质向低地址方向增长。

asm
mov ax, 0123H
push ax
mov bx, 2266H
push bx
pop ax
pop bx

push ax 表示将寄存器 ax 中的数据送入栈中,pop ax 表示从栈顶取出数据送入 ax。

其中,push ax 的执行由以下两步完成:

  1. 先更新 SP = SP - 2;
  2. 将 ax 的值送入 SS:SP 的位置。

push 通过这样的步骤,保持了 SP 一直在最新位置上,pop 命令也一样:

  1. 先将 SS:SP 指向内存单元处的数据送入 ax;
  2. 再更新 SP = SP + 2

该操作的对象可以是寄存器、段寄存器、内存单元。

用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先出栈。

转移指令

能够改变 CS、IP 的内容的指令被统称为转移指令。

JMP

jmp 段地址:偏移地址 的功能为:用指令中给出的段地址修改 CS,偏移地址修改 IP。

jmp ax 功能为:仅修改 IP 的内容,含义上可理解为:mov IP, ax 。

CALL

call 指令进行了两步操作:

  1. 将当前的 IP 或 CS 压入栈中。
  2. 转移

RET

ret 指令用栈中的数据,修改 IP 的内容,从而实现近转移。ret 指令进行下面两步操作:

  1. (IP) = ((ss) * 16 + (sp))
  2. (SP) = (sp) + 2

RETF

retf 指令用栈中的数据,修改 CS 和 IP 的内容,从而实现远转移。

IRET

循环控制指令

LOOP

asm
assume cs:code
code segment
	mov ax, 2
	mov cx, 11
s:add ax,ax
	loop s
	mov ax, 4c00h
	int 21h
code ends
end

CPU 执行 loop 指令的时候,要进行两步操作:

  1. (cx) = (cx) - 1
  2. 判断 cx 中的值,不为 0 则转至标号处执行程序,为 0 则向下执行。

可以得出,上面的 demo 代码将会执行 11 次 add ax, ax,然后再向下执行。

运算指令

INC

自增 1,例如:

asm
mov bx, 0
inc bx

执行后,(bx) = 1。

ADD

逻辑与,按位进行与运算。例如:

asm
mov al, 01100011B
and al, 00111011B

执行后,al = 00100011B。

OR

逻辑或,按位进行或运算。例如:

asm
mov al, 01100011B
or al, 00111011B

执行后,al = 01111011B。

SUB

减法指令

DIV

除法指令

SHL

逻辑左移指令,功能为:

  1. 将一个寄存器或内存单元中的数据向左移位。
  2. 将最后移出的一位写入 CF 中。
  3. 最低位用 0 补充。
asm
mov al, 01001000B
shl al, 1

执行后, (al) = 10010000B,CF = 0。

SHR

逻辑右移指令,功能为:

  1. 将一个寄存器或内存单元中的数据向右移位。
  2. 将最后移出的一位写入 CF 中。
  3. 最高位用 0 补充。
asm
mov al, 10000001B
shr al, 1

执行后 (al) = 01000000B,CF = 1。

中断指令

int

参考

汇编语言 - 王爽

https://www.felixcloutier.com/x86/index.html

本文共 2087 字,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

评论

博客助手

正在打开博客助手…