如何使用Linux内核模块中的地址转储/列出所有内核符号?

时间:2016-06-22 21:06:48

标签: linux-kernel kernel-module

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

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

1 个答案:

答案 0 :(得分:4)

实施例

工作模块代码:

#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:将包含当前内核符号的地址

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

补充

回答评论中的问题。

  

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

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

symbol_name+offset/size [module_name]

示例:

psmouse_poll+0x0/0x30 [psmouse]

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

  

我尝试了模块并使用&#34; dmesg&#34;查看结果。但是缺少很多符号,例如&#34; futex_requeue&#34;。输出符号数约为10K,而使用&#34; nm vmlinux&#34;时为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");