如何 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");
在内核模块中,如何列出所有内核符号及其地址? 不应该重新编译内核。
我在接口中知道 "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");