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

KPM 模块系统详解

2026-06-29 · 阅读 0

概述

KPM (KernelPatch Module) 是 KernelPatch 的模块系统,允许在运行时动态加载内核模块,无需内核源码树。

核心特性

  • ELF 格式的内核模块
  • 无需内核源码树编译
  • 支持热加载和热卸载
  • 可以调用 KernelPatch 导出的符号
  • 支持 hook 和系统调用拦截

核心文件

  • kernel/patch/module/module.c(565 行)- 模块加载器
  • kernel/patch/module/relo.c(328 行)- ELF 重定位
  • kernel/include/kpmodule.h - KPM 开发接口

KPM 结构

KPM 元数据

// kernel/include/kpmodule.h

// 定义模块信息的宏
#define KPM_NAME(name) \
    const char __kpm_name[] __section(.kpm.info) = name

#define KPM_VERSION(ver) \
    const char __kpm_version[] __section(.kpm.info) = ver

#define KPM_LICENSE(lic) \
    const char __kpm_license[] __section(.kpm.info) = lic

#define KPM_AUTHOR(author) \
    const char __kpm_author[] __section(.kpm.info) = author

#define KPM_DESCRIPTION(desc) \
    const char __kpm_description[] __section(.kpm.info) = desc

KPM 回调函数

// 初始化回调
typedef long (*kpm_init_t)(const char *args, const char *event, void *reserved);
#define KPM_INIT(init_func) \
    const kpm_init_t __kpm_init __section(.kpm.init) = init_func

// 退出回调
typedef long (*kpm_exit_t)(void *reserved);
#define KPM_EXIT(exit_func) \
    const kpm_exit_t __kpm_exit __section(.kpm.exit) = exit_func

// 控制回调
typedef long (*kpm_control0_t)(const char *args, char __user *out_msg, int outlen);
#define KPM_CTL0(ctl0_func) \
    const kpm_control0_t __kpm_ctl0 __section(.kpm.ctl0) = ctl0_func

typedef long (*kpm_control1_t)(void *a1, void *a2, void *a3);
#define KPM_CTL1(ctl1_func) \
    const kpm_control1_t __kpm_ctl1 __section(.kpm.ctl1) = ctl1_func

KPM 示例:Hello World

完整代码

// kpms/demo-hello/hello.c
#include <compiler.h>
#include <kpmodule.h>
#include <linux/printk.h>
#include <common.h>
#include <kputils.h>
#include <linux/string.h>

// 模块元数据
KPM_NAME("kpm-hello-demo");
KPM_VERSION("1.0.0");
KPM_LICENSE("GPL v2");
KPM_AUTHOR("bmax121");
KPM_DESCRIPTION("KernelPatch Module Example");

// 初始化函数
static long hello_init(const char *args, const char *event, void *__user reserved)
{
    pr_info("kpm hello init, event: %s, args: %s\n", event, args);
    pr_info("kernelpatch version: %x\n", kpver);
    return 0;
}

// 控制函数 0(字符串参数)
static long hello_control0(const char *args, char __user *out_msg, int outlen)
{
    pr_info("kpm hello control0, args: %s\n", args);
    
    char echo[64] = "echo: ";
    strncat(echo, args, 48);
    compat_copy_to_user(out_msg, echo, sizeof(echo));
    
    return 0;
}

// 控制函数 1(指针参数)
static long hello_control1(void *a1, void *a2, void *a3)
{
    pr_info("kpm hello control1, a1: %llx, a2: %llx, a3: %llx\n", a1, a2, a3);
    return 0;
}

// 退出函数
static long hello_exit(void *__user reserved)
{
    pr_info("kpm hello exit\n");
    return 0;
}

// 注册回调
KPM_INIT(hello_init);
KPM_CTL0(hello_control0);
KPM_CTL1(hello_control1);
KPM_EXIT(hello_exit);

编译

# kpms/demo-hello/Makefile
export TARGET_COMPILE=aarch64-none-elf-

CC = $(TARGET_COMPILE)gcc
LD = $(TARGET_COMPILE)ld

hello.kpm: hello.o
	$(LD) -r -T hello.lds hello.o -o hello.kpm

hello.o: hello.c
	$(CC) -c hello.c -I../../kernel/include -o hello.o

链接脚本

/* kpms/demo-hello/hello.lds */
SECTIONS {
    /DISCARD/ : {
        *(.discard)
        *(.discard.*)
        *(.modinfo)
    }
    
    .kpm.info : { *(.kpm.info) }
    .kpm.init : { *(.kpm.init) }
    .kpm.exit : { *(.kpm.exit) }
    .kpm.ctl0 : { *(.kpm.ctl0) }
    .kpm.ctl1 : { *(.kpm.ctl1) }
    
    .text : { *(.text .text.*) }
    .rodata : { *(.rodata .rodata.*) }
    .data : { *(.data .data.*) }
    .bss : { *(.bss .bss.*) }
}

模块加载流程

load_module() - 主函数

// kernel/patch/module/module.c:280-390
int load_module(const char *img, size_t img_size, const char *args, 
                const char *event, void *reserved)
{
    kpm_t *kpm = NULL;
    int rc = 0;
    
    
    // ============ 阶段 1:解析 ELF ============
    
    Elf64_Ehdr *ehdr = (Elf64_Ehdr *)img;
    
    // 验证 ELF 魔数
    if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
        pr_err("invalid ELF magic\n");
        return -ENOEXEC;
    }
    
    // 验证架构
    if (ehdr->e_machine != EM_AARCH64) {
        pr_err("invalid architecture: %d\n", ehdr->e_machine);
        return -ENOEXEC;
    }
    
    Elf64_Shdr *sechdrs = (Elf64_Shdr *)(img + ehdr->e_shoff);
    const char *secstrs = img + sechdrs[ehdr->e_shstrndx].sh_offset;
    
    
    // ============ 阶段 2:提取模块信息 ============
    
    kpm_info_t info = { 0 };
    rc = get_kpm_info(img, img_size, &info);
    if (rc) {
        pr_err("get kpm info error: %d\n", rc);
        return rc;
    }
    
    pr_info("loading kpm: %s, version: %s\n", info.name, info.version);
    
    // 检查是否已加载
    kpm_t *old_kpm = get_module(info.name);
    if (old_kpm) {
        pr_warn("kpm %s already loaded\n", info.name);
        return -EEXIST;
    }
    
    
    // ============ 阶段 3:分配 kpm 结构 ============
    
    kpm = (kpm_t *)kpmalloc(sizeof(kpm_t));
    if (!kpm) return -ENOMEM;
    
    memset(kpm, 0, sizeof(kpm_t));
    strncpy(kpm->name, info.name, KPM_NAME_LEN - 1);
    strncpy(kpm->version, info.version, KPM_VER_LEN - 1);
    strncpy(kpm->license, info.license, KPM_LIC_LEN - 1);
    strncpy(kpm->author, info.author, KPM_AUT_LEN - 1);
    strncpy(kpm->description, info.description, KPM_DESC_LEN - 1);
    
    
    // ============ 阶段 4:计算内存大小 ============
    
    unsigned long core_size = 0;
    unsigned long init_size = 0;
    
    for (int i = 0; i < ehdr->e_shnum; i++) {
        Elf64_Shdr *shdr = &sechdrs[i];
        const char *name = secstrs + shdr->sh_name;
        
        if (!(shdr->sh_flags & SHF_ALLOC)) continue;  // 跳过非分配段
        
        // 对齐
        if (shdr->sh_addralign) {
            core_size = align_ceil(core_size, shdr->sh_addralign);
        }
        
        shdr->sh_entsize = core_size;  // 记录段的运行时偏移
        core_size += shdr->sh_size;
    }
    
    
    // ============ 阶段 5:分配内存 ============
    
    void *core_addr = kpmalloc(core_size);
    if (!core_addr) {
        rc = -ENOMEM;
        goto err_free_kpm;
    }
    
    memset(core_addr, 0, core_size);
    kpm->core_layout.base = core_addr;
    kpm->core_layout.size = core_size;
    
    
    // ============ 阶段 6:复制段内容 ============
    
    for (int i = 0; i < ehdr->e_shnum; i++) {
        Elf64_Shdr *shdr = &sechdrs[i];
        
        if (!(shdr->sh_flags & SHF_ALLOC)) continue;
        
        shdr->sh_addr = (uint64_t)core_addr + shdr->sh_entsize;
        
        if (shdr->sh_type != SHT_NOBITS) {
            // 复制段内容(非 BSS)
            memcpy((void *)shdr->sh_addr, img + shdr->sh_offset, shdr->sh_size);
        }
    }
    
    
    // ============ 阶段 7:符号解析 ============
    
    for (int i = 0; i < ehdr->e_shnum; i++) {
        if (sechdrs[i].sh_type != SHT_SYMTAB) continue;
        
        Elf64_Sym *syms = (Elf64_Sym *)sechdrs[i].sh_addr;
        const char *strtab = (const char *)sechdrs[sechdrs[i].sh_link].sh_addr;
        int num_syms = sechdrs[i].sh_size / sizeof(Elf64_Sym);
        
        for (int j = 0; j < num_syms; j++) {
            Elf64_Sym *sym = &syms[j];
            const char *name = strtab + sym->st_name;
            
            if (sym->st_shndx == SHN_UNDEF) {
                // 未定义符号,需要解析
                unsigned long addr = kallsyms_lookup_name(name);
                if (!addr) {
                    // 尝试 KernelPatch 导出的符号
                    addr = get_preset_symbol_addr(name);
                }
                
                if (!addr) {
                    pr_err("unresolved symbol: %s\n", name);
                    rc = -ENOENT;
                    goto err_free_mem;
                }
                
                sym->st_value = addr;
            } else if (sym->st_shndx < ehdr->e_shnum) {
                // 已定义符号,计算地址
                sym->st_value = sechdrs[sym->st_shndx].sh_addr + sym->st_value;
            }
        }
    }
    
    
    // ============ 阶段 8:重定位 ============
    
    for (int i = 0; i < ehdr->e_shnum; i++) {
        if (sechdrs[i].sh_type != SHT_RELA) continue;
        
        unsigned int symindex = sechdrs[i].sh_link;
        unsigned int relsec = sechdrs[i].sh_info;
        
        rc = apply_relocate_add(sechdrs, strtab, symindex, relsec, kpm);
        if (rc) {
            pr_err("apply relocation error: %d\n", rc);
            goto err_free_mem;
        }
    }
    
    
    // ============ 阶段 9:提取回调函数 ============
    
    for (int i = 0; i < ehdr->e_shnum; i++) {
        const char *name = secstrs + sechdrs[i].sh_name;
        
        if (!strcmp(name, ".kpm.init")) {
            kpm->init = *(kpm_init_t *)sechdrs[i].sh_addr;
        } else if (!strcmp(name, ".kpm.exit")) {
            kpm->exit = *(kpm_exit_t *)sechdrs[i].sh_addr;
        } else if (!strcmp(name, ".kpm.ctl0")) {
            kpm->control0 = *(kpm_control0_t *)sechdrs[i].sh_addr;
        } else if (!strcmp(name, ".kpm.ctl1")) {
            kpm->control1 = *(kpm_control1_t *)sechdrs[i].sh_addr;
        }
    }
    
    
    // ============ 阶段 10:调用初始化函数 ============
    
    if (kpm->init) {
        rc = kpm->init(args, event, reserved);
        if (rc) {
            pr_err("kpm init error: %d\n", rc);
            goto err_free_mem;
        }
    }
    
    
    // ============ 阶段 11:添加到模块列表 ============
    
    add_module(kpm);
    
    pr_info("kpm %s loaded successfully\n", info.name);
    return 0;
    
err_free_mem:
    kpmfree(core_addr);
err_free_kpm:
    kpmfree(kpm);
    return rc;
}

符号解析

get_preset_symbol_addr() - KernelPatch 导出的符号

// kernel/base/symbol.c
unsigned long get_preset_symbol_addr(const char *name)
{
    // 遍历 .kp.symbol 段
    const char *sym_start = (const char *)_kp_symbol_start;
    const char *sym_end = (const char *)_kp_symbol_end;
    
    for (const char *p = sym_start; p < sym_end;) {
        const char *sym_name = p;
        p += strlen(sym_name) + 1;
        
        const void **sym_addr = (const void **)p;
        p += sizeof(void *);
        
        if (!strcmp(sym_name, name)) {
            return (unsigned long)*sym_addr;
        }
    }
    
    return 0;
}

导出的符号示例

// kernel/base/start.c
KP_EXPORT_SYMBOL(kallsyms_lookup_name);
KP_EXPORT_SYMBOL(kallsyms_on_each_symbol);
KP_EXPORT_SYMBOL(printk);
KP_EXPORT_SYMBOL(kver);
KP_EXPORT_SYMBOL(kpver);

// kernel/base/hook.c
KP_EXPORT_SYMBOL(hook_wrap4);
KP_EXPORT_SYMBOL(hook_wrap8);
KP_EXPORT_SYMBOL(hook_wrap12);
KP_EXPORT_SYMBOL(hook);
KP_EXPORT_SYMBOL(unhook);

// kernel/patch/common/syscall.c
KP_EXPORT_SYMBOL(sys_call_table);
KP_EXPORT_SYMBOL(syscalln_addr);

ELF 重定位处理

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);
            break;
            
        case R_AARCH64_CALL26:
        case R_AARCH64_JUMP26:
            ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 26, AARCH64_INSN_IMM_26);
            break;
            
        case R_AARCH64_ADR_PREL_PG_HI21:
            ovf = reloc_insn_imm(RELOC_OP_PAGE, loc, val, 12, 21, AARCH64_INSN_IMM_ADR);
            break;
            
        case R_AARCH64_ADD_ABS_LO12_NC:
            overflow_check = false;
            ovf = reloc_insn_imm(RELOC_OP_ABS, loc, val, 0, 12, AARCH64_INSN_IMM_12);
            break;
            
        // ... 其他重定位类型(见文档 05)
        
        default:
            pr_err("unsupported RELA relocation: %llu\n", ELF64_R_TYPE(rel[i].r_info));
            return -ENOEXEC;
        }
        
        if (overflow_check && ovf == -ERANGE) {
            pr_err("overflow in relocation type %d val %llx\n", 
                   (int)ELF64_R_TYPE(rel[i].r_info), val);
            return -ENOEXEC;
        }
    }
    
    return 0;
}

模块管理

kpm_t 结构

// kernel/patch/include/module.h
typedef struct {
    char name[KPM_NAME_LEN];           // 模块名称
    char version[KPM_VER_LEN];         // 版本
    char license[KPM_LIC_LEN];         // 许可证
    char author[KPM_AUT_LEN];          // 作者
    char description[KPM_DESC_LEN];    // 描述
    
    kpm_init_t init;                   // 初始化函数
    kpm_exit_t exit;                   // 退出函数
    kpm_control0_t control0;           // 控制函数 0
    kpm_control1_t control1;           // 控制函数 1
    
    struct {
        void *base;                    // 内存基地址
        unsigned int size;             // 内存大小
    } core_layout;
    
    int state;                         // 模块状态
    struct list_head list;             // 链表节点
} kpm_t;

add_module() - 添加到列表

// kernel/patch/module/module.c:50-60
static LIST_HEAD(kpm_list);
static int kpm_num = 0;

static void add_module(kpm_t *kpm)
{
    kpm->state = KPM_STATE_LOADED;
    list_add_tail(&kpm->list, &kpm_list);
    kpm_num++;
}

unload_module() - 卸载模块

// kernel/patch/module/module.c:400-440
int unload_module(const char *name, void *reserved)
{
    kpm_t *kpm = get_module(name);
    if (!kpm) return -ENOENT;
    
    // 调用退出函数
    if (kpm->exit) {
        int rc = kpm->exit(reserved);
        if (rc) {
            pr_err("kpm exit error: %d\n", rc);
            return rc;
        }
    }
    
    // 从列表移除
    list_del(&kpm->list);
    kpm_num--;
    
    // 释放内存
    kpmfree(kpm->core_layout.base);
    kpmfree(kpm);
    
    pr_info("kpm %s unloaded\n", name);
    return 0;
}

KPM 示例:Inline Hook

demo-inlinehook

// kpms/demo-inlinehook/inlinehook.c
#include <kpmodule.h>
#include <linux/printk.h>
#include <linux/string.h>

KPM_NAME("demo-inlinehook");
KPM_VERSION("1.0.0");
KPM_LICENSE("GPL v2");
KPM_AUTHOR("bmax121");
KPM_DESCRIPTION("Inline hook demo");

// Hook 回调
static void before_execve(hook_fargs3_t *args, void *udata)
{
    const char __user *filename = (const char __user *)args->arg0;
    char buf[256];
    
    long len = compat_strncpy_from_user(buf, filename, sizeof(buf));
    if (len > 0) {
        pr_info("execve: %s\n", buf);
    }
}

static long inlinehook_init(const char *args, const char *event, void *reserved)
{
    // 查找函数地址
    unsigned long execve_addr = kallsyms_lookup_name("__arm64_sys_execve");
    if (!execve_addr) {
        pr_err("kallsyms_lookup_name __arm64_sys_execve failed\n");
        return -1;
    }
    
    // 安装 hook
    hook_err_t err = hook_wrap3((void *)execve_addr, before_execve, 0, 0);
    if (err) {
        pr_err("hook execve error: %d\n", err);
        return err;
    }
    
    pr_info("hook execve success\n");
    return 0;
}

static long inlinehook_exit(void *reserved)
{
    // 卸载 hook
    unsigned long execve_addr = kallsyms_lookup_name("__arm64_sys_execve");
    unhook((void *)execve_addr);
    
    pr_info("unhook execve\n");
    return 0;
}

KPM_INIT(inlinehook_init);
KPM_EXIT(inlinehook_exit);

KPM 示例:Syscall Hook

demo-syscallhook

// kpms/demo-syscallhook/syscallhook.c
#include <kpmodule.h>
#include <linux/printk.h>

KPM_NAME("demo-syscallhook");
KPM_VERSION("1.0.0");
KPM_LICENSE("GPL v2");

static uintptr_t orig_openat = 0;

// 自定义 openat 处理函数
static long my_openat(int dfd, const char __user *filename, int flags, umode_t mode)
{
    char buf[256];
    compat_strncpy_from_user(buf, filename, sizeof(buf));
    
    pr_info("openat: %s, flags: %x\n", buf, flags);
    
    // 调用原始函数
    typedef long (*openat_t)(int, const char __user *, int, umode_t);
    return ((openat_t)orig_openat)(dfd, filename, flags, mode);
}

static long syscallhook_init(const char *args, const char *event, void *reserved)
{
    // 保存原始地址
    orig_openat = sys_call_table[__NR_openat];
    
    // 修改系统调用表
    uint64_t *pte = pgtable_entry_kernel((uint64_t)sys_call_table);
    uint64_t old_pte = *pte;
    *pte = (*pte | PTE_DBM) & ~PTE_RDONLY;
    flush_tlb_kernel_page((uint64_t)sys_call_table);
    
    sys_call_table[__NR_openat] = (uintptr_t)my_openat;
    
    *pte = old_pte;
    flush_tlb_kernel_page((uint64_t)sys_call_table);
    
    pr_info("hook openat success\n");
    return 0;
}

static long syscallhook_exit(void *reserved)
{
    // 恢复原始地址
    uint64_t *pte = pgtable_entry_kernel((uint64_t)sys_call_table);
    uint64_t old_pte = *pte;
    *pte = (*pte | PTE_DBM) & ~PTE_RDONLY;
    flush_tlb_kernel_page((uint64_t)sys_call_table);
    
    sys_call_table[__NR_openat] = orig_openat;
    
    *pte = old_pte;
    flush_tlb_kernel_page((uint64_t)sys_call_table);
    
    pr_info("unhook openat\n");
    return 0;
}

KPM_INIT(syscallhook_init);
KPM_EXIT(syscallhook_exit);

Extra Items 机制

嵌入 KPM 到内核镜像

# 使用 kptools 嵌入
kptools -p -i Image -k kpimg -s mykey \
    -M module.kpm \
    -T kpm \
    -N "my-module" \
    -V "pre-kernel-init" \
    -A "param1=value1 param2=value2" \
    -o Image.patched

自动加载

// kernel/patch/patch.c:101-110
static int extra_event_pre_kernel_init(const patch_extra_item_t *extra, 
                                       const char *args, const void *data, void *udata)
{
    if (extra->type == EXTRA_TYPE_KPM) {
        if (!strcmp(EXTRA_EVENT_PRE_KERNEL_INIT, extra->event) || !extra->event[0]) {
            int rc = load_module(data, extra->con_size, args, EXTRA_EVENT_PRE_KERNEL_INIT, 0);
            log_boot("load kpm: %s, rc: %d\n", extra->name, rc);
        }
    }
    return 0;
}

加载时机

  • paging-init:在 _paging_init 阶段(EXTRA_EVENT_PAGING_INIT,见下文)
  • pre-kernel-init:在 kernel_init 之前(默认,即 EXTRA_EVENT_KPM_DEFAULT
  • post-kernel-init:在 kernel_init 之后

extra_event_init 与事件系统

v2.0 更新extra_event_init 现在是一个导出函数,可以被 KPM 模块调用。同时新增了 EXTRA_EVENT_KPM_DEFAULT 概念和 EXTRA_EVENT_PAGING_INIT 事件。

extra_event_init() 导出

extra_event_init() 现在通过 KP_EXPORT_SYMBOL(extra_event_init) 导出,KPM 模块可以直接调用它来触发自定义的 extra item 加载事件。这使得模块能够在运行时动态加载其他嵌入的 extra items。

// kernel/patch/patch.c
KP_EXPORT_SYMBOL(extra_event_init);

// KPM 模块中使用
extern int extra_event_init(const char *event);
extra_event_init("my-custom-event");

EXTRA_EVENT_KPM_DEFAULT

EXTRA_EVENT_KPM_DEFAULT 是一个新的事件常量,表示 KPM 的默认加载时机。当 extra item 的 event 字段为空或等于 EXTRA_EVENT_KPM_DEFAULT 时,模块将在 pre-kernel-init 阶段加载。这提供了一个明确的语义,替代了之前依赖空字符串的隐式行为。

EXTRA_EVENT_PAGING_INIT

v2.0 新增EXTRA_EVENT_PAGING_INIT 事件现在在 patch() 函数中被触发。

patch() 执行时,它会遍历所有 extra items,对标记为 paging-init 事件的 KPM 模块执行加载。这意味着这些模块在内存管理子系统初始化完成后、但在大部分内核子系统初始化之前就已经运行。

// kernel/patch/patch.c:patch() 中的调用
static void patch()
{
    // ... 其他初始化 ...
    
    // 触发 paging-init 事件
    on_each_extra_item(extra_event_paging_init, NULL);
    
    // ... 继续 hook rest_init 等 ...
}

典型用途

  • 需要在极早期阶段修改内存映射的模块
  • 需要在页表初始化后立即安装 hook 的模块
  • 安全模块需要在其他代码执行前建立保护

模块控制接口

module_control0() - 字符串参数

// kernel/patch/module/module.c:450-470
int module_control0(const char *name, const char *args, 
                    char __user *out_msg, int outlen)
{
    kpm_t *kpm = get_module(name);
    if (!kpm) return -ENOENT;
    
    if (!kpm->control0) return -ENOSYS;
    
    return kpm->control0(args, out_msg, outlen);
}

用户空间调用

char out_msg[256];
sc_kpm_control(key, "module-name", "input args", out_msg, sizeof(out_msg));
printf("Module response: %s\n", out_msg);

模块查询

list_modules() - 列出所有模块

// kernel/patch/module/module.c:500-530
int list_modules(char *buf, int buf_len)
{
    char *pos = buf;
    kpm_t *kpm;
    
    list_for_each_entry(kpm, &kpm_list, list) {
        int len = snprintf(pos, buf_len - (pos - buf), 
                          "%s\n", kpm->name);
        if (len < 0 || pos + len >= buf + buf_len) {
            return -ENOBUFS;
        }
        pos += len;
    }
    
    return pos - buf;
}

输出示例

kpm-hello-demo
demo-inlinehook
demo-syscallhook
my-custom-module

get_module_info() - 获取模块详情

// kernel/patch/module/module.c:540-580
int get_module_info(const char *name, char *buf, int buf_len)
{
    kpm_t *kpm = get_module(name);
    if (!kpm) return -ENOENT;
    
    return snprintf(buf, buf_len,
                    "name=%s\n"
                    "version=%s\n"
                    "license=%s\n"
                    "author=%s\n"
                    "description=%s\n"
                    "base=%llx\n"
                    "size=%x\n"
                    "state=%d\n",
                    kpm->name, kpm->version, kpm->license,
                    kpm->author, kpm->description,
                    kpm->core_layout.base, kpm->core_layout.size,
                    kpm->state);
}

常见问题

问题 1:符号未解析

Error: unresolved symbol: some_func

原因

  • 内核未导出该符号
  • 函数名称错误
  • KernelPatch 未导出该符号

解决

// 使用 kallsyms_lookup_name 手动查找
unsigned long addr = kallsyms_lookup_name("some_func");

问题 2:重定位失败

Error: overflow in relocation type 283

原因

  • 符号地址超出指令的表示范围
  • 例如 BL 指令只能跳转 ±128MB

解决

  • 使用函数指针间接调用
  • 避免全局变量的直接引用

问题 3:模块崩溃

调试方法

// 在 init 函数中添加日志
static long my_init(const char *args, const char *event, void *reserved)
{
    pr_info("Step 1: start init\n");
    
    // ... 每一步都打印
    pr_info("Step 2: lookup symbol\n");
    
    // 使用 dmesg 查看
}

高级技巧

技巧 1:访问内核数据结构

#include <linux/sched.h>

void my_function()
{
    struct task_struct *task = current;
    
    pid_t pid = task->pid;
    char *comm = task->comm;
    
    pr_info("Current: pid=%d, comm=%s\n", pid, comm);
}

技巧 2:调用内核函数

// 方法 1:直接调用(如果有声明)
#include <linux/slab.h>

void *ptr = kmalloc(1024, GFP_KERNEL);
kfree(ptr);

// 方法 2:动态查找
typedef void *(*kmalloc_t)(size_t, gfp_t);
kmalloc_t my_kmalloc = (kmalloc_t)kallsyms_lookup_name("kmalloc");
void *ptr = my_kmalloc(1024, GFP_KERNEL);

技巧 3:Hook 链

// 多个 KPM 可以 hook 同一个函数
// KPM 1
hook_wrap4(func, before1, after1, data1);

// KPM 2(后续加载)
hook_chain_add(func, before2, after2, data2);

// 执行顺序:
// before1 -> before2 -> 原函数 -> after2 -> after1

总结

KPM 系统的优势

  1. 无需内核源码树:独立编译
  2. 动态加载:运行时加载/卸载
  3. 完整功能:可以调用任何内核函数
  4. 符号导出:KernelPatch 提供丰富的接口
  5. 灵活控制:通过 control 接口交互

开发流程

  1. 编写 KPM 源码(.c)
  2. 编译为 ELF(.kpm)
  3. 加载到内核(通过 SuperCall 或嵌入镜像)
  4. 模块自动初始化
  5. 通过 control 接口控制
  6. 卸载(可选)

下一篇12-kpatch用户工具详解.md - 用户空间 CLI 工具的完整实现


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

评论

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

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