get_kernel_syms – 获取内核符号表信息(已废弃)
1. 函数介绍
get_kernel_syms
是一个已废弃的 Linux 系统调用,用于获取内核符号表的信息。它曾经被用来检索内核中导出的符号(函数和变量)的列表,包括它们的地址、类型和名称。
重要说明:这个系统调用在现代 Linux 内核中已被废弃和移除,不再推荐使用。现代系统应该使用其他方式来获取内核符号信息。
2. 函数原型
#include <linux/kernel.h>
int get_kernel_syms(struct kernel_sym *table);
3. 功能
获取内核符号表中导出符号的信息,包括符号名称、地址和类型。主要用于内核调试、模块加载和系统分析工具。
4. 参数
struct kernel_sym *table
: 指向kernel_sym
结构体数组的指针- 如果为
NULL
:返回符号表中的符号总数 - 如果非
NULL
:将符号信息填充到该数组中
- 如果为
5. kernel_sym 结构体定义
struct kernel_sym {
unsigned long ks_addr; /* 符号地址 */
char ks_name[60]; /* 符号名称(最多59个字符+null终止符)*/
};
6. 返回值
- 成功时:
- 如果
table
为NULL
:返回内核符号表中的符号总数 - 如果
table
非NULL
:返回实际填充的符号数量
- 如果
- 失败时返回 -1,并设置
errno
7. 常见 errno 错误码
EPERM
: 权限不足(需要CAP_SYS_ADMIN
或CAP_SYS_MODULE
权限)EFAULT
:table
指针指向无效内存地址ENOMEM
: 内存不足ENOSYS
: 系统调用不支持(现代内核中常见)
8. 相似函数,或关联函数
/proc/kallsyms
: 现代系统中获取内核符号的标准方式/proc/sys/kernel/kptr_restrict
: 控制内核指针显示的设置klogctl()
: 控制内核日志缓冲区uname()
: 获取系统信息sysinfo()
: 获取系统统计信息nm
命令:用户态工具查看目标文件符号表/boot/System.map
: 内核构建时生成的符号映射文件
9. 示例代码
示例1:检查系统是否支持 get_kernel_syms
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>
#ifndef SYS_get_kernel_syms
# define SYS_get_kernel_syms 177 // x86_64 架构下的系统调用号
#endif
int main() {
long result;
printf("检查系统是否支持 get_kernel_syms...\n");
// 首先尝试获取符号数量(传入 NULL)
result = syscall(SYS_get_kernel_syms, NULL);
if (result == -1) {
printf("get_kernel_syms 调用失败\n");
printf("errno = %d: %s\n", errno, strerror(errno));
switch (errno) {
case ENOSYS:
printf("系统调用已废弃或不支持\n");
break;
case EPERM:
printf("权限不足,需要特权权限\n");
break;
default:
printf("其他错误\n");
break;
}
} else {
printf("系统支持 get_kernel_syms,符号数量: %ld\n", result);
}
return 0;
}
示例2:传统使用方式(仅在旧系统上可能工作)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>
#ifndef SYS_get_kernel_syms
# define SYS_get_kernel_syms 177
#endif
// 注意:现代 glibc 可能不包含这个结构体定义
struct kernel_sym {
unsigned long ks_addr;
char ks_name[60];
};
int main() {
long sym_count;
struct kernel_sym *sym_table;
long result;
int i;
printf("=== get_kernel_syms 测试 ===\n");
// 第一次调用:获取符号数量
sym_count = syscall(SYS_get_kernel_syms, NULL);
if (sym_count == -1) {
printf("获取符号数量失败: %s\n", strerror(errno));
if (errno == ENOSYS) {
printf("提示: get_kernel_syms 已在现代内核中废弃\n");
printf("请使用 /proc/kallsyms 替代\n");
}
return 1;
}
printf("内核符号数量: %ld\n", sym_count);
if (sym_count == 0) {
printf("没有可用的内核符号\n");
return 0;
}
// 限制获取的符号数量以避免内存问题
if (sym_count > 1000) {
printf("符号数量过多,限制为前1000个\n");
sym_count = 1000;
}
// 分配内存
sym_table = malloc(sym_count * sizeof(struct kernel_sym));
if (sym_table == NULL) {
perror("内存分配失败");
return 1;
}
// 第二次调用:获取符号信息
result = syscall(SYS_get_kernel_syms, sym_table);
if (result == -1) {
printf("获取符号信息失败: %s\n", strerror(errno));
free(sym_table);
return 1;
}
printf("成功获取 %ld 个符号信息\n", result);
// 显示前几个符号作为示例
printf("\n前10个内核符号:\n");
printf("%-18s %s\n", "地址", "符号名称");
printf("%-18s %s\n", "----", "--------");
for (i = 0; i < result && i < 10; i++) {
printf("0x%016lx %s\n",
sym_table[i].ks_addr,
sym_table[i].ks_name);
}
free(sym_table);
return 0;
}
10. 现代替代方案
由于 get_kernel_syms
已被废弃,现代系统应该使用以下替代方案:
方案1:使用 /proc/kallsyms
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
char line[256];
char address[32];
char type;
char symbol[128];
int count = 0;
fp = fopen("/proc/kallsyms", "r");
if (fp == NULL) {
perror("打开 /proc/kallsyms 失败");
return 1;
}
printf("读取内核符号表 (/proc/kallsyms):\n");
printf("%-18s %-8s %s\n", "地址", "类型", "符号");
printf("%-18s %-8s %s\n", "----", "----", "----");
// 读取前20个符号
while (fgets(line, sizeof(line), fp) && count < 20) {
if (sscanf(line, "%s %c %s", address, &type, symbol) == 3) {
printf("%-18s %-8c %s\n", address, type, symbol);
count++;
}
}
fclose(fp);
// 检查 kptr_restrict 设置
fp = fopen("/proc/sys/kernel/kptr_restrict", "r");
if (fp != NULL) {
int restrict_level;
if (fscanf(fp, "%d", &restrict_level) == 1) {
printf("\nkptr_restrict 级别: %d\n", restrict_level);
if (restrict_level > 0) {
printf("注意: 内核指针可能被限制显示\n");
}
}
fclose(fp);
}
return 0;
}
方案2:使用 System.map 文件
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
char line[256];
char address[32];
char type;
char symbol[128];
int count = 0;
// 尝试打开 System.map 文件
const char *system_map_paths[] = {
"/boot/System.map-$(uname -r)", // 需要 shell 展开
"/boot/System.map",
"/lib/modules/$(uname -r)/System.map", // 需要 shell 展开
NULL
};
// 简化版本:直接尝试常见路径
fp = fopen("/boot/System.map", "r");
if (fp == NULL) {
printf("System.map 文件不可用\n");
printf("现代系统推荐使用 /proc/kallsyms\n");
return 1;
}
printf("读取 System.map 文件:\n");
printf("%-18s %-8s %s\n", "地址", "类型", "符号");
printf("%-18s %-8s %s\n", "----", "----", "----");
// 读取前10个符号
while (fgets(line, sizeof(line), fp) && count < 10) {
if (sscanf(line, "%s %c %s", address, &type, symbol) == 3) {
printf("%-18s %-8c %s\n", address, type, symbol);
count++;
}
}
fclose(fp);
return 0;
}
11. 符号类型说明
在 /proc/kallsyms
中,符号类型字符含义:
T/t
: 代码段中的符号(大写表示全局,小写表示局部)D/d
: 数据段中的符号B/b
: BSS段中的符号R/r
: 只读数据段中的符号A
: 绝对符号U
: 未定义符号W/w
: 弱符号
12. 安全考虑
现代 Linux 系统出于安全考虑,可能会限制内核符号信息的访问:
- kptr_restrict: 控制内核指针的显示
- 0: 允许显示内核指针
- 1: 普通用户看不到内核指针(显示为0)
- 2: 所有用户都看不到内核指针
- 权限要求: 访问内核符号信息通常需要特殊权限
总结
get_kernel_syms
是一个已废弃的系统调用,现代 Linux 系统开发应该:
- 避免使用:该系统调用在新内核中已不可用
- 使用替代方案:推荐使用
/proc/kallsyms
获取内核符号信息 - 注意安全限制:了解
kptr_restrict
对符号显示的影响 - 权限管理:确保有足够的权限访问内核信息
- 兼容性考虑:在代码中检查系统调用是否可用
对于内核调试、模块开发和系统分析需求,现代工具链提供了更安全、更可靠的替代方案。