X86汇编语言从实模式到保护模式04:编写主引导扇区代码

目录

1. 主引导扇区程序烧写与调试

1.1 创建主引导扇区程序

1.2 将程序写入硬盘主引导扇区

1.3 用调试器观察程序的执行

1.3.1 工具选用

1.3.2 调试器状态说明

1.3.3 常用调试命令

2. 在屏幕上显示文字

2.1 代码段与功能

2.2 显卡和显存

2.2.1 显卡与显示器功能

2.2.2 显存内容与显示器内容的关系

2.2.3 显存的访问方式

2.3 初始化段寄存器

2.4 显示字符的实现

2.5 MOV指令使用小结

3. 在屏幕上显示数字

3.1 代码段与功能

3.2 列表文件的创建与使用

3.3 汇编地址与偏移地址的关系

3.4 标号

3.4 如何显示十进制数

3.4.1 方法概述

3.4.2 无符号除法指令div

3.4.2 在程序中声明并初始化数据

3.4.3 显示分解出的数位

3.4.4 异或指令xor

3.4.5 加法指令add

4. 使程序进入无限循环

4.1 代码段与功能

4.2 为什么要进入无限循环

4.3 跳转指令

4.3.1 段间直接绝对跳转指令

4.3.2 使用寄存器的绝对间接近跳转

4.3.3 使用相对偏移量的短跳转和近跳转

5. 写入MBR有效标识

5.1 代码段与功能

5.2 times伪指令

5.3 预留字节数的计算

5.3.1 通过list文件计算

5.3.2 通过标号计算


1. 主引导扇区程序烧写与调试


下面以一个实例说明主引导扇区的烧写方法,以及如何使用Bochs虚拟机调试写入主引导扇区的程序

1.1 创建主引导扇区程序


此处我们使用课程配套的c05_mbr.asm代码作为实例,该程序实现在屏幕上显示文字,程序源码截图如下

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第1张图片


在nasmide中编译该程序后将生成c05_mbr.bin文件,下节将说明如何将该文件烧写到虚拟硬盘的主引导扇区中

1.2 将程序写入硬盘主引导扇区


使用课程配套的fixvhdw工具将编译出的主引导扇区程序写入虚拟硬盘的主引导扇区

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第2张图片


说明:fixvhdw工具使用LBA模式访问硬盘,因此LBA0对应的就是0磁头0磁道1扇区的主引导扇区

1.3 用调试器观察程序的执行

1.3.1 工具选用


使用Bochs虚拟机调试主引导扇区程序时,需要使用bochsdbg.exe而不是bochs.exe,但是二者使用同一份配置文件

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第3张图片

1.3.2 调试器状态说明

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第4张图片


启动调试后,会启动2个窗口,左侧为调试窗口,右侧为虚拟机显示器窗口。调试窗口会停留在系统上电后的第1条指令处,如下图所示,

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第5张图片


下面说明各字段含义,


① 0x0000fffffff0


指令的物理地址


② f000 : fff0


指令的逻辑地址,即当前[CS : IP]寄存器的值


③ jmpf 0xf000 : e05b


指令的汇编形式


④ ; ea5be000f0


指令的机器码


说明:[CS : IP]上电初值问题


8086处理器上电后[CS : IP]初始为[0xFFFF : 0x0000],从而构成0xFFFF0的物理地址


如截图所示,在后续的X86体系结构中,虽然物理地址仍为0xFFFF0,但是[CS : IP]初值被修改为[0xF000 : 0xFFF0]


说明2:运行的第1条指令确实是跳转指令


根据之前的分析,第1条指令所在的0xFFFF0地址处距离1MB地址空间的顶端只有16B,因此可能部署了一条跳转指令,跳转到BIOS的其他位置继续运行


从调试结果分析,跳转的目的地为[0xf000 : e05b] = 0xFE05B,该地址确实在BIOS中


说明3:关于指令的物理地址


在Bochs虚拟机中,系统上电后的第1条指令的物理地址为0x0000FFFFFFF0,而不是20位的0xFFFF0,是由于如下原因,


① Bochs统一使用64位的宽度显示物理地址,便于适配16  / 32 / 64位处理器


② 后续的X86处理器能够兼容8086的功能,但是却拥有超过32根地址线,在当前的Bochs虚拟机中,地址线为32根,所以会有高12位的0xFFF


③ 高12位的地址为0xFFF,是因为在刚启动时,处理器将其余高位部分的地址线强制为高电平,直到遇到并执行第一个段间跳转指令


经过验证,确实如此,

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第6张图片


说明4:Next at t=0


Bochs虚拟机维护了一个内部时钟,每模拟执行一条指令,递增该值

1.3.3 常用调试命令

指令

指令全称

指令功能

s

step

单步执行指令

b

break

设置程序执行断点(e.g. b 0x7c00)

c

continue

持续执行程序,直到遇到断点

r

register

显示通用寄存器的内容

sreg

segment register

显示段寄存器的内容

xp

eXamine memory at Physical address

显示指定物理内存地址处的内容

q

quit

退出调试

h

help

帮助命令,单独使用列出Bochs虚拟机的所有命令

h 命令名:查询指定命令的用法

n

next

以step over的方式执行下一条指令

u

disassemble

反汇编指定数量的指令


说明1:跳转到主引导扇区执行后的处理器状态


首先使用break & continue命令,将程序运行至跳转到0x7C00执行主引导扇区的状态

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第7张图片


之后使用register & segment register命令查看寄存器状态

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第8张图片

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第9张图片


① RAX寄存器中的值为0xaa55,为主引导扇区有效标志,可见BIOS中可能使用该寄存器参与判断主引导扇区的有效性


② [RCS : RIP]的值为[0x0000 : 0x7C00],进而构成0x7C00的物理地址


说明2:通用寄存器扩展

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第10张图片


① 32 & 64位X86体系结构对AX / BX / CX / DX / SI / DI / SP / BP的位数进行了扩展,并保持了兼容性


② 32 & 64位X86体系结构对IP的位数进行了扩展,并保持了兼容性


③ 64位X86处理器新增了8个寄存器(R8 ~ R15),他们只能整体作为64位寄存器使用


说明3:段寄存器扩展


① 段寄存器仍然是16位


② 新增了2个段寄存器(FS、GS)


③ 新增了段描述符高速缓冲器,他们只能由处理器内部使用,不能在程序中访问,其中存储了段寄存器中段选择符对应的段描述符信息(X86保护保护模式中使用)


在Bochs调试器中,使用sreg命令可以查看相关内容


说明4:xp命令默认每次显示一个双字(4B)的内容


要显示更多双字,需要使用"/"附加一个数量


xp命令的完整格式如下,

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第11张图片


可见xp命令可以指定显示的长度、格式、字长,比如如下指令就可以显示当前显存中的数据


说明5:step命令与next命令的区别


在单步调试c05_mbr.asm程序时,只有使用n命令才能实时在屏幕上显示写入显存的文字


说明6:u命令


u命令可对指定数量的指令进行反汇编


在实际调试过程中,u指令可帮助设置断点位置

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第12张图片


因为反汇编后有指令及其物理地址,通过该物理地址即可设置断点


下面以c05_mbr.asm程序为例,说明其中涉及的相关知识点

2. 在屏幕上显示文字

2.1 代码段与功能


程序中的如下代码段实现在屏幕上显示"Label offset:"字符串

mov ax,0xb800 ;指向文本模式的显示缓冲区
mov es,ax

;以下显示字符串"Label offset:"
mov byte [es:0x00],'L'
mov byte [es:0x01],0x07
mov byte [es:0x02],'a'
mov byte [es:0x03],0x07
mov byte [es:0x04],'b'
; 略

2.2 显卡和显存

2.2.1 显卡与显示器功能


① 为了在屏幕上显示文字,通常需要2种硬件,即显卡和显示器


② 显卡的功能是为显示器提供内容,并控制显示器的显示模式和状态


③ 显示器的功能是将那些内容以视觉可见的方式呈现在屏幕上


显卡控制显示器的最小单位是像素,一个像素对应着屏幕上的一个点。通过控制每个像素的明暗和颜色,就可以在屏幕上显示文字和图像


控制像素的方式就是将要显示的内容写入显存(显示存储器,Video RAM,VRAM),再由显卡周期性地从显存中读取信息,并将他们按顺序显示在屏幕上

2.2.2 显存内容与显示器内容的关系


显卡有图形模式和文本模式两种基本工作模式,可以用指令访问显存,设置他的显示模式。在不同的工作模式下,显卡对显存内容的解释是不同的


2.2.2.1 图形模式

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第13张图片


① 根据色彩方式使用相应长度的比特位描述一个像素,比如真彩色使用24个比特(即3B)描述一个像素


② 显卡根据显存中一个像素的信息,将其显示在屏幕上


2.2.2.2 文本模式

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第14张图片


① 将字符的编码存放在显存中,之后由字符发生器和控制电路将字符显示在屏幕上


② 由于历史原因,所有在PC上使用的显卡,在加电自检之后都会把自己初始化到80 * 25的文本模式。即屏幕上可以显示25行,每行80个字符,每屏共2000个字符


说明:字符编码的构成


在文本模式下,每个字符的编码由ASCII码(1B)+ 显示属性(1B)构成,ASCII码大家比较熟悉,此处说明一下显示属性


显示属性的1B分为高低4位,分别描述字符颜色(前景色)和底色(背景色)

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第15张图片


其中RGB为三原色,前景色中的I位标识是否高亮,背景色中的K位标识是否闪烁,具体构成的显示属性如下

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第16张图片

2.2.3 显存的访问方式


① 显存位于显卡上,因此处理器访问显存是与外设交互


② 为了提升访问显存的性能,将显存映射到处理器可以直接访问的地址空间中

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第17张图片


③ 在文本模式中,地址空间中的0xB8000 ~ 0xBFFFF被映射到显存,共32KB,足够存储每屏2000个字符的内容(字符编码 + 显示属性共4000B)

2.3 初始化段寄存器


由于已经将显存映射到处理器内存空间,所以可以像访问内存一样访问显存,即也是通过[段地址 : 偏移地址]的方式进行访问


显存的起始地址为0xB8000,因此设置段地址为0xB800,在代码中实现如下,

mov ax,0xb800                 ;指向文本模式的显示缓冲区
mov es,ax


说明1:为何借用AX寄存器设置段寄存器


Intel处理器不允许将一个立即数传送到段寄存器,只允许使用通用寄存器或内存单元设置段寄存器,即

mov 段寄存器, 通用寄存器
mov 段寄存器, [内存地址]


说明2:此处将显存段地址设置在ES寄存器中,后续在寻址时就需要从ES中取得段地址

2.4 显示字符的实现


在代码中使用如下指令将要显示的字符及其显示属性写入显存

mov byte [es:0x00],'L'
mov byte [es:0x01],0x07


说明1:在指令中使用字符的字面值需要加单引号


说明2:段超越前缀es:


① 由于显存的段地址在ES段寄存器中,因此在生成物理地址时,我们希望使用ES段寄存器


② 在不指定段寄存器的情况下,处理器访问内存时默认使用DS寄存器


③ 段超越前缀用于指定使用的段寄存器


附加段寄存器ES就是用于这种需要同时使用2个数据段的场景,如果没有ES寄存器,则只能不断修改DS寄存器的值,以指向要访问的数据段


说明3:关键字byte的作用


关键字byte用来说明本次传送的单位为字节,相对应的还有word说明传送的单位为字,dword说明传送的单位为双字(4B),qword说明传送的单位为4字(8B,在16位实模式下不支持)


说明4:在什么情况下需要指定传送长度


当指令本身无法判断传输的长度时,需要指定传送长度。假设我们将代码修改为如下形式,


此时即可以解释为将立即数0x07传送到[es:0x01]字节,也可以解释为将立即数0x0007传送到[es:0x01]开始的字中,此时编译器将报错


如果操作数的长度确定,则无需修饰

mov [0x00], al ;按字节操作
mov ax, [0x02] ;按字操作

2.5 MOV指令使用小结


mov指令用于数据传送,传送指令只影响目的操作数的内容,不改变源操作数的内容,指令格式如下,

mov 目的操作数[寄存器 / 内存地址], 源操作数[寄存器 / 内存地址 / 立即数]


使用过程中,需要注意如下事项,


① mov指令可以在寄存器之间传送数据,但是寄存器的宽度必须相同

mov ax, bl ;编译错误,寄存器宽度不同


② mov指令可以将内存中的数据传送到寄存器,默认段地址在DS中


③ mov指令可以将立即数传送到寄存器


④ mov指令可以将寄存器中的内容传送到指定的内存中,操作宽度取决于寄存器宽度

mov [0x0c], dx ;传输2B
mov [0x20], ah ;传输1B 


⑤ mov指令可以将立即数传送到指定的内存单元,同时需要指定操作宽度

mov byte [0x02], 0xe9 ;按字节操作
mov word [0x06], 0x3c ;按字操作


⑥ mov指令的目的操作数不能是立即数


⑦ mov指令的源操作数和目的操作数不能同时为内存单元

mov [0x01], [0x02] ;编译报错


不仅是mov指令,其他指令也都不支持在两个内存单元之间直接进行操作


⑧ 不可直接访问指令指针寄存器IP,IP不能出现在任何指令中,只能间接修改(e.g. 通过跳转指令)

mov ip, 0xf000 ;编译报错


⑨ 如果目的操作数是段寄存器,则源操作数必须是通用寄存器或内存单元,不能是立即数


说明:处理器为什么不支持内存单元之间的直接操作


一种理解是,支持内存单元之间的直接操作会增加处理器的复杂度,但是收益不大,和通过2条指令实现传输并无区别

3. 在屏幕上显示数字

3.1 代码段与功能


程序中的如下代码段实现在屏幕上显示number标号的偏移地址

mov ax,number ;取得标号number的偏移地址
mov bx,10

;设置数据段的基地址,与代码段相同
mov cx,cs
mov ds,cx

;求个位上的数字
mov dx,0
div bx
mov [0x7c00+number+0x00],dl   ;保存个位上的数字

;求十位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x01],dl   ;保存十位上的数字

;求百位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x02],dl   ;保存百位上的数字

;求千位上的数字
xor dx,dx
div bx
mov [0x7c00+number+0x03],dl   ;保存千位上的数字

;求万位上的数字 
xor dx,dx
div bx
mov [0x7c00+number+0x04],dl   ;保存万位上的数字

;以下用十进制显示标号的偏移地址
mov al,[0x7c00+number+0x04]
add al,0x30
mov [es:0x1a],al
mov byte [es:0x1b],0x04
         
mov al,[0x7c00+number+0x03]
add al,0x30
mov [es:0x1c],al
mov byte [es:0x1d],0x04
         
mov al,[0x7c00+number+0x02]
add al,0x30
mov [es:0x1e],al
mov byte [es:0x1f],0x04

mov al,[0x7c00+number+0x01]
add al,0x30
mov [es:0x20],al
mov byte [es:0x21],0x04

mov al,[0x7c00+number+0x00]
add al,0x30
mov [es:0x22],al
mov byte [es:0x23],0x04
         
mov byte [es:0x24],'D'
mov byte [es:0x25],0x07

number db 0,0,0,0,0

3.2 列表文件的创建与使用


NASM编译器提供了生成列表文件的功能,可使用如下指令生成列表文件


NASMIDE也集成了生成列表的功能,在编译源文件时,会随二进制文件一起生成列表文件


列表文件的内容如下图所示,

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第18张图片


说明:什么是汇编地址


① 对于任何一个内存段,段地址可以开始于任何16B对齐的地址,偏移地址则总是从0x0000开始递增


为了支持这种内存访问模式,在源程序编译阶段,编译器会将程序整体上作为一个独立的段来处理,并从0开始计算每条指令的地址


③ 该地址在编译阶段计算确定,因此称为汇编地址


汇编地址标识该指令相对于段起始处的距离,以字节计算。当编译后的程序装入物理内存后,又标识了该指令在内存段中的偏移地址

3.3 汇编地址与偏移地址的关系


汇编地址:指令和数据在编译出的二进制文件中,相对于段起始处的偏移量


偏移地址:指令和数据在加载到内存中运行时,相对于段寄存器中的段地址的偏移量


① 程序在运行前,需要先加载到某个内存段中,如果将程序加载到段的起始地址处,则汇编地址等于偏移地址

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第19张图片


② 如果程序并不是加载到内存段的起始地址处,则偏移地址和汇编地址之间有一个固定的偏移量


典型示例就是MBR,他被加载到内存的[0x0000 : 0x7C00]处运行,该位置并不是段的起始地址,因此偏移地址和汇编地址之间的固定偏移量为0x7C00

X86汇编语言从实模式到保护模式04:编写主引导扇区代码_第20张图片


此时,指令和数据在当前物理段中的偏移地址 = 汇编地址 + 0x7C00

3.4 标号


① 在NASM汇编中,每条指令前面都可以拥有一个标号,代表和指示该指令或数据的汇编地址,即标号是符号化的汇编地址


② 标号的用途:通过符号化的方式定位指令和数据,避免人工计算位置


因此标号并不是必须的,只有在我们需要引用某条指令或数据的汇编地址时,才使用标号


③ 标号的构成


标号可以由字母、数字、"_"、"$"、"#"、"@"、"~"、"."、"?"构成,其中可以以字母、"."、"?"、"_"开头


④ 标号后面可以跟一个冒号,但他不是标号的一部分,因此是可选的

3.4 如何显示十进制数

3.4.1 方法概述


假设有标号代表的汇编地址为65535,要在屏幕上显示该数字,是无法直接显示的,需要如下2个步骤,


① 分解出该数的各个数位


② 显示各个数位


其中需要注意的是,分解出的数位是数字,而不是数字字符,需要加上0x30才能将其转换为ASCII码中对应的数字字符

3.4.2 无符号除法指令div


分解数位需要用到除法,通过将数字与10相除,通过余数即可从最低位向最高位分解出各个数字


因此需要先说明X86汇编语言中的除法指令,需要注意的是,div指令只能用于无符号数计算


div指令格式如下,

div 除数所在的寄存器或[内存地址]


div指令中的被除数由除数的形式决定,


① 如果在指令中指定的是8位的寄存器或者8位操作数的内存地址,则被除数必须在AX寄存器中,即类型为16位二进制数除以8位二进制数


相除后,AL = 商,AH = 余数

div bh ; AX / bh
div byte [0x2002] ; AX / [0x2002]


特别注意:此时要确保商和余数能够被AL & AH寄存器容纳,即不能超过8位可表示的范围(0 ~ 255)


② 如果在指令中指定的是16位寄存器或者16位操作数的内存地址,则意味着被除数是32位,其中低16位在AX寄存器中,高16位在DX寄存器中


相除后,AX = 商,DX = 余数

div bx ; DX : AX / bx
div word [0x2002]


③ 如果在指令中指定的是32位寄存器或者32位操作数的内存地址,则意味着被除数是64位的,其中低32位在EAX寄存器中,高32位在EDX寄存器中


相除后,EAX = 商,EDX = 余数

div ebx
div dword [0x2002]


注意:64位除法8086不支持,从80386开始支持


④ 如果在指令中指定的是64位寄存器或者64位操作数的内存地址,则意味着被除数是128位的,其中低64位在RAX寄存器中,高64位在RDX寄存器中


相除后,RAX = 商,RDX = 余数

div rbx
div qword [0x2002]


注意:128位除法只有X86 64位处理器支持


说明1:示例中为什么用32位除法


示例中将number表示的汇编地址存储在AX寄存器,将DX寄存器设置为0,这样DX : AX构成32位被除数

mov ax,number ;取得标号number的偏移地址
mov bx,10
mov dx,0
div bx


number表示的汇编地址为16位,为什么要用32位除法呢?


因为16位的无符号数最大值为65535,与10相除的商为6553,超过了8位寄存器能表示的范围,所以此处使用了32位除法


说明2:只使用余数的低8位


32位除法中,余数保存在DX中,但是示例代码仅使用了低8位

mov [0x7c00+number+0x00],dl ;保存个位上的数字


这是因为此处的余数为0 ~ 9,只用到了DL寄存器,所以只保存了1B

3.4.2 在程序中声明并初始化数据


示例代码中,使用db伪指令预留了5B内存,用于存储分解出的数位

number db 0,0,0,0,0


之所以要预留空间存储位数,是因为分解数位时是从低位到高位,而显示时是从高位到地位,因此需要暂存分解出的位数。而又没有足够的寄存器暂存数位,所以使用内存暂存


说明:db类伪指令


首先需要说明的是,db为汇编器提供的伪指令,没有对应的机器指令,仅仅在汇编阶段由汇编器执行


可用的db类伪指令如下,

db 0x55 ; 定义长度为字节(8位)的数据, declare byte
dw 0x55aa ; 定义长度为字(16位)的数据, declare word
dd 0xabcd1234 ; 定义长度为双字(32位)的数据, declare double word
dq 0x12345678aabbccdd ; 定义长度为四字(64位)的数据
                      ; declare quad word

3.4.3 显示分解出的数位


显示分解出的位数的方法与显示字符相同,示例中代码如下,

;设置数据段的基地址,与代码段相同
mov cx,cs
mov ds,cx

mov al,[0x7c00+number+0x04] ; 去除分解出数位的最高位
add al,0x30 ; 将数位转换为ASCII数字字符
mov [es:0x1a],al ; 将数字字符写入显存
mov byte [es:0x1b],0x04 ; 


说明:关于数位的寻址


① 数位寻址时,使用DS段寄存器,而此处DS段与CS段相同(此处也可以使用CS段超越的方式寻址)


② 寻址时在number标号的基础上加0x7C00是因为MBR被加载到[0x0000 : 0x7C00]处运行

3.4.4 异或指令xor


由于32位除法中,将余数存储在DX寄存器中,破坏了其中的内容,所以在示例代码中使用异或指令将DX寄存器清零

xor dx,dx


异或指令格式如下,

xor r/m, r/m/imm


其中要求两个操作数长度必须相同,结果保存在左操作数中


说明:使用如下2条指令均能实现DX寄存器清零

mov dx, 0 ; 机器码BA 00 00
xor dx, dx ; 机器码31 D2


使用xor指令的机器码更短;且由于操作数均为寄存器,所以速度更快

3.4.5 加法指令add


add指令格式如下,

add r/m, r/m/imm


其中要求两个操作数长度必须相同,结果保存在左操作数中

4. 使程序进入无限循环

4.1 代码段与功能


程序中的如下代码段实现让程序执行流程进入无限循环

infi: jmp near infi ;无限循环

4.2 为什么要进入无限循环


在示例代码中,没有区分数据段和代码段,或者说他们是重叠的。因此编译出的二进制文件中包含了不可执行的数据,而处理器是不停取指执行的


所以我们希望程序在执行完必要操作后,能进入一个无限循环

4.3 跳转指令

4.3.1 段间直接绝对跳转指令


段间直接绝对跳转指令格式如下,

jmp 段地址 : 偏移地址


执行该指令时,处理器将用指令中的"段地址"设置CS寄存器,使用"偏移地址"设置IP寄存器


段间:跳转到另一个段执行(准确说是跳转到"段地址"指定的段执行,也就是会更新DS寄存器的值,不一定会跳转到其他段)


直接绝对:跳转的目标地址,即段地址和偏移地址,直接在指令中给出,不需要计算


说明1:段间直接绝对跳转指令机器码分析


假设有如下跳转指令,


其对应的机器码如下,


可见指令码为ea,随后包含了跳转目标地址


说明2:使用段间直接绝对跳转指令进入无限循环

infi:
    jmp 0x0000:0x7c00 + infi


进入无限循环,就是让jmp指令跳转到自身所在的地址。由于此处需要给出直接绝对地址,所以在标号的基础上加0x7C00


tips:此处跳转并未切换代码段

4.3.2 使用寄存器的绝对间接近跳转


如上文所述,jmp 0x0000:0x7c00 + info指令并未实际切换代码段,所以可以使用段内跳转指令,示例如下,

mov bx, 0x7C00 + infi

infi:
    jmp bx


近跳转:在一个段的内部进行跳转


间接:跳转的目标地址不在指令中直接给出,而是通过寄存器间接给出


绝对:给出的地址是目标的实际物理偏移地址,仍然是绝对地址


说明:绝对间接近跳转机器码


上述指令的机器码如下,

4.3.3 使用相对偏移量的短跳转和近跳转


4.3.3.1 绝对跳转与相对跳转


① 使用绝对跳转指令时,需要知道跳转目的的绝对地址,但是大多数时候我们不知道程序加载的位置(MBR是一个特例,他被固定加载到[0x0000 : 0x7C00]处执行)


② 通过标号来跳转是最方便的,因此X86提供了基于标号的相对跳转指令,编译时,编译器会计算目标地址与跳转指令的偏移量,并将该偏移量编码到机器码中


③ 相对跳转之所以可行,是因为无论在程序编译时,还是程序运行时,跳转指令与目标地址之间的相对偏移量是不变的


④ 相对跳转可以向前也可以向后,所以相对偏移量可正可负


4.3.3.2 相对偏移短跳转

infi:
    jmp short infi


短跳转使用1B编码偏移量,所以可用范围为-128 ~ 127B


对应机器码如下,


可见偏移量为0xFE,也就是-2,即从jmp short指令之后向前跳转2B,也就是jmp short指令的长度


4.3.3.3 相对偏移近跳转

infi:
    jmp near infi


近跳转使用2B编码偏移量,所以可用范围为-32768 ~ 32767B


对应机器码如下,


可见偏移量为0xFFFD,也就是-3,即从jmp near指令之后向前跳转3B,也就是jmp near指令的长度


说明1:可以省略short & near,由编译器自己选择编码方式


说明2:如果指定使用jmp near,那么即使偏移量可以用8位表示,也会编码为16位(如上面的示例)

5. 写入MBR有效标识

5.1 代码段与功能


程序中的如下代码段实现将MBR的第510 & 511字节(从0开始)分别设置为0x55 & 0xaa


这是标识一个主引导扇区有效的标志

times 203 db 0
db 0x55,0xaa

5.2 times伪指令


times伪指令用来重复后面的指令若干次,此处就是预留203个字节,目的是确保在510 & 511字节写入0xaa55

5.3 预留字节数的计算

5.3.1 通过list文件计算


根据list文件,times伪指令的偏移量为0x133,即307D。除去MBR有效标识的2B,MBR共需510B,所以应当填充(510 - 307 = 203B)

5.3.2 通过标号计算


上面通过list文件计算的方式很不灵活,如果之前的程序有增减,此处的预留字节数就需要重新手工计算


我们也可以通过标号计算,实现如下,

start: ; start标号位于整个程序起始处
    ; 程序主体
current:
    times 510 - (current - start) db 0
    db 0x5

你可能感兴趣的:(计算机体系结构,linux,运维,服务器)