libiconv冲突 - Petalinux构建
问题现象:
使用petalinux-build构建时突然报告undefined reference to 'libiconv'等错误。
问题原因:
编译机器上同时安装了glibc和GNU libiconv。libiconv的头文件iconv.h通过宏将iconv定义成了libiconv,并且被优先包含。同时链接选项中没有加入链接libiconv的选项。Glibc定义的符号是iconv,不用显式链接。Libiconv定义的符号是libiconv,需要显式链接。
解决方案:
1. 在gcc的默认搜索路径,同时优先于libiconv的头文件路径中创建一个Glibc的iconv.h副本,使用iconv符号,默认链接到glibc。该方案需要root权限,并且会影响到所有用户。
2. 理论上更好的方案是通过CPATH或者C_INCLUDE_PATH变量。但是petalinux使用的是Yocto,外部shell定义的环境变量不能直接使用。Yocto定义了BB_ENV_EXTRAWHITE可以来传递外部环境变量。在.bashr中加入两行
export C_INCLUDE_PATH=/your/path/to/include/
# Append the name of environment variables which will be imported into Yocto
export BB_ENV_EXTRAWHITE="$BB_ENV_EXTRAWHITE C_INCLUDE_PATH"
在petalinux工程local.conf文件.../components/yocto/conf/local.conf
中加入export C_INCLUDE_PATH
。这样无论是系统自带gcc,还是petalinux的gcc都会先搜索C_INCLUDE_PATH下的文件。这时虽然会报告Taskhash mismatch的错误,但还是能成功构建出相应的文件。
知识总结:
Petalinux构建过程中代码的存放路径位于工程目录的tmp/子目录下,可以通过petalinux-config进行配置该路径。
Petalinux编译出错时,日志build.log可以提供很多信息。
libiconv和glibc中iconv的关系。
GCC的头文件搜索顺序。可以通过cpp -v /dev/null -o /dev/null
或echo | gcc -E -Wp,-v -
来获取。
Enable Buildtools Extended这个选项使能之后,会使用petalinux安装目录下的gcc进行编译,这时和系统自带的gcc搜索的路径不完全一样。
调查过程:
网上有人说原因是没有安装iconv库,或者是没有加上链接选项-liconv,由于之前没有发现这个错误,并且构建命令是petalinux自动生成的,不知道怎么修改,因此暂时没有顺着这个调查方向。
查看petalinux工程目录下build/build.log记录的构建过程,显示是在链接unzip时出现该错误。因为使用的Ubuntu-20.04上已经有了unzip工具,所以要先弄清楚为什么会构建unzip,是不是之前没有编译unzip就没有报错。
因为之前直接从bsp文件创建工程后是可以构建成功的,所以怀疑是工程被修改后造成构建不过。之前创建了工程后,使用petalinux-config导入过不同的XSA文件,以及修改了某些编译项,例如离线编译包路径等。换用其它XSA文件,仍然出现该错误。删除了当前构建不过的工程,重新从bsp文件再创建一次工程目录之后,仍然出现同样错误。
使用petalinux-config命令时,有一项配置路径"Yoco settings ---> Enable Buildtools Extended",帮助信息显示是用于控制是否编译gcc和其它工具。怀疑是使能了该配置所以会编译unzip。在重新从bsp文件创建工程后运行petalinux-config命令,显示这个选项默认是关闭的。
再次查看build.log,显示出了编译unzip对应的bitbake脚本所在的路径,位于components/yocto/layers/core/meta/recipes-extended/unzip/unzip_6.0.bb。脚本中显示了编译使用的源码以及一系列patch文件。怀疑Enable Buildtools Extended是否会影响创建相应的bitbake脚本。使用bsp文件创建了两个工程目录,分别关闭和打开这个选项,显示都会创建相应的目录和bitbake脚本。
通过bitbake脚本中显示的源码在离线包downloads目录下找到了unzip60.tar.gz这个目录,将目录拷贝到编译机器并解压,使用make -f unix/Makefile generic,没有出现错误。查看unix/unix.c源码,没有出现对libiconv的调用。网上说iconv是针对unzip一个补丁,bitbake脚本显示的一个patch文件带有iconv,有可能和这个文件相关,但是还没有找到对应的patch文件。
接下来就要想办法编译加入补丁后的unzip。再次查看build.log的信息,其中显示出了构建unzip的make命令工作目录,build/tmp/work/x86_64-linux/unzip-native/1_6.0-r5/unzip60。进入这个目录确实是一个unzip的构建目录,使用make -f unix/Makefile generic也得到了同样的错误。同时在上一级目录1_6.0-r5下还发现了一个带有iconv的文件,其内容显示确定有调用iconv。
查看该目录下的unix.c文件,发现调用的是iconv/iconv_open/iconv_close等函数,但是链接时却报告的是libiconv_xxx未定义等错误。怀疑是被宏定义替换掉了,上网搜索后验证了该想法。有篇文章提到包含了iconv.h文件,使用的是/usr/local/include/路径下,其中就包含了这样的宏定义。而另一个位置的头文件,/usr/include/iconv.h,则没有包含这样的宏定义。查看自己的编译机器上相同位置的头文件,确实如此。
创建了一个最简单的测试文件包含iconv_open/iconv_close的调用,编译也是报告libiconv_open未定义。GNU libiconv提供的符号以libiconv_xxx命令,Glibc提供的符号以iconv_xxx命名。GNU libiconv提供了字符集转换功能。安装GNU libiconv之后,会在/usr/local/include下安装相应的头文件。
libiconv提供了两种使用方式,第一种是库方式,第二种是插件方式。库方式下其定义的符号就是libiconv_xxx。第二种是插件方式,其定义的符号还是iconv_xxx,和glibc一样。如果要使用libiconv,可以通过控制LD的行为来选择优先加载libiconv,而不是glibc。在GNU/Linux上可以使用LD_PRELOAD。
在编译机器上使用gcc -v选项打印出头文件搜索路径及顺序,发现/usr/local/include优先于/usr/include
在编译机器上使用的libicon是以库方式安装的。因此解决方案之一就是要优先包含glibc定义的头文件iconv.h。在优先于/usr/local/include的目录下创建一个iconv.h的副本后,问题消失。
以下是从Yocto官方手册摘抄出来的解释。
15.15. |
When I try to build a native recipe, the build fails with |
If you get an error message that indicates GNU
If you find a previously installed file, you should either uninstall it or temporarily rename it and try the build again. This issue is just a single manifestation of "system leakage" issues caused when the OpenEmbedded build system finds and uses previously installed files during a native build. This type of issue might not be limited to |