- 🐚作者简介:花神庙码农(专注于Linux、WLAN、TCP/IP、Python等技术方向)
- 🐳博客主页:花神庙码农 ,地址:https://blog.csdn.net/qxhgd
- 🌐系列专栏:Linux技术
- 📰如觉得博主文章写的不错或对你有所帮助的话,还望大家三连支持一下呀!!! 👉关注✨、点赞👍、收藏📂、评论。
- 如需转载请参考转载须知!!
如何获取Linux内核函数及其地址的映射关系
System.map
- 在编译Linux内核时,会产生一个内核映像文件System.map,也叫内核符号表。
- 内核符号表是一个映射,它将内核代码段中的地址映射到对应的函数名或全局变量名。
- Linux内核源代码位于/usr/src/linux目录下,可通过下面命令在System.map中查看符号表:
find /usr/src/linux -name System.map* #查找System.map文件
head -5 /usr/src/linux/System.map #查看System.map前5行
grep functionName /usr/src/linux/System.map #使用grep查找函数地址
vmlinux
- vmlinux是Linux编译出来的内核映像文件,可以通过nm、objdump和readelf、addr2line、eu-addr2line等工具来查看它的符号表,从而获取函数地址:
- 使用nm的示例:
nm vmlinux | grep "do_fork" # nm查找vmlinux中函数名为do_fork的地址
nm vmlinux | grep c0105020# nm查找vmlinux中地址为c0105020的符号:
- 使用objdump 的示例:
objdump -d vmlinux | grep "do_fork" # 使用objdump查看vmlinux中do_fork函数的地址
objdump -D vmlinux > vmlinux_dump.txt #可以将vmlinux的内容全部反汇编出来,重定向到一个文件,然后直接查看文本内容:
- 使用readelf 的示例:
readelf -s vmlinux #读取vmlinux的整个符号表
readelf -s vmlinux | grep "do_fork" #查找某个函数的地址,可使用grep进行过滤。
- addr2line的常用参数如下:
-e
:指定可执行映像名称-a
:显示函数地址-f
:显示函数名称
如需查看某个内核地址所对应的函数,可使用如下命令:
addr2line -f -e vmlinux 0xffffffffb8558e90
kallsyms
- 在Linux内核2.6版本中引入了kallsyms,主要是为了方便内核的调试。kallsyms通过抽取内核中所有函数和非栈数据变量的地址,生成一个数据块,这个数据块作为只读数据链接进内核镜像,相当于内核中存储了一个System.map。
- kallsyms是Linux内核中的一个符号表,它包含了内核中所有的符号信息,如函数、变量和常量。它包含了内核中的函数符号(包括没有EXPORT_SYMBOL)、全局变量(用EXPORT_SYMBOL导出的全局变量)。此符号表对于内核开发和调试非常有用,因为它允许开发者通过符号名来查找和访问内核中的地址。
- 内核默认不会生成/proc/kallsyms文件,因为会。要在内核中启用kallsyms,需要开启下面各个宏:
CONFIG_KALLSYMS=y #在内核中启用kallsyms功能
CONFIG_KALLSYMS_ALL=y #包括全部符号(包括没有用EXPORT_SYMBOL导出的变量)
CONFIG_KALLSYMS_EXTRA_PASS=y
- 使用示例如下:
cat /proc/kallsyms
cat /proc/kallsyms | grep "do_fork"
cat /proc/kallsyms | grep "0xffffffffb8558e90"
- livepatch就是通过查找这个符号表,根据符号名得到符号的地址。
kallsyms_lookup_name
- 在 Linux 内核版本 2.6.33 到 5.7.0 之间,kallsyms_lookup_name 被导出,可以直接使用。但是,在内核版本 5.7.0 之后,这个函数不再被导出。这意味着在新版本的内核中,直接使用 kallsyms_lookup_name可能会遇到问题,需要手动导出。
- 在 Linux 内核中,sprint_symbol 函数是用于将一个内核符号(Kernel Symbol)的地址转换为对应的符号名称并将其打印到一个字符缓冲区中的函数。
- 此二个函数的原型如下,定义在kernel/kallsyms.c中:
#include<linux/kallsyms.h>
unsigned long kallsyms_lookup_name(const char *name); //已知函数名,获取地址
int sprint_symbol(char *buffer, unsigned long address); //已知地址,返回对应符号
- 接口可以在kallsyms中查看:
cat /proc/kallsyms | grep '\<kallsyms_lookup_name\>'
ffffffffb8558e90 T kallsyms_lookup_name
- kallsyms_lookup_name函数在kernel/kallsyms.c文件中定义的,需要打开内核选项:
CONFIG_KALLSYMS=y
- kallsyms_lookup_name()接受一个字符串格式内核函数名,返回那个内核函数的地址:
kallsyms_lookup_name("sys_open");
- 实践中,可以写一个内核模块,使用kallsyms_lookup_name来获取函数地址。以下仅是一个demo,实际上还可以注册一个procfs文件系统,通过参数来决定要查询哪个函数的地址。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kallsyms.h>
static int __init get_function_address_init(void) {
void *func_addr = (void *)kallsyms_lookup_name("function_name");
if (func_addr) {
printk(KERN_INFO "Function address: %px", func_addr);
} else {
printk(KERN_ERR "Function not found");
}
return 0;
}
static void __exit get_function_address_exit(void) {
printk(KERN_INFO "Module unloaded");
}
module_init(get_function_address_init);
module_exit(get_function_address_exit);
MODULE_LICENSE("GPL");
printk
- 众所周知的是,printk()是一个内核的记录日志的函数,经常用来记录信息或者警告。少为人知的是,printk还可以输出函数指针(对应某个地址)对应的函数名。
- 一个例子如下:
printk ("the func's caller is %pS\n", __builtin_return_address(0))); //打印某函数的调用者
printk("func %pS is at address: %px\n", pfunc, pfunc);//打印某函数的地址
- 关于printk的格式说明符:
– pS和ps说明符用于以符号格式打印指针。它们导致带有(S)或不带(s)偏移量的符号名称。如果禁用了KALLSYMS,则打印符号地址。
– 当您真正想打印指针地址时,使用px。在使用 %px 打印指针之前,请考虑您是否泄露了有关内核内存布局的敏感信息。 %px 在功能上等同于 %lx(或 %lu)。 %px 更受欢迎,因为它更容易被 grep。
如本文对你有些许帮助,欢迎大佬支持我一下(点赞+收藏+关注、关注公众号等),您的支持是我持续创作的竭动力
支持我的方式