get_kernel_syms系统调用及示例

get_kernel_syms - 获取内核符号表信息(已废弃)

1. 函数介绍

get_kernel_syms 是一个已废弃的 Linux 系统调用,用于获取内核符号表的信息。它曾经被用来检索内核中导出的符号(函数和变量)的列表,包括它们的地址、类型和名称。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

重要说明:这个系统调用在现代 Linux 内核中已被废弃和移除,不再推荐使用。现代系统应该使用其他方式来获取内核符号信息。

2. 函数原型

1
2
3
4
#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 结构体定义

1
2
3
4
5
struct kernel_sym {
unsigned long ks_addr; /* 符号地址 */
char ks_name&#91;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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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:传统使用方式(仅在旧系统上可能工作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#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&#91;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&#91;i].ks_addr,
sym_table&#91;i].ks_name);
}

free(sym_table);
return 0;
}

10. 现代替代方案

由于 get_kernel_syms 已被废弃,现代系统应该使用以下替代方案:

方案1:使用 /proc/kallsyms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp;
char line&#91;256];
char address&#91;32];
char type;
char symbol&#91;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 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp;
char line&#91;256];
char address&#91;32];
char type;
char symbol&#91;128];
int count = 0;

// 尝试打开 System.map 文件
const char *system_map_paths&#91;] = {
"/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 对符号显示的影响

权限管理:确保有足够的权限访问内核信息

兼容性考虑:在代码中检查系统调用是否可用

对于内核调试、模块开发和系统分析需求,现代工具链提供了更安全、更可靠的替代方案。

get_kernel_syms系统调用及示例-CSDN博客

data-ad-format="auto" data-full-width-responsive="true">