diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 39220932..7fb7dac6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -141,3 +141,61 @@ jobs: - name: Remove the test module run: sudo dkms remove --all -m dkms_test -v 1.0 + + test-vm-clang: + name: Test in Ubuntu VM with clang + strategy: + matrix: + version: + - 22.04 + runs-on: ubuntu-${{ matrix.version }} + + steps: + - uses: actions/checkout@v3 + + - name: Install dependencies + run: | + sudo apt update -q + sudo apt install -qqy make libc6-dev llvm clang lld llvm-11 clang-11 lld-11 llvm-14 clang-14 lld-14 flex bison libssl-dev openssl + + - name: Remove apport + run: | + sudo apt remove -qqy apport + + - name: Build tiny kernels with clang + run: | + sudo apt install linux-source-5.19.0 + tar -C /tmp -xf /usr/src/linux-source-5.19.0.tar.bz2 + cd /tmp/linux-source-5.19.0 + # make `make clean` happy + mkdir ubuntu/hio + touch ubuntu/hio/Makefile + # LLVM=-version is supported since 5.18 + make LLVM=-11 tinyconfig + echo "CONFIG_MODULES=y" >> .config + echo "CONFIG_MODULE_SIG=y" >> .config + echo "CONFIG_MODULE_SIG_SHA1=y" >> .config + echo "CONFIG_MODULE_SIG_HASH=\"sha1\"" >> .config + sed -i 's/^CONFIG_LOCALVERSION=.*$/CONFIG_LOCALVERSION="-clang11"/' .config + yes $'\n' | make LLVM=-11 + sudo make modules_install + sudo make install + make clean + make LLVM=-14 tinyconfig + echo "CONFIG_MODULES=y" >> .config + echo "CONFIG_MODULE_SIG=y" >> .config + echo "CONFIG_MODULE_SIG_SHA512=y" >> .config + echo "CONFIG_MODULE_SIG_HASH=\"sha512\"" >> .config + sed -i 's/^CONFIG_LOCALVERSION=.*$/CONFIG_LOCALVERSION="-clang14"/' .config + yes $'\n' | make LLVM=-14 + sudo make modules_install + sudo make install + + - name: Install dkms + run: sudo make install + + - name: Run tests + run: | + KERNEL_VER="$(cd /tmp/linux-source-5.19.0 && make kernelversion)" + sudo env KERNEL_VER="${KERNEL_VER}-clang11" ./run_test.sh + sudo env KERNEL_VER="${KERNEL_VER}-clang14" ./run_test.sh diff --git a/dkms.in b/dkms.in index 523b79f9..a6115802 100644 --- a/dkms.in +++ b/dkms.in @@ -593,23 +593,6 @@ read_conf() [[ ! $make_command ]] && make_command="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build" [[ ! $clean ]] && clean="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build clean" - # Check if clang was used to compile or lld was used to link the kernel. - if [[ -e $kernel_source_dir/vmlinux ]]; then - if readelf -p .comment $kernel_source_dir/vmlinux | grep -q clang; then - make_command="${make_command} CC=clang" - fi - if readelf -p .comment $kernel_source_dir/vmlinux | grep -q LLD; then - make_command="${make_command} LD=ld.lld" - fi - elif [[ -e "${kernel_config}" ]]; then - if grep -q CONFIG_CC_IS_CLANG=y "${kernel_config}"; then - make_command="${make_command} CC=clang" - fi - if grep -q CONFIG_LD_IS_LLD=y "${kernel_config}"; then - make_command="${make_command} LD=ld.lld" - fi - fi - # Set patch_array (including kernel specific patches) count=0 for ((index=0; index < ${#PATCH[@]}; index++)); do @@ -833,6 +816,90 @@ run_build_script() { fi } +# helper functions for getting cc/ld used by kernel +detect_toolchain_version() { + # $1 = program name, e.g. gcc, ld.lld + # $2 = major version + # $3 = minor version + # $4 = patch version, optional + local -a filenames + if [[ "$4" ]]; then + filenames+=( "$1-$((10#$2)).$((10#$3)).$((10#$4))" ) + fi + filenames+=( + "$1-$((10#$2)).$((10#$3))" + "$1-$((10#$2))" + "$1" + ) + for f in "${filenames[@]}"; do + if command -V "$f" &>/dev/null; then + echo "$f" + return 0 + fi + done + return 1 +} +detect_toolchain_from_text() { + # $1 = program version text, contains x.y.z + # $2 = program name, e.g. gcc, clang + local patterns=( + 's/^.*\b([0-9]+\.[0-9]+\.[0-9]+).*$/\1/p' + ) + local -a ver + for p in "${patterns[@]}"; do + readarray -d '.' -t ver <<< "$(echo "$1" | sed -En "$p")" + if [[ "$ver" ]]; then + if detect_toolchain_version "$2" ${ver[0]} ${ver[1]} ${ver[2]}; then + return 0 + fi + fi + done + return 1 +} +detect_toolchain_from_kconfig() { + # $1 = program name in kconfig, e.g. GCC, CLANG, LD, LLD + # $2 = program name, e.g. gcc, clang, ld.bfd, ld.lld + local ver_full="$(grep "^CONFIG_$1_VERSION=" "${kernel_config}" | cut -f2 -d= )" + if [[ -z "${ver_full}" ]] || [[ "${ver_full}" = 0 ]]; then + return 1 + fi + local ver_major="${ver_full:0: -4}" + local ver_minor="${ver_full: -4:2}" + local ver_patch="${ver_full: -2}" + detect_toolchain_version "$2" "${ver_major}" "${ver_minor}" "${ver_patch}" + return $? +} +detect_toolchain_from_cc_version_text() { + # since kernel 5.8 + local text="$(grep "^CONFIG_CC_VERSION_TEXT=" "${kernel_config}" | cut -f2 -d= )" + if [[ -z "$text" ]]; then + return 1 + fi + if echo "$text" | grep -q gcc; then + detect_toolchain_from_text "$text" "gcc" + return $? + elif echo "$text" | grep -q clang; then + detect_toolchain_from_text "$text" "clang" + return $? + fi + return 1 +} +detect_toolchain_from_vmlinux() { + # $1 = string in vmlinux .comment section, e.g. clang, LLD + # $2 = program name, e.g. clang, ld.lld + local vmlinux="${kernel_source_dir}/vmlinux" + if [[ ! -f "$vmlinux" ]]; then + return 1 + fi + local text="$(readelf -p .comment "$vmlinux" 2>/dev/null | grep "$1" 2>/dev/null)" + if [[ -z "$text" ]]; then + return 1 + fi + detect_toolchain_from_text "$text" "$2" + return $? +} + + # Register a DKMS-ified source tree with DKMS. # This function is smart enough to register the module if we # passed a source tree or a tarball instead of relying on the source tree @@ -1040,22 +1107,52 @@ prepare_build() $"Check $build_dir for more information." done - if [[ -e "${kernel_config}" ]]; then - if grep -q 'CONFIG_CC_IS_CLANG=y' "${kernel_config}"; then - local cc=clang - if command -v "$cc" >/dev/null; then - export CC="$cc" - export KERNEL_CC="$cc" + # Detect CC and LD used by kernel + if ! grep -qE '(^|\s)CC=' <<< "${make_command}"; then + # No CC specified within MAKE in dkms.conf + if [[ -e "${kernel_config}" ]]; then + if grep -q '^CONFIG_CC_IS_GCC=y' "${kernel_config}"; then + # Since kernel 4.18 + KERNEL_CC="$(detect_toolchain_from_kconfig GCC gcc)" + elif grep -q '^CONFIG_CC_IS_CLANG=y' "${kernel_config}"; then + # Since kernel 4.18 + KERNEL_CC="$(detect_toolchain_from_kconfig CLANG clang)" + elif [[ -f "$kernel_source_dir/.kernelvariables" ]]; then + # Debian specific + KERNEL_CC="$(echo -e "show-%:\n\t@echo \$(\$*)\ninclude $kernel_source_dir/.kernelvariables" | make -f - show-CC)" + else + KERNEL_CC="$(detect_toolchain_from_cc_version_text)" fi fi - - if grep -q 'CONFIG_LD_IS_LLD=y' "${kernel_config}"; then - local ld=ld.lld - if command -v "$ld" >/dev/null; then - export LD="$ld" - export KERNEL_LD="$ld" + if [[ -z "$KERNEL_CC" ]]; then + KERNEL_CC="$(detect_toolchain_from_vmlinux GCC gcc || \ + detect_toolchain_from_vmlinux clang clang)" + fi + if [[ "$KERNEL_CC" ]]; then + make_command="${make_command} CC=${KERNEL_CC}" + # the pre_build script may need these, e.g. zfs-dkms + export CC="${KERNEL_CC}" KERNEL_CC + fi + fi + if ! grep -qE '(^|\s)LD=' <<< "${make_command}"; then + # No LD specified within MAKE in dkms.conf + if [[ -e "${kernel_config}" ]]; then + if grep -q '^CONFIG_LD_IS_LLD=y' "${kernel_config}"; then + # Since kernel 5.8 + KERNEL_LD="$(detect_toolchain_from_kconfig LLD ld.lld)" + elif grep -q '^CONFIG_LD_IS_BFD=y' "${kernel_config}"; then + # Since kernel 5.14 + KERNEL_LD="$(detect_toolchain_from_kconfig LD ld.bfd)" fi fi + if [[ -z "$KERNEL_LD" ]]; then + KERNEL_LD="$(detect_toolchain_from_vmlinux LLD ld.lld)" + fi + if [[ "$KERNEL_LD" ]]; then + make_command="${make_command} LD=${KERNEL_LD}" + # the pre_build script may need these, e.g. zfs-dkms + export LD="${KERNEL_LD}" KERNEL_LD + fi fi # Run the pre_build script @@ -1073,12 +1170,6 @@ actual_build() echo $"" echo $"Building module:" - if [ -f "$kernel_source_dir/.kernelvariables" ]; then - export CC=$(echo -e "show-%:\n\t@echo \$(\$*)\ninclude $kernel_source_dir/.kernelvariables" | make -f - show-CC) - else - unset CC - fi - invoke_command "$clean" "Cleaning build area" background echo $"DKMS make.log for $module-$module_version for kernel $kernelver ($arch)" >> "$build_log" date >> "$build_log" @@ -2304,7 +2395,7 @@ PATH="$PATH:/usr/lib/dkms" umask 022 # Unset environment variables that may interfere with the build -unset CC CXX CFLAGS CXXFLAGS LDFLAGS +unset CC CXX LD CFLAGS CXXFLAGS LDFLAGS KERNEL_CC KERNEL_LD # Set important variables current_kernel=$(uname -r) diff --git a/run_test.sh b/run_test.sh index 876bb680..7cf47dfd 100755 --- a/run_test.sh +++ b/run_test.sh @@ -9,6 +9,11 @@ cd "$(dirname -- "$0")" KERNEL_VER="${KERNEL_VER:-$(uname -r)}" echo "Using kernel ${KERNEL_VER}" +case "$(uname -s)" in + Linux) KCONFIG="/lib/modules/${KERNEL_VER}/build/.config" ;; + GNU/kFreeBSD) KCONFIG="/usr/src/kfreebsd-headers-${KERNEL_VER}/sys/.config" ;; +esac + # Override PATH to use the local dkms binary PATH="$(pwd):$PATH" export PATH @@ -159,6 +164,9 @@ genericize_expected_output() { if [[ $# -ge 2 && "$2" =~ uninstall|unbuild|remove ]] ; then sed -i '/^depmod\.\.\.$/d' ${output_log} fi + # Remove "CC=... LD=..." part from the "make ..." line. They have different values on different platforms + sed -i '/^make /s/ CC=[0-9A-Za-z_\.\-]*[0-9A-Za-z]//' ${output_log} + sed -i '/^make /s/ LD=[0-9A-Za-z_\.\-]*[0-9A-Za-z]//' ${output_log} # Signing related output. Drop it from the output, to be more generic sed -i '/^Sign command:/d' ${output_log} sed -i '/^Signing key:/d' ${output_log} @@ -374,7 +382,16 @@ EOF else ALTER_HASH="sha512" fi - echo "CONFIG_MODULE_SIG_HASH=\"${ALTER_HASH}\"" > /tmp/dkms_test_kconfig + if [[ -f "$KCONFIG" ]]; then + cp "$KCONFIG" /tmp/dkms_test_kconfig + if grep -q '^CONFIG_MODULE_SIG_HASH=' /tmp/dkms_test_kconfig; then + sed -i "s/^CONFIG_MODULE_SIG_HASH=.*$/CONFIG_MODULE_SIG_HASH=\"${ALTER_HASH}\"/" /tmp/dkms_test_kconfig + else + echo "CONFIG_MODULE_SIG_HASH=\"${ALTER_HASH}\"" >> /tmp/dkms_test_kconfig + fi + else + echo "CONFIG_MODULE_SIG_HASH=\"${ALTER_HASH}\"" > /tmp/dkms_test_kconfig + fi run_with_expected_output dkms build -k "${KERNEL_VER}" -m dkms_test -v 1.0 --config /tmp/dkms_test_kconfig --force << EOF Building module: @@ -463,11 +480,11 @@ dkms_test/1.0, ${KERNEL_VER}, $(uname -m): installed EOF echo 'Checking modinfo' -run_with_expected_output sh -c "modinfo /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} | head -n 4" << EOF -filename: /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} -version: 1.0 +run_with_expected_output sh -c "modinfo /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} | head -n 4 | sort" << EOF description: A Simple dkms test module +filename: /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} license: GPL +version: 1.0 EOF if [[ "${NO_SIGNING_TOOL}" = 0 ]]; then @@ -585,11 +602,11 @@ if ! [[ -f "/lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_co fi echo 'Checking modinfo' -run_with_expected_output sh -c "modinfo /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} | head -n 4" << EOF -filename: /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} -version: 1.0 +run_with_expected_output sh -c "modinfo /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} | head -n 4 | sort" << EOF description: A Simple dkms test module +filename: /lib/modules/${KERNEL_VER}/${expected_dest_loc}/dkms_test.ko${mod_compression_ext} license: GPL +version: 1.0 EOF if [[ "${NO_SIGNING_TOOL}" = 0 ]]; then