赞
踩
交叉编译简单说来,就是编译成果物的地儿不是你运行这个成果物的地儿。最常见的场景,就是我们要编译一个 ARM版本
的可执行程序,但我们编译这个 ARM版本
可执行程序的地方,是在一个 x86_x64
的平台上。
绝大部分的原因,是目标平台不具备编译成果物的算力。具体说来,就是ARM平台早期是 并没有
编译代码所需的 算力
和 相关空间
的。所以,不得不借助性能更高的平台来辅助进行编译成果物,然后ARM平台仅负责运行成果物即可。
虽然绝大多数的 ARM Linux系统中编译的成果物是在对应的 x86_x64平台的Linux系统中进行的,所以大多数时候使用Windows平台电脑需要安装一个虚拟机或者连接到某个x86_x64平台的Linux编译服务器中,但实际上这种搭配大多数情况是为了方便,也为了让编译者熟悉Linux环境,但这种搭配并不是唯一的解决方案。
ARM官网 上就能直接下载各种平台( Linux
、Windows
)的编译工具链,另外一个很常见的第三方工具链制作商,linaro也是对外直接提供可用版本的工具链,但不支持全平台, linaro gnu。
如果上述两个网站你都觉得不是很符合自己的开发板,那么你也可以自己动手做一个链子,符合目标平台的交叉编译链制作及简单分析。 这里主要使用crosstool-ng
这个工具进行的制作,自己做链子的好处是你可以自己按需选择对应的gcc版本,以及glibc版本和一些其他重要基础库的版本。而上面现成的链子可能并不刚好是你需要的版本搭配。
以下编译前提均假设编译人员已经获得了一个可以使用的交叉编译工具链,并且已经将编译工具链的可执行程序设置进环境变量。
大多数的简单第三方库,均可以尝试以下的方式。这里的简单,值得是不额外依赖一些其他第三方库的库。
configure --host=arm-linux-gnueabi --prefix=${PWD}/build
make
make install
一般验证自己是不是编译的正确的主要检查步骤就是,在make的时候查看对应输出打印,如果开头的的编译的确是对应编译工具链名称的开头,那么至少配置是对的,这里给个例子:
/bin/sh ../libtool --tag=CXX --mode=compile arm-at91-linux-gnueabi-g++ -std=gnu++11 -DHAVE_CONFIG_H -I. -I.. -I/data1/xiaoyanyi/work/snmp++/snmp++-3.5.0/include -pthread -I/data1/xiaoyanyi/work/openssllll/include -D_GNU_SOURCE -g -O2 -pthread -MT asn1.lo -MD -MP -MF .deps/asn1.Tpo -c -o asn1.lo asn1.cpp
libtool: compile: arm-at91-linux-gnueabi-g++ -std=gnu++11 -DHAVE_CONFIG_H -I. -I.. -I/data1/xiaoyanyi/work/snmp++/snmp++-3.5.0/include -pthread -I/data1/xiaoyanyi/work/openssllll/include -D_GNU_SOURCE -g -O2 -pthread -MT asn1.lo -MD -MP -MF .deps/asn1.Tpo -c asn1.cpp -fPIC -DPIC -o .libs/asn1.o
libtool: compile: arm-at91-linux-gnueabi-g++ -std=gnu++11 -DHAVE_CONFIG_H -I. -I.. -I/data1/xiaoyanyi/work/snmp++/snmp++-3.5.0/include -pthread -I/data1/xiaoyanyi/work/openssllll/include -D_GNU_SOURCE -g -O2 -pthread -MT asn1.lo -MD -MP -MF .deps/asn1.Tpo -c asn1.cpp -o asn1.o >/dev/null 2>&1
make的过程中,输出的打印里的确是以我们指定的host开头的,那么这步骤至少是没问题的了。
先简单的介绍一下 configure
make
make install
三部曲每部分是做了什么,然后在展开介绍configure的通用参数。
configure
: 通过配置生成makefile文件
make
:使用configure生成的Makefile文件进行编译工作
make install
:将make生成的成果物文件按照prefix的路径进行复制,有时候也会动态生成一些说明文档
所以大多数时候,configure
配置对了,后面两步就一定能走通,99%的编译不过问题基本都是configure
的时候配置不对。以下重点讲解configure
相关的参数。
这里prefix中文解释就是安装路径,也就是make install的时候,最终这些库会放到哪里去。一般对于交叉编译而言,是需要指定的,因为默认的路径是 /usr/local/
,但这路径实际上对于交叉编译而言一定是不行的。因为这个路径通常放的是本身系统的库,如果交叉编译的库放进去后,本身系统也会去检索这个路径下的库,名字虽然匹配上了,但是用不起来,后续会造成极大的麻烦。
这里还有一点需要额外注意的,后文会重新展开这个问题。这里先说结论:
如果你需要编译的库ABC依赖DEF,那么你先编译DEF的时候,最好把prefix设置成自己交叉编译链的
sysroot/usr
中。
简单解释一下,交叉编译和普通的编译第三方库差异主要在于需要指定host这个变量。这里贴一段标准解释:
System types:
--build=BUILD configure for building on BUILD [guessed]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
这里仅需要额外强调一点,host这个参数的指定逻辑和使用的目标编译工具链名称有关,假设你的编译工具链的gcc名字叫 arm-at91-linux-gnueabi-gcc
,那么这里的host名字就是 arm-at91-linux-gnueabi
。具体的逻辑就是把对应链子的 -gcc
部分拆掉就是host的名字。其实configure脚本的逻辑也就是对应的反向在host后拼接一个 -gcc
而已。
这两个参数一般是在编译的时候,可配置的打开某些功能或者关闭某些功能的时候,会使用到。每个第三方库的特色不一样,这里推荐遇到编译不过的问题,首先就去看看:
./configure --help
有时候发现你编译的第三方库依赖了过多的其他库,而且这些功能对你并不需要的时候,可以尽可能的--disable-xxxxx
和 --without-xxxx
。这样,在后续make的时候,就不会出现某些依赖库找不到的报错了。
这些shell环境变量也会产生一定的效果,有时候你百度一些博客教程的时候,会搜到交叉编译的某些指导文档会这么写:
./configure CC=arm-linux-gnueabi-gcc --prefix=${PWD}/build
make
make install
或者写成这样:
export CC=arm-linux-gnueabi-gcc
./configure --prefix=${PWD}/build
make
make instal
这两种手法的思路一致的,都是利用./configure 脚本是可以阅读当前shell环境变量中的CC
,并且将这个环境变量替换到脚本里,从而实现CC
替换成对应链子的目的。相关解释同样可以在--help
中看到
Some influential environment variables:
CC C compiler command
CFLAGS C compiler flags
LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
nonstandard directory <lib dir>
但需要注意的一点是:这种方法有一些隐患,主要在于在很多时候,交叉编译并不是把gcc
变成arm-linux-gnueabi-gcc
一切就完事了。我们可以在Makefile中和交叉编译链的bin
路径中看到如下打印:
(base) xiaoyanyi@snmp++-3.5.0$ls ~/cross-tool/arm-at91-linux-gnueabi/bin
arm-at91-linux-gnueabi-addr2line arm-at91-linux-gnueabi-gcc-4.9.2 arm-at91-linux-gnueabi-nm
arm-at91-linux-gnueabi-ar arm-at91-linux-gnueabi-gcc-ar arm-at91-linux-gnueabi-objcopy
arm-at91-linux-gnueabi-as arm-at91-linux-gnueabi-gcc-nm arm-at91-linux-gnueabi-objdump
arm-at91-linux-gnueabi-c++ arm-at91-linux-gnueabi-gcc-ranlib arm-at91-linux-gnueabi-populate
arm-at91-linux-gnueabi-cc arm-at91-linux-gnueabi-gcov arm-at91-linux-gnueabi-ranlib
arm-at91-linux-gnueabi-c++filt arm-at91-linux-gnueabi-gdb arm-at91-linux-gnueabi-readelf
arm-at91-linux-gnueabi-cpp arm-at91-linux-gnueabi-gprof arm-at91-linux-gnueabi-size
arm-at91-linux-gnueabi-ct-ng.config arm-at91-linux-gnueabi-ld arm-at91-linux-gnueabi-strings
arm-at91-linux-gnueabi-elfedit arm-at91-linux-gnueabi-ld.bfd arm-at91-linux-gnueabi-strip
arm-at91-linux-gnueabi-g++ arm-at91-linux-gnueabi-ldd
arm-at91-linux-gnueabi-gcc arm-at91-linux-gnueabi-ld.gold
有些Makefile生成的环境中还需要使用AR
,NM
,RANLIB
等等,这些东西也需要使用对应的交叉编译工具链版本的程序。而上述的CC
手法仅仅替换了一个。所以为什么是存在风险和后续还会遇到问题的。
这里推荐,如果能用host,就优先使用host指定,这种方式相当于自动帮你设置了上述每一个需要替换的变量。
在极少数的时候,host不被confiugre支持,那么能用的方法只有这种了,但同时也是最不同推荐的。
有时候,我们使用的第三方库有两层甚至多层,底层他们自己开发了一个基础库,然后在自己的基础库上,封了一层应用。例如protobuf和grpc,snmp++和agent++。这个时候,我们需要先编译基础库,然后在编译应用库。这一点很容易理解,从下到上,但对于交叉编译来说,又有一点需要注意的。主要和 prefix
有关。
当我们编译完基础库之后,一般make install
之后,成果物大多情况是这样的[假设我们安装到了一个build目录]:
(base) xiaoyanyi@build$ls
bin include lib
bin
: 目录一般是这个库的一些demo样例或者一些可执行程序。
include
:目录一般就是这个第三方库的头文件
lib
:目录一般就是这个第三方库的静态、动态库文件
那么在编译上层应用库的时候,有些教程会推荐按照configure
中的指定底层库路径宏变量的方式,去显式指定对应路径。
例如agent++
这个库,依赖snmp++
,其中agent++的confiugre文件里有这样一个变量:
Some influential environment variables:
PKG_CONFIG path to pkg-config utility
PKG_CONFIG_PATH
directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR
path overriding pkg-config's built-in search path
CXXCPP C++ preprocessor
snmp_CFLAGS C compiler flags for snmp, overriding pkg-config
snmp_LIBS linker flags for snmp, overriding pkg-config
LT_SYS_LIBRARY_PATH
User-defined run-time library search path.
这里可以通过snmp_LIB
这个变量显式指定去libsnmp++.so
的位置。这种方式在表面上是能够通过编译并且不会出现什么太大问题的。但会买下一个隐患在于后续使用库或者维护的时候会出现问题。这里需要讲解造成这个隐患问题的另外两个文件。
一般编译完成的第三方库的lib文件夹下面,会有一个对应扩展名为la的文件。
例如:
(base) xiaoyanyi@lib$ls
libsnmp++.a libsnmp++.la libsnmp++.so libsnmp++.so.35 libsnmp++.so.35.0.0 pkgconfig
这个la文件实际上并不是一个静/动态库程序,而是一个配置文件,我们可以直接vim打开:
# libsnmp++.la - a libtool library file # Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14 # # Please DO NOT delete this file! # It is necessary for linking the library. # The name that we can dlopen(3). dlname='libsnmp++.so.35' # Names of this library. library_names='libsnmp++.so.35.0.0 libsnmp++.so.35 libsnmp++.so' # The name of the static archive. old_library='libsnmp++.a' # Linker flags that cannot go in dependency_libs. inherited_linker_flags=' -pthread' # Libraries that this one depends upon. dependency_libs=' -lssl -lcrypto /data1/xiaoyanyi/cross-tool/arm-at91-linux-gnueabi/arm-at91-linux-gnueabi/lib/libstdc++.la' # Names of additional weak libraries provided by this library weak_library_names='' # Version information for libsnmp++. current=35 age=0 revision=0 # Is this an already installed library? installed=yes # Should we warn about portability when linking against -modules? shouldnotlink=no # Files to dlopen/dlpreopen dlopen='' dlpreopen='' # Directory that this library needs to be installed in: libdir='/data1/xiaoyanyi/work/snmp++/snmp++-3.5.0/build/lib'
关键的问题就在最后的
libdir
里,这个路径指明了这个动态库的安装路径,在连接的过程中,如果有动态库依赖动态库的情况,gcc的连接应用会顺着路径去查找随影目标文件。而如果我们安装的时候,就随意选一个build目录。最后即便是把这个路径放到了sysroot里去之后,这个la文件里面的内容依旧不会变,导致最后就找不到了。
大多数库编完之后,除了上述的la,其实大家也可以在lib里面找到有一个pkgconfig文件夹,里面会有一个对应的pc文件。
这个文件和la文件的作用极为相似,也是一个配置文件,我们可以打开看看。
prefix=/data1/xiaoyanyi/work/snmp++/snmp++-3.5.0/build
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
modules=
Name: snmp++
Version: 3.5.0
Description: SNMP C++ framework version 3
Requires:
Libs: -L${libdir} -lsnmp++
Libs.private: -lssl -lcrypto
Cflags: -I${includedir}
这里造成错误的原因是因为prefix这个变量也是会因为随意指定从而即便pc文件的位置移动也无法正确索引。pc文件主要是pkg-config这个应用为了编译的时候自动指定FLAGS和自动找库用的。
这里推荐的路径是安装在编译工具链的sysroot中。
一般用crosstool-ng做的交叉工具链,对应的sysroot是在
${host}/${host}/sysroot/
对于其他的链子,基本也都有一个sysroot,可以自行查找。而我们安装一般的第三方库,一般推荐放在sysroot中的usr中,因为不是系统库,而是用户自己制作的。
这个路径下的,例如我做了一个链子,host是 arm-at91-linux-gnueabi
,那么对应的sysroot/usr路径是:
arm-at91-linux-gnueabi/arm-at91-linux-gnueabi/sysroot/usr/
所以绝大部分的时候,将第三方库的prefix指定为上述路径,可以直接将第三方库安装到编译工具链中,从而达到后续编译其他库的时候,链子会自动索引已经安装过的库,不需要显示指定对应路径的目的。
但这里同样有一个风险点:如果对应第三方库的更新较为频繁,那么就可能存在要编译的新库但sysroot里有个老库的场景。这里推荐是更新较为频繁的库不要放到sysroot里去。
4.1里介绍的手法基本上能处理绝大多数的第三方库,但仍旧有某些库的configure
写的比较奇特,按照常规三部曲无法解决的。一般通用的处理思路还是好好阅读configure --help
,同时这里重点解释两个场景。
这个动态库的交叉编译就不合适上述的情况,configure
中并没有指定host
的能力,这里推荐的建议是这样,先说结果:
./Configure linux-armv4 no-asm shared
这个库为什么会这么输入,我们又是如何知道的呢?其实还是看configure --help
就能发现端倪。
(base) xiaoyanyi@openssl-1.0.2t$./Configure --help Configuring for Usage: Configure [no-<cipher> ...] [enable-<cipher> ...] [experimental-<cipher> ...] [-Dxxx] [-lxxx] [-Lxxx] [-fxxx] [-Kxxx] [no-hw-xxx|no-hw] [[no-]threads] [[no-]shared] [[no-]zlib|zlib-dynamic] [no-asm] [no-dso] [no-krb5] [sctp] [386] [--prefix=DIR] [--openssldir=OPENSSLDIR] [--with-xxx[=vvv]] [--test-sanity] os/compiler[:flags] pick os/compiler from: BC-32 BS2000-OSD BSD-generic32 BSD-generic64 BSD-ia64 BSD-sparc64 BSD-sparcv8 BSD-x86 BSD-x86-elf BSD-x86_64 Cygwin Cygwin-x86_64 DJGPP MPE/iX-gcc OS2-EMX OS390-Unix QNX6 QNX6-i386 ReliantUNIX SINIX SINIX-N UWIN VC-CE VC-WIN32 VC-WIN64A VC-WIN64I aix-cc aix-gcc aix3-cc aix64-cc aix64-gcc android android-armv7 android-mips android-x86 android64-aarch64 aux3-gcc beos-x86-bone beos-x86-r5 bsdi-elf-gcc cc cray-j90 cray-t3e darwin-i386-cc darwin-ppc-cc darwin64-ppc-cc darwin64-x86_64-cc dgux-R3-gcc dgux-R4-gcc dgux-R4-x86-gcc dist gcc hpux-cc hpux-gcc hpux-ia64-cc hpux-ia64-gcc hpux-parisc-cc hpux-parisc-cc-o4 hpux-parisc-gcc hpux-parisc1_1-cc hpux-parisc1_1-gcc hpux-parisc2-cc hpux-parisc2-gcc hpux64-ia64-cc hpux64-ia64-gcc hpux64-parisc2-cc hpux64-parisc2-gcc hurd-x86 iphoneos-cross irix-cc irix-gcc irix-mips3-cc irix-mips3-gcc irix64-mips4-cc irix64-mips4-gcc linux-aarch64 linux-alpha+bwx-ccc linux-alpha+bwx-gcc linux-alpha-ccc linux-alpha-gcc linux-aout linux-armv4 linux-elf linux-generic32 linux-generic64 linux-ia32-icc linux-ia64 linux-ia64-icc linux-mips32 linux-mips64 linux-ppc linux-ppc64 linux-ppc64le linux-sparcv8 linux-sparcv9 linux-x32 linux-x86_64 linux-x86_64-clang linux-x86_64-icc linux32-s390x linux64-mips64 linux64-s390x linux64-sparcv9 mingw mingw64 ncr-scde netware-clib netware-clib-bsdsock netware-clib-bsdsock-gcc netware-clib-gcc netware-libc netware-libc-bsdsock netware-libc-bsdsock-gcc netware-libc-gcc newsos4-gcc nextstep nextstep3.3 osf1-alpha-cc osf1-alpha-gcc purify qnx4 rhapsody-ppc-cc sco5-cc sco5-gcc solaris-sparcv7-cc solaris-sparcv7-gcc solaris-sparcv8-cc solaris-sparcv8-gcc solaris-sparcv9-cc solaris-sparcv9-gcc solaris-x86-cc solaris-x86-gcc solaris64-sparcv9-cc solaris64-sparcv9-gcc solaris64-x86_64-cc solaris64-x86_64-gcc sunos-gcc tandem-c89 tru64-alpha-cc uClinux-dist uClinux-dist64 ultrix-cc ultrix-gcc unixware-2.0 unixware-2.1 unixware-7 unixware-7-gcc vos-gcc vxworks-mips vxworks-ppc405 vxworks-ppc60x vxworks-ppc750 vxworks-ppc750-debug vxworks-ppc860 vxworks-ppcgen vxworks-simlinux debug debug-BSD-x86-elf debug-VC-WIN32 debug-VC-WIN64A debug-VC-WIN64I debug-ben debug-ben-darwin64 debug-ben-debug debug-ben-debug-64 debug-ben-debug-64-clang debug-ben-macos debug-ben-macos-gcc46 debug-ben-no-opt debug-ben-openbsd debug-ben-openbsd-debug debug-ben-strict debug-bodo debug-darwin-i386-cc debug-darwin-ppc-cc debug-darwin64-x86_64-cc debug-geoff32 debug-geoff64 debug-levitte-linux-elf debug-levitte-linux-elf-extreme debug-levitte-linux-noasm debug-levitte-linux-noasm-extreme debug-linux-elf debug-linux-elf-noefence debug-linux-generic32 debug-linux-generic64 debug-linux-ia32-aes debug-linux-pentium debug-linux-ppro debug-linux-x86_64 debug-linux-x86_64-clang debug-rse debug-solaris-sparcv8-cc debug-solaris-sparcv8-gcc debug-solaris-sparcv9-cc debug-solaris-sparcv9-gcc debug-steve-opt debug-steve32 debug-steve64 debug-vos-gcc NOTE: If in doubt, on Unix-ish systems use './config'.
这个库显式指定了所有它能支持的平台种类,所以我们只能按照这个指定了。这样指定完之后,需要把生成的Makefile中对应宏定义进行显式修改,包括但不限于CC
、AR
、NM
、RANLIB
、CXX
等等。
有些开源库就没给configure,但是能看到cmakelist的文件,那么这个时候,把cmakelist当做configure就好,但是它的指定写法会稍显不同。一般需要提供一个对应的cmake配置,对于我们交叉编译来说,需要指定对应的host系统名称CMAKE_SYSTEM_NAME
和处理器名称CMAKE_SYSTEM_PROCESSOR
,然后需要指定对应的编译链路径CMAKE_C_COMPILER
和CMAKE_CXX_COMPILER
。
这里给出一个样例:
#arm.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(tools /data1/xiaoyanyi/cross-tool/arm-imx6ul-linux-gnueabihf/bin/arm-imx6ul-linux-gnueabihf-)
set(CMAKE_C_COMPILER ${tools}gcc)
set(CMAKE_CXX_COMPILER ${tools}g++)
cmake文件准备好后,就可以直接cmake了,机理是和configure一样的:
cmake CMakeLists.txt -DCMAKE_TOOLCHAIN_FILE=./arm.cmake
上述命令执行完后,最终会生成一个Makefile文件,接着make
,make install
就好了。
绝大多数场景,交叉编译的时候configure
,make
,make install
三部曲就好,与普通编译不一样的是需要指定host
自依赖第三方库在不频繁迭代更新的条件下,建议安装到交叉编译链的sysroot目录中
如果怎么编译都有点问题,建议仔细阅读configure --help
或者README
分析查找端倪
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。