我目前正在开发一个Linux内核模块,以拦截一些系统调用,从而在系统范围内打印有关它们的统计信息。
我遇到过不同的方法来获取sys_call_table
符号的地址,但还没有找到一种适用于最近的内核(例如5.11(的方法。在旧的内核上,我们不是使用了kallsyms_lookup_name
吗?看起来该符号不再导出。
我可以看看/proc/kallsyms
,但这似乎是个坏主意,不能推广。还有其他选择吗?
免责声明:使用非导出符号通常不是一个好主意,因此您只应将其用于测试/教育目的,而不应用于生产模块/驱动程序。
在Linux v5.7之前,您确实会使用kallsyms_lookup_name()
来查找模块中未导出的内核符号。请参阅如何访问内核模块中的任何内核符号?如果你想知道怎么做。
然而,该符号在v5.7中停止了导出,因为在核心内核代码之外没有人使用它,它只是被模块滥用来查找和使用其他未导出的符号。这里还有一篇相关的LWN文章。如今;适当的方式";为了解决这个问题;黑客;你可以考虑。
以下方法涵盖了内核函数和全局对象(即全局变量(:
-
如果您已经在编译内核,您可以在感兴趣的符号定义后添加
EXPORT_SYMBOL()
。如果您愿意修改内核并构建自定义内核,这是最简单的选项。您也可以在kernel/kallsyms.c
中导出kallsyms_lookup_name()
,然后根据需要使用它。 -
您可以使用
unsigned long
模块参数,在加载模块时传递所需的符号地址(取自/proc/kallsyms
(,然后将其转换为适当的类型:static unsigned long addr; module_param_named(addr, addr, ulong, 0); MODULE_PARM_DESC(addr, "Address of the `foo` symbol"); static <type_of_foo_here> *foo_ptr; // Examples: // int foo(char *) -> int (*foo_ptr)(char *) // unsigned long foo -> unsigned long *foo_ptr static int __init mymodule_init(void) { foo_ptr = (typeof(foo_ptr))addr; // ... return 0; }
然后你就可以做这样的事情:
sudo insmod mymodule.ko addr=0x$(sudo grep ' some_symbol_name' /proc/kallsyms | cut -d' ' -f1)
-
如果您的内核支持kprobe,那么您可以[ab]使用kprobe使内核查找成为
kprobe_register()
中的一个符号。这种方法在另一个答案中有详细说明。由于kprobes的预期用途,这只适用于函数,但您可以先找到kallsyms_lookup_name()
,然后使用它查找任何其他符号。为了实现这一点,您的内核需要配置
CONFIG_KPROBES=y
和CONFIG_KALLSYMS=y
(根据您想要的符号,可能还有CONFIG_KALLSYMS_ALL=y
(,因为register_kprobe()
在后台使用的正是kallsyms_lookup_name()
。kprobes的自动符号地址解析从Linux v2.6.16开始就得到支持。 -
仅对于函数,您还可以考虑重新实现模块中的功能。例如,在
fs/proc/task_mmu.c
中实现的task_statm()
是一个相当小的函数,它只使用其他导出的函数,因此";借款;在您的模块中使用它会非常简单。很可能您想要调用一些非导出函数的目的比它的设计目的更具体。在这种情况下,一个好主意是查看内核源代码,了解它是如何工作的,并且只重新实现模块所需的最低限度。
-
最后,您可以从技术上使用
filp_open()
+kernel_read()
从<linux/fs.h>
打开并读取内核空间中的/proc/kallsyms
,尽管这可能是客观上最糟糕的解决方案。
我们还可以使用kprobes
找到kallsyms_lookup_name
函数的地址。
报价取自此处(kprobes(
Kprobes使您能够动态闯入任何内核例程,并以无中断的方式收集调试和性能信息。你可以在几乎任何内核代码地址上进行陷阱
要注册kprobe
,首先需要使用需要捕获的符号的名称初始化kprobe
结构。我们可以通过在kprobe
结构中设置symbol_name
来实现这一点。
#include <linux/kprobes.h>
static struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name"
};
kprobe
结构中包含以下元素(为简洁起见,缩写为(:
struct kprobe {
...
/* location of the probe point */
kprobe_opcode_t *addr;
/* Allow user to indicate symbol name of the probe point */
const char *symbol_name;
...
}
随着在structkprobe中引入"symbol_name"字段,probepoint地址解析现在将由内核负责。
一旦设置了symbol_name
,探测点的地址将由内核确定。所以,现在剩下的就是注册探测器,提取探测器点地址,然后注销它:
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
kallsyms_lookup_name_t kallsyms_lookup_name;
register_kprobe(&kp);
kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
unregister_kprobe(&kp);
我们现在有了kallsyms_lookup_name
地址。利用它,我们可以用老式的方法找到sys_call_table
地址:
kallsyms_lookup_name("sys_call_table");
kprobe结构的来源
kprobe技术的来源