汇编语言
首先,我必须赞扬王爽老师,这是我见过写的最好的教科书了.
然后要注意,汇编语言这本书的前提是8086PC机十六位处理器.还要学x86和x64以及其他奇奇怪怪的架构的汇编
全称
AH&AL=AX(accumulator):累加寄存器
BH&BL=BX(base):基址寄存器
CH&CL=CX(count):计数寄存器
DH&DL=DX(data):数据寄存器
SP(Stack Pointer):堆栈指针寄存器
BP(Base Pointer):基址指针寄存器
SI(Source Index):源变址寄存器
DI(Destination Index):目的变址寄存器
IP(Instruction Pointer):指令指针寄存器
CS(Code Segment)代码段寄存器
DS(Data Segment):数据段寄存器
SS(Stack Segment):堆栈段寄存器
ES(Extra Segment):附加段寄存器
FLAG 标志寄存器: FLAG 寄存器中存储的信息通常又被称作程序状态字(PSW)
OF overflow flag 溢出标志 操作数超出机器能表示的范围表示溢出,溢出时为1.
SF sign Flag 符号标志 记录运算结果的符号,结果负时为1.
ZF zero flag 零标志 运算结果等于0时为1,否则为0.
CF carry flag 进位标志 最高有效位产生进位时为1,否则为0.
AF auxiliary carry flag 辅助进位标志 运算时,第3位向第4位产生进位时为1,否则为0.
PF parity flag 奇偶标志 运算结果操作数位为1的个数为偶数个时为1,否则为0.
DF direcion flag 方向标志 用于串处理.DF=1时,每次操作后使SI和DI减小.DF=0时则增大.
IF interrupt flag 中断标志 IF=1时,允许CPU响应可屏蔽中断,否则关闭中断.
TF trap flag 陷阱标志 用于调试单步操作.
psw: program status word
tcon: timer control
ie: interrupt enable
scon: serial control
EA --Effective Address:有效地址 ,即偏移地址。
SA--segment address
基础知识
(1) 汇编指令是机器指令的助记符, 同机器指令一一对应。
(2) 每一种CPU都有自己的汇编指令集。
(3) CPU 可以直接使用的信息在存储器中存放。
(4) 在存储器中指令和数据没有任何区别,都是二进制信息。
(5) 存储单元从零开始顺序编号。
(6) 在计算机中专门有连接 CPU 和其他芯片的导线, 通常称为总线。总线从物理上来讲, 就是一根根导线的集合。根据传送信息的不同, 总线从逻辑上又分为3类,地址总线、控制总线和数据总线。每一个CPU 芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU 可以引出3 种总线的宽度标志了这个CPU 的不同方面的性能:
地址总线的宽度决定了CPU 的寻址能力;
数据总线的宽度决定了CPU 与其他器件进行数据传送时的一次数据传送量;
控制总线的宽度决定了CPU 对系统中其他器件的控制能力。
出于对兼容性的考虑, 8086CPU 可以一次性处理以下两种尺寸的数据。
- 字节: 记为byte , 一个字节由8 个加组成,可以存在8 位寄存器中。
- 字: 记为word, 一个字由两个字节组成, 这两个字节分别称为这个字的高位字节和低位字节.
寄存器
通用寄存器
一个典型的CPU(此处讨论的不是某一具体的CPU) 由运算器、控制器、寄存器(CPU工作原理)等器件构成, 这些器件靠内部总线相连。
8086CPU 有14 个寄存器,每个寄存器有一个名称。这些寄存器是: AX 、BX 、EX 、DX 、SI 、DI 、SP 、BP 、IP 、CS 、SS 、DS 、ES 、PSW 。
8086CPU 的上一代CPU 中的寄存器都是8 位的, 为了保证兼容, 使原来基于上代
CPU 编写的程序稍加修改就可以运行在8086 之上, 8086CPU 的AX、BX、EX 、DX 这4个寄存器都可分为两个可独立使用的8 位寄存器来用:
- AX 可分为AH 和AL ;
- BX 可分为BH 和BL ;
- EX 可分为CH 和CL ;
- DX 可分为DH 和DL 。
内存地址管理
8086只有16位总线,但却可以寻址1Mb的大小.以下是它的做法:
如图2 . 6 所示, 当8086CPU 要读写内存时:
21
内存
(1) CPU 中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
(2) 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
(3) 地址加法器将两个16位地址合成为一个20位的物理地址,地址加法器采用物理地址=段地址x16+偏移地址的方法用段地址和偏移地址合成物理地址;
(4) 地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
(5) 输入输出控制电路将20位物理地址送上地址总线;
(6) 20 位物理地址被地址总线传送到存储器。
在这里就引入了段的概念.
CS和IP
CS 为代码段寄存器, IP 为指令指针寄存器.
以上可以概括为:
(1) 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
(2) IP=IP+所读取指令的长度,从而指向下一条指令;
(3) 执行指令。转到步骤(1) , 重复这个过程。
可用jmp 段地址:偏移地址
来修改CSIP,也可以使用jmp 合法寄存器
等于mov IP,寄存器
.
寄存器(内存访问)
CPU 要读写一个内存单元的时候, 必须先给出这个内存单元的地址, 在8086PC 中,内存地址由段地址和偏移地址组成。8086CPU 中有一个DS 寄存器, 通常用来存放要访问数据的段地址。
CPU 如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU 中,有两个寄存器,段寄存器SS 和寄存器SP, 栈顶的段地址存放在SS中,偏移地址存放在SP 中。任意时刻,SS:SP 指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址。
第一个程序
先对汇编代码的结构做个了解.
以下是一段汇编代码:
assume cs:codesg
codesg segmentmov ax, 0123Hmov bx, 0456Hadd ax, bxadd ax, axmov ax, 4c00Hint 21H
codesg ends
end
伪代码
xxx segment
和xxx ends
是一对,也是必须用到的,功能是定义一个代码段
end
是汇编程序结束的标志
assume
是关联相关的段寄存器
程序返回代码
mov ax,4c00H
int 21H ;程序返回代码
编译与链接
编译与链接不做了解.
[BX]和loop指令
一些定义
[BX]表示的是内存单元的偏移地址
(BX)则表示里面的内容
idata表达常量
loop
这是一段含loop的指令:
assume cs:code
code segmentmov ax, 2mov cx, 11
s: add ax, axloop s ;cx为0则执行下一条,否则令cx减1mov ax,4c00hint 21h
code ends
end
段前缀
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的"ds:" "cs:" "ss:" " es:",在汇编语言中称为段前缀。默认则在ds中
包含多个段的程序
代码段使用数据
以下是一段asm代码:
assume cs:code
code segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hmov bx, Omov ax, Omov cx, 8
s: add ax, cs:[bx]add bx, 2loop smov ax, 4c00hint 21h
code ends
end
程序第一行中的“dw" 的含义是定义字型数据。dw 即“ define word "。
在这里,使用dw定义了8个字型数据(数据之间以逗号分隔),它们所占的内存空间的大小为16个字节。
入口
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
start: mov bx, 0mov ax, 0mov cx , 8
s: add ax, cs:[bx]add bx, 2
code ends
end start
在程序的第一条指令的前面加上了一个标号start,而这个标号在伪指令end的后面出现。这里,我们要再次探讨end的作用。end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。在程序6.2中我们用end指令指明了程序的入口在标号start处,也就是说,“ mov bx,0"是程序的第一条指令。
不同的段
assume cs:code, ds:data, ss:stack
data segment
dw 0123h , 0456h, 0789h, 0abch, 0defh , 0fedh , 0cbah, 0987h
data ends
stack segmentdw 0 , 0 , 0, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
stack ends
code segment
start: mov ax, stackmov ss, axmov sp, 20h ;设置栈顶ss:sp 指向s tack : 20mov ax, datamov ds, axmov bx, 0mov cx, 8
s : push [bx]add bx, 2loop smov bx, Omov cx, 8
s0 : pop [bx]add bx, 2loop s0mov ax, 4c00hint 21h
code ends
end start
这里是定义多个段,由于有符号,所以符号可以对应段地址,比如mov ax, stack
就是把stack的段地址放入ax中.
更灵活的定位内存地址的方法
字符形式的数据
" db'unIX' ” 相当于“db 75H 6EH 49H 58H”.
si和di
si和di在8086机里与bx功能接近,但不能分成两个8位寄存器
不同的寻址方式的灵活应用
(1) [idata]用一个常量来表示地址, 可用于直接定位一个内存单元;
(2) [bx]用一个变量来表示内存地址, 可用于间接定位一个内存单元;
(3) [bx+data]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接
定位一个内存单元;
(4) [bx+si]用两个变量表示地址;
(5) [bx+si+idata]用两个变最和一个常量表示地址。
数据处理的两个基本问题
Warnings
mov ax, [bx]
mov ax, [si]
mov ax, [di]
mov ax, [bp]
mov ax, [bx+si]
mov ax, [bx+di]
mov ax, [b p+si]
mov ax, [b p+di]
mov ax, [bx+si+idata]
mov ax, [bx+di+idata]
mov ax, [bp+si+idata]
mov ax, [bp+di+idata]
下面的指令是错误的:
mov ax, [bx+bp]
mov ax, [si+di]
只要在[ ]中使用寄存器bp ,而指令中没有显性地给出段地址,段地址就默认在ss 中。比如下面的指令。
mov ax, [bp]
mov ax, [bp+i data]
mov ax, [bp+si]
含义: (ax) = ((ss) *16+ (bp) )
含义: (ax) = ((ss) *16+ (bp) +idata)
含义: (ax) = ((ss) *16+ (bp) + (si))
mov ax, [bp+si+idata] 含义: (ax) = ((ss) *16+ (bp) + (si) +idata)
寻址方式小结
指令转移的原理
可以修改IP, 或同时修改CS和IP的指令统称为转移指令。
依据位移进行转移的jmp指令
jmp short 标号(转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,它对IP 的修改范围为-128~ 127, 也就是说,它向前转移时可以最多越过128 个字节,向后转移可以最多越过127个字节。jmp指令中的“short"符号,说明指令进行的是短转移。jmp 指令中的“标号”是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应该指向标号处的指令。
在"jmp short 标号"指令所对应的机器码中, 并不包含转移的目的地址,而包含的是转移的位移。这个位移,是编译器根据汇编指令中的“ 标号”计算出来的.
实际上, jmp short 标号”的功能为:(IP)=(IP)+8 位位移。
(1) 8 位位移=标号处的地址-jmp指令后的第一个字节的地址;
(2) short 指明此处的位移为8位位移;
(3) 8 位位移的范围为- 128~ 127, 用补码表示(如果你对补码还不了解, 请阅读附
注2);
(4) 8 位位移由编译程序在编译时算出。
还有一种和“jmp short 标号”功能相近的指令格式,jmp near ptr 标号,它实现的是
段内近转移。
jmp near ptr 标号”的功能为: (IP)=(IP)+ 16 位位移。
(1) 16 位位移=标号处的地址-jmp 指令后的第一个字节的地址;
(2) near ptr 指明此处的位移为16 位位移,进行的是段内近转移;
(3) 16 位位移的范围为—32768~32767, 用补码表示;
(4) 16 位位移由编译程序在编译时算出。
转移的目的地址在指令中的jmp指令
"jmp far ptr 标号"实现的是段间转移,又称为远转移。
转移地址在寄存器中的jmp指令
指令格式:jmp 16位reg
功能:(IP)=(l6位reg)
转移地址在内存中的jmp指令
(1) jmp word ptr 内存单元地址(段内转移)
(2) jmp dword ptr 内存单元地址(段间转移)
有条件转移
jcxz 当cx为zero时,短转移
loop其实是类似的,而且也是短转移.
CALL 和RET 指令
跳过,已经品味得够多了.
标识寄存器
FLAG 标志寄存器: FLAG 寄存器中存储的信息通常又被称作程序状态字(PSW)
OF overflow flag 溢出标志 操作数超出机器能表示的范围表示溢出,溢出时为1.
SF sign Flag 符号标志 记录运算结果的符号,结果负时为1.
ZF zero flag 零标志 运算结果等于0时为1,否则为0.
CF carry flag 进位标志 最高有效位产生进位时为1,否则为0.
AF auxiliary carry flag 辅助进位标志 运算时,第3位向第4位产生进位时为1,否则为0.
PF parity flag 奇偶标志 运算结果操作数位为1的个数为偶数个时为1,否则为0.
DF direcion flag 方向标志 用于串处理.DF=1时,每次操作后使SI和DI减小.DF=0时则增大.
IF interrupt flag 中断标志 IF=1时,允许CPU响应可屏蔽中断,否则关闭中断.
TF trap flag 陷阱标志 用于调试单步操作.
adc sbb
adc 是带进位加法指令, 它利用了CF 位上记录的进位值。
指令格式: adc 操作对象1, 操作对象2
功能: 操作对象1 = 操作对象1+ 操作对象2+CF
比如指令adc ax,bx 实现的功能是: (ax)=(ax)+(bx)+ CF
sbb 是带借位减法指令,它利用了CF位上记录的借位值。
指令格式: sbb 操作对象1, 操作对象2
功能: 操作对象l =操作对象1-操作对象2-CF
比如指令sbb ax,bx 实现的功能是: (ax)=(ax)—(bx)—CF
用于更大数据的高位运算
cmp
cmp 是比较指令, cmp 的功能相当于减法指令,只是不保存结果。cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp 指令格式:cmp 操作对象1, 操作对象2
功能:计算操作对象l —操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存
器进行设置。
比如,指令cmp ax,ax,做(ax)-(ax)的运算,结果为0, 但并不在ax中保存,仅影响
flag的相关各位。指令执行后:zf=1, pf=1, sf=0, cf=0 of=0。
相关跳转
下面是常用的根据无符号数的比较结果进行转移的条件转移指令。
这些指令比较常用,它们都很好记忆,它们的第一个字母都是j ,表示jump; 后面的
字母表示意义如下。
e : 表示equal
ne: 表示not equal
b: 表示below
nb : 表示not below
a : 表示above
na: 表示not above
popf pushf
压入和弹出 相关寄存器为标识寄存器
内中断和外中断
没啥好了解的,跳过
指令
mov add sub div mul abc sbb
push pop
int
inc:令寄存器加1
loop
and:逻辑与指令,按位进行与运算。 or:逻辑或指令, 按位进行或运算。
db dw dd(dword) dup(db 重复的次数 dup(重复的数据)
) offset(获取偏移值)
jmp
shl shr 位移,移出的最后以为放到CF中