x86架构内核引导流程


Bootstrap

  • The PC hardware is designed to make sure BIOS always gets control of the machine first after the computer is powered on. The BIOS will be performing some test and initialization, e.g., checking memory available and activating video card.

  • After this initialization, the BIOS will try to find a bootable device from some appropriate location such as a floppy disk, hard disk, CD-ROM, or the network.

  • Then the BIOS will pass control of the machine to the bootloader who will load the operating system.

    from pintos guide book

BIOS

BIOS(Basic Input/Output System), 是一种为操作系统和用户程序提供初始化运行时环境的程序

QEMU在i386 下,采用SeaBIOS开源项目作为默认引导程序

所以我们采用qmeu0-system-i386中提供的gdbserver实际感受BIOS的行为

环境采用Pintos这个精简的x86内核进行动态调试 环境配置

debug hint

(gdb) target remote :1234
(gdb) set architecture i8086 # 设置架构为16位
(gdb) set disassembly-flavor intel # intel style asm

Getting Real (Debug)

在BIOS中,CPU刚启动时处于16位实模式,硬件电路对段寄存器做了一个非常特殊的初始化

代码段选择子 CS:0xF000

代码段基址 CS Base:被硬件强制设为 0xFFFF0000(高位全 1)

指令指针 IP:0xFFF0

所以在第一步调试就会遇到问题

check-pc

显然这是一个无效反汇编

CS Register在Real Mode中是定位代码段的寄存器,CS存储段基地址,IP则是该代码段的offset ,这样共同定位代码指令

但是gdb软件层面的默认寻址机制并不知道 CPU 内部这些由硬件隐式维护的段基址

所以直接读取$ip的时候,会直接去找0xfff0地址上的指令,而不是0xffff0上的指令

cs

这样可以直观展示出来


BIOS 中断向量表(IVT)的建立

Interrupt Vector Table, IVT是 x86 架构 CPU 在实模式 (Real Mode) 下用于处理中断和系统调用的内存数据结构

本质上是一个包含 256 个内存地址指针的数组,存放于系统物理内存的最底部,0x00000 到 0x003FF

ivt

我们走到0x7c00地址处,此时BIOS已经根据标准初始化硬件结束,查看IVT

在 16 位实模式下,IVT 占据内存物理地址的最前端 0x00000 到 0x003FF(共 256 个中断,每个中断占 4 字节,总计 1024 字节/1KB)

每个中断向量占用 4 字节

格式为:[16位 Offset][16位 Segment]

这些中断处理函数逻辑是在出厂时写入到ROM中的,IVT的构建是运行时构建的

中断的函数接口调用规范有严格的标准,IBM PC 兼容性规范

(gdb) printf "INT 10h -> CS=%04X, IP=%04X\n", *(unsigned short*)0x42, *(unsigned short*)0x40

使用这个macro可以直观观察对应中断向量的映射


POST

IVT的构建不是一成不变的,它是通过BIOS动态分析

在初始化IVT后会扫描 Option ROM

例如: 发现了VGA显卡ROM,调用显卡 ROM 初始化代码(显卡自检等)

随后主版将显卡 ROM 的代码映射到了内存物理地址 0xC0000 处

主板 BIOS 通过一条 远跳转/远调用指令(Far Call 0xC000:0x0003),让 CPU 指针 跳转到显卡 ROM 映射区去执行代码

CPU 开始逐行执行显卡 ROM 里的指令。当执行到 mov [0x0040], 0x560E 和 mov [0x0042], 0xC000 这类写入指令时,CPU 会将显卡 ROM 自己的入口地址(C000:560E)强行写进内存最底部的 IVT(0x00000)中

这就是硬件的动态挂载

所以主版和这些Optional设备是相互协作的

优点:

解耦与即插即用:主板 BIOS 不需要懂任何显卡细节,只需要找到 0xC0000 处的 Option ROM 并调用它,显卡就会自动把自己的驱动挂载进系统。

统一的系统接口:对你的 MBR 引导代码或 DOS 来说,你根本不需要关心当前插的是什么显卡。你只需要触发 int 0x10,CPU 就会自动顺着 IVT 跳转到 C000:560E 去执行该显卡专属的渲染代码。


Optional ROM

x86早期显卡和扩展硬件设计中一个非常经典的概念就是Optional ROM

在 16 位实模式下,整个 1MB(0x00000 ~ 0xFFFFF)内存空间被 IBM 严格划分成了不同用途的区块

在刚刚上面的指令执行时发现,CS寄存器为0xC00,这是因为IBM对内存空间有严格划分

0x00000 ~ 0x9FFFF (0 ~ 640KB):常规 RAM,供 OS、中断表(IVT)、BDA 和 MBR 使用。

0xA0000 ~ 0xBFFFF (640KB ~ 768KB):保留给显存(Video RAM),比如文本模式的 0xB8000。

0xC0000 ~ 0xC7FFF (768KB ~ 800KB):专门预留给 VGA 显卡 BIOS (Video BIOS)

0xF0000 ~ 0xFFFFF: BIOS ROM (主板 BIOS 代码)

VGA就属于扩展设备,所以中断向量表中的指针指向了对应的区域

qemu-system-i386 -device isa-debug-exit -drive format=raw,media=disk,index=0,file=/tmp/Et5FLbl28G.dsk -m 4 -net none -nS

回顾刚刚的qemu启动脚本,发现他也是默认开启模拟的VGA

Tags:
recent-work

ELF格式解析

TL;DR 一个 ELF 文件同时被四个角色读写:编译器产出它、链接器改造它、内核加载器消费它、工具与攻击者审视它。这四个读者看的是同一坨字节,但需求完全不同。ELF 的解法是把文件切成两套正交的索引表: Program Header Table(段视图)——给加载器看,描述“运 …

Read more →

NEMU RISC-V32 指令模式匹配与汇编级优化

TL;DR 本文沿着一条「先问为什么,再看怎么做」的线索,拆开 NEMU 的 RISC-V32 指令解码:从「怎么知道当前是哪条指令」这个问题出发,引出模式字符串,再手推 pattern_decode 如何把它变成 key/mask/shift,最后贴出宿主机 Intel 汇编, …

Read more →