启动劫持流程详解
2026-06-29 · 阅读 1
概述
本文详细解读 KernelPatch 如何在内核启动的最早期阶段接管执行流程。这是整个 patch 机制能够工作的基础。
核心文件:kernel/base/setup1.S(381 行汇编代码)
主要阶段:
- setup_entry:内核入口被劫持后的第一站
- start_prepare:准备 kpimg 启动所需的数据
- map_prepare:劫持
paging_init,准备内存映射 - 恢复并跳转:恢复内核头部,跳回原始入口
启动流程概览
Bootloader
│
├─ 加载 Image.patched 到物理内存 PA
├─ 跳转到 PA + 0x0 (或 PA + 0x4,UEFI)
│
▼
setup_entry (kernel/base/setup1.S:369)
│
├─ 设置临时栈
├─ 调用 setup()
│ │
│ ├─ start_prepare()
│ │ ├─ 复制配置数据到 start_preset
│ │ │ (含 sprintf_offset, symbol_lookup_anchor_offset)
│ │ ├─ 备份 tcp_init_sock 到 map_backup
│ │ └─ 复制 kpimg 到 PA + start_offset
│ │
│ ├─ map_prepare()
│ │ ├─ 备份 paging_init 第一条指令
│ │ ├─ 写入 B _paging_init 指令
│ │ └─ 复制 _map_start 到 tcp_init_sock 位置
│ │
│ ├─ 恢复内核头部(从 header_backup)
│ └─ 返回
│
├─ 恢复原始栈指针
└─ 跳转到 PA (内核原始入口 _head)
_head (内核原始入口)
│
└─ 内核继续正常启动...
setup_entry - 入口点
代码分析
// kernel/base/setup1.S:366-380
.section .entry.text, "ax"
.global setup_entry
.type entry, %function
setup_entry:
// x0 = physical address to the FDT blob (设备树)
// Bootloader 传递的参数保存在 x0..x3
// 1. 保存原始栈指针
mov x9, sp
// 2. 设置临时栈
adrp x11, stack
add x11, x11, :lo12:stack
add x11, x11, STACK_SIZE // stack + 2KB
mov sp, x11
// 3. 保存原始 sp 到新栈
stp x9, x10, [sp, -16]! // 将 x9 (原始 sp) 压栈
// 4. 跳转到 setup
b setup
为什么需要临时栈?
- Bootloader 设置的栈可能很小或不稳定
- Setup 代码会调用函数(memcpy 等),需要可靠的栈
- 使用 kpimg 自带的栈(在
.setup.data段)
栈的定义:
// kernel/base/setup.c:25-29
struct {
uint8_t fp[STACK_SIZE]; // 2KB 栈空间
uint8_t sp[0]; // sp 指向栈顶
} stack __section(.setup.data) __aligned(16);
setup() - 主设置函数
寄存器保存
// kernel/base/setup1.S:299-313
setup:
// 保存所有寄存器(Bootloader 的参数和状态)
stp x29, x30, [sp, -16]!
stp x0, x1, [sp, -16]! // x0 = FDT 地址
stp x2, x3, [sp, -16]!
stp x4, x5, [sp, -16]!
stp x6, x7, [sp, -16]!
stp x8, x18, [sp, -16]!
stp x19, x20, [sp, -16]!
stp x21, x22, [sp, -16]!
stp x23, x24, [sp, -16]!
stp x25, x26, [sp, -16]!
stp x27, x28, [sp, -16]!
为什么保存所有寄存器?
- 内核需要 Bootloader 传递的参数(FDT 地址等)
- Setup 代码会修改寄存器
- 跳回 _head 前必须恢复原始状态
计算 kernel_pa
// kernel/base/setup1.S:314-326
// 1. 获取当前位置
adrp x9, _link_base
add x9, x9, :lo12:_link_base
// x9 = _link_base 的运行时地址
// 2. 读取 setup_offset
adrp x10, setup_preset
add x10, x10, :lo12:setup_preset
// x10 = setup_preset 的运行时地址
ldr x11, [x10, #setup_setup_offset_offset]
// x11 = setup_preset.setup_offset (kpimg 在镜像中的偏移)
// 3. 计算 kernel_pa
sub x12, x9, x11
mov x19, x12
// x19 = kernel_pa = _link_base 运行时地址 - setup_offset
计算原理:
_link_base 链接地址 = 0xD000
setup_offset = 0x1ab3000
Bootloader 将镜像加载到物理地址 PA:
_link_base 实际位置 = PA + 0x1ab3000 + 0xD000
= PA + 0x1ab4000
因此:
kernel_pa = (PA + 0x1ab4000) - (0x1ab3000 + 0xD000)
= PA
这是一个巧妙的自举计算!
start_prepare() - 准备启动数据
复制配置到 start_preset
// kernel/base/setup1.S:40-162
start_prepare:
stp x29, x30, [sp, -16]!
stp x19, x20, [sp, -16]!
stp x21, x22, [sp, -16]!
stp x23, x24, [sp, -16]!
mov x19, x0 // x19 = kernel_pa
// 获取数据结构地址
adrp x9, map_data
add x9, x9, :lo12:map_data
// x9 = map_data 地址
adrp x10, setup_preset
add x10, x10, :lo12:setup_preset
// x10 = setup_preset 地址
adrp x11, start_preset
add x11, x11, :lo12:start_preset
// x11 = start_preset 地址
adrp x12, header
add x12, x12, :lo12:header
// x12 = setup_header 地址
// ============ 复制配置数据 ============
// start_preset.kernel_version = setup_preset.kernel_version
ldr w13, [x10, #setup_kernel_version_offset]
str w13, [x11, #start_kernel_version_offset]
// start_preset.kallsyms_lookup_name_offset = setup_preset.kallsyms_lookup_name_offset
ldr x13, [x10, #setup_kallsyms_lookup_name_offset_offset]
str x13, [x11, #start_kallsyms_lookup_name_offset_offset]
// start_preset.kernel_size = setup_preset.kernel_size
ldr x13, [x10, #setup_kernel_size_offset]
str x13, [x11, #start_kernel_size_offset]
// start_preset.start_offset = setup_preset.start_offset
ldr x13, [x10, #setup_start_offset_offset]
str x13, [x11, #start_start_offset_offset]
mov x21, x13 // x21 = start_offset
// start_preset.extra_size = setup_preset.extra_size
ldr x13, [x10, #setup_extra_size_offset]
str x13, [x11, #start_extra_size_offset]
// start_preset.kernel_pa = kernel_pa
str x19, [x11, #start_kernel_pa_offset]
// start_preset.map_offset = setup_preset.map_offset
ldr x13, [x10, #setup_map_offset_offset]
str x13, [x11, #start_map_offset_offset]
mov x20, x13 // x20 = map_offset
// [0.13.2 新增] start_preset.sprintf_offset = setup_preset.sprintf_offset
ldr x13, [x10, #setup_sprintf_offset_offset]
str x13, [x11, #start_sprintf_offset_offset]
// [0.13.2 新增] start_preset.symbol_lookup_anchor_offset = setup_preset.symbol_lookup_anchor_offset
ldr x13, [x10, #setup_symbol_lookup_anchor_offset_offset]
str x13, [x11, #start_symbol_lookup_anchor_offset_offset]
为什么要复制?
setup_preset在磁盘镜像的 setup 段,只读start_preset在 kpimg 的运行时数据段,可读写- 后续
start()函数需要访问这些数据
0.13.2 新增的偏移:
sprintf_offset:内核sprintf函数的偏移,供 kpimg 运行时调用内核格式化输出symbol_lookup_anchor_offset:符号查找锚点偏移,用于运行时kallsyms符号解析的辅助定位
立即数偏移修正 (0.13.2)
在 start_prepare 后续的 memcpy 调用中,计算 start_preset 内部字段的地址时,部分偏移值(如 start_superkey_offset、start_root_superkey_offset、start_patch_config_offset)超过了 ARM64 ADD 指令的 12 位立即数范围(0~4095)。0.13.2 将这些调用改为两条指令形式:
// kernel/base/setup1.S:108-113
// 旧写法(0.12.0):
// add x0, x11, #start_superkey_offset // 偏移超出 12 位立即数范围,编译报错
// 新写法(0.13.2):
mov x3, #start_superkey_offset // MOV 可以加载 16 位立即数
add x0, x11, x3 // 用寄存器加法代替立即数加法
add x1, x10, #setup_superkey_offset
mov x2, #SUPER_KEY_LEN
bl memcpy8
// start_preset.root_superkey、start_preset.patch_config 同理
mov x3, #start_root_superkey_offset
add x0, x11, x3
...
为什么需要修正?
start_preset结构体在 0.13.2 中增加了新字段(sprintf_offset、symbol_lookup_anchor_offset 等),导致后续字段的偏移量增大- 当偏移超过 4095 字节时,
add x0, x11, #imm会因立即数溢出而无法编码 - 使用
mov+add两条指令绕过此限制
备份 map area (tcp_init_sock)
// kernel/base/setup1.S:130-142
// backup map area
adrp x13, _map_end
add x13, x13, :lo12:_map_end
adrp x14, _map_start
add x14, x14, :lo12:_map_start
sub x2, x13, x14 // x2 = map 代码大小 (~2KB)
// start_preset.map_backup_len = map 大小
str x2, [x11, #start_map_backup_len_offset]
// 复制原始代码到 map_backup
add x0, x11, #start_map_backup_offset // 目标:start_preset.map_backup
add x1, x19, x20 // 源:kernel_pa + map_offset (tcp_init_sock)
bl memcpy8 // 备份!
关键点:
- 源地址:
kernel_pa + map_offset(tcp_init_sock 的物理地址) - 目标地址:
start_preset.map_backup(在 kpimg 数据段) - 大小:
_map_end - _map_start(约 2KB)
此时的备份:
start_preset.map_backup[0:2048] = tcp_init_sock 的原始机器码
复制 kpimg 到 start_offset
// kernel/base/setup1.S:144-163
// 计算 start_img_size
ldr x22, [x10, #setup_kpimg_size_offset] // kpimg 总大小
adrp x23, _kp_start
add x23, x23, :lo12:_kp_start
adrp x24, _link_base
add x24, x24, :lo12:_link_base
sub x23, x23, x24 // _kp_start - _link_base
sub x22, x22, x23 // start_img_size
// map_data.start_img_size = start_img_size
str x22, [x9, #map_start_img_size_offset]
// 复制 kpimg(从 _kp_start 开始)+ extra items
add x0, x19, x21 // 目标:kernel_pa + start_offset
adrp x1, _kp_start
add x1, x1, :lo12:_kp_start // 源:_kp_start
ldr x2, [x10, #setup_extra_size_offset]
add x2, x2, x22 // 大小:start_img_size + extra_size
bl rmemcpy32 // 反向复制(32 位)
rmemcpy32() - 反向复制:
// kernel/base/setup1.S:24-36
rmemcpy32:
subs x2, x2, #0x4
b.lt .r32end
.r32loop:
ldr w3, [x1, x2] ; 从末尾开始读
str w3, [x0, x2] ; 写到目标
subs x2, x2, #0x4
b.ge .r32loop
.r32end:
ret
为什么反向复制?
- 源和目标可能重叠
- 反向复制可以避免覆盖未复制的数据
- 类似
memmove()的处理
复制后的内存:
物理地址 PA + start_offset:
├─ _kp_start 开始的所有代码和数据
├─ Extra Items
└─ start_preset (包含 map_backup)
map_prepare() - 劫持 paging_init
填充 map_data
// kernel/base/setup1.S:174-231
map_prepare:
stp x29, x30, [sp, -16]!
stp x19, x20, [sp, -16]!
mov x19, x0 // x19 = kernel_pa
// 获取地址
adrp x9, map_data
add x9, x9, :lo12:map_data
adrp x10, setup_preset
add x10, x10, :lo12:setup_preset
// map_data.kernel_pa = kernel_pa
str x19, [x9, #map_kernel_pa_offset]
// map_data.map_offset = setup_preset.map_offset
ldr x11, [x10, #setup_map_offset_offset]
str x11, [x9, #map_map_offset_offset]
mov x14, x11 // x14 = map_offset
// map_data.paging_init_relo = setup_preset.paging_init_offset
ldr x11, [x10, #setup_paging_init_offset_offset]
str x11, [x9, #map_paging_init_relo_offset]
mov x15, x11 // x15 = paging_init_offset
// map_data.start_offset = setup_preset.start_offset
ldr x11, [x10, #setup_start_offset_offset]
str x11, [x9, #map_start_offset_offset]
// map_data.start_size = _kp_end - _kp_start
adrp x11, _kp_end
add x11, x11, :lo12:_kp_end
adrp x12, _kp_start
add x12, x12, :lo12:_kp_start
sub x11, x11, x12
str x11, [x9, #map_start_size_offset]
// map_data.extra_size = setup_preset.extra_size
ldr x11, [x10, #setup_extra_size_offset]
str x11, [x9, #map_extra_size_offset]
// map_data.alloc_size = HOOK_ALLOC_SIZE + MEMORY_ROX_SIZE + MEMORY_RW_SIZE
mov x11, #HOOK_ALLOC_SIZE // 1 MB
add x11, x11, #MEMORY_ROX_SIZE // + 4 MB
add x11, x11, #MEMORY_RW_SIZE // + 2 MB
str x11, [x9, #map_alloc_size_offset]
// 复制 map_symbol(memblock 函数地址)
add x0, x9, #map_map_symbol_offset
add x1, x10, #setup_map_symbol_offset
mov x2, #MAP_SYMBOL_SIZE
bl memcpy8
map_data_t 结构:
// kernel/base/setup.h:25-50
typedef struct {
uint32_t paging_init_backup; // paging_init 的原始指令
int64_t map_offset; // tcp_init_sock 偏移
int64_t start_offset; // kpimg 运行时位置
int64_t start_size; // _kp_end - _kp_start
int64_t start_img_size; // 要复制的 kpimg 大小
int64_t extra_size; // Extra Items 大小
int64_t alloc_size; // 额外分配大小 (7 MB)
uint64_t kernel_pa; // 内核物理地址
uint64_t paging_init_relo; // paging_init 虚拟地址
map_symbol_t map_symbol; // memblock 函数地址
// 运行时填充:
int64_t va1_bits;
int64_t page_shift;
uint64_t kimage_voffset;
uint64_t linear_voffset;
} map_data_t;
备份和劫持 paging_init
// kernel/base/setup1.S:233-295
// backup and hook paging_init
// 1. 计算 paging_init 物理地址
add x13, x15, x19 // x13 = paging_init_offset + kernel_pa
// 2. 读取第一条指令
ldr w12, [x13] // w12 = paging_init[0]
// ============ BTI 检测(当前有 BUG)============
mov w3, #0x201F
movk w3, #0xD503, lsl#16 // w3 = 0xD503201F (NOP)
orr w1, w3, #0x100 // w1 = 0xD503211F (BTI C)
mov w2, #0xFFFFFD1F // w2 = 掩码
and w0, w12, w2
cmp w0, w1 // 检查是否是 BTI
b.ne .backup
// 如果是 BTI:
mov w12, w3 // ❌ 备份改为 NOP(错误!)
// 查找并 NOP 化 AUTIASP
add x11, x13, #4
.cmp_auti:
ldr w0, [x11], #4
and w0, w0, w2
cmp w0, w1
b.ne .cmp_auti
stur w3, [x11, #-4] // ❌ NOP 化 AUTIASP(错误!)
// 3. 备份第一条指令
.backup:
str w12, [x9, #map_paging_init_backup_offset]
dsb ish
// ============ 写入跳转指令 ============
// 4. 计算 _paging_init 的位置
adrp x11, _paging_init
add x11, x11, :lo12:_paging_init
adrp x12, _map_start
add x12, x12, :lo12:_map_start
sub x11, x11, x12 // _paging_init - _map_start
add x11, x11, x14 // + map_offset = 替换位置
// 5. 生成 B 指令
// B_REL(src, dst) = 0x14000000 | ((dst - src) >> 2)
sub x15, x11, x15 // dst - src
ubfx w15, w15, #2, #26 // 提取 bit[27:2],得到 imm26
mov w12, #0x14000000 // B 操作码
orr w15, w15, w12 // 组合
str w15, [x13] // ❌ 写入到 paging_init
dsb ish
// ============ 复制 map 代码 ============
// 6. 复制 _map_start 到 tcp_init_sock
adrp x2, _map_end
add x2, x2, :lo12:_map_end
adrp x1, _map_start
add x1, x1, :lo12:_map_start
sub x2, x2, x1 // x2 = map 代码大小
add x0, x19, x14 // x0 = kernel_pa + map_offset
bl memcpy8 // ❌ 覆盖 tcp_init_sock
// 恢复寄存器
ldp x19, x20, [sp], 16
ldp x29, x30, [sp], 16
dsb ish
ret
执行后的物理内存:
PA + 0x0: 内核头部(仍是 B setup_entry)
PA + map_offset: _paging_init 代码(覆盖了 tcp_init_sock)
PA + paging_init: B (PA + map_offset)(劫持了)
PA + start_offset: kpimg 代码和数据
恢复内核头部并跳转
// kernel/base/setup1.S:333-364
// memcpy(kernel_pa, setup_preset.header_backup, HDR_BACKUP_SIZE)
mov x0, x19 // 目标:kernel_pa
add x1, x20, #setup_header_backup_offset // 源:header_backup
mov x2, #HDR_BACKUP_SIZE // 8 字节
bl memcpy8 // 恢复原始头部
// 刷新缓存
dsb ish
ic iallu // 刷新整个指令缓存
dsb ish
isb
mov x16, x19 // x16 = kernel_pa
// ============ 恢复所有寄存器 ============
ldp x27, x28, [sp], 16
ldp x25, x26, [sp], 16
ldp x23, x24, [sp], 16
ldp x21, x22, [sp], 16
ldp x19, x20, [sp], 16
ldp x8, x18, [sp], 16
ldp x6, x7, [sp], 16
ldp x4, x5, [sp], 16
ldp x2, x3, [sp], 16
ldp x0, x1, [sp], 16 // 恢复 FDT 地址
ldp x29, x30, [sp], 16
// 恢复原始栈指针
ldp x9, x10, [sp], 16
mov sp, x9
// ============ 跳转回内核入口 ============
br x16 // 跳到 kernel_pa (_head)
此时的状态:
- 内核头部已恢复(B _stext)
- 所有寄存器恢复到 Bootloader 传递的状态
- 栈指针恢复
- CPU 跳转到内核原始入口
_head
内核完全不知道刚才发生了什么!
辅助函数
memcpy8() - 字节复制
// kernel/base/setup1.S:10-22
.text
.align 2
.type memcpy8, %function
memcpy8:
cmp x2, 0
ble .l8end
.l8loop:
ldrb w3, [x1], 1 ; 逐字节读取
strb w3, [x0], 1 ; 逐字节写入
subs x2, x2, 1
cbnz x2, .l8loop
.l8end:
ret
为什么逐字节?
- 源地址和目标地址可能未对齐
- 可能跨越缓存行边界
- 确保数据完整性
rmemcpy32() - 反向 32 位复制
// kernel/base/setup1.S:24-36
.text
.align 2
.type rmemcpy32, %function
rmemcpy32:
subs x2, x2, #0x4
b.lt .r32end
.r32loop:
ldr w3, [x1, x2] ; 从末尾读取 32 位
str w3, [x0, x2] ; 写到目标
subs x2, x2, #0x4
b.ge .r32loop
.r32end:
ret
为什么 32 位?
- ARM64 指令是 32 位
- 确保指令不会被拆分
- 性能更好(减少内存访问次数)
为什么反向?
- 处理源和目标重叠的情况
- 如果
目标 > 源 && 目标 < 源 + 大小,正向复制会覆盖数据 - 反向复制可以安全处理
时序图
时间轴:T0 ────────────────────────────────────────────> T10
T0: Bootloader 完成
内存:[PA: 内核镜像,包含 kpimg]
T1: 跳转到 PA + 0x0
CPU PC = PA (B setup_entry 指令)
T2: setup_entry 执行
栈:临时栈
寄存器:全部保存
T3: start_prepare() 开始
复制配置:setup_preset → start_preset ✅
(含 sprintf_offset, symbol_lookup_anchor_offset)
备份 tcp_init_sock:map_backup ✅
T4: 复制 kpimg
源:PA + setup_offset + 0x10000 (_kp_start)
目标:PA + start_offset
状态:✅ kpimg 已复制到运行时位置
T5: map_prepare() 开始
填充 map_data ✅
T6: 备份 paging_init[0]
map_data.paging_init_backup = *(PA + paging_init_offset) ✅
T7: 劫持 paging_init
*(PA + paging_init_offset) = B (PA + map_offset) ❌
T8: 覆盖 tcp_init_sock
复制 _map_start → PA + map_offset ❌
T9: 恢复头部并跳转
*(PA + 0) = header_backup[0] ✅
br PA
T10: 内核继续启动
CPU 执行 _head
寄存器:完全恢复
栈:原始栈
数据流图
┌────────────────────────────────────────────────────────┐
│ 物理内存(PA) │
├────────────────────────────────────────────────────────┤
│ │
│ PA + 0x0 ┌─ Header (B setup_entry) │
│ │ ↓ 在 T9 恢复为 B _stext │
│ PA + 0x40 ├─ Kernel Code │
│ │ │
│ PA + map_offset ├─ tcp_init_sock (原始代码) │
│ (0xd73550) │ ↓ 在 T3 备份到 map_backup │
│ │ ↓ 在 T8 覆盖为 _paging_init │
│ │ ↓ 在后续 restore_map() 恢复 │
│ │ │
│ PA + paging_init ├─ paging_init (原始代码) │
│ (0x1abe5d0) │ ↓ 在 T6 备份第一条指令 │
│ │ ↓ 在 T7 改为 B (PA+map_offset) │
│ │ ↓ 在 _paging_init 中恢复 │
│ │ │
│ PA + setup_offset ├─ setup_preset (只读配置) │
│ (0x1ab3000) ├─ setup_entry (一次性代码) │
│ ├─ _map_start (会被复制) │
│ │ │
│ PA + start_offset ├─ kpimg (复制后的运行时位置) │
│ (0x1ca6000) │ ├─ _kp_start │
│ │ │ └─ start_preset ✅ │
│ │ │ └─ map_backup ✅ │
│ │ ├─ _kp_end │
│ │ └─ Extra Items │
│ │ │
└────────────────────────────────────────────────────────┘
关键设计决策
决策 1:为什么不在 setup 阶段恢复 tcp_init_sock?
原因:
1. setup 阶段没有虚拟内存,只能访问物理地址
2. 无法使用页表修改权限
3. 代码段可能是只读的(物理内存保护)
解决:
延迟到 start() 阶段恢复
此时虚拟内存已建立,可以修改页表
决策 2:为什么需要两次劫持?
第一次劫持:内核入口 → setup_entry
目的:在物理内存阶段执行准备工作
限制:无虚拟内存,无法分配内存
第二次劫持:paging_init → _paging_init
目的:在页表初始化时执行内存映射
优势:可以使用 memblock 分配内存
可以创建页表映射
决策 3:为什么临时借用 tcp_init_sock?
需求:
_paging_init 代码必须在内核虚拟地址空间
但 kpimg 还未映射到虚拟地址
解决:
找一个启动早期不会被调用的函数
临时覆盖,完成后恢复
选择 tcp_init_sock:
✅ 网络栈在 kernel_init 后才初始化
✅ 大小足够(~2KB)
✅ 位置稳定(所有内核版本都有)
潜在问题分析
问题 1:BTI 处理 bug
当前代码的问题:
ldr w12, [x13] // w12 = BTI C (0xD503211F)
// ...
mov w12, w3 // w12 = NOP (0xD503201F) ❌
str w12, [x9, #map_paging_init_backup_offset]
后果:
- 备份的是 NOP 而不是 BTI C
- 恢复时
paging_init缺少 BTI 指令 - 可能导致 BTI 验证失败
修复(见文档 15):
ldr w12, [x13] // 读取原始指令
str w12, [x9, #map_paging_init_backup_offset] // ✅ 直接备份
// 删除所有 BTI 检测和修改代码
问题 2:缓存一致性
// 必须的缓存刷新
dsb ish // 数据同步屏障
ic iallu // 刷新整个指令缓存
dsb ish
isb // 指令同步屏障
如果缺少:
- CPU 可能执行旧的指令(缓存中的)
- 跳转到错误的地址
- 数据不一致
问题 3:栈对齐
// ARM64 要求栈 16 字节对齐
adrp x11, stack
add x11, x11, :lo12:stack
add x11, x11, STACK_SIZE
mov sp, x11 // sp 指向 stack + 2KB
// stack 定义时已对齐
struct { ... } stack __section(.setup.data) __aligned(16);
总结
setup1.S 的核心任务:
- ✅ 计算 kernel_pa:通过自举算法获取物理地址
- ✅ 准备数据:填充 start_preset 和 map_data
- ✅ 备份关键区域:tcp_init_sock、paging_init[0]
- ✅ 复制 kpimg:到运行时位置(start_offset)
- ✅ 劫持 paging_init:写入跳转指令
- ✅ 恢复状态:内核头部、寄存器、栈
- ✅ 无缝衔接:跳回 _head,内核继续启动
代码的精妙之处:
- 仅使用汇编和物理地址
- 无任何外部依赖
- 完全自举
- 对内核透明
下一篇:07-paging_init阶段.md - 详解内存映射和虚拟地址初始化
文档版本:2.0
最后更新:2026-06-26
评论
- 还没有评论,来说点什么吧。