KPM 模块系统详解
概述
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 系统的优势:
- ✅ 无需内核源码树:独立编译
- ✅ 动态加载:运行时加载/卸载
- ✅ 完整功能:可以调用任何内核函数
- ✅ 符号导出:KernelPatch 提供丰富的接口
- ✅ 灵活控制:通过 control 接口交互
开发流程:
- 编写 KPM 源码(.c)
- 编译为 ELF(.kpm)
- 加载到内核(通过 SuperCall 或嵌入镜像)
- 模块自动初始化
- 通过 control 接口控制
- 卸载(可选)
下一篇:12-kpatch用户工具详解.md - 用户空间 CLI 工具的完整实现
文档版本:2.0
最后更新:2026-06-26
评论
- 还没有评论,来说点什么吧。