x86汇编语言笔记
8086通用寄存器
16位寄存器:AX、BX、CX、DX、SI、DI、BP、SP。
前4个可分为高8位和低8位来使用:AH、AL、BH、BL、CH、CL、DH、DL。
内存分段
采用分段技术解决地址重定位问题,在硬件级别用两个段寄存器来支持,代码段寄存器CS和数据段寄存器DS。
实模式下CPU访问物理地址的方式:
段基址:偏移地址
。
8086段寄存器
- 代码段寄存器:CS
- 数据段寄存器:DS
- 附加段寄存器:ES
- 栈段寄存器:SS
如何访问指令?
当程序开始时,CS指向代码段的起始地址,IP指令指针指向段内偏移。即
CS:IP
。注意:一般没有指定段寄存器时,默认使用DS。
如何访问数据?
在访问内存单元时,则采用
DS:偏移地址
。注意1:由于8086提供20位的物理地址,所以在计算物理地址时,要将段寄存器左移4位再加上偏移地址。由此可以得出8086最大只能访问1MB的内存空间。
注意2:内存是以字节为单位,内存中的每一个字节都对应一个物理地址。
计算机的启动流程
注意:主引导扇区的最有两个字节是0x55和0xaa。
显存
显存存在于显卡之中,一般CPU是通过把显存映射到
0xB8000~0xBFFFF
这部分内存空间,来直接访问显存,显卡在加电自检后会初始化为80 * 25模式,即屏幕上显示25行,每行80个字符,每屏总共2000个字符。显卡通过ASCII编码来识别CPU传入显存的数据。
屏幕上每个字符对应着显存中的连续两个字节,前一个字节为字符的ASCII代码,后一个字节为字符的显示属性。
注意:Intel的处理器不允许把一个立即数传送到段寄存器,必须通过其他寄存器来中转。
mov指令
mov 目的操作数, 源操作数
注意:mov指令不允许目的操作数和源操作数都为内存单元,并且目的操作数不能为立即数。
注意:内存的增长是从低地址到高地址的。
除法
无符号除法指令:div
-
16位除法:
被除数放在ax寄存器中。
格式:
\[ax / x = s \cdots m \]div x ;这里的x可以代表寄存器或者[内存单元]或者立即数
参数:
- x:除数。
- s:表示商,放在al寄存器。
- m:表示余数,放在ah寄存器。
-
32位除法:
被除数的高16位放在dx寄存器中,低16位放在ax中。
格式:
\[(dx << 4 + ax) / x = s \cdots m \]div x ;这里的x可以代表寄存器或者[内存单元]或者立即数
参数:
- x:除数。
- s:表示商,放在ax寄存器。
- m:表示余数,放在dx寄存器。
在段之间批量传送数据
使用movsb和movsw指令可以在两个段之间批量传送数据。movsb每次传送一个字节,movsw每次传送一个字。
格式:
; 重复执行
rep movsb ; rep指令前缀表示cx寄存器的值不为0,则重复执行该指令。
rep movsw
; 只执行一次
movsb
movsw
参数:
- 源地址:
DS:SI
,即段地址由DS寄存器提供,SI寄存器提供偏移地址。 - 目的地址:
ES:DI
,即段地址由ES寄存器提供,DI寄存器提供偏移地址。 - 数量:由CX寄存器指定。
传送方向:
- 正向:从低地址到高地址,每次使SI和DI加1或2。
- 反向:从高地址到低地址,每次使SI和DI减1或2。
一个特殊的寄存器flag
通过cld指令来将flag寄存器的DF位设置为0,std指令将flag寄存器的DF位设置位1。
ZF位表示运算结果是否为0。
1
:运算结果为00
:运算结果不为0DF位表示movsb/movsw指令是正向还是反向。
0
:正向1
:反向
关于使用寄存器提供偏移地址的问题
在8086处理器上只能使用BX、SI、DI、BP寄存器来提供偏移地址。
加一和减一、加法和减法
加一和减一对应的指令为inc
和dec
。
加法和减法对应的指令为add
和sub
。
负数相关指令
-
neg 寄存器
:将对应寄存器的值变为负数。 -
cbw
:无操作数,将AL寄存器的数值扩展到AX寄存器。 -
cwd
:无操作数,将AX寄存器的数值扩展到DX和AX寄存器中,DX存高16位,AX存低16位。
jns指令
标志寄存器的SF位:当计算结果的最高位是0时,该位为0,否则为1。
jns
指令,当标志寄存器的SF位为0时,则进行跳转。
标记寄存器的其他标志位
- 奇偶标志位PF:当运算结果的低8位中,有偶数个1则PF为1,否则为0。
- 进位标志位CF:当进行算术运算时,如果有向最高位进位或借位,则CF为1,否则为0。
- 溢出标志位OF:如果发生溢出则为1,否则为0。
指令对标志寄存器的影响:
条件跳转指令
根据标志跳转:
-
jz
:ZF为1则转移,jnz
:ZF为0则转移。 -
jo
:OF为1则转移,jno
:OF为0则转移。 -
jc
:CF为1则转移,jnc
:CF为0则转移。 -
jp
:PF为1则转移,jnp
:PF为0则转移。
根据比较结果跳转:
可以通过cmp
指令进行比较。
根据CX寄存器进行跳转:
jcxz
:如果CX寄存器为0,则进行跳转。
栈段
由SS寄存器来确定段地址,SP寄存器确定偏移地址。
压栈:push
指令,会将SP的内容减去操作数的字长,再把操作数压入栈中。
弹栈:pop
指令,会将SP的内容加上操作数的字长,再把栈顶的数据弹出。
注意:实模式下,栈的大小为64KB。
寻址方式
-
寄存器寻址
-
立即寻址
-
内存寻址
-
直接寻址
-
基址寻址:使用BX和BP寄存器进行寻址
注意:BP寄存器默认使用SS段寄存器,使得栈内数据可以像数据段一样访问。
-
变址寻址:使用SI和DI寄存器进行寻址
-
基址变址寄存器:同时使用BX/BP和SI/DI寄存器进行寻址
-
I/O端口读写方式
I/O端口的范围:0 ~ 65535(0x0 ~ 0xffff)。
-
读取I/O端口
in 存放位置, 端口号
存放位置:必须是AL或AX寄存器。
端口号:立即数或DX寄存器。
-
写入I/O端口
out 端口号, 写入数据
端口号:立即数或DX寄存器。
写入数据:必须是AL或AX寄存器。
一些磁盘相关的知识:
-
LBA28逻辑扇区编制法:即通过28个bit来表示逻辑扇区号,而不关心扇区的具体位置。(从0开始算起)
-
端口号:
端口号 含义 1f2 表示要读取的扇区数 1f3 表示逻辑扇区号的0~7位 1f4 表示逻辑扇区号的8~15位 1f5 表示逻辑扇区号的16~23位 1f6 低4位存放逻辑扇区号的24~27位,第4位表示硬盘号(0主盘,1从盘),第6位表示扇区编制方式(0:CHS、1:LBA) 1f7 命令端口:写入0x20表示读硬盘请求,状态端口:第7位表示硬盘忙碌状态(0不忙,1忙碌),第3位表示硬盘数据是否准备好(1:ok,0:no),第0位表示读取数据是否出错(1出错,0没出错) 1f0 硬盘接口的数据端口(16位端口),当硬盘准备好后,可以从这个端口读取数据
过程调用
call指令:
近调用的原理是先把IP寄存器的值压入栈中,再修改IP寄存器,而远调用则是分别把CS和IP寄存器的值压入栈,再修改CS和IP寄存器的值。
-
直接近调用
call 立即数
-
间接近调用
call 寄存器/[内存地址]
-
直接远调用
call 段地址:偏移地址
-
间接远调用
call far [内存地址]
这种方式会从对应的内存地址中获取两个字的数据,前一个字作为段地址,后一个字作为偏移地址。
返回指令:
ret
的原理是弹出栈中的值到IP寄存器,retf
的原理是先弹出到IP寄存器,再弹到CS寄存器。
-
ret
:相对近返回 -
retf
:相对远返回
abc、shr、ror指令
-
abc
:同add
指令差不多,但是会加上CF位的值(CF是表示是否进位)。 -
shr
:逻辑右移,类似C语言中的>>
。 -
shl
:逻辑左移,类似C语言中的<<
。 -
ror
:循环右移,将移出的bit放到左边空缺的位置,同时送入CF标志位。 -
rol
:循环左移,将移出的bit放到右边空缺的位置,同时送入CF标志位。
无条件跳转指令
-
相对短转移:立即数表示偏移量
jmp short 立即数
-
相对近转移:立即数表示偏移量
jmp near 立即数 ; 或 jmp 立即数
-
间接绝对近转移:寄存器或内存地址表示偏移地址
jmp near 寄存器/[内存地址] ; 或 jmp 寄存器/[内存地址]
-
直接绝对远转移
jmp 段地址:偏移地址
-
间接绝对远转移
jmp far 寄存器/[内存地址]
伪指令resb
-
resb
从当前位置开始,保留指定数量的字节,但不初始化它们的值。(区分db、dw、dd、dq)
-
resw
:与resb相同,但是单位为字。 -
resd
:与resb相同,但是单位为双字。
光标操作
屏幕上光标的位置保存在显卡内部的两个光标寄存器(8位)中,合起来是一个16位数值,表示在第几个位置(\(0 \sim {(80 * 25) - 1}\))。
注意:文字模式下,屏幕显示80*25个字符。
读取光标:
步骤:
- 向端口号0x3d4的索引寄存器指定索引,0xe是光标位置的高8位,0xf是光标位置的低8位。
- 通过端口号0x3d5的数据寄存器读取数据。
设置光标:
步骤:
- 向端口号0x3d4的索引寄存器写入光标位置的索引。
- 向端口号0x3d5的数据寄存器写入光标位置。
乘法
mul
指令
mul 寄存器/[内存地址]
- 8位乘法:将mul指令里指定的值乘以AL寄存器中的值,并把结果保存到AX寄存器中。
- 16位乘法:将mul指令里指定的值乘以AX寄存器中的值,并把结果的高16位保存到DX,低16位保存到AX中。
cpuid指令
用于返回处理器的标识和特性信息。
在eax寄存器中指定要返回CPU信息。返回结果放在eax、ebx、ecx或edx中。
eflags寄存器
comvcc指令
可以看成mov
指令加上条件判断功能,与cmp
或test
指令配合使用。
伪代码:
if (condition) {
mov dest, source
}
sgdt指令
格式:sgdt 寄存器/[内存单元]
把gdtr寄存器的内容保存到指定位置。
movzx/movsx指令
-
movzx:带零扩展传送指令
格式:
movzx r16/r32, r8/r16/m8/m16
把指定8位/16位寄存器/内存单元的数据放到16/32位寄存器中,并且将高位设置为0。
-
movsx:带符号扩展传送指令
格式:
movsx r16/r32, r8/r16/m8/m16
把指定8位/16位寄存器/内存单元的数据放到16/32位寄存器中,并且将高位设置为跟符号位一致。
cmps指令
cmp
指令的升级版,通过cx(16位)或ecx(32位)寄存器来指定比较次数,ds:si/esi
寄存器指定源地址,es:di/edi
寄存器指定目的地址,并根据eflags寄存器中的df位来决定地址变化的方向,\(df=0\)则正向比较,地址递增,\(df=1\)则反向比较,地址递减,如果没有加上rep
指令前缀,则只比较一次。
cmpsb ;字节比较
cmpsw ;字比较
cmpsd ;双字比较
有关的rep
指令前缀:
-
rep
:一直重复到cx寄存器的值为0。 -
repz/repe
:一直重复到cx寄存器的值为0或比较的内容不相等。 -
repnz/repne
:一直重复到cx寄存器的值为0或比较的内容相等。
调用门
调用门(Call Gate)用于不同特权级的程序之间进行控制转移。本质上只是一个描述符(不同于代码段和数据段)。
调用门的特权级检查规则:
pushf/popf指令
把16位的flags寄存器/32位的eflags寄存器压或弹栈到flag寄存器中。
GDT、LDT和TSS的关系
任务切换与特权级切换的区别
任务门
注意:任务是不可重入的。
任务切换:
-
中断引起:
在保护模式下,产生中断后CPU会根据中断号查询中断描述符表,如果对应的中断描述符是一个任务门,那么就会进行任务切换。
-
使用远过程调用指令引起:
在使用远过程调用指令时,CPU会去GDT中查找对应的段描述符,如果该描述符为任务门描述符,则会发起任务切换。
页目录项和页表项的结构
参数解释:
参数 | 含义 |
---|---|
P | 该页表/页是否存在于内存中,1存在,0不存在 |
RW | 读写位,0该页只读,1可读可写 |
US | 用户/管理位,1允许所有特权级访问,0只允许特权级为0、1、2的程序访问 |
PWT | 页级通写位,与高速缓存相关 |
PCD | 页级高速缓存禁止位 |
A | 访问位,表示该页是否被访问过 |
D | 脏位,表示该页是否被写过 |
PAT | 页属性表支持位,与高速缓存相关 |
G | 全局位,表示该页是否为全局性的,如果是,则一直保留在高速缓存中 |
AVL | 被处理器忽略,软件可使用 |
CR3寄存器的内容
bts指令
将指定位置的bit设置为1,并将其旧值设置到eflags寄存器的CF位中。
格式:
bts r/m16, r16
bts r/m32, r32
保护模式下的中断和异常向量分配
在实模式下,是由中断向量表定义了中断的入口地址(位于内存最低端的1KB),而保护模式下,是由中断描述符表(IDT)定义了中断的入口。(由idtr寄存器指定了中断描述符表所在位置)
中断描述符表里面保存了中断门、陷阱门和任务门。
中断门、陷阱门:
中断描述符寄存器:
保护模式下的中断处理过程:
保护模式下通过中断实现任务切换:
bound指令
用于检查数组是否超出索引(这个数组是指源操作数所指向的内存单元里面存放了上限和下限,大小为双字)
格式:
bound r16, m16
bound r32, m32
ud2指令
该指令无操作数,执行该指令会引发一个无效操作码异常。
格式:
ud2