代码炼金工坊

【x86汇编学习】编写主引导扇区

说明

x86系列文章为书籍《x86汇编语言:从实模式到保护模式》的学习笔记,内容属于笔者学习总结性质。

笔者将之发表到博客上,一方面是作为笔记存档,另一方面希望对同在阅读本书的小伙伴起到理解帮助作用。

计算机的启动过程

前置概念

注:需要特别注意,扇区是从1开始编号的。

8086启动过程

执行复位(RESET),使代码段寄存器(CS)内容为0xFFFF,其他寄存器内容为0x0000

地址线分配内存空间给设备,其中顶部64KB分配给ROM,范围0xF0000~0xFFFFF;较低端的640KB分配给DRAM,范围0x00000~0x9FFFF;中间的一部分分配给其他设备。

复位时,CS(0xFFFF)和IP(0x0000)形成物理地址0xFFFF0,就是计算机取的第一条指令的物理地址,正好是ROM-BIOS的范围内。

ROM-BIOS执行完本身的指令后,将硬盘主引导扇区内容加载到0x0000:0x7C00,执行跳转指令到达该位置执行。

主引导扇区继续引导计算机从硬盘的其他部分读取更多的内容加以执行。

编写主引导扇区代码

根据上面所述的启动过程,我们只需要在硬盘的主引导扇区注入我们写的汇编代码编译成的二进制机器码,就可以在计算机启动后引导计算机执行自定义的指令。

使用工具说明

由于笔者使用的操作系统为OSX,与原书的Windows存在差别,部分工具使用替代品,现进行说明:

# 编译源文件到二进制
nasm -f bin /path/to/xxx.asm -o /path/to/xxx.bin
# 将二进制文件写入虚拟硬盘 
./main vhd /path/to/xxx.vhd -n=0 -w=/path/to/xxx.bin

一些指令、概念和要求说明

两张表

文本模式

地址线将0xB8000~0xBFFFF分配给显卡设备的内存(即显存),用于文本模式。

在该模式下,屏幕上可以显示25行,每行80个字符,共2000个字符。

使用逻辑地址访问显存

由于显存的起始物理地址是0xB8000,所以它的段地址可以看成0xB800,它的起始逻辑地址就是0xB800:0x0000

前文提过,数据寄存器分为DS和ES,我们将显存所在的内存段地址(0xB800)存到ES中。这样访问显存可以通过段超越前缀"es:",例如[es:0x00],的方式进行。

在屏幕上显示字符

文本模式的屏幕右下角偏移地址是多少?

由于每个字符对应着显存中的两个连续字节,也就是1个字符占用了2byte,而文本模式一共有80x25=2000个字符,所以占用了4000byte。屏幕右下角即最后一个字符,它的偏移地址为初始偏移地址加3998byte长度后的结果,也就是3998D,转换为十六进制就是0xF9EH

案例:在屏幕第一个位置显示一个黑底白字的H字符

; Intel处理器不允许将一个立即数传送到段寄存器
; 必须先将立即数传到通用寄存器
mov ax, 0xb800
; 然后从通用寄存器传到段寄存器
mov es, ax
; 指定数据宽度为字节,把字面值H的8位ASCII码100 1000B
; 即0x48H传送到es寄存器的段内偏移地址0x7C0上
; 这里使用了段超越前缀指定使用的寄存器
mmov byte [0x7C0], 'H'
; 然后在相邻的下个字节处传送颜色信息指令
; 继续以此类推
mov byte [0x7C1], 0x04
mov byte [0x7C2], 'E'
mov byte [0x7C4], 'L'
mov byte [0x7C6], 'L'
mov byte [0x7C8], 'O'
mov byte [0x7CA], ' '
mov byte [0x7CC], ' '
mov byte [0x7CE], 'Y'
mov byte [0x7CF], 0x03
mov byte [0x7D0], 'U'
mov byte [0x7D2], 'C'
mov byte [0x7D4], 'H'
mov byte [0x7D6], 'A'
mov byte [0x7D8], 'N'
mov byte [0x7DA], 'N'
mov byte [0x7DC], 'S'
jmp $%