汇编语言与接口设计
寄存器:
以8086为例:
寄存器都是16位
*通用寄存器:*AX, BX, CX, DX
每个寄存器又可以分为高8位和低8位:如AX -> AH, AL 每个寄存器都可以独立使用
处理数据的尺寸:
字节 byte 8位 字 word 16位
简单的汇编指令:
mov ax bx ax <- bx
add ax bx ax <- ax + bx
jmp 3:0b16 CS <- 3h IP <- 0b16h
jmp ax ip <- ax
16位cpu:
1.运算器一次最多处理16位数据
2.寄存器最大宽度位16
3.reg和alu之间的数据通路为16位
地址:(内存中没有段的划分)
20位物理地址 16位段地址 16位偏移地址 -> 一个段的最大长度为 2 << 16 = 64kb
20位地址只在内存中使用
物理地址 = 段地址 << 4 + 偏移地址
段寄存器:
CS DS SS ES (段:segment)
CS和IP:
CS 代码段寄存器 IP 指令指针寄存器
读取一个指令后IP自动增加一个指令的长度
启动,复位后 CS = FFFFH IP= 0000H
代码段:
最大64kb, 起始地址16倍数
模块化程序设计的相关问题
例子:简单的模块实例,设计一个子程序,根据提供的N,计算N的三次方
1 | assmume cs:code |
这是一个很简单的子程序,没有任何的寄存器冲突
寄存器冲突存在下的模块化设计:
1 | assume cs:code |
汇编代码の使用教程
ret和retf
ret 相当于,pop ip
适用于近迁移
retf 相当于pop ippop cs
适用于远迁移
call
CPU执行call操作的时候进行两步操作:
1.将当前的CS和IP压栈,或者只压栈当前的IP
2.转移
第一种情况:
call标号
相当于:
push IP
jmp near ptr 标号第二种情况:
call far ptr 标号
相当于:
push CSpush IPjmp far ptr 标号`第三种情况:
call 16位reg
相当于:
push IP
jmp 16位reg第四种情况:
call word ptr 内存单元地址相当于:
push IP
jmp word ptr 内存单元地址
如果是dword的话
call dword ptr 内存单元地址
相当于:
push CS
push IP
jmp dword ptr 内存单元地址
mul
格式如下:mul regmul 内存单元
分两种情况:
如果是两个8位相乘,那么结果全部放在AX中。
如果是两个16位相乘,那么结果的高16位放在DX中,低16位放在AX中。
adc
adc带进位加法指令
用法:adc ax, bx
相当于计算:(ax) = (ax) + (bx) + CF
sbb
sbb带借位的减法
用法:sbb ax, bx
相当于计算:(ax) = (ax) - (bx) - CF
cmp
cmp是比较指令, 本质上是不保存结果的减法指令
用法:cmp ax, bx
先进行计算ax - bx,计算后
![[Pasted image 20260426232144.png]]
对应的跳转指令如下:
![[Pasted image 20260426232305.png]]
pushf和popf
pushf:标志寄存器入栈popf:标志寄存器出栈
标志寄存器Related
ZF寄存器:判断相关指令结果是否为0,是0则ZF= 1PF寄存器:奇偶标准位,记录相关指令执行后结果的所以bit中1的个数是否为偶数,如果是偶数则PF= 1,16位寄存器进行操作时,只看低8位。SF寄存器,记录相关指令执行后结果是否位负,如果为负则SF= 1CF寄存器,在进行无符号数运算的时候,记录运算结果最高有效位向更高位的进位值OF寄存器,在进行有符号数的运算的时候,如果结果超过机器表示范围,称为溢出DF寄存器和串传送指令:
df是方向标志位,在传处理指令中控制si, di的增减movsb相当于执行下面几步操作
es:[di] = ds:[si]if df = 0:
(si) = (si) + 1
(di) = (di) + 1if df = 1:
(si) = (si) - 1
(di) = (di) - 1
使用格式为:rep movsb
相当于:s:movsbloop s
所以使用这个指令的时候要设置cx, si, di, es, dsmovsw具体操作与
movsb相同,si, di的步长改为2df的设置指令:cld:df <- 0sld:df <- 1
内中断
当
cpu发生以下情况时,将产生中断信息
- 除法错误,如
div的指令溢出 –中断码0 - 单步执行 –中断码1
- 执行
into指令 –中断码4 - 执行
int指令 –中断码为指令参数
中断向量表:
储存在内存的
0000:0000到0000:03FF这1024个单元中
中断过程(8086CPU):
- 从中断信息中获取中断类型码
- 标志寄存器入栈(中断过程要改变标志寄存器的值)
- 设置标志寄存器第八位的
TF和第九位IF的值为0 CS入栈IP入栈- 从内存地址为
中断类型码 * 4 和中断类型码* 4 + 2两个字单元中读取中断处理程序的入口地址,设置CS和IP
相当于:1. 获取终端类型码N2. pushf3. TF = 0, IF = 04. push CS5. push IP6 (IP) = (N * 4), (CS) = (N * 4 + 2) - 中端处理程序和
iret指令iret指令相当于:
pop IP
pop CS
popf
编程处理0号中断
现在我们考虑,重新写一个0号中断处理程序,功能是在拼命中间显示”overflow!”,然后返回系统。
为了实现这个功能我们来进行分析:
- 当除法发生溢出的时候,产生0号中断信息,从而引发中断过程。
- 此时CPU进行以下工作:
(1). 获取中断类型码0;
(2). 标志寄存器入栈,TF和IF设置为0
(3). CS, IP入栈
(4). (IP) = (0 * 4), (CS) = (0 * 4 + 2) - 可见中断0发生的时候,CPU将转去执行中断处理程序,只要按照以下步骤编写中断处理程序,当中断0发生的时候,即可显示“overflow!”
- 相关处理;
- 向显示缓冲区宋字符串”overflow!”;
- 返回DOS
我们将这段程序成为do0
- 现在问题是:do0应该放在内存中。因为除法溢出随时有可能发生,CPU随时可能将CS:IP指向do0的入口,执行程序。
- 内存0000:0000-0000:03ff,大小为1kb的空间是存放中断程序入口地址的中断向量表。8086支持256个中断,但是实际上系统要处理的中断远没有256个。一般情况下,从0000:0200-0000:02ff的256个字节对于空间的中断向量表是空的,我们可以把do0放到0000:0200后
程序的整体框架如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18assume cs:code
code segment
start:
do0安装程序
设置中断向量表
mov ax, 4c00h
int 21h
do0:
显示字符串"overflow!"
mov ax, 4c00h
int 21h
code ends
end start - 此时CPU进行以下工作:
do0的安装
可以用movsb指令,将do0的代码送入0:200处。
1 | assume cs:code |
do0
按照我们一贯的思路:
1 | assume cs:code |
看似很合理,实际上大错特错。”overflow!”在程序的data段中,当程序执行完后返回,它所占用的空间会被系统是否,其中存放的字符串很肯可能被其他的信息覆盖。
正确的程序如下:
1 | asuume cs:code |