Files
2024-09-27 17:44:50 +05:30

345 lines
8.9 KiB
Bash
Executable File

#!/bin/bash
# test_lkm
# Test loadable kernel module(s) (LKM) from the given directory, recursively
# NOTE:
# - we ASSUME a file named Makefile will be present within the LKM dir
# - currently, there's no automated way to pass module parameters
# - nice bash colo[u]r values can be found here:
# https://gist.github.com/acook/1199000
# (c) Kaiwan NB, kaiwanTECH
# License: MIT
# Turn on Bash 'strict mode'! V useful
# "Convert many kinds of hidden, intermittent, or subtle bugs into immediate, glaringly obvious errors"
# ref: http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
name=$(basename $0)
ECHO_PFX=">>>"
DEBUG=0
decho()
{
[[ ${DEBUG} -eq 0 ]] && return
echo "$*"
}
QUIET=0
SUCCESS_MSG="^^^ SUCCESS ^^^"
WARNING_BUILD_MSG="
### WARNING(s) during build; must check ###
"
FAIL_MSG="
*** *** *** *** *** *** *** FAIL *** *** *** *** *** *** *** ***
"
MAKEFILE_FAIL_MSG="The Makefile doesn't have targets corresponding to the 'better' Makefile
(https://github.com/PacktPublishing/Linux-Kernel-Programming_2E/blob/main/ch6/foreach/prcs_showall/Makefile)"
failit()
{
# highlight the failure msg in red bg white fg!
echo -e "\e[1m\e[41m${FAIL_MSG} \e[0m" >&2
echo "message: $*" >&2
return 1
}
#----------------------------------------------------------------------
LINE="-------------------------------------------------------------------------"
CHECKPATCH=/lib/modules/$(uname -r)/build/scripts/checkpatch.pl
OPEN_IN_ED=0
ED=gedit
TMP_CHECKPATCH=/tmp/tmp_checkpatch
TMP_FLAWFINDER=/tmp/tmp_flawfinder
TMP_CPPCHECK=/tmp/tmp_cppcheck
TMP_SPARSE=/tmp/tmp_sparse
TMP_SGCC=/tmp/tmp_sgcc
# 'better' Makefile?
verify_makefile()
{
local okay=1
[[ ! -f Makefile ]] && {
echo "no Makefile, skipping..."
return 1
}
grep -w "^install:" Makefile >/dev/null || okay=0
grep -w "^code-style:" Makefile >/dev/null || okay=0
grep -w "^checkpatch:" Makefile >/dev/null || okay=0
grep -w "^sa:" Makefile >/dev/null || okay=0
grep -w "^sa_flawfinder:" Makefile >/dev/null || okay=0
grep -w "^sa_cppcheck:" Makefile >/dev/null || okay=0
grep -w "^sa_sparse:" Makefile >/dev/null || okay=0
grep -w "^help:" Makefile >/dev/null || okay=0
[[ ${okay} -eq 1 ]] && return 0 || return 2
}
lkm_static_analysis_checks()
{
make clean >/dev/null 2>&1
local SRC=$(ls *.c 2>/dev/null)
local SRC_NOEXT=${SRC::-2}
decho "SRC=${SRC} SRC_NOEXT=${SRC_NOEXT}"
chmod 0644 ${SRC} Makefile
[ ${OPEN_IN_ED} -eq 1 ] && ${ED} ${SRC} Makefile
#echo ${LINE}
grep -q "MODULE_LICENSE(.*)" ${SRC} || echo "${ECHO_PFX} *** NO license! ***" && \
echo "${ECHO_PFX} License present"
if [[ ${QUIET} -eq 1 ]] ; then
make checkpatch > ${TMP_CHECKPATCH} 2>&1 || true
else
make checkpatch 2>&1 |tee ${TMP_CHECKPATCH} || true
fi
local chkpatch_err=0
chkpatch_err=$(grep -w "^ERROR:" ${TMP_CHECKPATCH} |wc -l) || chkpatch_err=0
local chkpatch_warns=0
chkpatch_warns=$(grep -w "^WARNING:" ${TMP_CHECKPATCH} |wc -l) || chkpatch_warns=0
(
set +e # Bash strict mode side effects
grep "pr_fmt(.*)" ${SRC} >/dev/null
if [ $? -ne 0 ]; then
echo "${ECHO_PFX} ### pr_fmt() macro missing! ###"
else
echo "${ECHO_PFX} pr_fmt() macro present"
fi
set -e
echo "${ECHO_PFX} checkpatch: errors=${chkpatch_err} warnings=${chkpatch_warns}"
#echo ${LINE}
)
#--- --- Static analysis --- ---
echo "-------------------- Static analysis --------------------"
if [[ ${QUIET} -eq 1 ]] ; then
make sa_flawfinder > ${TMP_FLAWFINDER} 2>&1 || true
else
make sa_flawfinder 2>&1 | tee ${TMP_FLAWFINDER} || true
fi
local flawfinder_hits=0
flawfinder_hits=$(grep "^Hits =" ${TMP_FLAWFINDER} |cut -d'=' -f2) || flawfinder_hits=0
grep -q "No hits found" ${TMP_FLAWFINDER} && flawfinder_hits=0
echo "${ECHO_PFX} flawfinder_hits = ${flawfinder_hits}" # | tee -a ${RESFILE}
#--- cppcheck
if [[ ${QUIET} -eq 1 ]] ; then
make sa_cppcheck > ${TMP_CPPCHECK} 2>&1 || true
else
make sa_cppcheck 2>&1 | tee ${TMP_CPPCHECK} || true
fi
local cppcheck_hits=0
cppcheck_hits=$(grep "\^" ${TMP_CPPCHECK} |wc -l) || cppcheck_hits=0
echo "${ECHO_PFX} cppcheck_hits = ${cppcheck_hits}" #| tee -a ${RESFILE}
#--- sparse
if [[ ${QUIET} -eq 1 ]] ; then
make sa_sparse > ${TMP_SPARSE} 2>&1 || true
else
make sa_sparse 2>&1 | tee ${TMP_SPARSE} || true
fi
local sparse_err=0
sparse_err=$(grep -w "error:" ${TMP_SPARSE} |wc -l) || sparse_err=0
local sparse_warns=0
sparse_warns=$(grep -w "warning:" ${TMP_SPARSE} |wc -l) || sparse_warns=0
echo "${ECHO_PFX} sparse: errors=${sparse_err} warnings=${sparse_warns}" #| tee -a ${RESFILE}
#--- (static analysis) gcc
if [[ ${QUIET} -eq 1 ]] ; then
make sa_gcc > ${TMP_SGCC} 2>&1 || true
else
make sa_gcc 2>&1 | tee ${TMP_SGCC} || true
fi
local sgcc_err=0
sgcc_err=$(grep -w "error:" ${TMP_SGCC} |wc -l) || sgcc_err=0
local sgcc_warns=0
sgcc_warns=$(grep -w "warning:" ${TMP_SGCC} |wc -l) || sgcc_warns=0
echo "${ECHO_PFX} (static)gcc: errors=${sgcc_err} warnings=${sgcc_warns}" #| tee -a ${RESFILE}
#echo ${LINE}
rm -f ${TMP_CHECKPATCH} ${TMP_FLAWFINDER} ${TMP_CPPCHECK} ${TMP_SPARSE} ${TMP_SGCC}
[ 0 -eq 1 ] && {
echo "---Static analysis with Coccinelle---"
coccichk ${SRC}
echo ${LINE}
}
return 0
}
#----------------------------------------------------------------------
build_lkm()
{
echo "----------------------- LKM Build -----------------------"
local TMP_BUILD=.tmp_build
if [[ ${QUIET} -eq 1 ]] ; then
decho "make clean ; make"
make clean >/dev/null 2>&1
make >${TMP_BUILD} 2>&1
else
make clean
make 2>&1 |tee ${TMP_BUILD}
fi
local make_err=0
make_err=$(grep -w "error:" ${TMP_BUILD} |wc -l) || make_err=0
local make_warns=0
make_warns=$(grep -w "warning:" ${TMP_BUILD} |wc -l) || make_warns=0
echo "${ECHO_PFX} make: errors=${make_err} warnings=${make_warns}" #| tee -a ${RESFILE}
[[ ${make_err} -ge 1 ]] && {
failit "LKM build failed"
echo ${LINE}
return 1
}
#echo ${LINE}
grep "warning:" ${TMP_BUILD} && echo -e "\e[1m\e[1;33m${WARNING_BUILD_MSG} \e[0m"
#cat ${TMP_BUILD}
#rm -f ${TMP_BUILD}
return 0
}
# No intelligence... simply performs insmod, rmmmod
# -no module params, checking of o/p
insmod_rmmod_lkm()
{
echo "-------------------- LKM insmod+rmmod -------------------"
[[ $(ls *.ko 2>/dev/null) ]] || {
make clean >/dev/null 2>&1
echo "<<< No LKM .ko found? (likely it's usermode code here); skipping... >>>"
return 2
}
local LKM_KO=$(basename $(ls *.ko))
local LKM=${LKM_KO::-3} # get rid of the trailing .ko
[[ -z "${LKM}" ]] && {
make clean >/dev/null 2>&1
failit "couldn't get module name"
return 1
}
sudo rmmod ${LKM} 2>/dev/null || true
sudo dmesg -C
sudo insmod ${LKM_KO} || {
make clean >/dev/null 2>&1
failit "insmod ${LKM_KO} failed"
[[ ${QUIET} -eq 0 ]] && sudo dmesg
return 1
}
lsmod|grep ${LKM}
sudo rmmod ${LKM} 2>/dev/null
[[ ${QUIET} -eq 1 ]] && sudo dmesg -C || sudo dmesg -c
make clean >/dev/null 2>&1
echo -e "\e[1m\e[42m${SUCCESS_MSG} \e[0m" >&2
#echo "${SUCCESS_MSG}"
return 0
}
# Parameter(s):
# $1 : directory containing the LKM
# Returns:
# 0 on success
# non-zero on failure
testlkm()
{
cd $1 || {
failit "cd to $1"
return 1
}
local BETTER_MAKEFILE_OK=1
verify_makefile
local _ret=$?
[[ ${_ret} -ne 0 ]] && {
echo "*** 'better' Makefile verification failed ***"
[[ ${_ret} -eq 2 ]] && echo "${MAKEFILE_FAIL_MSG}"
BETTER_MAKEFILE_OK=0
}
if [[ ${BETTER_MAKEFILE_OK} -eq 1 ]]; then
lkm_static_analysis_checks
[[ $? -ne 0 ]] && {
echo "*** lkm_static_analysis_checks() failed ***"
return 1
}
else
echo "${ECHO_PFX} No 'better' Makefile, so skipping LKM static analysis checks"
fi
build_lkm
[[ $? -ne 0 ]] && {
# echo "*** build_lkm() failed ***"
return 1
}
# Ok, the LKM build is fine; insmod & rmmod it now
insmod_rmmod_lkm
[[ $? -ne 0 ]] && {
# echo "*** insmod_rmmod_lkm() failed ***"
return 1
}
return 0
}
usage()
{
echo -n "Usage: ${name} [-q|-h] starting-dir
Parameters:
starting-dir : test all kernel modules starting from this directory, recursively (mandatory)
[Optional]:
[-q] : run in quiet mode (default: "
[[ ${QUIET} -eq 1 ]] && echo "ON)" || echo "OFF)"
echo " [-h] : show this help screen"
echo "Expects usage of the 'better' Makefile (but will work regardless)"
}
#--- 'main'
[[ $# -eq 0 ]] && {
usage ; exit 1
}
if [[ $# -eq 1 ]] ; then
[[ "$1" = "-h" ]] && {
usage ; exit 0
}
[[ "$1" = "-q" ]] && { # only '-q' isn't ok
usage ; exit 1
}
LKM_DIR=$1
elif [[ $# -eq 2 ]] ; then
[[ "$1" = "-q" ]] && QUIET=1
LKM_DIR=$2
fi
decho "LKM_DIR=${LKM_DIR}, quiet=${QUIET}"
[[ ${QUIET} -eq 0 ]] && {
echo "FYI, can pass -q to run in quiet mode"
ECHO_PFX="
${ECHO_PFX}"
}
i=1
TOP=$(pwd)
for KDIR in $(find ${LKM_DIR}/ -type d)
do
[[ "${KDIR}" == *".git"* || "${KDIR}" == "." ]] && continue || true
#--- special case!
[[ "${KDIR}" == *"3_lockdep"* || "${KDIR}" == "." ]] && continue || true
if [[ ! -f ${TOP}/${KDIR}/Makefile ]] ; then
[[ ${QUIET} -eq 0 ]] && echo "<no Makefile in ${KDIR}, skipping..>"
continue
fi
[[ $i -ne 1 ]] && echo
#echo -e "\e[1m\e[41m${FAIL_MSG} \e[0m" >&2
echo -e "\e[1m\e[43m====================== $i: LKM ${KDIR} ====================== \e[0m"
cd ${TOP}
testlkm ${KDIR} || true
let i=i+1
done
exit 0