Search code examples
clinuxlinux-kernelclionkernel-module

How to make CLion recognize linux/init.h and other linux kernel headers?


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


Solution

  • Things are not that simple for two main reasons:

    1. 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.

    2. 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.

    A CMakeLists.txt to build kernel modules with CLion

    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:

    1. Find the current kernel release (using the uname -r command).
    2. Find the kernel headers at /usr/src/linux-headers-$(uname -r). We will save this path in a KDIR variable.
    3. Specify the architecture to build for. We'll set an 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).
    4. Include the appropriate directories with include_directories().
    5. Generate a Kbuild (or Makefile) file that declares the obj-m needed to build the module.
    6. Run a custom make command using custom_command() to build the module.
    7. Finally, declare a 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:

    clion-screenshot

    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.