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

ARM64 重定位机制详解

2026-06-29 · 阅读 3

概述

重定位(Relocation) 是 KernelPatch 中最核心的技术之一。它解决了一个关键问题:

ARM64 架构大量使用 PC 相对寻址,当代码被复制到其他位置时,如何保证这些指令仍然正确工作?

KernelPatch 中的重定位主要用于两个场景:

  1. Inline Hook:将被 hook 的指令复制到 trampoline
  2. KPM 模块加载:将 ELF 模块加载到任意地址

核心文件

  • kernel/base/hook.c(814 行)- Inline Hook 重定位
  • kernel/patch/module/relo.c(328 行)- ELF 重定位

ARM64 指令集简介

PC 相对寻址指令

ARM64 为了高效和位置无关代码(PIC),大量使用 PC 相对寻址:

B label              ; 跳转到 PC + offset
BL func              ; 调用函数(PC 相对)
ADR X0, data         ; X0 = PC + offset
ADRP X0, page        ; X0 = Page(PC) + offset
LDR X0, =value       ; 从 PC + offset 加载数据
CBZ X0, label        ; X0==0 时跳转

问题示例

【原始位置 0x100000】
  0x100000: ADRP X0, #0x200000    ; X0 = 0x100000 + 0x200000 = 0x300000
  0x100004: ADD X0, X0, #0x100    ; X0 = 0x300100
  0x100008: LDR X1, [X0]          ; 加载 0x300100 处的数据

【复制到 0xA00000(未重定位)】
  0xA00000: ADRP X0, #0x200000    ; X0 = 0xA00000 + 0x200000 = 0xC00000 ❌
  0xA00004: ADD X0, X0, #0x100    ; X0 = 0xC00100 ❌
  0xA00008: LDR X1, [X0]          ; 加载错误的地址 ❌

【正确重定位后】
  0xA00000: LDR X0, #8            ; 直接加载绝对地址
  0xA00004: B #12
  0xA00008: .quad 0x300100        ; 原始的绝对地址
  0xA00010: LDR X1, [X0]

Inline Hook 指令重定位

需要重定位的指令类型

// kernel/base/hook.c:23-40
#define INST_B          0x14000000    // 无条件跳转
#define INST_BC         0x54000000    // 条件跳转(B.cond)
#define INST_BL         0x94000000    // 跳转并链接
#define INST_ADR        0x10000000    // 地址相对寻址
#define INST_ADRP       0x90000000    // 页面地址寻址
#define INST_LDR_32     0x18000000    // 32 位 LDR literal
#define INST_LDR_64     0x58000000    // 64 位 LDR literal
#define INST_LDRSW_LIT  0x98000000    // 符号扩展加载
#define INST_PRFM_LIT   0xD8000000    // 预取
#define INST_LDR_SIMD_32   0x1C000000    // SIMD 32 位加载
#define INST_LDR_SIMD_64   0x5C000000    // SIMD 64 位加载
#define INST_LDR_SIMD_128  0x9C000000    // SIMD 128 位加载
#define INST_CBZ        0x34000000    // 比较为 0 跳转
#define INST_CBNZ       0x35000000    // 比较非 0 跳转
#define INST_TBZ        0x36000000    // 测试位为 0 跳转
#define INST_TBNZ       0x37000000    // 测试位非 0 跳转

// v2.0 更新:TRAMPOLINE_NUM 4 -> TRAMPOLINE_MAX_NUM 6
// 原先固定使用 4 条 trampoline 指令,现在最大支持 6 条
// 这允许 hook 更大的函数序言,特别是带有 PAC 指令的函数

// 重定位后的长度(指令数)
// v2.0 更新:RELOCATE_INST_NUM 公式变化
// 新公式考虑了 PACIASP/PACIBSP 指令的处理
static int32_t relo_len[] = { 6, 8, 8, 4, 4, 6, 6, 6, 8, 8, 8, 8, 6, 6, 6, 6, 2 };

重定位策略 1:B/BL/BC 指令

指令格式

B/BL 指令(26 位偏移):
  31 30 29 28 27 26 25                    0
  ┌─┬─┬─┬─┬─┬─┬────────────────────────────┐
  │0│op│0│1│0│1│      imm26               │
  └─┴─┴─┴─┴─┴─┴────────────────────────────┘
  
  op = 0: B  (跳转)
  op = 1: BL (跳转并链接,设置 X30)
  
  目标 = PC + SignExtend(imm26 << 2, 28)
  范围: ±128 MB

B.cond 指令(19 位偏移):
  31     24 23    5 4   0
  ┌────────┬──────┬─────┐
  │01010100│ imm19│ cond│
  └────────┴──────┴─────┘
  
  cond: EQ/NE/CS/CC/MI/PL/VS/VC/HI/LS/GE/LT/GT/LE/AL
  目标 = PC + SignExtend(imm19 << 2, 21)
  范围: ±1 MB

重定位实现

// kernel/base/hook.c:141-173
static hook_err_t relo_b(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_num;
    
    // ============ 步骤 1:解析原始跳转目标 ============
    
    uint64_t imm64;
    if (type == INST_BC) {  // 条件跳转
        uint64_t imm19 = bits32(inst, 23, 5);
        imm64 = sign64_extend(imm19 << 2u, 21u);
    } else {  // B/BL
        uint64_t imm26 = bits32(inst, 25, 0);
        imm64 = sign64_extend(imm26 << 2u, 28u);
    }
    
    uint64_t addr = inst_addr + imm64;  // 计算绝对目标地址
    addr = relo_in_tramp(hook, addr);   // 如果目标也在 tramp,调整地址
    
    
    // ============ 步骤 2:生成重定位序列 ============
    
    uint32_t idx = 0;
    
    if (type == INST_BC) {
        // 条件跳转需要特殊处理
        buf[idx++] = (inst & 0xFF00001F) | 0x40u;  // B.<cond> #8
        buf[idx++] = 0x14000006;                    // B #24
    }
    
    // 通用跳转序列
    buf[idx++] = 0x58000051;           // LDR X17, #8
    buf[idx++] = 0x14000003;           // B #12
    buf[idx++] = addr & 0xFFFFFFFF;    // 目标地址低 32 位
    buf[idx++] = addr >> 32u;          // 目标地址高 32 位
    
    if (type == INST_BL) {
        // BL 需要保存返回地址
        buf[idx++] = 0x1000001E;       // ADR X30, .
        buf[idx++] = 0x910033DE;       // ADD X30, X30, #12
        buf[idx++] = 0xD65F0220;       // RET X17
    } else {
        buf[idx++] = 0xD65F0220;       // RET X17
    }
    
    buf[idx++] = ARM64_NOP;
    
    return HOOK_NO_ERR;
}

生成的代码分析

B 指令重定位(6 条指令,24 字节):

原始:
  B #0x1000         ; 跳转到 PC + 0x1000

重定位后:
  LDR X17, #8       ; 加载目标地址到 X17
  B   #12           ; 跳过地址数据
  .quad 0x101000    ; 64 位绝对目标地址
  RET X17           ; 通过 X17 跳转(实际是 BR X17 的变体)
  NOP               ; 填充对齐

BL 指令重定位(8 条指令,32 字节):

原始:
  BL func           ; 调用函数,X30 = PC + 4

重定位后:
  LDR X17, #8       ; 加载函数地址
  B   #12
  .quad 0x函数地址
  ADR X30, .        ; X30 = 当前 PC
  ADD X30, X30, #12 ; X30 = 下一条指令的地址
  RET X17           ; 跳转到函数
  NOP

B.EQ 条件跳转重定位(8 条指令,32 字节):

原始:
  B.EQ #0x1000      ; 如果 ZF=1,跳转

重定位后:
  B.EQ #8           ; 如果条件满足,跳到 LDR
  B    #24          ; 如果条件不满足,跳过整个序列
  LDR  X17, #8
  RET  X17
  .quad 0x101000
  NOP

重定位策略 2:ADR/ADRP 指令

指令格式

ADR 指令(21 位偏移):
  31 30 29 28 27 26 25 24 23      5 4   0
  ┌─┬───┬─┬─┬─┬─┬─┬─┬───────┬─────┐
  │0│imm│1│0│0│0│0│0│ immhi │  Rd │
  └─┴───┴─┴─┴─┴─┴─┴─┴───────┴─────┘
  
  immlo = bits[30:29] (2 位)
  immhi = bits[23:5] (19 位)
  imm = immhi:immlo (21 位)
  
  Rd = PC + SignExtend(imm, 21)
  范围: ±1 MB

ADRP 指令(页面偏移):
  31 30 29 28 27 26 25 24 23      5 4   0
  ┌─┬───┬─┬─┬─┬─┬─┬─┬───────┬─────┐
  │1│imm│1│0│0│0│0│0│ immhi │  Rd │
  └─┴───┴─┴─┴─┴─┴─┴─┴───────┴─────┘
  
  Rd = Page(PC) + SignExtend(imm << 12, 33)
  范围: ±4 GB(页面粒度)

重定位实现

// kernel/base/hook.c:175-195
static hook_err_t relo_adr(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_num;
    
    uint32_t xd = bits32(inst, 4, 0);   // 目标寄存器
    uint64_t immlo = bits32(inst, 30, 29);
    uint64_t immhi = bits32(inst, 23, 5);
    uint64_t addr;
    
    if (type == INST_ADR) {
        // ADR: PC 相对地址
        addr = inst_addr + sign64_extend((immhi << 2u) | immlo, 21u);
    } else {  // ADRP
        // ADRP: 页面对齐地址
        addr = (inst_addr + sign64_extend((immhi << 14u) | (immlo << 12u), 33u)) 
               & 0xFFFFFFFFFFFFF000;
        
        if (is_in_tramp(hook, addr)) return -HOOK_BAD_RELO;
    }
    
    // 生成加载绝对地址的代码
    buf[0] = 0x58000040u | xd;    // LDR Xd, #8
    buf[1] = 0x14000003;          // B #12
    buf[2] = addr & 0xFFFFFFFF;   // 地址低 32 位
    buf[3] = addr >> 32u;         // 地址高 32 位
    
    return HOOK_NO_ERR;
}

生成的代码(4 条指令,16 字节):

原始:
  ADRP X0, #0x200000    ; X0 = Page(PC) + 0x200000

重定位后:
  LDR X0, #8            ; 直接加载绝对地址
  B   #12
  .quad 0x300000        ; 计算好的页面对齐地址

重定位策略 3:LDR literal

指令格式

LDR literal(19 位偏移):
  31 30 29 28 27 26 25 24 23    5 4   0
  ┌─┬─┬───┬─┬─┬─┬───┬────────┬─────┐
  │0│op│011│0│0│0│size│ imm19 │  Rt │
  └─┴─┴───┴─┴─┴─┴───┴────────┴─────┘
  
  size = 00: LDR Wt (32 位)
  size = 01: LDR Xt (64 位)
  size = 10: LDRSW Xt (符号扩展)
  op = 11: PRFM (预取)
  
  地址 = PC + SignExtend(imm19 << 2, 21)
  范围: ±1 MB

重定位实现

// kernel/base/hook.c:197-244
static hook_err_t relo_ldr(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_num;
    
    uint32_t rt = bits32(inst, 4, 0);   // 目标寄存器
    uint64_t imm19 = bits32(inst, 23, 5);
    uint64_t offset = sign64_extend((imm19 << 2u), 21u);
    uint64_t addr = inst_addr + offset;
    
    if (is_in_tramp(hook, addr) && type != INST_PRFM_LIT) 
        return -HOOK_BAD_RELO;
    
    addr = relo_in_tramp(hook, addr);
    
    if (type == INST_LDR_32 || type == INST_LDR_64 || type == INST_LDRSW_LIT) {
        // ============ 通用寄存器加载 ============
        
        buf[0] = 0x58000060u | rt;      // LDR Xt, #12
        
        if (type == INST_LDR_32) {
            buf[1] = 0xB9400000 | rt | (rt << 5u);  // LDR Wt, [Xt]
        } else if (type == INST_LDR_64) {
            buf[1] = 0xF9400000 | rt | (rt << 5u);  // LDR Xt, [Xt]
        } else {  // LDRSW
            buf[1] = 0xB9800000 | rt | (rt << 5u);  // LDRSW Xt, [Xt]
        }
        
        buf[2] = 0x14000004;            // B #16
        buf[3] = ARM64_NOP;
        buf[4] = addr & 0xFFFFFFFF;
        buf[5] = addr >> 32u;
        
    } else {
        // ============ SIMD/FP 寄存器加载(需要保护 X16/X17)============
        
        buf[0] = 0xA93F47F0;            // STP X16, X17, [SP, -0x10]
        buf[1] = 0x58000091;            // LDR X17, #16
        
        if (type == INST_PRFM_LIT) {
            buf[2] = 0xF9800220 | rt;   // PRFM Rt, [X17]
        } else if (type == INST_LDR_SIMD_32) {
            buf[2] = 0xBD400220 | rt;   // LDR St, [X17]
        } else if (type == INST_LDR_SIMD_64) {
            buf[2] = 0xFD400220 | rt;   // LDR Dt, [X17]
        } else {  // 128-bit
            buf[2] = 0x3DC00220u | rt;  // LDR Qt, [X17]
        }
        
        buf[3] = 0xF85F83F1;            // LDR X17, [SP, -0x8]
        buf[4] = 0x14000004;            // B #16
        buf[5] = ARM64_NOP;
        buf[6] = addr & 0xFFFFFFFF;
        buf[7] = addr >> 32u;
    }
    
    return HOOK_NO_ERR;
}

生成的代码(LDR X0, =data)

原始:
  LDR X0, #0x1000       ; X0 = *(PC + 0x1000)

重定位后:
  LDR X0, #12           ; X0 = 数据地址
  LDR X0, [X0]          ; X0 = *数据地址
  B   #16
  NOP
  .quad 0x102000        ; 数据的绝对地址

生成的代码(LDR S0, =data,SIMD)

原始:
  LDR S0, #0x1000       ; S0 = *(PC + 0x1000)

重定位后:
  STP X16, X17, [SP, -0x10]  ; 保存临时寄存器
  LDR X17, #16               ; X17 = 数据地址
  LDR S0, [X17]              ; S0 = *X17
  LDR X17, [SP, -0x8]        ; 恢复 X17(X16 未修改)
  B   #16
  NOP
  .quad 0x102000

重定位策略 4:CBZ/CBNZ

指令格式

CBZ/CBNZ(19 位偏移):
  31 30 29 28 27 26 25 24 23    5 4   0
  ┌─┬─┬───────┬─┬─┬────────┬─────┐
  │sf│0│011010│op│0│ imm19  │  Rt │
  └─┴─┴───────┴─┴─┴────────┴─────┘
  
  sf = 0: 32 位比较(W 寄存器)
  sf = 1: 64 位比较(X 寄存器)
  op = 0: CBZ (Compare and Branch if Zero)
  op = 1: CBNZ (Compare and Branch if Not Zero)
  
  if (Rt == 0) PC += SignExtend(imm19 << 2, 21)
  范围: ±1 MB

重定位实现

// kernel/base/hook.c:246-262
static hook_err_t relo_cb(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_num;
    
    uint64_t imm19 = bits32(inst, 23, 5);
    uint64_t offset = sign64_extend((imm19 << 2u), 21u);
    uint64_t addr = inst_addr + offset;
    addr = relo_in_tramp(hook, addr);
    
    buf[0] = (inst & 0xFF00001F) | 0x40u;  // CB(N)Z Rt, #8
    buf[1] = 0x14000005;                    // B #20
    buf[2] = 0x58000051;                    // LDR X17, #8
    buf[3] = 0xD65F0220;                    // RET X17
    buf[4] = addr & 0xFFFFFFFF;
    buf[5] = addr >> 32u;
    
    return HOOK_NO_ERR;
}

生成的代码(6 条指令):

原始:
  CBZ X0, #0x1000       ; 如果 X0==0,跳转

重定位后:
  CBZ X0, #8            ; 如果 X0==0,跳到 LDR
  B   #20               ; 如果 X0!=0,跳过整个序列
  LDR X17, #8
  RET X17
  .quad 0x101000

重定位策略 5:TBZ/TBNZ

指令格式

TBZ/TBNZ(14 位偏移):
  31 30 29 28 27 26 25 24 23 19 18    5 4   0
  ┌─┬───────┬─┬───────┬─────┬──────┬─────┐
  │b5│011011│op│  b40  │ imm14│  Rt │
  └─┴───────┴─┴───────┴─────┴──────┴─────┘
  
  bit = b5:b40 (6 位,0-63)
  op = 0: TBZ (Test Bit and Branch if Zero)
  op = 1: TBNZ (Test Bit and Branch if Not Zero)
  
  if (Rt[bit] == 0) PC += SignExtend(imm14 << 2, 16)
  范围: ±32 KB

重定位实现

// kernel/base/hook.c:264-280
static hook_err_t relo_tb(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_num;
    
    uint64_t imm14 = bits32(inst, 18, 5);
    uint64_t offset = sign64_extend((imm14 << 2u), 16u);
    uint64_t addr = inst_addr + offset;
    addr = relo_in_tramp(hook, addr);
    
    buf[0] = (inst & 0xFFF8001F) | 0x40u;  // TB(N)Z Rt, #<imm>, #8
    buf[1] = 0x14000005;                    // B #20
    buf[2] = 0x58000051;                    // LDR X17, #8
    buf[3] = 0xd61f0220;                    // BR X17
    buf[4] = addr & 0xFFFFFFFF;
    buf[5] = addr >> 32u;
    
    return HOOK_NO_ERR;
}

KPM ELF 重定位

ELF 重定位原理

// kernel/patch/module/relo.c:16-22
enum aarch64_reloc_op {
    RELOC_OP_NONE,
    RELOC_OP_ABS,     // S + A(绝对地址)
    RELOC_OP_PREL,    // S + A - P(PC 相对)
    RELOC_OP_PAGE,    // Page(S + A) - Page(P)(页面相对)
};

// S = 符号值(Symbol value)
// A = 加数(Addend)
// P = 位置(Place,即重定位位置)

核心重定位函数

// kernel/patch/module/relo.c:24-39
static u64 do_reloc(enum aarch64_reloc_op reloc_op, void *place, u64 val)
{
    switch (reloc_op) {
    case RELOC_OP_ABS:
        return val;                                     // S + A
    case RELOC_OP_PREL:
        return val - (u64)place;                        // S + A - P
    case RELOC_OP_PAGE:
        return (val & ~0xfff) - ((u64)place & ~0xfff);  // Page(S+A) - Page(P)
    case RELOC_OP_NONE:
        return 0;
    }
    return 0;
}

apply_relocate_add() - 主处理函数

// kernel/patch/module/relo.c:168-328
int apply_relocate_add(Elf64_Shdr *sechdrs, const char *strtab, 
                       unsigned int symindex, unsigned int relsec,
                       struct module *me)
{
    Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;
    
    for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
        // P: 重定位位置
        loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + rel[i].r_offset;
        
        // S: 符号值
        sym = (Elf64_Sym *)sechdrs[symindex].sh_addr + ELF64_R_SYM(rel[i].r_info);
        
        // val = S + A
        val = sym->st_value + rel[i].r_addend;
        
        overflow_check = true;
        
        // 根据重定位类型处理
        switch (ELF64_R_TYPE(rel[i].r_info)) {
        
        // ============ 数据重定位 ============
        
        case R_AARCH64_ABS64:
            overflow_check = false;
            ovf = reloc_data(RELOC_OP_ABS, loc, val, 64);
            // *(u64*)loc = val
            break;
            
        case R_AARCH64_ABS32:
            ovf = reloc_data(RELOC_OP_ABS, loc, val, 32);
            // *(u32*)loc = val
            break;
            
        case R_AARCH64_PREL64:
            overflow_check = false;
            ovf = reloc_data(RELOC_OP_PREL, loc, val, 64);
            // *(u64*)loc = val - (u64)loc
            break;
        
        
        // ============ MOVW 指令重定位 ============
        
        case R_AARCH64_MOVW_UABS_G0:  // MOVZ/MOVK X?, #imm, LSL #0
            ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0, AARCH64_INSN_IMM_16);
            // 提取 val[15:0],编码到指令
            break;
            
        case R_AARCH64_MOVW_UABS_G1:  // MOVK X?, #imm, LSL #16
            ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16, AARCH64_INSN_IMM_16);
            // 提取 val[31:16]
            break;
            
        case R_AARCH64_MOVW_UABS_G2:  // MOVK X?, #imm, LSL #32
            ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32, AARCH64_INSN_IMM_16);
            // 提取 val[47:32]
            break;
            
        case R_AARCH64_MOVW_UABS_G3:  // MOVK X?, #imm, LSL #48
            overflow_check = false;
            ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 48, AARCH64_INSN_IMM_16);
            // 提取 val[63:48]
            break;
        
        
        // ============ 地址计算指令重定位 ============
        
        case R_AARCH64_ADR_PREL_PG_HI21:  // ADRP
            ovf = reloc_insn_imm(RELOC_OP_PAGE, loc, val, 12, 21, AARCH64_INSN_IMM_ADR);
            // imm21 = (Page(val) - Page(loc)) >> 12
            break;
            
        case R_AARCH64_ADD_ABS_LO12_NC:   // ADD X?, X?, #imm
            overflow_check = false;
            ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 0, 12, AARCH64_INSN_IMM_12);
            // imm12 = val & 0xFFF
            break;
        
        
        // ============ 加载/存储指令重定位 ============
        
        case R_AARCH64_LDST64_ABS_LO12_NC:  // LDR/STR X?, [X?, #imm]
            overflow_check = false;
            ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 3, 9, AARCH64_INSN_IMM_12);
            // imm12 = (val & 0xFFF) >> 3
            break;
        
        
        // ============ 分支指令重定位 ============
        
        case R_AARCH64_CALL26:  // BL
        case R_AARCH64_JUMP26:  // B
            ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 26, AARCH64_INSN_IMM_26);
            // imm26 = (val - loc) >> 2
            break;
            
        case R_AARCH64_CONDBR19:  // B.cond
            ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 19, AARCH64_INSN_IMM_19);
            // imm19 = (val - loc) >> 2
            break;
        
        
        default:
            pr_err("unsupported RELA relocation: %llu\n", ELF64_R_TYPE(rel[i].r_info));
            return -ENOEXEC;
        }
        
        if (overflow_check && ovf == -ERANGE) goto overflow;
    }
    
    return 0;
    
overflow:
    pr_err("overflow in relocation type %d val %llx\n", 
           (int)ELF64_R_TYPE(rel[i].r_info), val);
    return -ENOEXEC;
}

常见重定位模式

模式 1:ADRP + ADD(加载全局变量地址)

// C 代码
extern int global_var;
int *ptr = &global_var;

// 编译器生成
ADRP X0, global_var          ; R_AARCH64_ADR_PREL_PG_HI21
ADD  X0, X0, #:lo12:global_var  ; R_AARCH64_ADD_ABS_LO12_NC

重定位过程

假设:
  global_var 最终地址 = 0xFFFFFFC008ABC123
  指令位置 = 0xFFFFFFC008500000

ADRP 重定位:
  page_offset = (0xFFFFFFC008ABC000 - 0xFFFFFFC008500000) >> 12
              = 0x5BC
  编码到指令的 imm21

ADD 重定位:
  page_offset_lo12 = 0xFFFFFFC008ABC123 & 0xFFF = 0x123
  编码到指令的 imm12

模式 2:MOVZ/MOVK(加载 64 位常量)

// C 代码
void *ptr = (void *)0x123456789ABCDEF0ULL;

// 编译器生成
MOVZ X0, #0xDEF0, LSL #0      ; R_AARCH64_MOVW_UABS_G0
MOVK X0, #0x9ABC, LSL #16     ; R_AARCH64_MOVW_UABS_G1
MOVK X0, #0x5678, LSL #32     ; R_AARCH64_MOVW_UABS_G2
MOVK X0, #0x1234, LSL #48     ; R_AARCH64_MOVW_UABS_G3

重定位过程

// reloc_insn_movw() 处理
val = 0x123456789ABCDEF0

G0: imm16 = (val >> 0) & 0xFFFF = 0xDEF0
G1: imm16 = (val >> 16) & 0xFFFF = 0x9ABC
G2: imm16 = (val >> 32) & 0xFFFF = 0x5678
G3: imm16 = (val >> 48) & 0xFFFF = 0x1234

编码到各自的 MOVW 指令

模式 3:B/BL(函数调用)

// C 代码
extern void other_func(void);
void my_func(void) {
    other_func();
}

// 编译器生成
my_func:
    BL other_func     ; R_AARCH64_CALL26

重定位过程

假设:
  other_func 地址 = 0xFFFFFFC008100000
  BL 指令位置 = 0xFFFFFFC008080000

计算:
  offset = 0xFFFFFFC008100000 - 0xFFFFFFC008080000 = 0x80000
  imm26 = offset >> 2 = 0x20000
  
  指令 = 0x94000000 | 0x20000 = 0x94020000

辅助函数

branch_relative() - 相对跳转

// kernel/base/hook.c:297-306
int32_t branch_relative(uint32_t *buf, uint64_t src_addr, uint64_t dst_addr)
{
    if (can_b_rel(src_addr, dst_addr)) {
        buf[0] = 0x14000000u | (((dst_addr - src_addr) & 0x0FFFFFFFu) >> 2u);
        buf[1] = ARM64_NOP;
        return 2;
    }
    return 0;
}

static uint32_t can_b_rel(uint64_t src_addr, uint64_t dst_addr)
{
    #define B_REL_RANGE ((1 << 25) << 2)  // 128 MB
    return ((dst_addr >= src_addr) & (dst_addr - src_addr <= B_REL_RANGE)) ||
           ((src_addr >= dst_addr) & (src_addr - dst_addr <= B_REL_RANGE));
}

branch_absolute() - 绝对跳转

// kernel/base/hook.c:308-316
int32_t branch_absolute(uint32_t *buf, uint64_t addr)
{
    buf[0] = 0x58000051;    // LDR X17, #8
    buf[1] = 0xd61f0220;    // BR X17
    buf[2] = addr & 0xFFFFFFFF;
    buf[3] = addr >> 32u;
    return 4;
}

生成代码

LDR X17, #8
BR  X17
.quad <target_address>

ret_absolute() - 绝对返回

// kernel/base/hook.c:318-326
int32_t ret_absolute(uint32_t *buf, uint64_t addr)
{
    buf[0] = 0x58000051;    // LDR X17, #8
    buf[1] = 0xD65F0220;    // RET X17
    buf[2] = addr & 0xFFFFFFFF;
    buf[3] = addr >> 32u;
    return 4;
}

RET vs BR 的区别

  • BR X17:分支跳转
  • RET X17:返回跳转(CPU 可能优化分支预测)

完整的重定位类型表

重定位类型 用途 指令 操作 值范围
R_AARCH64_ABS64 64位指针 .quad *(u64*)P = S + A 全范围
R_AARCH64_ABS32 32位指针 .word *(u32*)P = S + A 32 位
R_AARCH64_PREL64 PC 相对指针 .quad *(u64*)P = S + A - P 全范围
R_AARCH64_CALL26 函数调用 BL imm26 = (S+A-P) >> 2 ±128MB
R_AARCH64_JUMP26 无条件跳转 B imm26 = (S+A-P) >> 2 ±128MB
R_AARCH64_CONDBR19 条件跳转 B.cond imm19 = (S+A-P) >> 2 ±1MB
R_AARCH64_ADR_PREL_PG_HI21 页面地址 ADRP imm21 = Page(S+A) - Page(P) ±4GB
R_AARCH64_ADD_ABS_LO12_NC 页内偏移 ADD imm12 = (S+A) & 0xFFF 4KB
R_AARCH64_LDST64_ABS_LO12_NC 加载/存储 LDR/STR imm12 = ((S+A) & 0xFFF) >> 3 4KB
R_AARCH64_MOVW_UABS_G0 立即数[15:0] MOVZ/MOVK imm16 = (S+A) & 0xFFFF 16 位
R_AARCH64_MOVW_UABS_G1 立即数[31:16] MOVK imm16 = ((S+A)>>16) & 0xFFFF 16 位
R_AARCH64_MOVW_UABS_G2 立即数[47:32] MOVK imm16 = ((S+A)>>32) & 0xFFFF 16 位
R_AARCH64_MOVW_UABS_G3 立即数[63:48] MOVK imm16 = ((S+A)>>48) & 0xFFFF 16 位

实际案例分析

案例 1:Hook 函数的重定位

// 假设 hook do_execve
void *target_func = (void *)0xFFFFFFC008123456;

hook_t hook;
hook.origin_addr = (uint64_t)target_func;
hook.replace_addr = (uint64_t)my_trampoline;

// do_execve 的前几条指令:
// 0xFFFFFFC008123456: D503245F    BTI C
// 0xFFFFFFC00812345A: A9BF7BFD    STP X29, X30, [SP, #-16]!
// 0xFFFFFFC00812345E: 910003FD    MOV X29, SP
// 0xFFFFFFC008123462: 14000123    B #0x48C  (跳转到某处)
// 0xFFFFFFC008123466: 90001234    ADRP X20, #0x246800

重定位过程

指令 1:BTI C

inst = 0xD503245F
// 这不是 PC 相对指令,直接复制
relo_ignore(hook, 0xFFFFFFC008123456, 0xD503245F, INST_IGNORE);

生成:
  0xD503245F    BTI C
  0xD503201F    NOP

指令 2:STP

inst = 0xA9BF7BFD
// 非 PC 相对,直接复制
生成:
  0xA9BF7BFD    STP X29, X30, [SP, #-16]!
  0xD503201F    NOP

指令 3:MOV

inst = 0x910003FD
// 非 PC 相对,直接复制
生成:
  0x910003FD    MOV X29, SP
  0xD503201F    NOP

指令 4:B #0x48C

inst = 0x14000123
type = INST_B

// 计算目标
imm26 = 0x123
offset = SignExtend(0x123 << 2, 28) = 0x48C
target = 0xFFFFFFC008123462 + 0x48C = 0xFFFFFFC0081238EE

// 重定位
relo_b(hook, 0xFFFFFFC008123462, 0x14000123, INST_B);

生成:
  0x58000051    LDR X17, #8
  0x14000003    B #12
  .quad 0xFFFFFFC0081238EE
  0xD65F0220    RET X17
  0xD503201F    NOP

指令 5:ADRP X20, #0x246800

inst = 0x90001234
type = INST_ADRP

// 解析
xd = 20
immlo = bits(0x90001234, 30, 29) = 0
immhi = bits(0x90001234, 23, 5) = 0x91A
imm = (0x91A << 2) | 0 = 0x2468

// 计算目标页面
target = Page(0xFFFFFFC008123466) + SignExtend(0x2468 << 12, 33)
       = 0xFFFFFFC008123000 + 0x2468000
       = 0xFFFFFFC00A58B000

// 重定位
relo_adr(hook, 0xFFFFFFC008123466, 0x90001234, INST_ADRP);

生成:
  0x58000054    LDR X20, #8      ; 加载到 X20(保持寄存器一致)
  0x14000003    B #12
  .quad 0xFFFFFFC00A58B000

案例 2:KPM 模块重定位

// KPM 源码
extern unsigned long kallsyms_lookup_name;

void my_init() {
    typedef unsigned long (*lookup_t)(const char *);
    lookup_t lookup = (lookup_t)kallsyms_lookup_name;
    
    unsigned long addr = lookup("do_execve");
    pr_info("do_execve at: %lx\n", addr);
}

// 编译后的 ELF
.text:
  my_init:
    ADRP X0, kallsyms_lookup_name     ; R_AARCH64_ADR_PREL_PG_HI21
    LDR  X0, [X0, #:lo12:kallsyms_lookup_name]  ; R_AARCH64_LDST64_ABS_LO12_NC
    ...

加载时重定位

  1. 解析符号

    // 在 KernelPatch 的符号表中查找
    kallsyms_lookup_name 地址 = 0xFFFFFFC008982B0
  2. 应用重定位

    // R_AARCH64_ADR_PREL_PG_HI21
    loc = 模块加载地址 + 0x100
    val = 0xFFFFFFC008982B0
    
    page_offset = (Page(val) - Page(loc)) >> 12
    编码到 ADRP 指令
    
    // R_AARCH64_LDST64_ABS_LO12_NC
    loc = 模块加载地址 + 0x104
    val = 0xFFFFFFC008982B0
    
    page_offset_lo12 = (val & 0xFFF) >> 3 = 0x2B0 >> 3 = 0x56
    编码到 LDR 指令

重定位错误处理

溢出检测

// kernel/patch/module/relo.c:41-74
static int reloc_data(enum aarch64_reloc_op op, void *place, u64 val, int len)
{
    u64 imm_mask = (1 << len) - 1;
    s64 sval = do_reloc(op, place, val);
    
    switch (len) {
    case 16:
        *(s16 *)place = sval;
        break;
    case 32:
        *(s32 *)place = sval;
        break;
    case 64:
        *(s64 *)place = sval;
        break;
    default:
        pr_err("Invalid length (%d) for data relocation\n", len);
        return 0;
    }
    
    // 溢出检查
    sval = (s64)(sval & ~(imm_mask >> 1)) >> (len - 1);
    
    // 值必须能用 len 位表示(符号扩展)
    if ((u64)(sval + 1) > 2) return -ERANGE;
    
    return 0;
}

溢出示例

// R_AARCH64_CALL26
loc = 0xFFFFFFC008000000
val = 0xFFFFFFC020000000  // 相距 384 MB

offset = val - loc = 0x18000000  // 384 MB
imm26_max = (1 << 25) << 2 = 0x8000000  // 128 MB

offset > imm26_max  ❌ 溢出!

结果:pr_err("overflow in relocation type %d val %llx\n", ...)
      return -ENOEXEC;

位操作辅助宏

// kernel/base/hook.c:14-18
#define bits(n, high, low) (((n) << (63u - (high))) >> (63u - (high) + (low)))
#define bits32(n, high, low) ((uint32_t)((n) << (31u - (high))) >> (31u - (high) + (low)))
#define bit(n, st) (((n) >> (st)) & 1)
#define sign64_extend(n, len) \
    (((uint64_t)((n) << (63u - (len - 1))) >> 63u) ? ((n) | (0xFFFFFFFFFFFFFFFF << (len))) : n)

使用示例

// 提取 B 指令的 imm26
inst = 0x14000123
imm26 = bits32(inst, 25, 0) = 0x123

// 符号扩展到 28 位
imm28 = sign64_extend(0x123 << 2, 28)
      = sign64_extend(0x48C, 28)
      = 0x48C (正数,无需扩展)

// 如果是负数
imm26 = 0x3FFFFFE  // -8
imm28 = sign64_extend(0x3FFFFFE << 2, 28)
      = sign64_extend(0xFFFFFF8, 28)
      = 0xFFFFFFFFFFFFFFF8  // -8(符号扩展)

relo_in_tramp() - 处理 tramp 内跳转

// kernel/base/hook.c:92-109
static uint64_t relo_in_tramp(hook_t *hook, uint64_t addr)
{
    uint64_t tramp_start = hook->origin_addr;
    uint64_t tramp_end = tramp_start + hook->tramp_insts_num * 4;
    
    // 如果目标地址不在 tramp 范围内,直接返回
    if (!(addr >= tramp_start && addr < tramp_end)) 
        return addr;
    
    // 目标在 tramp 内,需要调整到重定位后的位置
    uint32_t addr_inst_index = (addr - tramp_start) / 4;
    uint64_t fix_addr = hook->relo_addr;
    
    // 累加前面指令的重定位后长度
    for (int i = 0; i < addr_inst_index; i++) {
        inst_type_t inst = hook->origin_insts[i];
        
        // 查找指令类型
        for (int j = 0; j < sizeof(relo_len) / sizeof(relo_len[0]); j++) {
            if ((inst & masks[j]) == types[j]) {
                fix_addr += relo_len[j] * 4;
                break;
            }
        }
    }
    
    return fix_addr;
}

案例

原始代码(被 hook 的函数):
  0x100000: B #0x10        ; 跳到 0x100010(tramp 内)
  0x100004: ...
  ...
  0x100010: RET            ; 目标

重定位后:
  第 0 条指令 B #0x10 重定位为 6 条指令(24 字节)
  第 1 条指令重定位为 2 条指令(8 字节)
  ...
  
  原始目标 0x100010 (第 4 条指令)
  新目标 = relo_addr + 24 + 8 + ... = relo_addr + offset

性能与优化

指令长度对比

原始指令类型 原始长度 重定位后长度 膨胀率
B/BL 4 B 24/32 B 6-8x
ADR/ADRP 4 B 16 B 4x
LDR literal (通用寄存器) 4 B 24 B 6x
LDR literal (SIMD) 4 B 32 B 8x
CBZ/CBNZ 4 B 24 B 6x
其他指令 4 B 8 B 2x

为什么可以接受这么大的膨胀?

  • Hook 通常只复制函数的前 3-5 条指令
  • 总大小仍然很小(~100 字节)
  • Hook 区域有 1MB 专用空间

优化:相对跳转优先

// kernel/base/hook.c:328-340
int32_t branch_from_to(uint32_t *tramp_buf, uint64_t src_addr, uint64_t dst_addr)
{
#if 0
    // 如果距离允许,优先使用相对跳转(只需 2 条指令)
    uint32_t len = branch_relative(tramp_buf, src_addr, dst_addr);
    if (len) return len;
#else
    // 当前实现:总是使用绝对跳转
    return ret_absolute(tramp_buf, dst_addr);
#endif
}

为什么注释掉了?

  • 绝对跳转更可靠(无距离限制)
  • 性能差异可忽略(只在 hook 入口执行一次)

边界情况处理

1. 跳转到 tramp 内部

// 如果被 hook 的函数内部有跳转
func:
    0x100000: B #0x10        ; 跳到函数内部
    0x100004: ...
    0x100010: RET

// 重定位时
relo_b() 会调用 relo_in_tramp()
计算新的目标地址(考虑重定位后的膨胀)

2. ADRP 引用 tramp 内数据

// ADRP 不支持 tramp 内引用
if (is_in_tramp(hook, addr)) return -HOOK_BAD_RELO;

// 原因:ADRP 是页面粒度,无法精确定位 tramp 内的小数据

3. BTI 指令

// kernel/base/hook.c:580-583
uint32_t *bti = hook->relo_insts + hook->relo_insts_num;
bti[0] = ARM64_BTI_JC;    // 0xD50324DF
bti[1] = ARM64_NOP;
hook->relo_insts_num += 2;

// 在 relo 代码前插入 BTI JC
// 确保从 trampoline 跳转过来时通过 BTI 检查

4. PACIASP/PACIBSP 指令处理

v2.0 新增:KernelPatch 现在正确处理 PACIASP0xD503233F)和 PACIBSP0xD503237F)指令。这些指令属于 ARM64 的 Pointer Authentication Code (PAC) 机制,在支持 PAC 的内核中广泛出现在函数入口处。

PACIASP 指令格式:
  31                 0
  ┌──────────────────┐
  │ 0xD503233F       │  PACIASP
  └──────────────────┘
  
  功能:使用 IA key 对 LR (X30) 进行签名
        PAC = ComputePAC(LR, SP, IA_KEY)
        LR = LR | (PAC << high_bits)

PACIBSP 指令格式:
  ┌──────────────────┐
  │ 0xD503237F       │  PACIBSP
  └──────────────────┘
  
  功能:使用 IB key 对 LR 进行签名

Hook 时的处理策略

当 hook 的目标函数以 PACIASPPACIBSP 开头时,需要特殊处理:

  1. trampoline 中的处理:PACIASP/PACIBSP 在原始函数入口执行 PAC 签名,如果被复制到 trampoline 中,签名时的 SP 值可能不同,导致后续 AUTIASP/AUTIBSP 验证失败。因此在 trampoline 中需要保留这些指令但调整上下文。

  2. TRAMPOLINE_MAX_NUM 增大的原因:从 4 增大到 6 正是为了容纳 PAC 指令的额外处理空间。带有 PAC 指令的函数序言通常是:

    PACIASP                        ; PAC 签名
    STP X29, X30, [SP, #-16]!      ; 保存帧指针和链接寄存器
    MOV X29, SP                    ; 建立帧
    ...
  3. 与 BTI 的交互:某些内核同时启用了 BTI 和 PAC,函数入口可能是 BTI C + PACIASP 的组合,trampoline 需要正确处理这种情况。


5. hook_install 改用 hotpatch

v2.0 更新hook_installhook_uninstall 现在使用 hotpatch 机制(stop_machine)来安装和卸载 hook,而不是直接修改指令。

旧方式(直接修改):

// 旧实现:直接写入 trampoline 跳转指令
// 存在多核并发执行的风险
*(uint32_t *)target = branch_insn;
flush_icache_range(target, target + 4);

新方式(hotpatch / stop_machine):

// 新实现:通过 stop_machine 停止所有 CPU
// 确保修改指令时没有其他 CPU 正在执行该区域
hook_install(hook) {
    // 1. 准备 trampoline 代码
    prepare_trampoline(hook);
    
    // 2. 通过 hotpatch 安全地替换指令
    //    stop_machine 会暂停所有其他 CPU
    //    修改完成后恢复执行
    hotpatch_install(hook->origin_addr, hook->tramp_insts, 
                     hook->tramp_insts_num * 4);
}

为什么改用 hotpatch?

  • 安全性:避免其他 CPU 正在执行被修改的指令区域时发生不可预测的行为
  • 原子性stop_machine 确保修改过程中没有并发执行
  • 兼容性:某些 ARM64 实现对非对齐的指令修改有严格要求
  • 可靠性:消除了竞态条件导致的偶发崩溃

总结

重定位机制的核心思想

  1. 识别 PC 相对指令:通过操作码和掩码匹配
  2. 计算绝对地址:从相对偏移还原真实目标
  3. 生成绝对跳转:使用 LDR + RET/BR 模式
  4. 保持语义一致:条件跳转保留条件,BL 保留返回地址

重定位的优势

  • 无距离限制(64 位地址空间)
  • 支持所有 ARM64 指令
  • 保持原始功能完整性

代码膨胀的代价

  • 每条 PC 相对指令膨胀 4-8 倍
  • 但总体影响很小(只复制少量指令)

下一篇06-启动劫持流程.md - 详解 setup_entry 如何接管内核启动


文档版本:2.0
最后更新:2026-06-26

评论

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

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