获取 Linux 内核模块中非导出内核符号地址的正确方法

Sig*_*erm 7 c system-calls linux-kernel

我目前正在开发一个 Linux 内核模块来拦截一些系统调用,以便在系统范围内打印有关它们的统计信息。

我遇到过获取符号地址的不同方法sys_call_table,但尚未找到适用于最新内核(例如 5.11)的方法。在较旧的内核上,我们不会使用吗kallsyms_lookup_name?看起来该符号不再导出。

我可以看看/proc/kallsyms,但这似乎是一个坏主意并且不能概括。还有什么其他选择?

Mar*_*lli 10

免责声明:使用非导出符号通常不是一个好主意,因此您应该仅将其用于测试/教育目的,而不是用于生产就绪的模块/驱动程序。

在 Linux v5.7 之前,您确实会习惯kallsyms_lookup_name()从模块中查找非导出的内核符号。请参阅如何访问内核模块中的任何内核符号?如果你想知道怎么做。

然而,该符号在 v5.7 中停止导出,因为没有人在核心内核代码之外使用它,并且它只是被模块滥用来查找和使用其他未导出的符号。这里还有一篇相关的 LWN 文章。如今,并没有真正的“正确方法”来解决这个问题,但是您可以考虑许多不同的“技巧”。

以下方法涵盖了内核函数和全局对象(即全局变量):

  1. 如果您已经在编译内核,则可以EXPORT_SYMBOL()在您感兴趣的符号的定义之后添加。如果您愿意修改内核并构建自定义内核,这是最简单的选项。如果您确实需要,也可以导出kallsyms_lookup_name()然后kernel/kallsyms.c使用它。

  2. 您可以使用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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后你就可以做这样的事情:

    sudo insmod mymodule.ko addr=0x$(sudo grep ' some_symbol_name' /proc/kallsyms | cut -d' ' -f1)
    
    Run Code Online (Sandbox Code Playgroud)
  3. 如果您的内核支持kprobes,您可以[ab]使用 kprobe 使内核通过 查找符号kprobe_register()。这个方法在另一个答案中有详细介绍。由于 kprobes 的预期用途,这仅适用于函数,但是您可以简单地kallsyms_lookup_name()先查找,然后使用它来查找任何其他符号。

    为了使其工作,您的内核需要配置为CONFIG_KPROBES=y以及CONFIG_KALLSYMS=y(并且可能还CONFIG_KALLSYMS_ALL=y取决于您想要的符号),因为register_kprobe()完全kallsyms_lookup_name()在幕后使用。自 Linux v2.6.16 起,就支持 kprobes 的自动符号地址解析。

  4. 仅对于函数,您还可以考虑在模块中重新实现该功能。例如,task_statm()实现的fs/proc/task_mmu.c是一个相当小的函数,仅使用其他导出函数,因此“借用”它以在您的模块中使用将相当简单。

    您可能想要调用某些非导出函数来实现比其设计目的更具体的目的。在这种情况下,一个好主意是查看内核源代码以了解它是如何工作的,并且仅重新实现模块所需的最低限度。

  5. 最后,您可以在技术上使用+ from/proc/kallsyms从内核空间打开和读取,尽管这可能是客观上最糟糕的解决方案。filp_open()kernel_read()<linux/fs.h>