在交叉编译时,常常需要设定 CROSS_COMPILE 变量指定编译器的前缀,即不包含最后的 gcc 字符的名称。
一个简单的示例如下:
CROSS_COMPILE="aarch64-linux-gnu-"
当我们不指定时,默认使用 gcc 来进行编译。我们指定的这个前缀会在 Makefile 文件中被引用,添加 gcc 字符来制作完整的 gcc 编译命令名称。
linux 内核的顶层 Makefile 中,如下语句用来配置编译过程中用到的一些变量值。
# Make variables (CC, etc...)
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
AWK = awk
GENKSYMS = scripts/genksyms/genksyms
INSTALLKERNEL := installkernel
DEPMOD = /sbin/depmod
PERL = perl
CHECK = sparse
可以看到,当我们设定了 CROSS_COMPILE 之后,编译过程中使用到的工具的名称会改变。编译时会在 PATH 中搜索对应的可执行程序,没有找到则会报错,这常常是我们没有配置环境变量的问题。
上文中描述了 CROSS_COMPILE 变量的常规使用过程,最近我在制作内核热补丁的时候发现了对 CROSS_COMPILE 变量的不同使用方式。
kpatch-build 是内核热补丁的生成脚本,在生成内核热补丁的过程中需要编译打补丁前后的不同版本,并将变化的 .o 文件复制到临时目录中使用。
一般情况下,我们首先编译未打补丁的版本,这之后打上补丁后源文件修改,make 会检查到源文件的变化,只编译变化的文件。
kpatch-build 脚本中的相关代码如下:
unset KPATCH_GCC_TEMPDIR
# $TARGETS used as list, no quotes.
# shellcheck disable=SC2086
CROSS_COMPILE="$TOOLSDIR/kpatch-gcc " make "-j$CPUS" $TARGETS 2>&1 | logger || die
echo "Building patched source"
apply_patches
mkdir -p "$TEMPDIR/orig" "$TEMPDIR/patched"
KPATCH_GCC_TEMPDIR="$TEMPDIR"
export KPATCH_GCC_TEMPDIR
KPATCH_GCC_SRCDIR="$SRCDIR"
export KPATCH_GCC_SRCDIR
# $TARGETS used as list, no quotes.
# shellcheck disable=SC2086
CROSS_COMPILE="$TOOLSDIR/kpatch-gcc " \
KBUILD_MODPOST_WARN=1 \
make "-j$CPUS" $TARGETS 2>&1 | logger || die
上述脚本中,首先在未设定 KPATCH_GCC_TEMPDIR
变量的环境下编译了未打补丁前的版本,这之后执行 apply_patches
函数补丁,然后设定了 KPATCH_GCC_TEMPDIR
并重新编译。
注意这里对 CROSS_COMPILE 变量的设定内容为 "$TOOLSDIR/kpatch-gcc "
相较常规的使用方式,这里有两处不同。
按照上面的设置方式,CC 变量的值将变为 “$TOOLSDIR/kpatch-gcc gcc”。下面是在这种设定下的内核模块编译过程中执行的一些命令。
/home/longyu/proc_learning/kpatch-gcc gcc -Wp,-MD,/home/longyu/proc_learning/.proc_seq_file.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 -std=gnu89 -m64 -mno-mmx -mno-sse -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_CRC32=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -fno-delete-null-pointer-checks -Os -Wno-maybe-uninitialized -Wframe-larger-than=2048 -fno-stack-protector -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-var-tracking-assignments -g -femit-struct-debug-baseonly -fno-var-tracking -pg -mfentry -DCC_USING_FENTRY -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -DCC_HAVE_ASM_GOTO -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(proc_seq_file.mod)" -D"KBUILD_MODNAME=KBUILD_STR(proc_seq_file)" -DMODULE -c -o /home/longyu/proc_learning/proc_seq_file.mod.o /home/longyu/proc_learning/proc_seq_file.mod.c
/home/longyu/proc_learning/kpatch-gcc ld -r -m elf_x86_64 -T ./scripts/module-common.lds --build-id -o /home/longyu/proc_learning/proc_seq_file.ko /home/longyu/proc_learning/proc_seq_file.o /home/longyu/proc_learning/proc_seq_file.mod.o
这里通过 kpatch-gcc 脚本来调用 gcc、ld 等工具。由 kpatch-gcc 实现将打补丁后变化了的 .o 文件拷贝到 tmp 目录中使用的功能,这算是对 CROSS_COMPILE 的一种巧妙的使用方法。
kpatch-gcc 文件的内容如下:
#!/bin/bash
if [[ ${KPATCH_GCC_DEBUG:-0} -ne 0 ]]; then
set -o xtrace
fi
TOOLCHAINCMD="$1"
shift
if [[ -z "$KPATCH_GCC_TEMPDIR" ]]; then
exec "$TOOLCHAINCMD" "$@"
fi
declare -a args=("$@")
if [[ "$TOOLCHAINCMD" =~ "gcc" ]] ; then
while [ "$#" -gt 0 ]; do
if [ "$1" = "-o" ]; then
obj="$2"
# skip copying the temporary .o files created by
# recordmcount.pl
[[ "$obj" = */.tmp_mc_*.o ]] && break;
[[ "$obj" = */.tmp_*.o ]] && obj="${obj/.tmp_/}"
relobj=${obj//$KPATCH_GCC_SRCDIR\//}
case "$relobj" in
*.mod.o|\
*built-in.o|\
*built-in.a|\
vmlinux.o|\
.tmp_kallsyms1.o|\
.tmp_kallsyms2.o|\
init/version.o|\
arch/x86/boot/version.o|\
arch/x86/boot/compressed/eboot.o|\
arch/x86/boot/header.o|\
arch/x86/boot/compressed/efi_stub_64.o|\
arch/x86/boot/compressed/piggy.o|\
kernel/system_certificates.o|\
arch/x86/vdso/*|\
arch/x86/entry/vdso/*|\
drivers/firmware/efi/libstub/*|\
arch/powerpc/kernel/prom_init.o|\
lib/*|\
.*.o|\
*/.lib_exports.o)
break
;;
*.o)
mkdir -p "$KPATCH_GCC_TEMPDIR/orig/$(dirname "$relobj")"
[[ -e "$obj" ]] && cp -f "$obj" "$KPATCH_GCC_TEMPDIR/orig/$relobj"
echo "$relobj" >> "$KPATCH_GCC_TEMPDIR/changed_objs"
break
;;
*)
break
;;
esac
fi
shift
done
elif [[ "$TOOLCHAINCMD" = "ld" ]] ; then
while [ "$#" -gt 0 ]; do
if [ "$1" = "-o" ]; then
obj="$2"
relobj=${obj//$KPATCH_GCC_SRCDIR\//}
case "$obj" in
*.ko)
mkdir -p "$KPATCH_GCC_TEMPDIR/module/$(dirname "$relobj")"
cp -f "$obj" "$KPATCH_GCC_TEMPDIR/module/$relobj"
break
;;
.tmp_vmlinux*|vmlinux)
args+=(--warn-unresolved-symbols)
break
;;
*)
break
;;
esac
fi
shift
done
fi
exec "$TOOLCHAINCMD" "${args[@]}"
上述脚本实现了如下需求:
第一次编译时,不复制 .o 等文件
第二次编译时,复制变化的 .o 等文件到某临时目录中
第一次编译与第二次编译串行执行,kpatch-gcc 中通过设定 KPATCH_GCC_TEMPDIR
变量的内容区别第一次与第二次的执行逻辑。
在给 dpdk 的外部内核模块 rte_kni.ko 与 igb_uio.ko 制作热补丁时与到的第一个问题时如何单独编译 rte_kni.ko 与 igb_uio.ko,这是制作内核热补丁需要达成的条件。
RTE_KERNELDIR=/home/longyu/linux-3.16.35/
RTE_SDK=/home/longyu/dpdk-16.04
RTE_TARGET=x86_64-native-linuxapp-gcc
Index: Makefile
===================================================================
--- Makefile (revision 18024)
+++ Makefile (working copy)
@@ -36,6 +36,7 @@
#
MODULE = rte_kni
+RTE_OUTPUT=/home/longyu/dpdk-16.04/x86_64-native-linuxapp-gcc/
执行了上述操作后,我们可以在 lib 中的内核模块 Makefile 所在的目录中执行 make,这时侯就能够单独编译出对应的内核模块。
具体的示例如下:
[longyu@localhost kni]$ make
make[1]: Entering directory `/home/longyu/linux-3.16.35'
make[1]: Entering directory `/home/longyu/linux-3.16.35'
LD /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/built-in.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_main.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_api.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_common.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_ethtool.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_82599.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_82598.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_x540.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ixgbe_phy.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_82575.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_i210.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_api.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_mac.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_manage.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_mbx.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_nvm.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/e1000_phy.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_ethtool.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_hwmon.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_main.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_debugfs.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_param.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_procfs.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/igb_vmdq.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/80003es2lan.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/82571.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ethtool.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/ich8lan.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/kcompat.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/kcompat_ethtool.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/mac.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/manage.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/netdev.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/nvm.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/param.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/phy.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/i40e_main.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/i40e_common.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/i40e_ethtool.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/i40e_nvm.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/kni_misc.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/kni_net.o
CC [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/kni_ethtool.o
LD [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/rte_kni.o
(cat /dev/null; echo kernel//home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/rte_kni.ko;) > /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/modules.order
Building modules, stage 2.
MODPOST 1 modules
CC /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/rte_kni.mod.o
LD [M] /home/longyu/dpdk-16.04/lib/librte_eal/linuxapp/kni/rte_kni.ko
make[1]: Leaving directory `/home/longyu/linux-3.16.35'
INSTALL-MODULE rte_kni.ko
执行了上述操作后如果直接用 kpatch-build 脚本编译 rte_kni、igb_uio 的热补丁脚本会报错,报错信息如下:
no changed objects found
经过研究发现,这是 dpdk 的编译脚本中对 CROSS_COMPILE 重新设定导致的问题。
mk/rte.module.mk 是 dpdk 中编译外部内核模块会执行的 Makefile 脚本,脚本中由如下内容:
# build module
$(MODULE).ko: $(SRCS_LINKS)
@if [ ! -f $(notdir Makefile) ]; then ln -nfs $(SRCDIR)/Makefile . ; fi
@$(MAKE) -C $(RTE_KERNELDIR) M=$(CURDIR) O=$(RTE_KERNELDIR) \
CC="$(KERNELCC)" CROSS_COMPILE=$(CROSS) V=$(if $V,1,0)
可以看到这里 CROSS_COMPILE 被设定为 CROSS 变量的值。我们要成功编译出热补丁,只需要设定 CROSS 变量内容就可以了。
在我的系统中,我通过如下设定就成功的编译出了热补丁。
export CROSS='/home/longyu/kpatch_bin/libexec/kpatch/kpatch-gcc '
这种问题可能在其它地方也会遇到,根源还是在于 kpatch-build 中对 CROSS_COMPILE 的非常规使用上,尽管它能够实现其功能,但是在一定的情况下却带来了新的问题。