如何使用Linux内核模块中的地址转储/列出所有内核符号? [英] How to dump/list all kernel symbols with addresses from Linux kernel module?

查看:137
本文介绍了如何使用Linux内核模块中的地址转储/列出所有内核符号?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In a kernel module, how to list all the kernel symbols with their addresses? The kernel should not be re-compiled.

我在一个界面中知道"cat/proc/kallsyms",但是如何使用kallsyms_lookup_name之类的函数直接从内核数据结构中获取它们.

I know "cat /proc/kallsyms" in an interface, but how to get them directly from kernel data structures, using functions like kallsyms_lookup_name.

推荐答案

示例

工作模块代码:

Example

Working module code:

#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许可证才能使用它.这些功能是:

Explanation

kernel/kallsyms.c implements /proc/kallsyms. Some of its functions are available for external usage. They are exported via EXPORT_SYMBOL_GPL() macro. Yes, your module should have GPL license to use it. Those functions are:

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

要使用这些功能,请在其中包含 <linux/kallsyms.h> 您的模块.应该提到的是,必须在内核配置中启用CONFIG_KALLSYMS(=y).

To use those functions, include <linux/kallsyms.h> in your module. It should be mentioned that CONFIG_KALLSYMS must be enabled (=y) in your kernel configuration.

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

To print all the symbols you obviously have to use kallsyms_on_each_symbol() function. The documentation says next about it:

/* 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是指向您的某些私有数据的指针(将作为第一个参数传递给您的回调函数).

where fn is your callback function that should be called for each symbol found, and data is a pointer to some private data of yours (will be passed as first parameter to your callback function).

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

Callback function must have next signature:

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

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

This function will be called for each kernel symbol with next parameters:

  • data:将包含指向您作为最后一个参数传递给kallsyms_on_each_symbol()
  • 的私有数据的指针
  • namebuf:将包含当前内核符号的名称
  • module:将始终为NULL,只需忽略
  • address:将包含当前内核符号的地址
  • data: will contain pointer to your private data you passed as last argument to kallsyms_on_each_symbol()
  • namebuf: will contain name of current kernel symbol
  • module: will always be NULL, just ignore that
  • address: will contain address of current kernel symbol

返回值应始终为0(对于非零返回值,通过符号进行的迭代将被中断).

Return value should always be 0 (on non-zero return value the iteration through symbols will be interrupted).

回答您评论中的问题.

还可以输出每个函数的大小吗?

Also, is there a way to output the size of each function?

是的,您可以使用我上面提到的sprint_symbol()函数来执行此操作.它将以以下格式打印符号信息:

Yes, you can use sprint_symbol() function I mentioned above to do that. It will print symbol information in next format:

symbol_name+offset/size [module_name]

示例:

psmouse_poll+0x0/0x30 [psmouse]

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

Module name part can be omitted if symbol is built-in.

我尝试了该模块,并使用"dmesg"查看了结果.但是缺少许多符号,例如"futex_requeue".输出的符号数约为10K,而当我使用"nm vmlinux"时则为100K.

I tried the module and see the result with "dmesg". But a lot of symbols are missing such as "futex_requeue". The output symbol number is about 10K, while it is 100K when I use "nm vmlinux".

这很可能是因为您的 printk缓冲区大小不足以存储模块的所有输出以上.

This is most likely because your printk buffer size is insufficient to store all the output of module above.

让我们对上述模块进行一些改进,以便它通过 miscdevice 提供符号信息.另外,我们还根据要求将函数大小添加到输出中.代码如下:

Let's improve above module a bit, so it provides symbols information via miscdevice. Also let's add function size to the output, as requested. The code as follows:

#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将包含以下格式的所有内核符号(内置和来自加载的模块):

File symbols.txt will contain all kernel symbols (both built-in and from loaded modules) in next format:

ffffffffc01dc0d0    psmouse_poll+0x0/0x30 [psmouse]

似乎我可以使用kallsyms_lookup_name()查找函数的地址,然后可以使用函数指针来调用该函数?

It seems that I can use kallsyms_lookup_name() to find the address of the function, can then use a function pointer to call the function?

是的,可以.如果我没记错的话,它叫做反射.下面是如何执行此操作的示例:

Yes, you can. If I recall correctly, it's called reflection. Below is an example how to do so:

    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");

这篇关于如何使用Linux内核模块中的地址转储/列出所有内核符号?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆