《x86汇编语言:从实模式到保护模式》笔记

2022/1/27 22:04:46

本文主要是介绍《x86汇编语言:从实模式到保护模式》笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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:内存是以字节为单位,内存中的每一个字节都对应一个物理地址。

计算机的启动流程

image-20220122202902641

注意:主引导扇区的最有两个字节是0x55和0xaa。

显存

显存存在于显卡之中,一般CPU是通过把显存映射到0xB8000~0xBFFFF这部分内存空间,来直接访问显存,显卡在加电自检后会初始化为80 * 25模式,即屏幕上显示25行,每行80个字符,每屏总共2000个字符。

显卡通过ASCII编码来识别CPU传入显存的数据。

屏幕上每个字符对应着显存中的连续两个字节,前一个字节为字符的ASCII代码,后一个字节为字符的显示属性。

image-20220122205323064

image-20220122205336604

image-20220122204330102

注意:Intel的处理器不允许把一个立即数传送到段寄存器,必须通过其他寄存器来中转。

mov指令

mov 目的操作数, 源操作数

注意:mov指令不允许目的操作数和源操作数都为内存单元,并且目的操作数不能为立即数。

注意:内存的增长是从低地址到高地址的。


除法

无符号除法指令:div

  • 16位除法:

    被除数放在ax寄存器中。

    格式:

    div x ;这里的x可以代表寄存器或者[内存单元]或者立即数
    

    \[ax / x = s \cdots m \]

    参数:

    • x:除数。
    • s:表示商,放在al寄存器。
    • m:表示余数,放在ah寄存器。
  • 32位除法:

    被除数的高16位放在dx寄存器中,低16位放在ax中。

    格式:

    div x ;这里的x可以代表寄存器或者[内存单元]或者立即数
    

    \[(dx << 4 + ax) / x = s \cdots m \]

    参数:

    • 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:运算结果为0
  • 0:运算结果不为0

DF位表示movsb/movsw指令是正向还是反向。

  • 0:正向
  • 1:反向

image-20220123204058429


关于使用寄存器提供偏移地址的问题

在8086处理器上只能使用BX、SI、DI、BP寄存器来提供偏移地址。


加一和减一、加法和减法

加一和减一对应的指令为incdec

加法和减法对应的指令为addsub


负数相关指令

  • 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。

指令对标志寄存器的影响:

image-20220123215129845

条件跳转指令

根据标志跳转:

  • 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指令进行比较。

image-20220123215659834

image-20220123215722000

根据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位端口),当硬盘准备好后,可以从这个端口读取数据

    image-20220124155733782

image-20220124160018268


过程调用

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标志位。

image-20220124172321064


无条件跳转指令

  • 相对短转移:立即数表示偏移量

    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个字符。

读取光标:

步骤:

  1. 向端口号0x3d4的索引寄存器指定索引,0xe是光标位置的高8位,0xf是光标位置的低8位。
  2. 通过端口号0x3d5的数据寄存器读取数据。

设置光标:

步骤:

  1. 向端口号0x3d4的索引寄存器写入光标位置的索引。
  2. 向端口号0x3d5的数据寄存器写入光标位置。

乘法

mul指令

mul 寄存器/[内存地址]
  • 8位乘法:将mul指令里指定的值乘以AL寄存器中的值,并把结果保存到AX寄存器中。
  • 16位乘法:将mul指令里指定的值乘以AX寄存器中的值,并把结果的高16位保存到DX,低16位保存到AX中。

cpuid指令

用于返回处理器的标识和特性信息。

在eax寄存器中指定要返回CPU信息。返回结果放在eax、ebx、ecx或edx中。


eflags寄存器

image-20220126165237566


comvcc指令

可以看成mov指令加上条件判断功能,与cmptest指令配合使用。

伪代码:

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或比较的内容相等。

image-20220126173106954


调用门

调用门(Call Gate)用于不同特权级的程序之间进行控制转移。本质上只是一个描述符(不同于代码段和数据段)。

image-20220126200038307

调用门的特权级检查规则:

image-20220126203513197


pushf/popf指令

把16位的flags寄存器/32位的eflags寄存器压或弹栈到flag寄存器中。


GDT、LDT和TSS的关系

image-20220126204826235


任务切换与特权级切换的区别

image-20220126205808314


任务门

image-20220126210543989

image-20220126211109269

注意:任务是不可重入的。

任务切换:

  • 中断引起:

    在保护模式下,产生中断后CPU会根据中断号查询中断描述符表,如果对应的中断描述符是一个任务门,那么就会进行任务切换。

  • 使用远过程调用指令引起:

    在使用远过程调用指令时,CPU会去GDT中查找对应的段描述符,如果该描述符为任务门描述符,则会发起任务切换。


页目录项和页表项的结构

image-20220127171112427

参数解释:

参数 含义
P 该页表/页是否存在于内存中,1存在,0不存在
RW 读写位,0该页只读,1可读可写
US 用户/管理位,1允许所有特权级访问,0只允许特权级为0、1、2的程序访问
PWT 页级通写位,与高速缓存相关
PCD 页级高速缓存禁止位
A 访问位,表示该页是否被访问过
D 脏位,表示该页是否被写过
PAT 页属性表支持位,与高速缓存相关
G 全局位,表示该页是否为全局性的,如果是,则一直保留在高速缓存中
AVL 被处理器忽略,软件可使用

CR3寄存器的内容

image-20220127175857850


bts指令

将指定位置的bit设置为1,并将其旧值设置到eflags寄存器的CF位中。

格式:

bts r/m16, r16
bts r/m32, r32

保护模式下的中断和异常向量分配

在实模式下,是由中断向量表定义了中断的入口地址(位于内存最低端的1KB),而保护模式下,是由中断描述符表(IDT)定义了中断的入口。(由idtr寄存器指定了中断描述符表所在位置)

中断描述符表里面保存了中断门、陷阱门和任务门。

image-20220127203910140

中断门、陷阱门:

image-20220127204617586

中断描述符寄存器:

image-20220127204549991

保护模式下的中断处理过程:

image-20220127204822859

保护模式下通过中断实现任务切换:

image-20220127205126792


bound指令

用于检查数组是否超出索引(这个数组是指源操作数所指向的内存单元里面存放了上限和下限,大小为双字)

格式:

bound r16, m16
bound r32, m32

ud2指令

该指令无操作数,执行该指令会引发一个无效操作码异常。

格式:

ud2


这篇关于《x86汇编语言:从实模式到保护模式》笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程