溺的文档
KernelPatch · 第 7 篇 / 共 22 篇

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_archbl 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 指向的地址

关键点

  1. _paging_init 是一个正常的 C 函数,有函数序言和尾声
  2. 函数序言会保存 LR 到栈stp x29, x30, [sp, #-16]!
  3. 调用原始 paging_init 时,使用 BLR(函数调用),会修改 LR
  4. 原始 paging_init 返回后,LR 恢复为 _paging_initblr x0 的下一条指令
  5. _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(),不再返回    │
└─────────────────────────────────────────────────────────┘

关键点分析

  1. LR 寄存器的保护

    • setup_arch 调用 paging_init 时,BL 指令将返回地址保存到 LR
    • paging_init[0]B _paging_init 是跳转,不会修改 LR
    • _paging_init 的函数序言会保存 LR 到栈
  2. 原始 paging_init 的调用

    • _paging_init 通过函数指针调用原始 paging_init
    • 这是正常的函数调用,会修改 LR(但保存在栈中)
    • 原始 paging_init 返回后,_paging_init 继续执行
  3. 为什么不需要返回到 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 的返回值和行为都正常

总结

核心机制

  1. LR 寄存器保护B 指令不会修改 LR,_paging_init 的函数序言会保存 LR
  2. 指令恢复:在调用原始 paging_init 之前恢复指令,确保函数完整性
  3. 函数调用语义:通过函数指针调用原始 paging_init,保持正常的函数调用语义
  4. 流程接管_paging_init 最后调用 start(),接管后续初始化流程

关键点

  • setup_arch 调用 paging_init() 时,LR 被正确设置
  • B _paging_init 是跳转,不会修改 LR
  • _paging_init 保存 LR 到栈,保护返回地址
  • ✅ 原始 paging_init 被正常调用,可以正常返回
  • _paging_init 最后调用 start(),接管后续流程

为什么 setup_arch 能正常工作?

答案:虽然 paging_init 被劫持,但:

  1. 原始 paging_init 被正常调用(通过函数指针)
  2. 原始 paging_init 可以正常执行和返回
  3. _paging_init 在调用原始 paging_init 之前恢复了指令
  4. 函数调用的语义和返回值都正常

注意:实际上,setup_arch 的后续代码可能不会执行,因为 _paging_init 最后调用 start() 接管了流程。但这不影响 paging_init 的正常执行。


相关文档

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。