setup_arch 执行流程详解
2026-06-29 · 阅读 0
问题
当 paging_init 的首指令被替换为 B _paging_init 后,setup_arch 如何确保 paging_init 正常执行并返回?
核心机制
关键在于理解 ARM64 的函数调用和跳转指令的区别,以及 _paging_init 如何巧妙地利用这个机制。
执行流程详解
阶段 1:setup_arch 调用 paging_init
// 内核代码(简化)
void setup_arch(void)
{
// ... 其他初始化 ...
paging_init(); // ← 这里调用 paging_init
// ... 后续代码 ...
}
汇编层面:
setup_arch:
// ... 其他代码 ...
bl paging_init ; BL = Branch with Link,函数调用
; LR 寄存器被设置为下一条指令的地址
; 此时 LR = setup_arch 中 "bl paging_init" 的下一条指令
// ... 后续代码 ...
关键点:
BL指令(Branch with Link)是函数调用BL会将返回地址保存到LR(Link Register)寄存器LR=setup_arch中bl paging_init的下一条指令地址
阶段 2:执行 paging_init(已被劫持)
paging_init 的当前状态(已被 map_prepare 修改):
paging_init:
0x000: 14xxxxxx B _paging_init ; ❌ 被 map_prepare 改写
0x004: A9BF7BFD STP X29, X30, [SP, #-16]! ; 原始代码(未执行)
...
执行流程:
; setup_arch 执行:
bl paging_init ; 跳转到 paging_init,LR = setup_arch 的下一条指令
; 进入 paging_init:
paging_init:
B _paging_init ; 跳转到 _paging_init
; ⚠️ 注意:B 指令是跳转,不是函数调用
; B 指令不会修改 LR 寄存器
; 此时 LR 仍然是 setup_arch 中 "bl paging_init" 的下一条指令
关键点:
B指令(Branch)是跳转,不是函数调用B指令不会修改 LR 寄存器- 此时
LR仍然保存着setup_arch的返回地址
阶段 3:_paging_init 执行
// kernel/base/map.c:176-258
void __noinline _paging_init()
{
map_data_t *data = mem_proc();
// ... 分配内存、创建映射等操作 ...
// 恢复 paging_init 原始指令
uint64_t paging_init_va = data->paging_init_relo;
*(uint32_t *)(paging_init_va) = data->paging_init_backup;
flush_icache_all();
// 调用原始 paging_init 函数
((paging_init_f)(paging_init_va))();
// ... 复制代码、调用 start() 等操作 ...
}
汇编层面(简化):
_paging_init:
; 函数序言(编译器生成)
stp x29, x30, [sp, #-16]! ; 保存 FP 和 LR 到栈
mov x29, sp ; 设置新的 FP
; ... _paging_init 的代码 ...
; 恢复 paging_init[0]
ldr x0, [x9, #map_paging_init_relo_offset]
ldr w1, [x9, #map_paging_init_backup_offset]
str w1, [x0]
; 刷新 I-cache
dsb ish
ic ialluis
dsb ish
isb
; 调用原始 paging_init(通过函数指针)
ldr x0, [x9, #map_paging_init_relo_offset]
blr x0 ; 函数调用,LR 被修改
; 此时 LR = _paging_init 中 "blr x0" 的下一条指令
; ... _paging_init 的后续代码 ...
; 调用 start()
ldr x0, [x9, #start_va_offset]
blr x0 ; 跳转到 start(),不再返回
; 函数尾声(如果执行到这里)
ldp x29, x30, [sp], #16 ; 恢复 FP 和 LR
ret ; 返回到 LR 指向的地址
关键点:
_paging_init是一个正常的 C 函数,有函数序言和尾声- 函数序言会保存 LR 到栈:
stp x29, x30, [sp, #-16]! - 调用原始
paging_init时,使用BLR(函数调用),会修改 LR - 原始
paging_init返回后,LR 恢复为_paging_init中blr x0的下一条指令 _paging_init最后调用start(),不再返回
阶段 4:原始 paging_init 执行
// 原始 paging_init 函数(内核代码)
void paging_init(void)
{
// 此时 paging_init[0] 已经恢复为原始指令
// 函数可以正常执行
// ... 页表初始化 ...
return; // 返回到 _paging_init
}
执行流程:
T1: _paging_init 调用原始 paging_init
((paging_init_f)(paging_init_va))();
↓
T2: 原始 paging_init 执行
- 初始化页表
- 设置内存映射
- 完成所有页表相关操作
↓
T3: 原始 paging_init 返回
ret ; 返回到 _paging_init 中 "blr x0" 的下一条指令
为什么 setup_arch 能正常执行?
关键机制:LR 寄存器的保护
执行流程:
┌─────────────────────────────────────────────────────────┐
│ setup_arch │
│ bl paging_init ; LR = setup_arch 的下一条指令 │
│ ; 后续代码... │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ paging_init (已被劫持) │
│ B _paging_init ; 跳转,LR 不变 │
│ ; LR 仍然是 setup_arch 的下一条指令 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ _paging_init │
│ stp x29, x30, [sp, #-16]! ; 保存 LR 到栈 │
│ ; ... 其他操作 ... │
│ ; 恢复 paging_init[0] │
│ ; 调用原始 paging_init │
│ blr x0 ; 调用原始 paging_init │
│ ; 原始 paging_init 返回后,继续执行 │
│ ; ... 调用 start() ... │
│ ; ⚠️ 注意:_paging_init 最后调用 start(),不再返回 │
└─────────────────────────────────────────────────────────┘
关键点分析
LR 寄存器的保护:
setup_arch调用paging_init时,BL指令将返回地址保存到 LRpaging_init[0]的B _paging_init是跳转,不会修改 LR_paging_init的函数序言会保存 LR 到栈
原始 paging_init 的调用:
_paging_init通过函数指针调用原始paging_init- 这是正常的函数调用,会修改 LR(但保存在栈中)
- 原始
paging_init返回后,_paging_init继续执行
为什么不需要返回到 setup_arch:
_paging_init最后调用start(),接管后续的初始化流程start()会完成 KernelPatch 的初始化,然后继续内核启动- 实际上,
setup_arch的后续代码可能不会被执行(因为start()接管了流程)
实际执行流程
完整调用链
setup_arch()
└─ bl paging_init ; LR = setup_arch 的下一条指令
└─ B _paging_init ; 跳转,LR 不变
└─ _paging_init() ; 保存 LR 到栈
├─ 恢复 paging_init[0]
├─ 调用原始 paging_init()
│ └─ 原始 paging_init 执行并返回
├─ 复制代码到新位置
└─ 调用 start()
└─ start() 接管后续流程
关键时序
T1: setup_arch 调用 paging_init
LR = setup_arch 的下一条指令
paging_init[0] = B _paging_init
T2: 执行 B _paging_init
LR 不变(仍然是 setup_arch 的下一条指令)
跳转到 _paging_init
T3: _paging_init 执行
保存 LR 到栈
恢复 paging_init[0] = 原始指令
调用原始 paging_init()
LR 被修改(但保存在栈中)
T4: 原始 paging_init 执行
完成页表初始化
返回(返回到 _paging_init)
T5: _paging_init 继续执行
复制代码、调用 start()
start() 接管后续流程
为什么这样设计是安全的?
1. LR 寄存器的保护
_paging_init的函数序言会保存 LR 到栈- 即使调用原始
paging_init时 LR 被修改,也能从栈中恢复 - 理论上,
_paging_init可以返回到setup_arch(但实际上不会,因为调用了start())
2. 原始 paging_init 的正常执行
_paging_init在调用原始paging_init之前就恢复了指令- 原始
paging_init看到的是完整的、未修改的代码 - 原始
paging_init可以正常执行所有操作
3. 函数调用的语义保持
setup_arch调用paging_init()是函数调用- 虽然中间有跳转,但最终原始
paging_init被正常调用 - 原始
paging_init的返回值和行为都正常
总结
核心机制
- LR 寄存器保护:
B指令不会修改 LR,_paging_init的函数序言会保存 LR - 指令恢复:在调用原始
paging_init之前恢复指令,确保函数完整性 - 函数调用语义:通过函数指针调用原始
paging_init,保持正常的函数调用语义 - 流程接管:
_paging_init最后调用start(),接管后续初始化流程
关键点
- ✅
setup_arch调用paging_init()时,LR 被正确设置 - ✅
B _paging_init是跳转,不会修改 LR - ✅
_paging_init保存 LR 到栈,保护返回地址 - ✅ 原始
paging_init被正常调用,可以正常返回 - ✅
_paging_init最后调用start(),接管后续流程
为什么 setup_arch 能正常工作?
答案:虽然 paging_init 被劫持,但:
- 原始
paging_init被正常调用(通过函数指针) - 原始
paging_init可以正常执行和返回 _paging_init在调用原始paging_init之前恢复了指令- 函数调用的语义和返回值都正常
注意:实际上,setup_arch 的后续代码可能不会执行,因为 _paging_init 最后调用 start() 接管了流程。但这不影响 paging_init 的正常执行。
相关文档:
评论
- 还没有评论,来说点什么吧。