CROSS_COMPILE 的妙用

在交叉编译时,常常需要设定 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 中 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 变量设定

注意这里对 CROSS_COMPILE 变量的设定内容为 "$TOOLSDIR/kpatch-gcc "

相较常规的使用方式,这里有两处不同。

  1. gcc 字符没有省略
  2. 末尾有一个空格

按照上面的设置方式,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 的功能需求与实现

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 制作热补丁时的问题

在给 dpdk 的外部内核模块 rte_kni.ko 与 igb_uio.ko 制作热补丁时与到的第一个问题时如何单独编译 rte_kni.ko 与 igb_uio.ko,这是制作内核热补丁需要达成的条件。

  1. 设定必须的环境变量
RTE_KERNELDIR=/home/longyu/linux-3.16.35/
RTE_SDK=/home/longyu/dpdk-16.04
RTE_TARGET=x86_64-native-linuxapp-gcc
  1. 修改 rte_kni.ko 与 igb_uio.ko 的 Makefile
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
  1. 设定 CROSS 变量的内容

执行了上述操作后如果直接用 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 的非常规使用上,尽管它能够实现其功能,但是在一定的情况下却带来了新的问题。

你可能感兴趣的:(CROSS_COMPILE,kpatch-build,内核热补丁,rte_kni.ko)