【问题标题】:How to dump/list all kernel symbols with addresses from Linux kernel module?如何使用 Linux 内核模块的地址转储/列出所有内核符号?
【发布时间】:2016-06-26 20:59:43
【问题描述】:

在内核模块中,如何列出所有内核符号及其地址? 内核不应重新编译。

我知道接口中的“cat /proc/kallsyms”,但是如何使用kallsyms_lookup_name 之类的函数直接从内核数据结构中获取它们。

【问题讨论】:

    标签: linux-kernel kernel-module


    【解决方案1】:

    示例

    工作模块代码:

    #include <linux/module.h>
    #include <linux/kallsyms.h>
    
    static int prsyms_print_symbol(void *data, const char *namebuf,
                                   struct module *module, unsigned long address)
    {
        pr_info("### %lx\t%s\n", address, namebuf);
        return 0;
    }
    
    static int __init prsyms_init(void)
    {
        kallsyms_on_each_symbol(prsyms_print_symbol, NULL);
        return 0;
    }
    
    static void __exit prsyms_exit(void)
    {
    }
    
    module_init(prsyms_init);
    module_exit(prsyms_exit);
    
    MODULE_AUTHOR("Sam Protsenko");
    MODULE_DESCRIPTION("Module for printing all kernel symbols");
    MODULE_LICENSE("GPL");
    

    说明

    kernel/kallsyms.c 实现/proc/kallsyms。它的一些功能可供外部使用。它们通过EXPORT_SYMBOL_GPL() 宏导出。是的,您的模块应该有GPL 许可证才能使用它。这些功能是:

    • kallsyms_lookup_name()
    • kallsyms_on_each_symbol()
    • sprint_symbol()
    • sprint_symbol_no_offset()

    要使用这些功能,请在您的模块中包含&lt;linux/kallsyms.h&gt;。需要指出的是,必须在内核配置中启用CONFIG_KALLSYMS (=y)。

    要打印所有符号,您显然必须使用 kallsyms_on_each_symbol() 函数。文档接下来对此进行了说明:

    /* Call a function on each kallsyms symbol in the core kernel */
    int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
                                unsigned long), void *data);
    

    其中fn 是您的回调函数,应该为找到的每个交易品种调用,data 是指向您的一些私有数据的指针(将作为第一个参数传递给您的回调函数)。

    回调函数必须有下一个签名:

    int fn(void *data, const char *namebuf, struct module *module,
           unsigned long address);
    

    将为每个带有下一个参数的内核符号调用此函数:

    • data:将包含指向您作为最后一个参数传递给 kallsyms_on_each_symbol() 的私有数据的指针
    • namebuf: 将包含当前内核符号的名称
    • module: 永远是NULL,忽略它
    • address: 将包含当前内核符号的地址

    返回值应始终为0(在非零返回值上,通过符号的迭代将被中断)。

    补充

    回答您评论中的问题。

    另外,有没有办法输出每个函数的大小?

    是的,你可以使用我上面提到的sprint_symbol() 函数来做到这一点。它将以以下格式打印符号信息:

    symbol_name+offset/size [module_name]
    

    例子:

    psmouse_poll+0x0/0x30 [psmouse]
    

    如果符号是内置的,模块名称部分可以省略。

    我尝试了该模块并使用“dmesg”查看结果。但是缺少很多符号,例如“futex_requeue”。输出符号数约为10K,而我使用“nm vmlinux”时为100K。

    这很可能是因为您的printk buffer size 不足以存储上述模块的所有输出。

    让我们稍微改进一下上面的模块,让它通过miscdevice 提供符号信息。还让我们根据要求将函数大小添加到输出中。代码如下:

    #include <linux/device.h>
    #include <linux/fs.h>
    #include <linux/kallsyms.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/sizes.h>
    #include <linux/uaccess.h>
    #include <linux/vmalloc.h>
    
    #define DEVICE_NAME         "prsyms2"
    /* 16 MiB is sufficient to store information about approx. 200K symbols */
    #define SYMBOLS_BUF_SIZE    SZ_16M
    
    struct symbols {
        char *buf;
        size_t pos;
    };
    
    static struct symbols symbols;
    
    /* ---- misc char device definitions ---- */
    
    static ssize_t prsyms2_read(struct file *file, char __user *buf, size_t count,
                                loff_t *pos)
    {
        return simple_read_from_buffer(buf, count, pos, symbols.buf,
                                       symbols.pos);
    }
    
    static const struct file_operations prsyms2_fops = {
        .owner  = THIS_MODULE,
        .read   = prsyms2_read,
    };
    
    static struct miscdevice prsyms2_misc = {
        .minor  = MISC_DYNAMIC_MINOR,
        .name   = DEVICE_NAME,
        .fops   = &prsyms2_fops,
    };
    
    /* ---- module init/exit definitions ---- */
    
    static int prsyms2_store_symbol(void *data, const char *namebuf,
                                    struct module *module, unsigned long address)
    {
        struct symbols *s = data;
        int count;
    
        /* Append address of current symbol */
        count = sprintf(s->buf + s->pos, "%lx\t", address);
        s->pos += count;
    
        /* Append name, offset, size and module name of current symbol */
        count = sprint_symbol(s->buf + s->pos, address);
        s->pos += count;
        s->buf[s->pos++] = '\n';
    
        if (s->pos >= SYMBOLS_BUF_SIZE)
            return -ENOMEM;
    
        return 0;
    }
    
    static int __init prsyms2_init(void)
    {
        int ret;
    
        ret = misc_register(&prsyms2_misc);
        if (ret)
            return ret;
    
        symbols.pos = 0;
        symbols.buf = vmalloc(SYMBOLS_BUF_SIZE);
        if (symbols.buf == NULL) {
            ret = -ENOMEM;
            goto err1;
        }
    
        dev_info(prsyms2_misc.this_device, "Populating symbols buffer...\n");
        ret = kallsyms_on_each_symbol(prsyms2_store_symbol, &symbols);
        if (ret != 0) {
            ret = -EINVAL;
            goto err2;
        }
        symbols.buf[symbols.pos] = '\0';
        dev_info(prsyms2_misc.this_device, "Symbols buffer is ready!\n");
    
        return 0;
    
    err2:
        vfree(symbols.buf);
    err1:
        misc_deregister(&prsyms2_misc);
        return ret;
    }
    
    static void __exit prsyms2_exit(void)
    {
        vfree(symbols.buf);
        misc_deregister(&prsyms2_misc);
    }
    
    module_init(prsyms2_init);
    module_exit(prsyms2_exit);
    
    MODULE_AUTHOR("Sam Protsenko");
    MODULE_DESCRIPTION("Module for printing all kernel symbols");
    MODULE_LICENSE("GPL");
    

    下面是如何使用它:

    $ sudo insmod prsyms2.ko
    $ sudo cat /dev/prsyms2 >symbols.txt
    $ wc -l symbols.txt
    $ sudo rmmod prsyms2
    

    文件symbols.txt 将包含下一种格式的所有内核符号(包括内置的和加载的模块):

    ffffffffc01dc0d0    psmouse_poll+0x0/0x30 [psmouse]
    

    看来我可以用kallsyms_lookup_name()找到函数的地址,然后可以用函数指针来调用函数吗?

    是的,你可以。如果我没记错的话,它叫做reflection。以下是如何执行此操作的示例:

        typedef int (*custom_print)(const char *fmt, ...);
    
        custom_print my_print;
    
        my_print = (custom_print)kallsyms_lookup_name("printk");
        if (my_print == 0) {
            pr_err("Unable to find printk\n");
            return -EINVAL;
        }
    
        my_print(KERN_INFO "### printk found!\n");
    

    【讨论】:

    • 感谢您的快速回复。我尝试了该模块并使用“dmesg”查看结果。但是缺少很多符号,例如“futex_requeue”。输出符号数约为 10K,而我使用“nm vmlinux”时为 100K。另外,有没有办法输出每个函数的大小?
    • @WindChaser 您的问题已在我的回答的补充部分解决。
    • 看来kallsyms_lookup(addr, &size, NULL, NULL, NULL) 可以从符号地址获取大小。
    • @WindChaser 你说得对,kallsyms_lookup() 没有导出,所以不能从模块中使用。至于符号大小的专用函数:不,没有导出这样的函数。如果您的目标只是教育性的——请随意开发解析sprint_symbol() 输出并将符号大小返回为unsigned long 的函数。如果这个任务来自实际工作——我鼓励你制作一个补丁来导出你需要的功能,并将这个补丁发送到相应的内核邮件列表以供审查。如果您指定为什么需要它(在提交消息中) - 它可以合并到主线内核中。
    • 看起来这个答案需要对 Linux 5.7 进行相当大的修改:lwn.net/Articles/813350
    猜你喜欢
    • 2014-03-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-21
    • 2021-08-17
    • 2013-03-10
    • 2017-03-18
    • 2012-04-11
    • 2013-12-17
    相关资源
    最近更新 更多