start 与 patch 阶段详解
概述
本文详解 KernelPatch 的核心初始化阶段。此时 kpimg 已经成功映射到虚拟地址空间,开始执行真正的 patch 工作。
核心文件:
kernel/base/start.c(617 行)- 初始化和内存保护kernel/patch/patch.c(178 行)- Hook 安装kernel/base/hotpatch.c(157 行)- Hotpatch 机制(0.13.2 新增独立文件)
主要任务:
- 初始化全局变量和符号系统(可失败,失败则提前返回)
- 设置 kpimg 各段的内存保护
- 恢复被覆盖的 tcp_init_sock
- 安装关键的 hooks
start() 函数入口
函数签名和调用
// kernel/base/start.c:604-616
int __attribute__((section(".start.text"))) __noinline
start(uint64_t kimage_voff, uint64_t linear_voff)
{
int rc = 0;
rc = start_init(kimage_voff, linear_voff); // 初始化(可失败)
if (rc) return rc; // 失败则提前返回
prot_myself(); // 设置内存保护
restore_map(); // 恢复 tcp_init_sock
log_regs(); // 记录寄存器(调试)
predata_init(); // 初始化预置数据
symbol_init(); // 初始化符号系统
rc = patch(); // 安装 hooks
return rc;
}
0.13.2 变化:
start_init()返回类型从void改为int,现在可以报告失败start()检查start_init()返回值,若 kallsyms/printk/vsnprintf 解析失败则提前返回- 这使得启动过程更加健壮,避免在关键符号缺失时继续执行导致崩溃
调用来源:
// kernel/base/map.c:251
((start_f)start_va)(data->kimage_voffset, data->linear_voffset);
// start_va 是 kpimg 在新位置的虚拟地址
start_init() - 初始化核心变量
// kernel/base/start.c:496-599
static int start_init(uint64_t kimage_voff, uint64_t linear_voff)
{
unsigned long kallsym_offset = 0;
const char *kallsyms_resolver = "preset";
// ============ 地址偏移 ============
kimage_voffset = kimage_voff;
linear_voffset = linear_voff;
kernel_pa = start_preset.kernel_pa;
kernel_va = kimage_voff + kernel_pa;
kernel_size = start_preset.kernel_size;
runtime_base_addr = (uint64_t)_link_base;
// ============ 更早定位 printk(从 preset 偏移)============
if (start_preset.patch_config.printk) {
printk = (typeof(printk))(kernel_va + start_preset.patch_config.printk);
}
// ============ 定位 kallsyms_lookup_name(多策略)============
// 策略 1:symbol_lookup_anchor 扫描(优先)
if (!kallsym_offset) {
kallsym_offset = resolve_kallsyms_lookup_name_by_symbol_lookup_anchor();
if (kallsym_offset) {
kallsyms_resolver = "symbol_lookup_anchor";
}
}
// 策略 2:回退到 preset offset
if (!kallsym_offset) {
kallsym_offset = start_preset.kallsyms_lookup_name_offset;
}
// 全部失败:返回错误
if (!kallsym_offset) {
log_kallsyms_lookup_name_unresolved();
return -1;
}
start_preset.kallsyms_lookup_name_offset = kallsym_offset;
kallsyms_lookup_name = (typeof(kallsyms_lookup_name))(kernel_va + kallsym_offset);
if (!kallsyms_lookup_name) {
log_kallsyms_lookup_name_unresolved();
return -1;
}
// 通过 kallsyms_lookup_name 查找其他符号
kernel_stext_va = kallsyms_lookup_name("_stext");
// ============ 验证并定位 printk(通过 kallsyms)============
printk = (typeof(printk))kallsyms_lookup_name("printk");
if (!printk) printk = (typeof(printk))kallsyms_lookup_name("_printk");
if (!printk) {
return -1;
}
vsnprintf = (typeof(vsnprintf))kallsyms_lookup_name("vsnprintf");
if (!vsnprintf) {
printk("KP failed to resolve vsnprintf\n");
return -1;
}
// ============ 打印启动信息 ============
log_boot(KERNEL_PATCH_BANNER);
endian = *(unsigned char *)&(uint16_t){ 1 } ? little : big;
setup_header = &start_preset.header;
kver = VERSION(start_preset.kernel_version.major,
start_preset.kernel_version.minor,
start_preset.kernel_version.patch);
kpver = VERSION(setup_header->kp_version.major,
setup_header->kp_version.minor,
setup_header->kp_version.patch);
log_boot("Kernel pa: %llx\n", kernel_pa);
log_boot("Kernel va: %llx\n", kernel_va);
log_boot("Kernel Version: %x\n", kver);
log_boot("KernelPatch Version: %x\n", kpver);
log_boot("KernelPatch Config: %llx\n", setup_header->config_flags);
log_boot("KernelPatch Compile Time: %s\n", setup_header->compile_time);
log_boot("kallsyms_lookup_name offset: %llx (%s)\n",
(uint64_t)start_preset.kallsyms_lookup_name_offset, kallsyms_resolver);
log_boot("KernelPatch link base: %llx, runtime base: %llx\n",
link_base_addr, runtime_base_addr);
kallsyms_on_each_symbol =
(typeof(kallsyms_on_each_symbol))kallsyms_lookup_name("kallsyms_on_each_symbol");
kernel_kallsyms_on_each_match_symbol =
(typeof(kernel_kallsyms_on_each_match_symbol))
kallsyms_lookup_name("kallsyms_on_each_match_symbol");
// ============ 页表参数计算 ============
uint64_t tcr_el1;
asm volatile("mrs %0, tcr_el1" : "=r"(tcr_el1));
uint64_t t1sz = bits(tcr_el1, 21, 16);
va_bits = 64 - t1sz;
uint64_t tg1 = bits(tcr_el1, 31, 30);
page_shift = 12;
if (tg1 == 1) {
page_shift = 14;
} else if (tg1 == 3) {
page_shift = 16;
}
page_size = 1 << page_shift;
page_level = (va_bits - 4) / (page_shift - 3);
uint64_t ttbr1_el1;
asm volatile("mrs %0, ttbr1_el1" : "=r"(ttbr1_el1));
uint64_t baddr = ttbr1_el1 & 0xFFFFFFFFFFFE;
uint64_t page_size_mask = ~(page_size - 1);
pgd_pa = baddr & page_size_mask;
pgd_va = phys_to_virt(pgd_pa);
return 0;
}
0.13.2 关键变化
1. 返回类型:void -> int
函数现在可以在以下情况返回 -1 表示失败:
kallsyms_lookup_name偏移无法解析(扫描和 preset 都失败)kallsyms_lookup_name地址为空printk/_printk都无法找到vsnprintf无法找到
2. symbol_lookup_anchor 机制(新增)
// kernel/base/start.c:111-115
static unsigned long resolve_kallsyms_lookup_name_by_symbol_lookup_anchor()
{
return kp_resolve_symbol_by_lookup_anchor(
kernel_va, kernel_size,
start_preset.sprintf_offset,
start_preset.symbol_lookup_anchor_offset,
"kallsyms_lookup_name");
}
符号解析采用优先级策略:
symbol_lookup_anchor 扫描(优先)
│
├─ 成功 -> 使用扫描结果
│
└─ 失败 -> 回退到 preset offset
│
├─ 成功 -> 使用 preset
│
└─ 失败 -> 返回 -1(致命错误)
这种策略的优势:
symbol_lookup_anchor基于内核二进制的实际内容动态扫描,不依赖固定偏移- 即使内核被轻微修改(如 LTO 优化导致布局变化),仍有可能成功定位
- 包含
symbol_lookup_scan.h头文件提供扫描实现
3. printk 更早定位
// 第一步:从 patch_config 的 preset 偏移获取 printk
if (start_preset.patch_config.printk) {
printk = (typeof(printk))(kernel_va + start_preset.patch_config.printk);
}
// ... 解析 kallsyms_lookup_name ...
// 第二步:通过 kallsyms 重新验证 printk
printk = (typeof(printk))kallsyms_lookup_name("printk");
if (!printk) printk = (typeof(printk))kallsyms_lookup_name("_printk");
好处:即使 kallsyms 解析失败,log_kallsyms_lookup_name_unresolved() 也能通过 preset printk 输出错误信息。
4. 新增日志输出
log_boot("kallsyms_lookup_name offset: %llx (%s)\n",
(uint64_t)start_preset.kallsyms_lookup_name_offset, kallsyms_resolver);
启动日志中会显示 kallsyms 解析所使用的策略("symbol_lookup_anchor" 或 "preset"),便于调试。
新增导出函数
kallsyms_on_each_match_symbol()
// kernel/base/start.c:142-158
int kallsyms_on_each_match_symbol(int (*fn)(void *, unsigned long),
const char *name, void *data)
{
struct kallsyms_match_symbol_context ctx = {
.fn = fn, .name = name, .data = data
};
if (unlikely(!fn || !name)) return 0;
// 优先使用内核原生的 kallsyms_on_each_match_symbol
if (likely(kernel_kallsyms_on_each_match_symbol)) {
return kernel_kallsyms_on_each_match_symbol(fn, name, data);
}
if (unlikely(!kallsyms_on_each_symbol)) return 0;
// 内核 <= 6.1:callback 带 module 参数
if (kver <= VERSION(6, 1, 0)) {
return kallsyms_on_each_symbol(kallsyms_on_each_match_symbol_cb, &ctx);
}
// 内核 > 6.1:callback 无 module 参数
kallsyms_on_each_symbol_nomod_t kallsyms_on_each_symbol_nomod =
(kallsyms_on_each_symbol_nomod_t)kallsyms_on_each_symbol;
return kallsyms_on_each_symbol_nomod(
kallsyms_on_each_match_symbol_cb_nomod, &ctx);
}
KP_EXPORT_SYMBOL(kallsyms_on_each_match_symbol);
兼容性设计:
内核版本判断:
│
├─ 有原生 kallsyms_on_each_match_symbol -> 直接委托
│
├─ kver <= 6.1 -> 使用带 module 参数的 callback
│ callback(data, name, module, addr) -> fn(data, addr)
│
└─ kver > 6.1 -> 使用无 module 参数的 callback
callback(data, name, addr) -> fn(data, addr)
内核 6.1+ 移除了 kallsyms_on_each_symbol 回调中的 module 参数,此函数统一了两种接口。
pgtable_phys() - 虚拟地址到物理地址翻译
// kernel/base/start.c:267-294
uint64_t pgtable_phys(uint64_t pgd, uint64_t va)
{
uint64_t pxd_bits = page_shift - 3;
uint64_t pxd_ptrs = 1u << pxd_bits;
uint64_t pxd_pa = 0;
uint64_t pxd_va = pgd;
__flush_dcache_area((void *)pxd_va, page_size);
for (int64_t lv = 4 - page_level; lv < 4; ++lv) {
uint64_t pxd_shift = pxd_bits * (4 - lv) + 3;
uint64_t pxd_index = (va >> pxd_shift) & (pxd_ptrs - 1);
uint64_t pxd_desc = ((uint64_t *)pxd_va)[pxd_index];
uint8_t valid_table = pxd_desc & 0b11;
if (valid_table == 0b11) { // Table descriptor
pxd_pa = pxd_desc & (((1ul << (48 - page_shift)) - 1) << page_shift);
} else if (valid_table == 0b01) { // Block descriptor
uint64_t bits = (3 - lv) * pxd_bits;
uint64_t block_bits = bits + page_shift;
pxd_pa = (pxd_desc & (((1ul << (48 - block_bits)) - 1) << block_bits)) +
(va & (((1ul << bits) - 1) << page_shift));
break;
} else { // Invalid
return 0;
}
pxd_va = phys_to_virt(pxd_pa);
}
return pxd_pa ? pxd_pa + (va & (page_size - 1)) : 0;
}
KP_EXPORT_SYMBOL(pgtable_phys);
与 pgtable_entry() 的区别:
pgtable_entry()返回 PTE 条目的虚拟地址(指向页表条目本身),用于修改页表pgtable_phys()返回目标虚拟地址对应的物理地址,用于地址翻译pgtable_phys()正确处理 block mapping(大页),将 block 基地址与页内偏移组合
两者都被 KP_EXPORT_SYMBOL 导出,供 KPM 模块和 hotpatch 使用。
prot_myself() - 设置内存保护
// kernel/base/start.c:296-415
static void prot_myself()
{
uint64_t *kpte = pgtable_entry_kernel(kernel_stext_va);
log_boot("Kernel stext prot: %llx\n", *kpte);
// ============ 计算 kpimg 内存范围 ============
_kp_region_start = (uint64_t)_kp_text_start;
_kp_region_end = (uint64_t)_kp_end +
align_ceil(start_preset.extra_size, page_size) +
HOOK_ALLOC_SIZE + // 1 MB
MEMORY_ROX_SIZE + // 4 MB
MEMORY_RW_SIZE; // 2 MB
log_boot("Region: %llx, %llx\n", _kp_region_start, _kp_region_end);
uint64_t *kppte = pgtable_entry_kernel(_kp_region_start);
log_boot("KernelPatch start prot: %llx\n", *kppte);
// ============ 设置 text/rodata 段:R-X ============
uint64_t text_start = (uint64_t)_kp_text_start;
uint64_t text_end = (uint64_t)_kp_text_end;
uint64_t align_text_end = align_ceil(text_end, page_size);
log_boot("Text: %llx, %llx\n", text_start, text_end);
for (uint64_t i = text_start; i < align_text_end; i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
*pte = (*pte | PTE_SHARED) & ~PTE_PXN & ~PTE_GP;
if (has_vmalloc_area()) {
*pte = (*pte | PTE_RDONLY) & ~PTE_DBM; // 只读
}
}
flush_tlb_kernel_range(text_start, align_text_end);
// ============ 设置 data/bss 段:RW- ============
uint64_t data_start = (uint64_t)_kp_data_start;
uint64_t data_end = (uint64_t)_kp_data_end;
uint64_t align_data_end = align_ceil(data_end, page_size);
log_boot("Data: %llx, %llx\n", data_start, data_end);
for (uint64_t i = data_start; i < align_data_end; i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
*pte = (*pte | PTE_DBM | PTE_SHARED) & ~PTE_RDONLY; // 可读写
if (has_vmalloc_area()) {
*pte |= PTE_PXN; // 不可执行
}
}
flush_tlb_kernel_range(data_start, align_data_end);
// ============ 设置 extra data 段:RW- ============
_kp_extra_start = (uint64_t)_kp_end;
_kp_extra_end = _kp_extra_start + start_preset.extra_size;
uint64_t align_extra_end = align_ceil(_kp_extra_end, page_size);
log_boot("Extra: %llx, %llx\n", _kp_extra_start, _kp_extra_end);
for (uint64_t i = _kp_extra_start; i < align_extra_end; i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
*pte = (*pte | PTE_DBM | PTE_SHARED) & ~PTE_RDONLY;
if (has_vmalloc_area()) {
*pte |= PTE_PXN;
}
}
flush_tlb_kernel_range(_kp_extra_start, align_extra_end);
// ============ 设置 hook 区域:RWX ============
_kp_hook_start = align_extra_end;
_kp_hook_end = _kp_hook_start + HOOK_ALLOC_SIZE;
log_boot("Hook: %llx, %llx\n", _kp_hook_start, _kp_hook_end);
for (uint64_t i = _kp_hook_start; i < _kp_hook_end; i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
*pte = (*pte | PTE_DBM | PTE_SHARED) & ~PTE_PXN & ~PTE_RDONLY & ~PTE_GP;
// RWX:可读、可写、可执行
}
flush_tlb_kernel_range(_kp_hook_start, _kp_hook_end);
hook_mem_add(_kp_hook_start, HOOK_ALLOC_SIZE); // 添加到 hook 内存池
// ============ 设置 rw_mem 区域:RW- ============
_kp_rw_start = _kp_hook_end;
_kp_rw_end = _kp_rw_start + MEMORY_RW_SIZE;
log_boot("RW: %llx, %llx\n", _kp_rw_start, _kp_rw_end);
for (uint64_t i = _kp_rw_start; i < _kp_rw_end; i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
*pte = (*pte | PTE_DBM | PTE_SHARED) & ~PTE_RDONLY;
if (has_vmalloc_area()) {
*pte |= PTE_PXN;
}
}
flush_tlb_kernel_range(_kp_rw_start, _kp_rw_end);
kp_rw_mem = tlsf_create_with_pool((void *)_kp_rw_start, MEMORY_RW_SIZE);
// ============ 设置 rox_mem 区域:R-X ============
kp_rox_mem = tlsf_malloc(kp_rw_mem, tlsf_size());
tlsf_create(kp_rox_mem);
_kp_rox_start = _kp_rw_end;
_kp_rox_end = _kp_rox_start + MEMORY_ROX_SIZE;
log_boot("ROX: %llx, %llx\n", _kp_rox_start, _kp_rox_end);
tlsf_add_pool(kp_rox_mem, (void *)_kp_rox_start, MEMORY_ROX_SIZE);
for (uint64_t i = _kp_rox_start; i < _kp_rox_end; i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
*pte = (*pte | PTE_SHARED) & ~PTE_PXN & ~PTE_GP;
// R-X:只读可执行
}
flush_tlb_kernel_range(_kp_rox_start, _kp_rox_end);
// ============ 添加到 vmalloc 区域 ============
void (*vm_area_add_early)(struct vm_struct *vm) =
(typeof(vm_area_add_early))kallsyms_lookup_name("vm_area_add_early");
if (vm_area_add_early) {
kp_vm.addr = (void *)_kp_region_start;
kp_vm.phys_addr = kp_kimg_to_phys(_kp_region_start);
kp_vm.size = _kp_region_end - _kp_region_start;
kp_vm.flags = 0x00000044; // VM_ALLOC
kp_vm.caller = (void *)_kp_region_start;
vm_area_add_early(&kp_vm);
log_boot("add vmalloc area: %llx, %llx\n", kp_vm.addr, kp_vm.size);
}
}
内存布局设置
kpimg 内存段划分
虚拟地址空间(start_va 开始):
start_va (0xFFFFFFC00A000000)
+------------------------------+
| _kp_text_start |
| .kp.text (代码段) | R-X(只读可执行)
| - start.o(.start.text) |
| - hook.o(.text) |
| - transit0/4/8/12 |
| - 所有 .text 和 .rodata |
| _kp_text_end |
+------------------------------+ (64KB 对齐)
| _kp_data_start |
| .kp.data (数据段) | RW-(可读写)
| - start_preset_t 实例 |
| - 全局变量 |
| - .kp.symbol (符号表) |
| _kp_data_end |
+------------------------------+ (64KB 对齐)
| _kp_end |
+------------------------------+
| Extra Items | RW-
| - 嵌入的 KPM 模块 |
| - Shell 脚本等 |
+------------------------------+
| Hook 区域 | RWX(全权限)
| - 动态生成的 trampoline | 1 MB
| - hook_chain_t 结构 |
+------------------------------+
| RW Memory Pool | RW-
| - 通用可读写内存 | 2 MB
| - TLSF 分配器管理 |
+------------------------------+
| ROX Memory Pool | R-X
| - 动态代码生成 | 4 MB
| - TLSF 分配器管理 |
+------------------------------+
PTE 标志位详解
// kernel/include/pgtable.h
#define PTE_VALID (1UL << 0) // 有效
#define PTE_TYPE_PAGE (3UL << 0) // 页面类型
#define PTE_USER (1UL << 6) // 用户可访问
#define PTE_RDONLY (1UL << 7) // 只读
#define PTE_SHARED (1UL << 8) // 共享
#define PTE_AF (1UL << 10) // 访问标志
#define PTE_NG (1UL << 11) // 非全局
#define PTE_DBM (1UL << 51) // Dirty Bit Modifier
#define PTE_CONT (1UL << 52) // 连续
#define PTE_PXN (1UL << 53) // Privileged eXecute Never
#define PTE_UXN (1UL << 54) // User eXecute Never
#define PTE_GP (1UL << 50) // Guarded Page
权限组合:
| 段类型 | PTE_RDONLY | PTE_PXN | 效果 |
|---|---|---|---|
| Text (R-X) | 1 | 0 | 只读,内核可执行 |
| Data (RW-) | 0 | 1 | 可读写,不可执行 |
| Hook (RWX) | 0 | 0 | 可读写执行 |
TLSF 内存分配器
初始化
// kernel/base/start.c
// 初始化 RW 内存池
kp_rw_mem = tlsf_create_with_pool((void *)_kp_rw_start, MEMORY_RW_SIZE);
// 在 RW 池中分配 TLSF 控制结构
kp_rox_mem = tlsf_malloc(kp_rw_mem, tlsf_size());
tlsf_create(kp_rox_mem);
// 添加 ROX 内存池
tlsf_add_pool(kp_rox_mem, (void *)_kp_rox_start, MEMORY_ROX_SIZE);
TLSF (Two-Level Segregated Fit):
- 实时内存分配器
- O(1) 分配和释放
- 低碎片化
- 适合内核环境
使用接口:
// 分配可读写内存
void *ptr = tlsf_malloc(kp_rw_mem, size);
tlsf_free(kp_rw_mem, ptr);
// 分配可执行内存(用于动态代码)
void *code = tlsf_malloc(kp_rox_mem, size);
tlsf_free(kp_rox_mem, code);
restore_map() - 恢复 tcp_init_sock
// kernel/base/start.c
static void restore_map()
{
uint64_t start = kernel_va + start_preset.map_offset;
uint64_t end = start + start_preset.map_backup_len;
log_boot("Restore: %llx, %llx\n", start, end);
for (uint64_t i = start; i < align_ceil(end, page_size); i += page_size) {
uint64_t *pte = pgtable_entry_kernel(i);
uint64_t orig = *pte;
// 1. 临时设置为可写
*pte = (orig | PTE_DBM) & ~PTE_RDONLY;
flush_tlb_kernel_page(i);
// 2. 从 map_backup 恢复数据
for (uint64_t j = i; j >= start && j < end && j < i + page_size; j += 8) {
*(uint64_t *)j = *(uint64_t *)(start_preset.map_backup + (j - start));
}
// 3. 恢复原始权限
*pte = orig;
flush_tlb_kernel_page(i);
}
flush_icache_all();
}
为什么逐页处理?
- tcp_init_sock 可能跨越多个页面
- 不同页面的原始权限可能不同
- 必须保持原始的内存保护属性
恢复后:
kernel_va + map_offset (tcp_init_sock):
恢复为原始机器码
可以安全调用 socket 相关函数了!
patch() - 安装 Hooks
// kernel/patch/patch.c:140-177
int patch()
{
linux_libs_symbol_init(); // 初始化库函数符号
linux_misc_symbol_init(); // 初始化杂项符号
hotpatch_symbol_init(); // 初始化 hotpatch 符号(0.13.2 新增)
module_init(); // 初始化模块系统
syscall_init(); // 初始化系统调用 hook
hook_err_t rc = 0;
// ============ Hook 1: panic(调试用)============
unsigned long panic_addr = patch_config->panic;
logkd("panic addr: %llx\n", panic_addr);
if (panic_addr) {
rc = hook_wrap12((void *)panic_addr, before_panic, 0, 0);
log_boot("hook panic rc: %d\n", rc);
}
if (rc) return rc;
// ============ 触发 PAGING_INIT 事件(0.13.2 新增)============
extra_event_init(EXTRA_EVENT_PAGING_INIT);
// ============ Hook 2: rest_init / cgroup_init ============
unsigned long init_addr = patch_config->rest_init;
if (!init_addr) init_addr = patch_config->cgroup_init;
if (init_addr) {
rc = hook_wrap4((void *)init_addr, before_rest_init, 0, (void *)init_addr);
log_boot("hook rest_init rc: %d\n", rc);
}
if (rc) return rc;
// ============ Hook 3: kernel_init ============
unsigned long kernel_init_addr = patch_config->kernel_init;
if (kernel_init_addr) {
rc = hook_wrap4((void *)kernel_init_addr, before_kernel_init, after_kernel_init, 0);
log_boot("hook kernel_init rc: %d\n", rc);
}
return rc;
}
0.13.2 patch() 变化
1. 新增 hotpatch_symbol_init() 调用
在 linux_misc_symbol_init() 之后、module_init() 之前调用。此函数查找内核的 aarch64_insn_patch_text_nosync 符号,为 hotpatch 机制准备必要的内核函数指针。
// kernel/base/hotpatch.c:135-144
void hotpatch_symbol_init()
{
#ifdef INIT_USE_KALLSYMS_LOOKUP_NAME
_hotpatch_symbol_init(0, 0, 0, 0);
#else
kallsyms_on_each_symbol(_hotpatch_symbol_init, 0);
#endif
table_pa_mask = (((1ul << (48 - page_shift)) - 1) << page_shift);
}
2. 新增 extra_event_init(EXTRA_EVENT_PAGING_INIT) 事件
在 panic hook 安装之后、rest_init hook 安装之前触发 PAGING_INIT 事件。这允许 KPM 模块在 paging 初始化完成后、rest_init 之前执行自定义逻辑。
3. extra_event_init() 成为导出函数
// kernel/patch/patch.c:122-128
void extra_event_init(const char *event)
{
if (!event) return;
log_boot("event: %s\n", event);
on_each_extra_item(extra_event_load_kpm, (void *)event);
}
KP_EXPORT_SYMBOL(extra_event_init);
0.12.0 中 KPM 加载逻辑是内联在 before_kernel_init 中的,现在被重构为独立的导出函数,使得:
- KPM 模块可以自定义触发加载事件
- 支持多个生命周期事件(
PAGING_INIT、PRE_KERNEL_INIT、POST_KERNEL_INIT) - 默认事件
EXTRA_EVENT_KPM_DEFAULT提供向后兼容
before_panic() - 调试 Hook
// kernel/patch/patch.c:35-40
void before_panic(hook_fargs12_t *args, void *udata)
{
printk("==== Start KernelPatch for Kernel panic ====\n");
print_bootlog();
printk("==== End KernelPatch for Kernel panic ====\n");
}
作用:
- 内核 panic 时打印 KernelPatch 的启动日志
- 帮助调试启动问题
before_rest_init() - 主初始化 Hook
// kernel/patch/patch.c:63-105
static void before_rest_init(hook_fargs4_t *args, void *udata)
{
int rc = 0;
log_boot("entering init ...\n");
// ============ Hotpatch 初始化(0.13.2 新增,第一步)============
if ((rc = hotpatch_init())) goto out;
log_boot("hotpatch_init done: %d\n", rc);
// ============ 绕过 KCFI ============
if ((rc = bypass_kcfi())) goto out;
log_boot("bypass_kcfi done: %d\n", rc);
// ============ 解析内核结构体 ============
if ((rc = resolve_struct())) goto out;
log_boot("resolve_struct done: %d\n", rc);
// ============ 绕过 SELinux(Android)============
if ((rc = bypass_selinux())) goto out;
log_boot("bypass_selinux done: %d\n", rc);
// ============ 任务监控器 ============
if ((rc = task_observer())) goto out;
log_boot("task_observer done: %d\n", rc);
// ============ 安装 SuperCall ============
rc = supercall_install();
log_boot("supercall_install done: %d\n", rc);
// ============ 初始化 KStorage ============
rc = kstorage_init();
log_boot("kstorage_init done: %d\n", rc);
// ============ 初始化 SU 兼容层 ============
rc = su_compat_init();
log_boot("su_compat_init done: %d\n", rc);
// ============ 解析 pt_regs ============
rc = resolve_pt_regs();
log_boot("resolve_pt_regs done: %d\n", rc);
// ============ Android 特定初始化 ============
#ifdef ANDROID
rc = android_sepolicy_flags_fix();
log_boot("android_sepolicy_flags_fix done: %d\n", rc);
rc = android_user_init();
log_boot("android_user_init done: %d\n", rc);
#endif
out:
return;
}
0.13.2 before_rest_init() 变化
1. hotpatch_init() 成为第一步
// kernel/base/hotpatch.c:146-156
int hotpatch_init()
{
alias_page = vmalloc(page_size); // 分配 alias page
if (alias_page) {
alias_entry = pgtable_entry_kernel((uintptr_t)alias_page);
if (alias_entry) alias_pte = *alias_entry;
}
log_boot("alias_page: %llx\n", alias_page);
log_boot("alias_pte: %llx\n", alias_pte);
return 0;
}
hotpatch_init() 通过 vmalloc 分配一个 alias page,用于安全的代码修改:
- alias page 是一个临时映射,指向需要修改的物理页面
- 通过修改 alias page 的 PTE,可以将其重映射到目标物理地址
- 这样可以在不修改目标页面权限的情况下写入指令
- 是
hotpatch_nosync()实现安全指令替换的基础
2. android_sepolicy_flags_fix() 调用
此函数在 0.12.0 版本中已存在于代码中(#ifdef ANDROID 块内),但文档之前未完整体现。0.13.2 中它被正式声明为前向声明(kernel/patch/patch.c:60),负责修复 Android SEPolicy 相关标志。
初始化顺序(fatal = 失败则跳出):
| 步骤 | 函数 | 致命性 | 说明 |
|---|---|---|---|
| 1 | hotpatch_init() |
fatal | 分配 alias page(0.13.2 新增) |
| 2 | bypass_kcfi() |
fatal | 绕过 KCFI |
| 3 | resolve_struct() |
fatal | 解析内核结构体偏移 |
| 4 | bypass_selinux() |
fatal | 绕过 SELinux |
| 5 | task_observer() |
fatal | 安装任务监控 |
| 6 | supercall_install() |
non-fatal | 安装 SuperCall |
| 7 | kstorage_init() |
non-fatal | 初始化 KStorage |
| 8 | su_compat_init() |
non-fatal | 初始化 SU 兼容层 |
| 9 | resolve_pt_regs() |
non-fatal | 解析 pt_regs |
| 10 | android_sepolicy_flags_fix() |
non-fatal | Android SEPolicy 修复 |
| 11 | android_user_init() |
non-fatal | Android 用户空间初始化 |
为什么在 rest_init Hook?
// init/main.c
noinline void __init rest_init(void)
{
// 此时大部分内核已初始化
// 但用户空间进程还未启动
// 是安装 hook 的最佳时机
kernel_thread(kernel_init, ...); // 创建 init 进程
// ...
}
Hotpatch 机制(0.13.2 新增独立文件)
modify_entry_kernel() - 已移至 hotpatch.c
// kernel/base/hotpatch.c:25-42
static void modify_entry_kernel(uintptr_t va, uintptr_t *entry, uintptr_t value)
{
// 非连续页面:直接修改
if (!pte_valid_cont(*entry) && !pte_valid_cont(value)) {
*entry = value;
flush_tlb_kernel_page(va);
return;
}
// 连续页面(CONT_PTE):需要修改整组权限
uintptr_t prot = value & ~table_pa_mask;
uintptr_t *p = (uintptr_t *)((uintptr_t)entry & ~(sizeof(entry) * CONT_PTES - 1));
for (int i = 0; i < CONT_PTES; ++i, ++p) {
*p = (*p & table_pa_mask) | prot;
}
*entry = value;
va &= CONT_PTE_MASK;
flush_tlb_kernel_range(va, va + CONT_PTES * page_size);
}
此函数原来在 start.c 中,0.13.2 将其移到了独立的 hotpatch.c 文件。
hotpatch_nosync() - 安全指令替换
// kernel/base/hotpatch.c:44-68
int hotpatch_nosync(void *addr, uint32_t value)
{
uintptr_t tp = (uintptr_t)addr;
if (tp & 0x3) return -EINVAL; // 4 字节对齐检查
// 优先路径:使用 alias page + 内核原生 patch 函数
if (kfunc(aarch64_insn_patch_text_nosync) && alias_pte) {
uintptr_t phys = pgtable_phys_kernel(tp); // 使用 pgtable_phys
if (!phys) return -EFAULT;
// 将 alias page 重映射到目标物理页面
*alias_entry = (alias_pte & ~table_pa_mask) | (phys & ~(page_size - 1));
dsb(ish);
void *alias_addr = alias_page + (tp & (page_size - 1));
int rc = kfunc(aarch64_insn_patch_text_nosync)(alias_addr, value);
// 恢复 alias page 原始映射
*alias_entry = alias_pte;
dsb(ish);
if (!rc) return rc;
}
// 回退路径:直接修改页表权限
uintptr_t *entry = pgtable_entry_kernel(tp);
if (!entry) return -EFAULT;
uintptr_t ori_prot = *entry;
modify_entry_kernel(tp, entry, (ori_prot | PTE_DBM) & ~PTE_RDONLY);
*(uint32_t *)tp = value;
modify_entry_kernel(tp, entry, ori_prot);
flush_icache_all();
return 0;
}
KP_EXPORT_SYMBOL(hotpatch_nosync);
Hotpatch 流程:
hotpatch_nosync(addr, value)
|
+-- 有 aarch64_insn_patch_text_nosync 且 alias_pte?
| |
| YES -> pgtable_phys_kernel(addr) 获取物理地址
| | 重映射 alias page -> 目标物理页
| | 通过 alias page 写入指令
| | 恢复 alias page 映射
| | 成功则返回
| |
| NO / 失败 -> 回退路径
|
+-- 回退路径:
pgtable_entry_kernel(addr) 获取 PTE
临时设为可写
直接写入指令
恢复原始权限
刷新 icache
before_kernel_init() / after_kernel_init() - KPM 加载 Hook
// kernel/patch/patch.c:130-138
static void before_kernel_init(hook_fargs4_t *args, void *udata)
{
extra_event_init(EXTRA_EVENT_PRE_KERNEL_INIT);
}
static void after_kernel_init(hook_fargs4_t *args, void *udata)
{
extra_event_init(EXTRA_EVENT_POST_KERNEL_INIT);
}
extra_event_init() 与 KPM 加载
// kernel/patch/patch.c:107-128
static int extra_event_load_kpm(const patch_extra_item_t *extra,
const char *args, const void *data, void *udata)
{
const char *event = (const char *)udata;
if (!event) return 0;
if (extra->type == EXTRA_TYPE_KPM) {
if (!strcmp(event, extra->event) ||
(!extra->event[0] && !strcmp(event, EXTRA_EVENT_KPM_DEFAULT))) {
int rc = load_module(data, extra->con_size, args, event, 0);
log_boot("load kpm: %s, event: %s, rc: %d\n", extra->name, event, rc);
}
}
return 0;
}
void extra_event_init(const char *event)
{
if (!event) return;
log_boot("event: %s\n", event);
on_each_extra_item(extra_event_load_kpm, (void *)event);
}
KP_EXPORT_SYMBOL(extra_event_init);
KPM 加载事件生命周期:
patch() 阶段:
extra_event_init(PAGING_INIT) <- 0.13.2 新增
|
+-- 加载绑定到 paging-init 事件的 KPM
内核 rest_init() -> before_rest_init():
(核心初始化,不加载 KPM)
内核 kernel_init() 之前:
extra_event_init(PRE_KERNEL_INIT)
|
+-- 加载绑定到 pre-kernel-init 事件的 KPM
+-- 加载未指定事件的 KPM(默认事件)
内核 kernel_init() 之后:
extra_event_init(POST_KERNEL_INIT)
|
+-- 加载绑定到 post-kernel-init 事件的 KPM
关键子功能详解
bypass_kcfi() - 绕过 KCFI
// kernel/patch/common/kcfi.c (未完整展示)
int bypass_kcfi()
{
// 查找 KCFI 相关函数
unsigned long report_cfi_failure = patch_config->report_cfi_failure;
unsigned long __cfi_slowpath = patch_config->__cfi_slowpath;
if (report_cfi_failure) {
// Hook report_cfi_failure,使其直接返回
// 或者 NOP 化 CFI 检查指令
}
return 0;
}
KCFI (Kernel Control Flow Integrity):
- 编译器插入的控制流检查
- 防止函数指针劫持
- KernelPatch 需要绕过才能 hook
resolve_struct() - 解析内核结构体
int resolve_struct()
{
// 解析 task_struct 的字段偏移
// 不同内核版本,结构体布局不同
// 通过启发式算法推断偏移
resolve_task_struct_offsets();
resolve_cred_offsets();
// ...
return 0;
}
task_observer() - 任务监控器
// Hook copy_process 或 cgroup_post_fork
int task_observer()
{
unsigned long copy_process_addr = patch_config->copy_process;
if (copy_process_addr) {
hook_err_t err = hook_wrap12((void *)copy_process_addr,
before_copy_process,
after_copy_process, 0);
}
return 0;
}
// 在新进程创建时调用
void after_copy_process(hook_fargs12_t *args, void *udata)
{
struct task_struct *task = (struct task_struct *)args->ret;
// 初始化 task_ext(扩展数据)
init_task_ext(task);
// 可以在这里拦截特定进程
// 或者修改进程属性
}
启动日志
log_boot() 实现
// kernel/base/start.c
#define BOOT_LOG_SIZE 0x2000
static char boot_log[BOOT_LOG_SIZE] = { 0 };
static int boot_log_offset = 0;
void log_boot(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
int ret = vsnprintf(boot_log + boot_log_offset,
sizeof(boot_log) - boot_log_offset, fmt, va);
va_end(va);
// 同时输出到内核日志
printk("KP %s", boot_log + boot_log_offset);
boot_log_offset += ret;
}
const char *get_boot_log()
{
return boot_log;
}
用途:
- 记录启动过程中的所有日志
- 如果内核 panic,可以通过 hook panic 打印
- 用户空间可以通过 SuperCall 读取
实际启动日志示例
KP _ __ _ ____ _ _
KP | |/ /___ _ __ _ __ ___| | _ \ __ _| |_ ___| |__
KP | ' // _ \ '__| '_ \ / _ \ | |_) / _` | __/ __| '_ \
KP | . \ __/ | | | | | __/ | __/ (_| | || (__| | | |
KP |_|\_\___|_| |_| |_|\___|_|_| \__,_|\__\___|_| |_|
KP
KP Kernel pa: 80000000
KP Kernel va: ffffffc008000000
KP Kernel Version: 50a95
KP KernelPatch Version: d02
KP KernelPatch Config: 2
KP KernelPatch Compile Time: 10:15:30 Jun 20 2026
KP kallsyms_lookup_name offset: 982b0 (symbol_lookup_anchor)
KP KernelPatch link base: d000, runtime base: ffffffc00a000000
KP Kernel stext prot: 60000000407fd
KP Region: ffffffc00a010000, ffffffc00b710000
KP Text: ffffffc00a010000, ffffffc00a1e8000
KP Data: ffffffc00a1f0000, ffffffc00a220000
KP Extra: ffffffc00a220000, ffffffc00a220000
KP Hook: ffffffc00a220000, ffffffc00a320000
KP RW: ffffffc00a320000, ffffffc00a520000
KP ROX: ffffffc00a520000, ffffffc00a920000
KP add vmalloc area: ffffffc00a010000, 1700000
KP Restore: ffffffc008d73550, ffffffc008d73d50
KP event: paging-init
KP entering init ...
KP alias_page: ffffff80xxxxxxxx
KP alias_pte: xxxxxxxxxx
KP hotpatch_init done: 0
KP bypass_kcfi done: 0
KP resolve_struct done: 0
KP bypass_selinux done: 0
KP task_observer done: 0
KP supercall_install done: 0
KP kstorage_init done: 0
KP su_compat_init done: 0
KP resolve_pt_regs done: 0
KP android_sepolicy_flags_fix done: 0
KP android_user_init done: 0
KP hook panic rc: 0
KP hook rest_init rc: 0
KP hook kernel_init rc: 0
0.13.2 日志变化(相对于 0.12.0):
KernelPatch Version: d02(0.13.2 而非 c00)- 新增
kallsyms_lookup_name offset: ... (symbol_lookup_anchor)行,显示解析策略 - 新增
event: paging-init行(EXTRA_EVENT_PAGING_INIT事件触发) - 新增
alias_page和alias_pte行(hotpatch_init输出) - 新增
hotpatch_init done: 0行(位于 bypass_kcfi 之前)
内存保护验证
pgtable_entry_kernel() - 获取 PTE
// kernel/base/start.c
uint64_t *pgtable_entry(uint64_t pgd, uint64_t va)
{
uint64_t pxd_bits = page_shift - 3;
uint64_t pxd_ptrs = 1u << pxd_bits;
uint64_t pxd_va = pgd;
uint64_t pxd_pa = virt_to_phys(pxd_va);
uint64_t pxd_entry_va = 0;
uint64_t block_lv = 0;
// 刷新 D-cache(重要!)
__flush_dcache_area((void *)pxd_va, page_size);
// 遍历页表层级
for (int64_t lv = 4 - page_level; lv < 4; lv++) {
uint64_t pxd_shift = (page_shift - 3) * (4 - lv) + 3;
uint64_t pxd_index = (va >> pxd_shift) & (pxd_ptrs - 1);
pxd_entry_va = pxd_va + pxd_index * 8;
if (!pxd_entry_va) return 0;
uint64_t pxd_desc = *((uint64_t *)pxd_entry_va);
if ((pxd_desc & 0b11) == 0b11) { // Table
pxd_pa = pxd_desc & (((1ul << (48 - page_shift)) - 1) << page_shift);
} else if ((pxd_desc & 0b11) == 0b01) { // Block
uint64_t block_bits = (3 - lv) * pxd_bits + page_shift;
pxd_pa = pxd_desc & (((1ul << (48 - block_bits)) - 1) << block_bits);
block_lv = lv;
} else { // Invalid
return 0;
}
pxd_va = phys_to_virt(pxd_pa);
if (block_lv) break;
}
return (uint64_t *)pxd_entry_va;
}
符号系统初始化
symbol_init()
// kernel/base/symbol.c
void symbol_init()
{
// 初始化符号导出表
// KernelPatch 导出的符号供 KPM 使用
// 示例:
// KP_EXPORT_SYMBOL(kallsyms_lookup_name);
// KP_EXPORT_SYMBOL(printk);
// KP_EXPORT_SYMBOL(hook_wrap4);
// ...
}
KP_EXPORT_SYMBOL 宏:
// kernel/include/symbol.h
#define KP_EXPORT_SYMBOL(sym) \
const char __kp_symbol_##sym[] __section(.kp.symbol) = #sym; \
const void *__kp_symbol_addr_##sym __section(.kp.symbol) = &sym;
0.13.2 新增导出的符号:
.kp.symbol 段:
"kallsyms_lookup_name" <- 已有
"kallsyms_on_each_symbol" <- 已有
"kallsyms_on_each_match_symbol"<- 0.13.2 新增
"pgtable_entry" <- 已有
"pgtable_phys" <- 0.13.2 新增
"hotpatch_nosync" <- 0.13.2 新增
"hotpatch" <- 0.13.2 新增
"extra_event_init" <- 0.13.2 新增
...
完整的初始化流程图
start(kimage_voff, linear_voff)
|
+-- start_init() [可失败,失败则 start() 提前返回]
| +-- 计算地址偏移
| +-- 从 patch_config.printk 偏移获取早期 printk
| +-- 定位 kallsyms_lookup_name
| | +-- [优先] symbol_lookup_anchor 扫描
| | +-- [回退] preset offset
| | +-- [失败] 返回 -1
| +-- 通过 kallsyms 验证并定位 printk
| +-- 定位 vsnprintf
| +-- 打印启动信息
| +-- 查找 kallsyms_on_each_symbol
| +-- 查找 kallsyms_on_each_match_symbol [0.13.2 新增]
| +-- 计算页表参数
| +-- 返回 0
|
+-- [若 start_init 失败] return rc [0.13.2 新增错误处理]
|
+-- prot_myself()
| +-- 计算 kpimg 内存范围
| +-- 设置 text 段:R-X
| +-- 设置 data 段:RW-
| +-- 设置 extra 段:RW-
| +-- 设置 hook 区:RWX
| +-- 设置 rw_mem:RW-
| +-- 设置 rox_mem:R-X
| +-- 初始化 TLSF 分配器
| +-- 添加到 vmalloc 区域
|
+-- restore_map()
| +-- 从 map_backup 恢复 tcp_init_sock
|
+-- log_regs()
| +-- 记录 CPU 寄存器状态(调试)
|
+-- predata_init()
| +-- 初始化预置数据
|
+-- symbol_init()
| +-- 初始化符号导出表
|
+-- patch()
+-- linux_libs_symbol_init()
+-- linux_misc_symbol_init()
+-- hotpatch_symbol_init() [0.13.2 新增]
+-- module_init()
+-- syscall_init()
|
+-- hook_wrap12(panic, before_panic, 0, 0)
|
+-- extra_event_init(PAGING_INIT) [0.13.2 新增]
| +-- 加载绑定到 paging-init 的 KPM
|
+-- hook_wrap4(rest_init, before_rest_init, 0, 0)
| +-- before_rest_init()
| +-- hotpatch_init() [0.13.2 新增,第一步]
| | +-- vmalloc alias page
| | +-- 获取 alias PTE
| +-- bypass_kcfi()
| +-- resolve_struct()
| +-- bypass_selinux()
| +-- task_observer()
| +-- supercall_install()
| +-- kstorage_init()
| +-- su_compat_init()
| +-- resolve_pt_regs()
| +-- android_sepolicy_flags_fix()
| +-- android_user_init()
|
+-- hook_wrap4(kernel_init, before_kernel_init, after_kernel_init, 0)
+-- before_kernel_init()
| +-- extra_event_init(PRE_KERNEL_INIT)
+-- after_kernel_init()
+-- extra_event_init(POST_KERNEL_INIT)
时间线分析
T0: _paging_init 跳转到 start()
状态:
- kpimg 已映射到虚拟地址
- tcp_init_sock 仍被覆盖
- paging_init 已恢复
T1: start_init() [0.13.2: 可失败]
状态:
- 全局变量已初始化
- printk 从 preset 偏移获取(早期可用)
- kallsyms_lookup_name 通过 symbol_lookup_anchor 或 preset 解析
- 若解析失败,start() 提前返回
T2: prot_myself()
状态:
- 内存保护已设置
- text: R-X, data: RW-, hook: RWX
T3: restore_map()
状态:
- tcp_init_sock 已恢复
- 网络功能可以安全使用
T4: patch()
状态:
- hotpatch 符号已初始化
- PAGING_INIT 事件已触发 [0.13.2 新增]
- 等待 rest_init 被调用
T5: 内核继续启动,调用 rest_init()
触发:before_rest_init()
状态:
- hotpatch_init(alias page 就绪) [0.13.2 新增]
- bypass_kcfi
- bypass_selinux
- supercall_install
- 所有核心功能已初始化
T6: 内核继续启动,调用 kernel_init()
触发:before_kernel_init()
状态:
- 加载绑定到 PRE_KERNEL_INIT 的 KPM
触发:after_kernel_init()
状态:
- 加载绑定到 POST_KERNEL_INIT 的 KPM
T7: 内核启动完成
状态:
- KernelPatch 完全激活
- 用户空间可以使用 SuperCall
- hotpatch 机制可用于安全的指令替换
内存保护的重要性
为什么需要细分权限?
安全性:
// 如果所有内存都是 RWX:
// - 方便开发
// - 任何代码注入漏洞都可以执行
// - 无法检测到内存损坏
正确的分段保护:
// text 段:R-X
// - 代码无法被修改
// - 防止代码注入攻击
// - 符合 W^X 原则
// data 段:RW-
// - 数据可以修改
// - 数据无法执行(防止数据执行攻击)
// hook 段:RWX
// - 允许动态生成代码(trampoline)
// - 需要严格管理(只用于 hook)
vmalloc 区域注册
// kernel/base/start.c
void (*vm_area_add_early)(struct vm_struct *vm) =
(typeof(vm_area_add_early))kallsyms_lookup_name("vm_area_add_early");
if (vm_area_add_early) {
kp_vm.addr = (void *)_kp_region_start;
kp_vm.phys_addr = kp_kimg_to_phys(_kp_region_start);
kp_vm.size = _kp_region_end - _kp_region_start;
kp_vm.flags = 0x00000044; // VM_ALLOC
kp_vm.caller = (void *)_kp_region_start;
vm_area_add_early(&kp_vm);
}
为什么注册?
- 让内核知道这块内存已被使用
- 避免 vmalloc 重复分配
- 在
/proc/vmallocinfo中可见 - 帮助调试和监控
总结
start 阶段的核心任务:
- 初始化环境:设置全局变量、通过 symbol_lookup_anchor 或 preset 定位关键符号(可失败)
- 内存保护:细分 kpimg 各段的权限
- 恢复原状:恢复被临时覆盖的 tcp_init_sock
- 安装 hooks:在关键位置插入回调
patch 阶段的核心任务:
- 初始化 hotpatch:准备 alias page 用于安全的指令替换
- 绕过安全机制:KCFI、SELinux
- 安装监控:任务创建、系统调用
- 暴露接口:SuperCall 供用户空间使用
- 加载模块:通过事件机制在多个阶段加载嵌入的 KPM
0.13.2 关键改进汇总:
| 改进 | 说明 |
|---|---|
start_init() 可失败 |
返回 int,解析失败返回 -1,start() 提前退出 |
| symbol_lookup_anchor | 优先基于内核二进制扫描定位 kallsyms_lookup_name |
| 更早的 printk | 从 patch_config.printk 偏移获取,早于 kallsyms 解析 |
| hotpatch 机制 | 独立的 hotpatch.c,alias page 实现安全指令替换 |
modify_entry_kernel 移动 |
从 start.c 移至 hotpatch.c |
pgtable_phys() 导出 |
虚拟地址到物理地址翻译,供 hotpatch 使用 |
kallsyms_on_each_match_symbol() |
统一 6.1+ 和旧内核的符号遍历接口 |
extra_event_init() 导出 |
统一的 KPM 事件加载机制 |
| PAGING_INIT 事件 | 新增 paging 初始化完成事件 |
此阶段完成后:
- KernelPatch 完全激活
- 内核继续正常启动
- 所有 hooks 就绪
- hotpatch 机制可用于运行时指令修改
- 用户空间工具可以使用
下一篇:09-hook机制详解.md - 深入剖析 inline hook 的完整实现
文档版本:2.0
最后更新:2026-06-26
对应 KernelPatch 版本:0.13.2
评论
- 还没有评论,来说点什么吧。