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 的非常規使用上,儘管它能夠實現其功能,但是在一定的情況下卻帶來了新的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章