runsisi's

technical notes

内核模块中使用本地头文件

2020-03-09 runsisi#linux

将 Linux 内核源代码树中的驱动模块代码挪到独立的工程中进行构建时,有时候需要涉及到对内核头文件的修改。虽然可以通过在 Makefile 中增加 ccflags-y 或者类似的选项将本地工程中头文件所在的文件夹加入编译器的搜索路径,但是,由于该路径是追加在头文件搜索路径的最后,导致修改后的头文件无法得到加载。

如下的信息是设置 ccflags-y := -I$(src)/../../include 后构建过程的输出:

# make -n
...
make -C /lib/modules/3.10.0-693.el7.x86_64/build M=/root/kmod-ceph/kmod-ceph/drivers/block modules
...
make -f scripts/Makefile.build obj=/root/kmod-ceph/kmod-ceph/drivers/block
set -e;  echo '  CC      /root/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.o'; gcc -Wp,-MD,/root/kmod-ceph/kmod-ceph/drivers/block/.rbd.mod.o.d  -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include -I./arch/x86/include -Iarch/x86/include/generated  -Iinclude -I./arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I./include/uapi -Iinclude/generated/uapi -include ./include/linux/kconfig.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -std=gnu89 -O2 -m64 -mno-mmx -mno-sse -mpreferred-stack-boundary=3 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -Wframe-larger-than=2048 -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -I/root/kmod-ceph/kmod-ceph/drivers/block/../../include  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(rbd.mod)"  -D"KBUILD_MODNAME=KBUILD_STR(rbd)" -DMODULE  -c -o /root/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.o /root/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.c;
...
make -f ./scripts/Makefile.modpost
...

注意其中头文件搜索路径最后一项即我们通过 ccflags-y 选项设置的路径:

-I/root/kmod-ceph/kmod-ceph/drivers/block/../../include

显然,此时我们的头文件已经没有任何机会得到优先加载了。

通过分析内核模块构建的 Makefile(/lib/modules/$(uname -r)/build/Makefile,实际上就是 linux 源代码树根目录下的 Makefile),可以得到如下的关系:

linux/Makefile
  include scripts/Kbuild.include
    build := -f $(srctree)/scripts/Makefile.build obj
      include scripts/Makefile.lib

其中与内核模块构建相关的关键代码如下(以 Linux kernel 5.5 为例):

// linux/Makefile

# Use make M=dir or set the environment variable KBUILD_EXTMOD to specify the
# directory of external module to build. Setting M= takes precedence.
ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

...

PHONY += $(objtree)/Module.symvers
$(objtree)/Module.symvers:
	@test -e $(objtree)/Module.symvers || ( \
	echo; \
	echo "  WARNING: Symbol version dump $(objtree)/Module.symvers"; \
	echo "           is missing; modules will have no dependencies and modversions."; \
	echo )

build-dirs := $(KBUILD_EXTMOD)
PHONY += modules
modules: descend $(objtree)/Module.symvers
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost

...

# Handle descending into subdirectories listed in $(build-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += descend $(build-dirs)
descend: $(build-dirs)
$(build-dirs): prepare
	$(Q)$(MAKE) $(build)=$@ \
	single-build=$(if $(filter-out $@/, $(filter $@/%, $(single-no-ko))),1) \
	need-builtin=1 need-modorder=1

// linux/scripts/Makefile.lib

c_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
		 -include $(srctree)/include/linux/compiler_types.h       \
		 $(_c_flags) $(modkern_cflags)                           \
		 $(basename_flags) $(modname_flags)

a_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
		 $(_a_flags) $(modkern_aflags)

cpp_flags      = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
		 $(_cpp_flags)

显然,只能从 NOSTDINC_FLAGS 或者 LINUXINCLUDE 的定义想办法,在 Linux kernel 4.7 以前,NOSTDINC_FLAGS 被定义成一个未定义的 recursively expanded 变量,因此只需要在内核模块的 Makefile 中将 NOSTDINC_FLAGS 定义成头文件搜索目录即可,如下所示:

ifeq ($(KERNELRELEASE),)

    # KVERSION should be set in the environment if this
    # build is not for the currently running kernel.
    KVERSION ?= $(shell uname -r)

    # BUILD_DIR should be set in the environment if a
    # subdirectory of /lib/modules/ is not appropriate.
    BUILD_DIR ?= /lib/modules/${KVERSION}/build

    PWD := $(shell pwd)

    export NOSTDINC_FLAGS
    NOSTDINC_FLAGS += -I$(PWD)/../../arch/x86/include
    NOSTDINC_FLAGS += -I$(PWD)/../../include

modules:
	echo $(PWD)
	$(MAKE) -C $(BUILD_DIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(BUILD_DIR) M=$(PWD) modules_install

clean:
	$(MAKE) -C $(BUILD_DIR) M=$(PWD) clean

.PHONY: modules modules_install clean

else

# Called from kernel build system -- just declare the module(s).

obj-m := rbd.o

endif

从下面构建过程的输出中可以看到内核模块本地头文件路径放在了最高优先级:

# make -n
...
make -C /lib/modules/3.10.0-693.el7.x86_64/build M=/root/kmod-ceph/kmod-ceph/drivers/block modules
...
make -f scripts/Makefile.build obj=/root/kmod-ceph/kmod-ceph/drivers/block
set -e;            echo '  CC [M]  /root/kmod-ceph/kmod-ceph/drivers/block/rbd.o'; gcc -Wp,-MD,/root/kmod-ceph/kmod-ceph/drivers/block/.rbd.o.d  -I/root/kmod-ceph/kmod-ceph/drivers/block/../../arch/x86/include -I/root/kmod-ceph/kmod-ceph/drivers/block/../../include -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include -I./arch/x86/include -Iarch/x86/include/generated  -Iinclude -I./arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I./include/uapi -Iinclude/generated/uapi -include ./include/linux/kconfig.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -std=gnu89 -O2 -m64 -mno-mmx -mno-sse -mpreferred-stack-boundary=3 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -Wframe-larger-than=2048 -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO  -DMODULE  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(rbd)"  -D"KBUILD_MODNAME=KBUILD_STR(rbd)" -c -o /root/kmod-ceph/kmod-ceph/drivers/block/.tmp_rbd.o /root/kmod-ceph/kmod-ceph/drivers/block/rbd.c;
...
make -f ./scripts/Makefile.modpost
...

但是需要注意的是,在 kbuild: Initialize exported variables 中,NOSTDINC_FLAGS 被初始定义成空,在 kbuild: Make NOSTDINC_FLAGS a simply expanded variable 中更是被定义成了 simply expanded 变量,因此,RHEL/CentOS 8.x 系列(基于 4.18 内核)无法使用上面的方法,当前我能想到的就是构建前手工修改 Linux 内核 Makefile,将 NOSTDINC_FLAGS 恢复成 4.7 以前的行为,当然,为了避免潜在的问题,更好的解决方案是在内核模块 Makefile 中新增一个导出变量,手工修改 Linux 内核 Makefile 并在 NOSTDINC_FLAGS 或者 LINUXINCLUDE 中引用该变量。

参考资料

The Two Flavors of Variables

https://www.gnu.org/software/make/manual/html_node/Flavors.html

Appending More Text to Variables

https://www.gnu.org/software/make/manual/html_node/Appending.html

Communicating Variables to a Sub-make

https://www.gnu.org/software/make/manual/html_node/Variables_002fRecursion.html

Functions for Transforming Text

https://www.gnu.org/software/make/manual/html_node/Functions.html

A Dive Into Kbuild

https://events19.linuxfoundation.org/wp-content/uploads/2017/11/A-Dive-into-Kbuild-Cao-Jin-Fujitsu.pdf

Linux Kernel Makefiles

https://github.com/torvalds/linux/blob/master/Documentation/kbuild/makefiles.rst

ccflag option in Makefile

https://stackoverflow.com/questions/51453193/ccflag-option-in-makefile

How do I force make/GCC to show me the commands?

https://stackoverflow.com/questions/5820303/how-do-i-force-make-gcc-to-show-me-the-commands

Gcc Options for Directory Search

https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html

Why would one use #include_next in a project?

https://stackoverflow.com/questions/10261382/why-would-one-use-include-next-in-a-project

CPP Wrapper Headers

https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html#Wrapper-Headers

CPP Common Predefined Macros

https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros