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

start 与 patch 阶段详解

2026-06-29 · 阅读 0

概述

本文详解 KernelPatch 的核心初始化阶段。此时 kpimg 已经成功映射到虚拟地址空间,开始执行真正的 patch 工作。

核心文件

  • kernel/base/start.c(617 行)- 初始化和内存保护
  • kernel/patch/patch.c(178 行)- Hook 安装
  • kernel/base/hotpatch.c(157 行)- Hotpatch 机制(0.13.2 新增独立文件)

主要任务

  1. 初始化全局变量和符号系统(可失败,失败则提前返回)
  2. 设置 kpimg 各段的内存保护
  3. 恢复被覆盖的 tcp_init_sock
  4. 安装关键的 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_INITPRE_KERNEL_INITPOST_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_pagealias_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 阶段的核心任务

  1. 初始化环境:设置全局变量、通过 symbol_lookup_anchor 或 preset 定位关键符号(可失败)
  2. 内存保护:细分 kpimg 各段的权限
  3. 恢复原状:恢复被临时覆盖的 tcp_init_sock
  4. 安装 hooks:在关键位置插入回调

patch 阶段的核心任务

  1. 初始化 hotpatch:准备 alias page 用于安全的指令替换
  2. 绕过安全机制:KCFI、SELinux
  3. 安装监控:任务创建、系统调用
  4. 暴露接口:SuperCall 供用户空间使用
  5. 加载模块:通过事件机制在多个阶段加载嵌入的 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

评论

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

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