I am trying to do some Linux Kernel programming with CLion. I observe that some headers, like <linux/kernel.h>
and <linux/module.h>
are correctly recognized, but some others, like <linux/init.h>
are not.
To be more clear, here is a sample program:
#include <linux/init.h>
#include <linux/kernel.h>
int init_module(void) {
printk("initializing");
return 0;
}
Here I have rows 1 and 5 highlighted in red with the typical "file not found"/"call to undeclared function" errors, but row 2 is correctly non-highlighted, and if I try to write something that is linked to that library there is no problem at all.
I have tried to install the latest headers for my kernel, i.e. sudo apt install linux-headers-$(uname -r)
, but it does not seem to work. What can I do?
EDIT:
Output of locate init.h | grep linux/init.h
:
(some thimeshift things...)
/usr/src/linux-headers-5.4.0-187/include/linux/init.h
/usr/src/linux-headers-5.4.0-189/include/linux/init.h
/usr/src/linux-headers-5.4.0-74/include/linux/init.h
... that is pretty curious, because I have kernel version 5.4.0-190, and the output of sudo apt-get install linux-headers-$(uname -r)
is the following (manually translated to english):
...
linux-headers-5.4.0-190-generic is already at the most recent version (5.4.0-190.210).
...
EDIT:
It somewhats works adding a CMake File with
include_directories(/usr/src/linux-headers-5.4.0-187/include/)
, but it is just a "patch", not the solution, so I leave the question open, hoping in some more "definitive" answer
Things are not that simple for two main reasons:
CLion wants to use CMake, so you will have to write a custom CMakeLists.txt
that finds kernel headers and calls make
to invoke the kernel Makefile and build the module using a custom command. This is not so easy as the kernel Makefile is rather complex and requires a number of variables to be defined correctly.
As you seem to have already figured out, usually the kernel headers for your current kernel sit at /usr/src/linux-headers-$(uname -r)/
. However, the .h
C header files reside in different directories. Inside the headers directory, there are at least 4 different include directories that I am aware of: include
, include/generated
, arch/XXX/include
and arch/XXX/include/generated
. These all need to be included.
Overall, I would advise you to avoid using CLion or similar overcomplicated IDEs for kernel module development. Use simpler IDEs such as Visual Studio Code, that allow you to specify include directories manually in a configuration file, but don't need CMake and don't do everything for you, so you can also write and use your own Kbuild
/Makefile
for the module.
I found different guides online that supposedly describe how to do this (one, two), but they all seemed broken for different reasons, so I decided to customize my own.
A CMakeLists.txt
to build a kernel module should do the following things:
uname -r
command)./usr/src/linux-headers-$(uname -r)
. We will save this path in a KDIR
variable.ARCH
variable for this. There's no easy way to automate this as command line tools like uname -m
will output the wrong names (e.g. x86_64
instead of x86
).include_directories()
.Kbuild
(or Makefile
) file that declares the obj-m
needed to build the module.make
command using custom_command()
to build the module.dummy_target
target to use for dependency checks.Here's the final result:
cmake_minimum_required(VERSION 3.28)
project(mymod C)
set(CMAKE_C_STANDARD 99)
add_definitions(-D__KERNEL__ -DMODULE)
# Find running kernel release
execute_process(
COMMAND uname -r
OUTPUT_VARIABLE KERNEL_RELEASE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(KDIR "/usr/src/linux-headers-${KERNEL_RELEASE}")
set(ARCH "x86")
# Check that the headers are installed
if (NOT EXISTS "${KDIR}/include/linux/module.h")
message(FATAL_ERROR "Kernel headers dir not found: ${KDIR}")
endif()
# Check that the headers are for the specified arch
if (NOT EXISTS "${KDIR}/arch/${ARCH}/include")
message(FATAL_ERROR "Arch-specific include dir not found: ${KDIR}/arch/${ARCH}/include")
endif()
message(NOTICE "Building for arch: ${ARCH}")
message(NOTICE "Kernel release: ${KERNEL_RELEASE}")
message(NOTICE "Kernel headers: ${KDIR}")
# Include generic kernel headers
include_directories("${KDIR}/include")
include_directories("${KDIR}/include/generated")
# Include arch-specific kernel headers
include_directories("${KDIR}/arch/${ARCH}/include")
include_directories("${KDIR}/arch/${ARCH}/include/generated")
# Set some useful variables
set(MODULE_NAME mymod)
set(SRC_FILE "${MODULE_NAME}.c")
set(OBJ_FILE "${MODULE_NAME}.o")
set(KO_FILE "${MODULE_NAME}.ko")
# Generate Kbuild file in the source directory
FILE(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/Kbuild "obj-m := ${OBJ_FILE}")
# Custom `make` command used to build the module
add_custom_command(
OUTPUT ${KO_FILE}
COMMAND $(MAKE) -C ${KDIR} modules ARCH=${ARCH} M=${CMAKE_CURRENT_BINARY_DIR} src=${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${SRC_FILE}
VERBATIM
)
add_custom_target(mymod ALL DEPENDS ${KO_FILE})
add_library(dummy_target "${SRC_FILE}")
The above CMakeLists.txt
works fine to build the following mymod.c
kernel module:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
int __init myinit(void) {
pr_info("Hello World!\n");
return 0;
}
void __exit myexit(void) {
pr_info("Goodbye World!\n");
}
module_init(myinit);
module_exit(myexit);
MODULE_VERSION("0.1");
MODULE_AUTHOR("Marco Bonelli");
MODULE_DESCRIPTION("Test module");
MODULE_LICENSE("GPL 2.0");
NOTE that MODULE_LICENSE()
is compulsory, the kernel Makefile will refuse to build the module if no license is specified!
The final project should look like this in CLion:
NOTE: the Kbuild
file is auto-generated by CMakeLists.txt
. The only two files that you need to write are CMakeLists.txt
and mymod.c
.
The only problem, which I am not sure how to fix, is that as you can see from the screenshot above CLion seems to think that it should also consider userspace include directories such as /usr/include
, /usr/x86_64-linux-gnu/include
and so on. Those should definitely not be considered for a kernel module, but I am not sure how to disable/remove them using CMakeLists.txt
. They will not be considered when building the module, but they are probably considered by CLion when providing suggestions and auto-completion.