找回密码
 立即注册
首页 业界区 安全 从零自制x86引导程序:实践笔记

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

任俊慧 3 天前
从零自制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=清屏范围0x0ETTY模式打印字符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):
  1. sudo apt install nasm bochs bochs-x  # 分别为汇编器、虚拟机、调试组件
复制代码

  • NASM:将汇编代码编译为二进制文件;
  • Bochs:x86虚拟机,测试引导程序(无需物理硬盘,避免误操作真实数据);
  • dd(系统自带):制作虚拟磁盘镜像,模拟“写入硬盘”操作。
四、完整引导程序代码(可直接复制)

保存为boot.asm,关键部分已标注详细注释,兼顾“易读性”和“最佳实践”(避免硬编码地址):
  1. bits 16                  ; 声明为16位实模式代码
  2. org 0x7C00               ; 告诉汇编器:程序加载到内存0x7C00,自动计算偏移
  3. ; 符号常量(避免硬编码,改一处即可全局生效)
  4. BOOTSECTOR_SIZE equ 512  ; 引导扇区固定512字节
  5. STACK_BASE     equ 0x7C00 + BOOTSECTOR_SIZE  ; 栈起始于引导扇区结束后(0x7E00)
  6. STACK_SIZE     equ 0x2000  ; 8KB栈空间(足够使用)
  7. ; 1. 初始化段寄存器与栈(避免寻址错误)
  8. xor ax, ax               ; 快速置0(比mov ax,0更高效)
  9. mov ds, ax               ; DS=0,配合org 0x7C00实现正确寻址
  10. mov es, ax               ; 附加段ES=0,保持统一
  11. ; 设置栈段:SS=栈物理地址÷16(右移4位),SP=栈大小
  12. mov ax, STACK_BASE >> 4
  13. mov ss, ax
  14. mov sp, STACK_SIZE
  15. ; 2. 调用核心功能子程序
  16. call clearscreen         ; 清屏
  17. push 0x0000              ; 压入光标位置参数(行0,列0)
  18. call movecursor          ; 移动光标到左上角
  19. add sp, 2                ; 清理栈参数(16位=2字节)
  20. push msg                 ; 压入字符串地址参数
  21. call print               ; 打印文字
  22. add sp, 2                ; 清理栈参数
  23. cli                      ; 关闭中断(防止CPU被唤醒)
  24. hlt                      ; 暂停CPU(程序结束)
  25. ; 3. 子程序1:清屏(利用BIOS中断0x10)
  26. clearscreen:
  27.     push bp               ; 保存基址指针(遵循调用规范)
  28.     mov bp, sp
  29.     pusha                 ; 保存所有通用寄存器(避免破坏调用者数据)
  30.    
  31.     mov ah, 0x07          ; AH=0x07:清屏/向下滚屏
  32.     mov al, 0x00          ; AL=0:清空整个屏幕
  33.     mov bh, 0x07          ; 颜色属性:黑底浅灰字
  34.     mov cx, 0x00          ; 清屏区域左上角(行0,列0)
  35.     mov dh, 0x18          ; 清屏区域右下角行=24(80x25文本模式最后一行)
  36.     mov dl, 0x4F          ; 清屏区域右下角列=79(80x25文本模式最后一列)
  37.     int 0x10              ; 触发视频中断执行清屏
  38.    
  39.     popa                  ; 恢复所有寄存器
  40.     mov sp, bp
  41.     pop bp
  42.     ret                   ; 子程序返回
  43. ; 4. 子程序2:移动光标(支持栈传参)
  44. movecursor:
  45.     push bp
  46.     mov bp, sp
  47.     pusha
  48.    
  49.     mov dx, [bp+4]        ; 从栈取参数(BP+4:BP占2字节,返回地址占2字节)
  50.     mov ah, 0x02          ; AH=0x02:设置光标位置
  51.     mov bh, 0x00          ; BH=0:操作第0个视频页(默认即可)
  52.     int 0x10              ; 触发中断移动光标
  53.    
  54.     popa
  55.     mov sp, bp
  56.     pop bp
  57.     ret
  58. ; 5. 子程序3:打印字符串(接收字符串地址参数)
  59. print:
  60.     push bp
  61.     mov bp, sp
  62.     pusha
  63.    
  64.     mov si, [bp+4]        ; 从栈取字符串地址
  65.     mov bh, 0x00          ; 视频页0
  66.     mov bl, 0x00          ; 文本模式下颜色无效
  67.     mov ah, 0x0E          ; AH=0x0E:TTY模式打印字符(自动后移光标)
  68. .char:
  69.     mov al, [si]          ; 取当前字符
  70.     add si, 1             ; 指针后移
  71.     or al, 0              ; 检查是否为字符串结束符(0)
  72.     je .return            ; 是结束符则返回
  73.     int 0x10              ; 打印当前字符
  74.     jmp .char             ; 循环处理下一个字符
  75. .return:
  76.     popa
  77.     mov sp, bp
  78.     pop bp
  79.     ret
  80. ; 6. 定义打印字符串(以0结尾标记结束)
  81. msg:    db "Hello, x86 Bootloader!", 0
  82. ; 7. 引导扇区收尾(必须!)
  83. times 510 - ($ - $$) db 0  ; 填充0到510字节($=当前地址,$$=段起始地址)
  84. dw 0xAA55                  ; 引导签名(BIOS识别关键)
复制代码
代码核心解析


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

核心思路:用“虚拟磁盘镜像”模拟物理硬盘,步骤如下:
步骤1:编译汇编代码为二进制文件
  1. nasm -f bin boot.asm -o boot.com
复制代码

  • 验证:执行ls -l boot.com,文件大小必须为512字节(否则编译失败)。
步骤2:创建1.44MB虚拟软盘镜像
  1. dd if=/dev/zero of=floppy.img bs=1024 count=1440
复制代码

  • 说明:bs=1024 count=1440 总大小=1.44MB,符合标准软盘规格。
步骤3:将引导程序写入镜像第一个扇区
  1. 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,复制以下内容:
  1. megs: 32                  ; 分配32MB内存(实模式仅用1MB,多分配不影响)
  2. romimage: file=/usr/share/bochs/BIOS-bochs-latest, address=0xfffe0000
  3. vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest
  4. floppya: 1_44=floppy.img, status=inserted  ; 加载虚拟软盘镜像
  5. boot: a                   ; 从软驱A启动
  6. log: bochsout.txt         ; 日志文件(报错时查看)
  7. mouse: enabled=0          ; 禁用鼠标(无需)
  8. display_library: x, options="gui_debug"  ; 开启GUI调试界面
复制代码
步骤2:启动Bochs并运行
  1. 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] 还能取到参数吗?

能!pusha在mov 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添加虚拟硬盘:

  • 创建Linux虚拟机,添加1GB虚拟硬盘;
  • 虚拟机内执行lsblk识别虚拟硬盘(如/dev/sdb);
  • 写入引导程序:sudo dd if=boot.com of=/dev/sdb bs=512 count=1;
  • 虚拟机设置从虚拟硬盘启动,即可运行。
八、常见问题排查(避坑)


  • 编译后boot.com非512字节:检查是否缺少times指令或0xAA55签名,或代码超长(删除多余注释);
  • Bochs黑屏/无输出:验证镜像签名(hexdump看aa 55),检查bochsrc.txt中floppy.img路径是否正确;
  • 写入镜像权限不足:给dd命令加sudo;
  • 打印乱码:字符串未以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.

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册