Skip to content

Instantly share code, notes, and snippets.

@kaiwan
Created July 24, 2024 08:19
Show Gist options
  • Save kaiwan/641c6b012a5f074705ba5270ab1ccccf to your computer and use it in GitHub Desktop.
Save kaiwan/641c6b012a5f074705ba5270ab1ccccf to your computer and use it in GitHub Desktop.
The 'better' Makefile for (Linux) kernel module build
# Makefile
# ***************************************************************
# Brief Description:
# A 'better' Makefile template for Linux LKMs (Loadable Kernel Modules); besides
# the 'usual' targets (the build, install and clean), we incorporate targets to
# do useful (and indeed required) stuff like:
# - adhering to kernel coding style (indent+checkpatch)
# - several static analysis targets (via sparse, gcc, flawfinder, cppcheck)
# - two _dummy_ dynamic analysis targets (KASAN, LOCKDEP); just to remind you!
# - a packaging (.tar.xz) target and
# - a help target.
#
# To get started, just type:
# make help
#
# For details on this so-called 'better' Makefile, please refer my earlier book
# 'Linux Kernel Programming', Packt, Mar 2021, Ch 5 section 'A "better" Makefile
# template for your kernel modules'.
#------------------------------------------------------------------
# Set FNAME_C to the kernel module name source filename (without .c)
# This enables you to use this Makefile as a template; just update this variable!
# As well, the DEBUG_MODE variable (see it below) can be set to 'y' or 'n' (no being
# the default)
FNAME_C := list_demo_rdwrlock_lkm
ifeq ($(FNAME_C),)
$(error You MUST pass the C file like this:\
make FNAME_C=csrc-filename-without-.c target-name)
else
$(info >>> FNAME_C = $(FNAME_C))
endif
#------------------------------------------------------------------
# To support cross-compiling for kernel modules:
# For architecture (cpu) 'arch', invoke make as:
# make ARCH=<arch> CROSS_COMPILE=<cross-compiler-prefix>
# The KDIR var is set to a sample path below; you're expected to update it on
# your box to the appropriate path to the kernel src tree for that arch.
ifeq ($(ARCH),arm)
# *UPDATE* 'KDIR' below to point to the ARM Linux kernel source tree on your box
export KDIR := ~/big/kernels/bb-kernel/KERNEL
else ifeq ($(ARCH),arm64)
# *UPDATE* 'KDIR' below to point to the ARM64 (Aarch64) Linux kernel source
# tree on your box
export KDIR := ~/kernel/linux-5.4
else ifeq ($(ARCH),powerpc)
# *UPDATE* 'KDIR' below to point to the PPC64 Linux kernel source tree on your box
export KDIR := ~/kernel/linux-5.0
else
# 'KDIR' is the Linux 'kernel headers' package on your host system; this is
# usually an x86_64, but could be anything, really (f.e. building directly
# on a Raspberry Pi implies that it's the host)
export KDIR := /lib/modules/$(shell uname -r)/build
endif
# Compiler
CC := $(CROSS_COMPILE)gcc
#CC := clang
PWD := $(shell pwd)
# Here we're compiling two C src files and linking them into one module
obj-m += list_demo_rdwrlock_lkm.o
list_demo_rdwrlock_lkm-objs := miscdrv_wrapper.o list_demo_rdwrlock.o
#--- Debug or production mode?
# Set the DEBUG_MODE variable accordingly to y/n resp.
# NOTE:
# It's important to understand that, actually, debug info is *always* going to
# be generated when you build a module on a debug kernel, a kernel where
# CONFIG_DEBUG_INFO=y . This makes the setting of the ccflags-y (or the older
# EXTRA_CFLAGS) variable mostly redundant (besides the still useful -DDEBUG).
# Also, realize that the macro named 'DEBUG' is turned on by many CONFIG_*DEBUG*
# options; hence, we use a different macro name, viz, DEBUG_MODE.
DEBUG_MODE := y
DBG_STRIP := y
ifeq (${DEBUG_MODE}, y)
$(info >>> 'DEBUG' build)
else
$(info >>> 'Production' build)
endif
ifeq (${DEBUG_MODE}, y)
# https://www.kernel.org/doc/html/latest/kbuild/makefiles.html#compilation-flags
# EXTRA_CFLAGS deprecated; use ccflags-y
# As such, we're told that we can't use anything but -O2 for kernel code;
# -Og though, works & can help for debugging code so use it when 'debug' is on!
ccflags-y += -DDEBUG -Og -g -ggdb -gdwarf-4 -Wall -fno-omit-frame-pointer -fvar-tracking-assignments
DBG_STRIP := n
$(info >>> KDIR = $(KDIR))
else
INSTALL_MOD_STRIP := 1
ccflags-y += -UDEBUG -Wall
$(info >>> KDIR = $(KDIR))
endif
# We always keep the dynamic debug facility enabled; this allows us to turn
# dynamically turn on/off debug printk's later... To disable it simply comment
# out the following line
ccflags-y += -DDYNAMIC_DEBUG_MODULE
KMODDIR ?= /lib/modules/$(shell uname -r)
STRIP := ${CROSS_COMPILE}strip
# Gain access to kernel configs:
# Try to copy the kernel config from a few locations..
$(shell cp /boot/config-$(shell uname -r) $(KDIR)/.config 2>/dev/null)
#$(shell zcat /proc/config.gz > $(KDIR)/.config 2>/dev/null)
include $(KDIR)/.config
# Strip the module? Note:
# a) Only strip debug symbols else it won't load correctly
# b) WARNING! Don't strip modules when using CONFIG_MODULE_SIG* crytographic security
ifdef CONFIG_MODULE_SIG
DBG_STRIP := n
endif
ifdef CONFIG_MODULE_SIG_ALL
DBG_STRIP := n
endif
ifdef CONFIG_MODULE_SIG_FORCE
DBG_STRIP := n
endif
# gcc-10 issue:
#ccflags-y += $(call cc-option,--allow-store-data-races)
.PHONY: all clean help install tarxz-pkg checkpatch code-style indent \
sa sa_cppcheck sa_gcc sa_flawfinder sa_sparse
# Set VERBOSE to 1 to run make in verbose mode (make V=1 ...)
VERBOSE = 0
all: kmod install
kmod:
@echo
@echo '--- Building : KDIR=${KDIR} ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} ccflags-y=${ccflags-y} ---'
@${CC} --version|head -n1
@echo
make V=$(VERBOSE) -C $(KDIR) M=$(PWD) modules
ifeq (${DBG_STRIP}, y)
$(info >>> Stripping module (as it's a debug build -AND- CONFIG_MODULE_SIG* is Off)])
${STRIP} ./${FNAME_C}.ko
else
$(info >>> NOT stripping module (as it's either a debug build -OR- CONFIG_MODULE_SIG* is On)])
endif
install:
# NOTE:
# a) >= 6.5 (?): modules get installed under /lib/modules/$(uname -r)/updates (not 'extra')
# b) here we do NOT set the INSTALL_MOD_PATH env var; set it appropriately as required
@echo
@echo "--- installing ---"
@echo " [First, invoking the 'make' ]"
# Build - call 'make' (it also takes care of stripping the module when required)
make V=$(VERBOSE) kmod
@echo
@echo " [Now for the 'sudo make install' ]"
sudo make V=$(VERBOSE) -C $(KDIR) M=$(PWD) modules_install
@echo " sudo depmod"
sudo depmod
clean:
@echo
@echo "--- cleaning ---"
@echo
make V=$(VERBOSE) -C $(KDIR) M=$(PWD) clean
# from 'indent'
rm -f *~
# Any usermode programs to build? Insert the build target(s) here
#--------------- More (useful) targets! -------------------------------
INDENT := indent
# code-style : "wrapper" target over the following kernel code style targets
code-style:
make V=$(VERBOSE) indent
make V=$(VERBOSE) checkpatch
# indent- "beautifies" C code - to conform to the the Linux kernel
# coding style guidelines.
# Note! original source file(s) is overwritten, so we back it up.
indent:
ifeq (,$(shell which indent))
$(error ERROR: install indent first)
endif
@echo
@echo "--- applying kernel code style indentation with indent ---"
@echo
mkdir bkp 2> /dev/null; cp -f *.[chsS] bkp/
${INDENT} -linux --line-length95 *.[chsS]
# add source files as required
# Detailed check on the source code styling / etc
checkpatch:
# make V=$(VERBOSE) clean
@echo
@echo "--- kernel code style check with checkpatch.pl ---"
@echo
$(KDIR)/scripts/checkpatch.pl --no-tree -f --max-line-length=95 *.[ch]
# add source files as required
#--- Static Analysis
# sa : "wrapper" target over the following kernel static analyzer targets
sa:
make V=$(VERBOSE) sa_sparse
make V=$(VERBOSE) sa_gcc
make V=$(VERBOSE) sa_flawfinder
make V=$(VERBOSE) sa_cppcheck
# static analysis with sparse
sa_sparse:
ifeq (,$(shell which sparse))
$(error ERROR: install sparse first)
endif
# make V=$(VERBOSE) clean
@echo
@echo "--- static analysis with sparse ---"
@echo
# if you feel it's too much, use C=1 instead
# NOTE: deliberately IGNORING warnings from kernel headers!
make V=$(VERBOSE) -Wsparse-all C=2 CHECK="/usr/bin/sparse --os=linux --arch=$(ARCH)" -C $(KDIR) M=$(PWD) modules 2>&1 |egrep -v "^\./include/.*\.h|^\./arch/.*\.h"
# static analysis with gcc
sa_gcc:
# make V=$(VERBOSE) clean
@echo
@echo "--- static analysis with gcc ---"
@echo
make V=$(VERBOSE) W=1 -C $(KDIR) M=$(PWD) modules
# static analysis with flawfinder
sa_flawfinder:
ifeq (,$(shell which flawfinder))
$(error ERROR: install flawfinder first)
endif
# make V=$(VERBOSE) clean
@echo
@echo "--- static analysis with flawfinder ---"
@echo
flawfinder *.[ch]
# static analysis with cppcheck
sa_cppcheck:
ifeq (,$(shell which cppcheck))
$(error ERROR: install cppcheck first)
endif
# make V=$(VERBOSE) clean
@echo
@echo "--- static analysis with cppcheck ---"
@echo
cppcheck -v --force --enable=all -i .tmp_versions/ -i *.mod.c -i bkp/ --suppress=missingIncludeSystem .
# Packaging; just tar.xz as of now
PKG_NAME := ${FNAME_C}
tarxz-pkg:
rm -f ../${PKG_NAME}.tar.xz 2>/dev/null
make V=$(VERBOSE) clean
@echo
@echo "--- packaging ---"
@echo
tar caf ../${PKG_NAME}.tar.xz *
ls -l ../${PKG_NAME}.tar.xz
@echo '=== package created: ../$(PKG_NAME).tar.xz ==='
@echo 'Tip: when extracting, to extract into a dir of the same name as the tar file,'
@echo ' do: tar -xvf ${PKG_NAME}.tar.xz --one-top-level'
help:
@echo '=== Makefile Help : additional targets available ==='
@echo
@echo 'TIP: type make <tab><tab> to show all valid targets'
@echo
@echo '--- 'usual' kernel LKM targets ---'
@echo 'make (or "make all") : invokes both the "kmod" and "install" targets'
@echo 'kmod : builds the kernel module object (the .ko)'
@echo 'install : installs the kernel module(s) to INSTALL_MOD_PATH (default here: /lib/modules/$(shell uname -r)/)'
@echo 'clean : cleanup - remove all kernel objects, build artifacts, temp files/dirs, etc'
@echo
@echo '--- kernel code style targets ---'
@echo 'code-style : "wrapper" target over the following kernel code style targets'
@echo ' indent : run the $(INDENT) utility on source file(s) to indent them as per the kernel code style'
@echo ' checkpatch : run the kernel code style checker tool on source file(s)'
@echo
@echo '--- kernel static analyzer targets ---'
@echo 'sa : "wrapper" target over the following kernel static analyzer targets'
@echo ' sa_sparse : run the static analysis sparse tool on the source file(s)'
@echo ' sa_gcc : run gcc with option -W1 ("Generally useful warnings") on the source file(s)'
@echo ' sa_flawfinder : run the static analysis flawfinder tool on the source file(s)'
@echo ' sa_cppcheck : run the static analysis cppcheck tool on the source file(s)'
@echo 'TIP: use coccinelle as well (requires spatch): https://www.kernel.org/doc/html/latest/dev-tools/coccinelle.html'
@echo
@echo '--- kernel dynamic analysis targets ---'
@echo 'da_kasan : DUMMY target: reminder: run your code with the dynamic analysis KASAN tool enabled; requires configuring the kernel with CONFIG_KASAN On, rebuild and boot it'
@echo 'da_lockdep : DUMMY target: reminder: run your code with the dynamic analysis LOCKDEP tool (for deep locking issues analysis) enabled; requires configuring the kernel with CONFIG_PROVE_LOCKING On, rebuild and boot it'
@echo 'TIP: best to build a debug kernel with several kernel debug config options turned On, boot via it and run all your test cases'
@echo
@echo '--- misc targets ---'
@echo 'tarxz-pkg : tar and compress the LKM source files as a tar.xz into the dir above; allows one to transfer and build the module on another system'
@echo ' Tip: when extracting, to extract into a dir of the same name as the tar file,'
@echo ' do: tar -xvf ${PKG_NAME}.tar.xz --one-top-level'
@echo 'help : this help target'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment