当前位置: 首页 > news >正文

从零自制x86引导程序:实践笔记

从零自制x86引导程序

一、前言

引导程序(Bootloader)是电脑启动时BIOS交接控制权的第一段代码,是衔接硬件和操作系统的关键。本文基于x86 16位实模式,从零实现“清屏+移动光标+打印文字”的极简引导程序,全程包含核心概念解析、可直接运行的代码、完整实践步骤,并解答新手常见疑问,适合零基础入门汇编和底层启动原理。

二、核心基础概念(新手必懂)

1. 引导程序的本质

  • 电脑开机后,CPU先执行BIOS完成硬件自检(POST),随后BIOS会扫描“可引导介质”(U盘/硬盘/软盘);
  • 可引导介质的核心特征:第一个扇区(512字节)末尾必须有0xAA55签名,BIOS会把这512字节加载到内存0x7C00地址,并将控制权交给这段代码——这就是引导程序;
  • 我们的目标:编写符合规则的512字节汇编代码,验证引导程序的运行流程。

2. 16位实模式与寻址规则

  • BIOS交接控制权时,CPU处于16位实模式:仅能访问1MB内存,支持直接调用BIOS中断(无需手动操作硬件);
  • 实模式地址计算:物理地址 = 段寄存器值 × 16 + 偏移量(例如DS=0x7C0 + 偏移0x00,对应物理地址0x7C00);
  • 段寄存器的“坑”:不能直接赋值立即数(如mov ds, 0x7C0非法),必须通过通用寄存器(如AX)中转。

3. BIOS中断:硬件操作的“快捷方式”

无需直接操作显卡/键盘等硬件,只需给寄存器传参数再触发中断,即可实现对应功能。核心使用int 0x10(视频服务中断),常用子功能如下:

AH值 子功能 关键寄存器参数
0x02 设置光标位置 BH=视频页号,DX=行号(DH)+列号(DL)
0x07 清屏/向下滚屏 AL=滚动行数(0=清屏),BH=颜色属性,CX/DX=清屏范围
0x0E TTY模式打印字符 AL=字符ASCII码,BH=视频页号

4. 颜色属性编码(BH寄存器)

仅在AH=0x07(清屏)时,BH表示字符颜色属性,编码规则:

  • 高4位=背景色,低4位=前景色;
  • 常用值:黑色(0x0)、浅灰色(0x7)→ BH=0x07即“黑底浅灰字”。

5. 引导扇区的硬性规则

  • 代码总长度必须为512字节;
  • 最后2字节必须是0xAA55(BIOS识别可引导的核心标志)。

三、开发环境准备(Linux系统)

只需安装3个工具,执行以下命令(Ubuntu/Debian系,Arch用pacman,CentOS用yum):

sudo apt install nasm bochs bochs-x  # 分别为汇编器、虚拟机、调试组件
  • NASM:将汇编代码编译为二进制文件;
  • Bochs:x86虚拟机,测试引导程序(无需物理硬盘,避免误操作真实数据);
  • dd(系统自带):制作虚拟磁盘镜像,模拟“写入硬盘”操作。

四、完整引导程序代码(可直接复制)

保存为boot.asm,关键部分已标注详细注释,兼顾“易读性”和“最佳实践”(避免硬编码地址):

bits 16                  ; 声明为16位实模式代码
org 0x7C00               ; 告诉汇编器:程序加载到内存0x7C00,自动计算偏移; 符号常量(避免硬编码,改一处即可全局生效)
BOOTSECTOR_SIZE equ 512  ; 引导扇区固定512字节
STACK_BASE     equ 0x7C00 + BOOTSECTOR_SIZE  ; 栈起始于引导扇区结束后(0x7E00)
STACK_SIZE     equ 0x2000  ; 8KB栈空间(足够使用); 1. 初始化段寄存器与栈(避免寻址错误)
xor ax, ax               ; 快速置0(比mov ax,0更高效)
mov ds, ax               ; DS=0,配合org 0x7C00实现正确寻址
mov es, ax               ; 附加段ES=0,保持统一; 设置栈段:SS=栈物理地址÷16(右移4位),SP=栈大小
mov ax, STACK_BASE >> 4
mov ss, ax
mov sp, STACK_SIZE; 2. 调用核心功能子程序
call clearscreen         ; 清屏
push 0x0000              ; 压入光标位置参数(行0,列0)
call movecursor          ; 移动光标到左上角
add sp, 2                ; 清理栈参数(16位=2字节)
push msg                 ; 压入字符串地址参数
call print               ; 打印文字
add sp, 2                ; 清理栈参数cli                      ; 关闭中断(防止CPU被唤醒)
hlt                      ; 暂停CPU(程序结束); 3. 子程序1:清屏(利用BIOS中断0x10)
clearscreen:push bp               ; 保存基址指针(遵循调用规范)mov bp, sppusha                 ; 保存所有通用寄存器(避免破坏调用者数据)mov ah, 0x07          ; AH=0x07:清屏/向下滚屏mov al, 0x00          ; AL=0:清空整个屏幕mov bh, 0x07          ; 颜色属性:黑底浅灰字mov cx, 0x00          ; 清屏区域左上角(行0,列0)mov dh, 0x18          ; 清屏区域右下角行=24(80x25文本模式最后一行)mov dl, 0x4F          ; 清屏区域右下角列=79(80x25文本模式最后一列)int 0x10              ; 触发视频中断执行清屏popa                  ; 恢复所有寄存器mov sp, bppop bpret                   ; 子程序返回; 4. 子程序2:移动光标(支持栈传参)
movecursor:push bpmov bp, sppushamov dx, [bp+4]        ; 从栈取参数(BP+4:BP占2字节,返回地址占2字节)mov ah, 0x02          ; AH=0x02:设置光标位置mov bh, 0x00          ; BH=0:操作第0个视频页(默认即可)int 0x10              ; 触发中断移动光标popamov sp, bppop bpret; 5. 子程序3:打印字符串(接收字符串地址参数)
print:push bpmov bp, sppushamov si, [bp+4]        ; 从栈取字符串地址mov bh, 0x00          ; 视频页0mov bl, 0x00          ; 文本模式下颜色无效mov ah, 0x0E          ; AH=0x0E:TTY模式打印字符(自动后移光标)
.char:mov al, [si]          ; 取当前字符add si, 1             ; 指针后移or al, 0              ; 检查是否为字符串结束符(0)je .return            ; 是结束符则返回int 0x10              ; 打印当前字符jmp .char             ; 循环处理下一个字符
.return:popamov sp, bppop bpret; 6. 定义打印字符串(以0结尾标记结束)
msg:    db "Hello, x86 Bootloader!", 0; 7. 引导扇区收尾(必须!)
times 510 - ($ - $$) db 0  ; 填充0到510字节($=当前地址,$$=段起始地址)
dw 0xAA55                  ; 引导签名(BIOS识别关键)

代码核心解析

  • org 0x7C00:让汇编器自动计算物理地址,避免手动硬编码0x7C0
  • 栈设置:栈起始于0x7E00(引导扇区结束后),避免覆盖引导程序代码;
  • 栈传参:子程序通过[bp+4]读取栈中参数,调用后用add sp, 2清理栈,符合16位汇编调用规范;
  • times 510 - ($ - $$) db 0:用0填充剩余空间,确保前510字节占满,为0xAA55留位置。

五、编译与虚拟镜像制作(无物理硬盘也能测)

核心思路:用“虚拟磁盘镜像”模拟物理硬盘,步骤如下:

步骤1:编译汇编代码为二进制文件

nasm -f bin boot.asm -o boot.com
  • 验证:执行ls -l boot.com,文件大小必须为512字节(否则编译失败)。

步骤2:创建1.44MB虚拟软盘镜像

dd if=/dev/zero of=floppy.img bs=1024 count=1440
  • 说明:bs=1024 count=1440 总大小=1.44MB,符合标准软盘规格。

步骤3:将引导程序写入镜像第一个扇区

sudo dd if=boot.com of=floppy.img bs=512 count=1 conv=notrunc
  • 关键参数conv=notrunc:仅覆盖前512字节(第一个扇区),不截断整个镜像文件;
  • 验证:执行hexdump -C -n 512 floppy.img,输出末尾需显示aa 55(签名正确)。

六、Bochs虚拟机运行测试

步骤1:创建Bochs配置文件

新建bochsrc.txt,复制以下内容:

megs: 32                  ; 分配32MB内存(实模式仅用1MB,多分配不影响)
romimage: file=/usr/share/bochs/BIOS-bochs-latest, address=0xfffe0000
vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest
floppya: 1_44=floppy.img, status=inserted  ; 加载虚拟软盘镜像
boot: a                   ; 从软驱A启动
log: bochsout.txt         ; 日志文件(报错时查看)
mouse: enabled=0          ; 禁用鼠标(无需)
display_library: x, options="gui_debug"  ; 开启GUI调试界面

步骤2:启动Bochs并运行

bochs -f bochsrc.txt
  • 启动后弹出Bochs窗口,按c(continue)继续;
  • 成功效果:屏幕清空,左上角显示Hello, x86 Bootloader!

七、新手常见疑问解答

1. times 510 - ($ - $$) db 0 是什么意思?

  • times:NASM伪指令,重复执行后续操作;
  • $:当前汇编地址,$$:段起始地址,$ - $$:已编写代码的总字节数;
  • 作用:填充0至510字节,确保最后2字节为0xAA55,总长度512字节。

2. pusha 后,mov dx, [bp+4] 还能取到参数吗?

能!pushamov bp, sp(固定栈帧)后执行,压入的寄存器数据存在BP指向位置的更低地址(栈深处),而参数存在BP指向位置的更高地址bp+4),两者不重叠。

3. BH到底是页号还是颜色?

取决于int 0x10的子功能(AH值):

  • AH=0x02(设置光标):BH=视频页号(默认0);
  • AH=0x07(清屏):BH=颜色属性(如0x07=黑底浅灰字);
  • AH=0x0E(打印):BH=视频页号(BL=颜色,文本模式下无效)。

4. 没有物理硬盘,如何模拟“写入硬盘”?

除了本文的“虚拟软盘镜像”,还可通过VirtualBox/VMware添加虚拟硬盘:

  1. 创建Linux虚拟机,添加1GB虚拟硬盘;
  2. 虚拟机内执行lsblk识别虚拟硬盘(如/dev/sdb);
  3. 写入引导程序:sudo dd if=boot.com of=/dev/sdb bs=512 count=1
  4. 虚拟机设置从虚拟硬盘启动,即可运行。

八、常见问题排查(避坑)

  1. 编译后boot.com非512字节:检查是否缺少times指令或0xAA55签名,或代码超长(删除多余注释);
  2. Bochs黑屏/无输出:验证镜像签名(hexdumpaa 55),检查bochsrc.txtfloppy.img路径是否正确;
  3. 写入镜像权限不足:给dd命令加sudo
  4. 打印乱码:字符串未以0结尾,或AH=0x0E参数配置错误(如BH设为颜色)。

参考文章

1、Joe Bergeron. Writing a Tiny x86 Bootloader[EB/OL]. https://www.joe-bergeron.com/posts/Writing a Tiny x86 Bootloader/, 2016-12-27.

http://www.jsqmd.com/news/390354/

相关文章:

  • CSS Display(显示)详解
  • Bootstrap 输入框组
  • 《C 变量:深入理解其类型、作用域和内存管理》
  • HTML 媒体(Media)详解
  • 【每日一题】LeetCode 401. 二进制手表
  • 中文接触角分析软件|支持五点拟合、量角法、插板法等多模式精准测量
  • HTML 速查列表
  • WebForms SortedList 深度解析
  • Go 语言范围(Range)
  • 完整教程:从「文件URL」到「模型可理解内容」:一套完整的文件上传与解析处理流程详解(含PDF/Excel/图片)
  • [算法进阶]dp+树状数组题目
  • [嵌入式系统-235]:传感器:小电流类检测的基本原理:是通过跨阻放大器(TIA)将微弱电流“无损”地转化为电压
  • AI元人文:在白河界面上架设金兰桥——基于空性界面自感理论的深化与整合
  • WebForms SortedList 深入解析
  • 基于Java Web的驾校考试管理系统的设计与实现
  • 《放置(Droppable)》:游戏体验与策略分析
  • ionic 对话框:深度解析与最佳实践
  • 大数据领域数据产品的一致性算法研究
  • 并查集 - ## 并查集
  • 数据产品监控:实时告警与性能追踪系统
  • 为什么使用 Web Services?
  • AI应用架构师的企业级AI平台架构设计的实践探索
  • Bootstrap5 网格系统
  • 大数据清洗面试经验:字节跳动数据开发岗,数据清洗考点总结
  • 基于uni-app+Nodejs+vue3的校园失物招领微信小程序
  • AI应用架构师带你深挖AI驱动质量管理与业务融合点
  • 第七章 LoRA训练稳赢指南:数据集工程“三件套“全解析
  • 别再记混了!阻止事件冒泡≠防止事件冒泡(附趣味解析)
  • 构建未来教育新生态:智慧校园信息系统方案关键模块建设浅析
  • 构建未来教育新生态:智慧校园信息平台方案关键模块建设浅析