You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将-mcmodel=kernel编译的内核目标文件链接到用户态:GS段问题求解

问题:内核模块目标文件用户态测试中GS段指令崩溃问题

我有一个针对Intel x86_64架构的Linux内核外置模块,源码通过标准内核模块构建流程编译为.o目标文件,再链接成.ko可加载模块:

foo.c → foo.o → foo.ko // 执行make -C <tree> M=<dir>完成

现在要为模块源码中的函数编写并运行用户态单元测试,我提供了模块依赖的模拟实现(确保测试在CPL3环境下不调用CPL0特权指令),并与测试框架入口链接生成可执行文件:

foo.o + test_mocks.c + test_entrypoint.c → foo_test // 由CMake配置构建

重要说明:foo.o直接复用内核模块构建步骤生成的文件,与生产环境使用的二进制代码完全一致。

但测试程序foo_test在调用foo.o中第一个函数的序言阶段就崩溃了,调试发现是指令mov %gs:0x28,%rax触发段错误——GS段未设置(值为0),导致目标地址无映射。函数尾声处还有sub %gs:0x28,%rdx指令,这些操作类似线程本地存储,但本该使用FS段而非GS段。

经排查内核构建过程的编译器选项,发现是-mcmodel=kernel导致使用GS段而非FS段。GCC文档仅提及该选项影响地址范围,未说明段选择的变化:

-mcmodel=kernel
生成适用于内核代码模型的代码。内核运行在地址空间的负2GB区域,该模型必须用于Linux内核代码。

我已验证,编译单个.c文件时移除该选项,目标文件中的%gs会全部替换为%fs。

核心问题:如何让模块文件中的函数能被正常调用而不崩溃?

我考虑过的几种方案:

  • 重新构建适用于用户态的foo.o目标文件,但内核构建环境的特殊配置(如-nostdinc、大量-isystem、重定义true和false等)会带来麻烦,且无法测试生产环境实际使用的二进制代码。
  • 在测试初始化阶段将GS段设置为当前FS段的值,避免mov %gs指令崩溃?测试中不打算使用线程本地存储,假设FS段在程序生命周期内保持不变,能否完全在用户态实现?
  • 让测试二进制的编译器/链接器知晓代码应使用GS段而非FS段?如何实现?
  • 使用其他测试框架简化操作?我仅了解KUnit,但它相对我的需求过于复杂,为调用模块函数而重新配置、构建和运行内核并不合理,且无法为测试场景提供精准的测试替身(如内存分配失败、特定硬件勘误行为、特定调度序列等)。

可行解决方案

方案2:用户态初始化时映射GS段到FS段(推荐)

完全可以在用户态实现GS段的设置,无需修改内核或重新编译模块。x86_64用户态下,可通过arch_prctl系统调用读取FS段基地址,再同步到GS段。

具体实现步骤:

  1. 在测试入口的初始化代码中添加如下逻辑:
    #include <sys/arch.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    void setup_gs_segment(void) {
        uint64_t fs_base;
        // 获取当前FS段的基地址
        if (arch_prctl(ARCH_GET_FS, &fs_base) != 0) {
            perror("Failed to get FS base");
            exit(EXIT_FAILURE);
        }
        // 将GS段的基地址设置为FS段的基地址
        if (arch_prctl(ARCH_SET_GS, fs_base) != 0) {
            perror("Failed to set GS base");
            exit(EXIT_FAILURE);
        }
    }
    
  2. 在调用任何foo.o中的函数之前,先调用setup_gs_segment()。

该方案优势:

  • 完全复用生产环境的foo.o二进制,保证测试真实性
  • 实现简单,无需修改构建流程或编译器选项
  • 满足测试中不使用线程本地存储的假设(GS和FS指向同一区域,函数中的GS访问不会触发错误)

方案3:让测试二进制适配GS段的使用

如果测试需要保留GS段独立使用,可通过编译器和链接器选项强制用户态代码使用GS段作为TLS段,但需修改CMake配置:

  1. 编译测试代码时添加-mtls-segment=gs选项(GCC 10+支持),强制编译器生成使用GS段的TLS代码
  2. 链接时确保TLS段正确映射

但这种方式可能导致测试代码与模块代码的TLS访问冲突,兼容性不如方案2,不推荐作为首选。

方案1的优化思路(不推荐)

若一定要重新构建用户态版本的foo.o,可提取内核构建的编译选项,剔除-mcmodel=kernel和-nostdinc等内核特有选项,再用调整后的选项编译foo.c。但需要维护两套构建配置,且无法保证与生产二进制的一致性,因此不推荐。

方案4:替代测试框架

除KUnit外,可考虑User Mode Linux (UML) 或QEMU用户态模拟,但这类方案仍需模拟内核环境,复杂度较高,不如方案2直接高效。若需要精准测试替身,方案2配合自定义Mock实现已足够满足需求。


内容的提问来源于stack exchange,提问作者Grigory Rechistov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:36:13