获取任意 Linux Kernel 函数

  • 内核模块开发中 获取任意 Kernel 函数地址

  • 资料来源:

    https://github.com/alibaba/diagnose-tools

  • 更新

    1
    2023.12.14 初始

导语

算是一点总结吧,最近则俩月杠上内核模块了, 学了不少东西,也还在坑底没啥起色呢…

正文

方法和代码均来自 diagnose-tools.

原本在内核模块开发中可以使用 kallsyms_lookup_name 获取到那些内核未导出的函数和变量地址, 但是 Kernel 5.7 以后, 这个函数不再是 export 了, 获取任意内核函数就成了个问题.

解决方法: 鸡生蛋还是蛋生急的问题, 使用 kprobe 查找到 kallsyms_lookup_name, 再操作 kallsyms_lookup_name 得到其他函数地址.

  • kprobe 是内核提供的动态追踪机制.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <linux/kprobes.h>

unsigned long (*diag_kallsyms_lookup_name)(const char *name);
struct kprobe kprobe_kallsyms_lookup_name = {.symbol_name = "kallsyms_lookup_name"};

/**
* @brief diag_kallsyms_lookup_name init
*
* @return int
*/
static int fn_kallsyms_lookup_name_init(void) {
register_kprobe(&kprobe_kallsyms_lookup_name);
diag_kallsyms_lookup_name = (void *)kprobe_kallsyms_lookup_name.addr;
unregister_kprobe(&kprobe_kallsyms_lookup_name);

printk(KERN_INFO "diag_kallsyms_lookup_name is %p\n",
diag_kallsyms_lookup_name);

if (!diag_kallsyms_lookup_name) {
return -EINVAL;
}
return 0;
}

// main
fn_kallsyms_lookup_name_init();

调用 fn_kallsyms_lookup_name_init() 完成 diag_kallsyms_lookup_name 函数初始化.

diagnose-tools 将其封装成了宏, 还带了错误信息提示, 非常方便.

  • LOOKUP_SYMS 会打印错误并终止执行 return.
  • LOOKUP_SYMS_NORET 会打印错误, 但是并不会终止执行.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// form
// https://github.com/alibaba/diagnose-tools/blob/8cd905a1c17f2201e460a2d607413a1303757a32/SOURCE/module/internal.h#L65
// look for current function address, all the function with prefix "orig_".

#define LOOKUP_SYMS(name) \
do { \
orig_##name = (void *)diag_kallsyms_lookup_name(#name); \
if (!orig_##name) { \
printk(KERN_ERR "kallsyms_lookup_name: %s\n", #name); \
return -EINVAL; \
} \
} while (0)

#define LOOKUP_SYMS_NORET(name) \
do { \
orig_##name = (void *)diag_kallsyms_lookup_name(#name); \
if (!orig_##name) \
pr_err("kallsyms_lookup_name: %s\n", #name); \
} while (0)

获取到的函数地址默认以 orig_X 开头, 以 show_stack 为例:

1
2
void (*orig_show_stack)(struct task_struct *task, unsigned long *sp, const char *loglvl);
LOOKUP_SYMS(show_stack); // show_stack

kallsyms_lookup_name 函数不止可以查找函数, 还可以是 全局变量. 只要在内核的符号表中的就好. 内核符号表 在编译时候的是 vmlinux 中, 运行时 则在 /proc/kallsyms 下可以找到.