如何 dump/list 所有具有来自 Linux 内核模块地址的内核符号?

How to dump/list all kernel symbols with addresses from Linux kernel module?

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

我在接口中知道 "cat /proc/kallsyms",但是如何使用 kallsyms_lookup_name.

等函数直接从内核数据结构中获取它们

例子

工作模块代码:

#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()

要使用这些函数,请在您的模块中包含 <linux/kallsyms.h>。应该提到的是,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: 将包含当前内核符号的地址

Return 值应始终为 0(非零值 return 将中断通过符号的迭代)。

补充

正在回答您评论中的问题。

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

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

symbol_name+offset/size [module_name]

示例:

psmouse_poll+0x0/0x30 [psmouse]

如果内置了symbol,模块名部分可以省略

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 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] = '[=15=]';
    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]

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?

是的,你可以。如果我没记错的话,它叫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");