/proc/kallsyms是运行时动态生成的内核符号表,包含所有加载模块符号且实时更新,但受kptr_restrict限制默认地址为0;System.map是编译时生成的静态快照,仅含vmlinux本体符号、不含模块、地址固定。

直接看 /proc/kallsyms 就是查看内核符号表最准、最实时的方式,但它默认对非 root 用户隐藏地址——这不是“查不到”,而是被 kptr_restrict 主动掩码了。
为什么 cat /proc/kallsyms 全是 0?
这是内核安全机制在起作用。从 2.6.38 起,默认启用 kptr_restrict=2,所有符号地址强制显示为 0000000000000000,哪怕你有 root 权限也一样。
- 临时放开:执行
echo 0 > /proc/sys/kernel/kptr_restrict(需 root) - 永久生效:在
/etc/sysctl.conf中加一行kernel.kptr_restrict = 0,再运行sysctl -p - 注意:设为 1 时,仅对具有
CAP_SYSLOG能力的进程(如 root)显示真实地址;设为 2 则彻底屏蔽,连 root 都看不到
如何快速定位某个函数或变量的地址?
用 grep 配合 awk 或直接管道过滤是最常用做法,但要注意大小写和符号类型匹配。
- 查函数(通常是大写
T或小写t):cat /proc/kallsyms | grep " T " | grep schedule - 查全局变量(
D或d):cat /proc/kallsyms | grep " D " | grep jiffies - 忽略大小写并精简输出:
grep -i "printk" /proc/kallsyms | awk '{print $1, $3}' - 注意:有些符号名带下划线前缀(如
__do_sys_ioctl),直接搜ioctl可能漏掉,建议用正则或模糊匹配
kallsyms_lookup_name() 在模块里怎么安全调用?
这个函数不是导出符号(EXPORT_SYMBOL),但自 5.7 起被标记为 __kprobes 并允许模块通过 kallsyms_lookup_name 名字动态查找——前提是内核没禁用该接口(部分发行版如 RHEL 8.8+ 默认禁用)。
- 必须包含头文件:
#include <linux></linux> - 调用前要检查返回值是否为 NULL:
unsigned long addr = kallsyms_lookup_name("unexported_func"); if (!addr) return -EINVAL; - 从 5.12 开始,
kallsyms_lookup_name本身也被移出导出列表,某些内核需改用kprobe_on_func_entry或直接解析/proc/kallsyms文件内容(绕过 API) - 编译时需开启
CONFIG_KALLSYMS,且模块不能使用-fno-PIE等破坏符号解析的选项
/proc/kallsyms 和 System.map 有什么本质区别?
/proc/kallsyms 是运行时内存中构建的、含全部加载模块符号的动态视图;System.map 是编译时生成的静态快照,只含 vmlinux 本体符号,不含任何 ko 模块。
- 调试崩溃现场(vmcore)时,
crash工具优先依赖System.map或 vmlinux 文件,而非/proc/kallsyms -
/proc/kallsyms地址受 KASLR 影响每次启动都变,而System.map中的地址是编译时确定的基址,需配合偏移计算 - 模块卸载后,其符号会立刻从
/proc/kallsyms消失,但System.map永远不变 - 如果你在容器里或无 root 的生产环境,
/proc/kallsyms可能完全不可读,这时只能靠 BTF 或离线符号映射
真正容易被忽略的是:即使你拿到了函数地址,也不代表能安全调用它——很多未导出函数依赖内部锁、上下文或隐式参数,硬 call 很可能触发 oops。查地址只是第一步,理解符号背后的语义和调用约束才是关键。


















