ARM64 重定位机制详解
概述
重定位(Relocation) 是 KernelPatch 中最核心的技术之一。它解决了一个关键问题:
ARM64 架构大量使用 PC 相对寻址,当代码被复制到其他位置时,如何保证这些指令仍然正确工作?
KernelPatch 中的重定位主要用于两个场景:
- Inline Hook:将被 hook 的指令复制到 trampoline
- 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
...
加载时重定位:
解析符号:
// 在 KernelPatch 的符号表中查找 kallsyms_lookup_name 地址 = 0xFFFFFFC008982B0应用重定位:
// 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 现在正确处理 PACIASP(
0xD503233F)和 PACIBSP(0xD503237F)指令。这些指令属于 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 的目标函数以 PACIASP 或 PACIBSP 开头时,需要特殊处理:
trampoline 中的处理:PACIASP/PACIBSP 在原始函数入口执行 PAC 签名,如果被复制到 trampoline 中,签名时的 SP 值可能不同,导致后续
AUTIASP/AUTIBSP验证失败。因此在 trampoline 中需要保留这些指令但调整上下文。TRAMPOLINE_MAX_NUM 增大的原因:从 4 增大到 6 正是为了容纳 PAC 指令的额外处理空间。带有 PAC 指令的函数序言通常是:
PACIASP ; PAC 签名 STP X29, X30, [SP, #-16]! ; 保存帧指针和链接寄存器 MOV X29, SP ; 建立帧 ...与 BTI 的交互:某些内核同时启用了 BTI 和 PAC,函数入口可能是
BTI C+PACIASP的组合,trampoline 需要正确处理这种情况。
5. hook_install 改用 hotpatch
v2.0 更新:
hook_install和hook_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 实现对非对齐的指令修改有严格要求
- 可靠性:消除了竞态条件导致的偶发崩溃
总结
重定位机制的核心思想:
- ✅ 识别 PC 相对指令:通过操作码和掩码匹配
- ✅ 计算绝对地址:从相对偏移还原真实目标
- ✅ 生成绝对跳转:使用
LDR + RET/BR模式 - ✅ 保持语义一致:条件跳转保留条件,BL 保留返回地址
重定位的优势:
- 无距离限制(64 位地址空间)
- 支持所有 ARM64 指令
- 保持原始功能完整性
代码膨胀的代价:
- 每条 PC 相对指令膨胀 4-8 倍
- 但总体影响很小(只复制少量指令)
下一篇:06-启动劫持流程.md - 详解 setup_entry 如何接管内核启动
文档版本:2.0
最后更新:2026-06-26
评论
- 还没有评论,来说点什么吧。