get_kernel_syms系统调用及示例

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 权限)
  • EFAULTtable 指针指向无效内存地址
  • 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 系统出于安全考虑,可能会限制内核符号信息的访问:

  1. kptr_restrict: 控制内核指针的显示
    • 0: 允许显示内核指针
    • 1: 普通用户看不到内核指针(显示为0)
    • 2: 所有用户都看不到内核指针
  2. 权限要求: 访问内核符号信息通常需要特殊权限

总结

get_kernel_syms 是一个已废弃的系统调用,现代 Linux 系统开发应该:

  1. 避免使用:该系统调用在新内核中已不可用
  2. 使用替代方案:推荐使用 /proc/kallsyms 获取内核符号信息
  3. 注意安全限制:了解 kptr_restrict 对符号显示的影响
  4. 权限管理:确保有足够的权限访问内核信息
  5. 兼容性考虑:在代码中检查系统调用是否可用

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

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

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注