【发布时间】:2016-06-26 20:59:43
【问题描述】:
在内核模块中,如何列出所有内核符号及其地址? 内核不应重新编译。
我知道接口中的“cat /proc/kallsyms”,但是如何使用kallsyms_lookup_name 之类的函数直接从内核数据结构中获取它们。
【问题讨论】:
标签: linux-kernel kernel-module
在内核模块中,如何列出所有内核符号及其地址? 内核不应重新编译。
我知道接口中的“cat /proc/kallsyms”,但是如何使用kallsyms_lookup_name 之类的函数直接从内核数据结构中获取它们。
【问题讨论】:
标签: linux-kernel kernel-module
工作模块代码:
#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]
如果符号是内置的,模块名称部分可以省略。
我尝试了该模块并使用“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");
【讨论】:
kallsyms_lookup() 没有导出,所以不能从模块中使用。至于符号大小的专用函数:不,没有导出这样的函数。如果您的目标只是教育性的——请随意开发解析sprint_symbol() 输出并将符号大小返回为unsigned long 的函数。如果这个任务来自实际工作——我鼓励你制作一个补丁来导出你需要的功能,并将这个补丁发送到相应的内核邮件列表以供审查。如果您指定为什么需要它(在提交消息中) - 它可以合并到主线内核中。