I have an out-of-tree Linux kernel module (for Intel x86_64). Its source code files are compiled into .o object files, which are then linked into the kernel .ko loadable file using the standard documented process for such modules:
foo.c → foo.o → foo.ko // done by make -C <tree> M=<dir>
Now, I want to prepare and run unit tests for functions inside one of the source files of that module. To be able to run the tests fully in user space, I provide mock implementations for linking dependencies of that module (meaning that the test will not be calling privileged stuff such as CPL0 instructions, while in CPL3). I also link it with the test framework's entrypoint to produce a new executable binary that can then be run:
foo.o + test_mocks.c + test_entrypoint.c → foo_test // configured by CMake
Important: foo.o is reused as-is from the kernel module build step, it is the same object code that would go into the production.
However, the test application foo_test crashes early in the prologue of the first function invoked from foo.o. Debugging shows that it segfaults on instruction mov %gs:0x28,%rax. GS is not set (contains 0), so naturally there is no mapping for the destination.
Another instruction that gets inserted into the test binary closer to function's epilogue is sub %gs:0x28,%rdx. Both of these reminded me about thread-local storage, except that it should be using the FS segment instead of GS.
foo.o comes from the module build process, so it must be some kernel-specific backend option passed to the compiler that causes this.
By going through compiler options used by the kbuild process I have discovered that it is -mcmodel=kernel that causes GS to be used instead of FS.
GCC's documentation on this flag only mentions effects on address range but not on the segment's choice:
-mcmodel=kernel Generate code for the kernel code model. The kernel runs in the negative 2 GB of the address space. This model has to be used for Linux kernel code.
I have verified that removing this option when compiling a single .c file into the .o file indeed causes all %gs instances to be replaced by %fs in the object file.
How to allow functions from the module file to be called without crashing?
Some options that I have or am considering.
- Rebuilding foo.c into a new, "userspace-friendly", object file is troublesome because of other things kbuild does to the environment (
-nostdinc, a bunch of-isystem, redefiningtrueandfalseetc.). Besides, it would miss the point of testing the binary code that is actually used in the production. - Initialize GS in the test's setup phase with current FS value, so that the
mov %gsinstructions won't crash? I am not planning to use thread-local storage in the tests, so I assume FS stays unmodified during the lifetime of the application. Can it be done entirely in userspace? - Make the compiler/linker of the test binary to be aware that the code should use GS instead of FS? How?
- Using another testing framework that abstracts away some of the woes? I only looked at KUnit, and it seemed disproportionally complicated relatively to my goal. To configure, build and run another kernel just to be able to call a function from module is unreasonable; besides, how would I provide precise test doubles for my test scenarios (e.g., memory allocation failure, specific hardware errata behavior, specific scheduling sequence etc.)?
-mcmodel=kernelto it to see if it makes any difference. Another crazy idea: align FS and GS in signal(SIGSEGV) handler :-)