一、引子
为了把编译好的bin和lib文件打包到镜像里面,新建了个package,在编译的时候,总是报库缺失的错误:
Package XXX is missing dependencies for the following libraries
这就有意思了:
1、它怎么知道我的bin和lib文件依赖哪些库?
2、我不需要编译器产生这些库依赖错误(已经确定系统里面这些库都是有的),怎么解决这个错误?
二、Demo
首先,创建一个测试用的Package:
$ tree package/pkg_test package/pkg_test ├── Makefile └── test_bin
Makefile:
include $(TOPDIR)/rules.mk include $(BUILD_DIR)/kernel.mk # Name and release number of this package PKG_NAME:=pkg_test PKG_VERSION:=0.0.1 PKG_RELEASE:=1 PKG_BUILD_DIR := $(COMPILE_DIR)/$(PKG_NAME) include $(BUILD_DIR)/package.mk define Package/$(PKG_NAME) SECTION:=utils CATEGORY:=Utilities TITLE:=pkg_test endef define Package/$(PKG_NAME)/description endef define Build/Prepare $(INSTALL_DIR) $(PKG_BUILD_DIR)/ $(CP) test_bin $(PKG_BUILD_DIR)/ endef define Build/Configure endef define Build/Compile endef define Package/$(PKG_NAME)/install $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/test_bin $(1)/usr/bin endef $(eval $(call BuildPackage,$(PKG_NAME)))
编译之:
$ make package/pkg_test/install V=99
初步判断:
$ find ./out/ -name test_bin
./out/astar-parrot/compile_dir/target/pkg_test/ipkg-sunxi/pkg_test/usr/bin/test_bin ./out/astar-parrot/compile_dir/target/pkg_test/test_bin
我们发现,pkg_test其实已经编译成功了,只是在检查依赖库的时候出了差错。
目前可以确定:错误与编译无关。接下来就需要分析依赖库的检查脚本。
三、深入分析库依赖的检查流程及原理
”Package XXX is missing dependencies for the following libraries“的出处:TOPDIR/build/package-ipkg.mk
变量值:
PKG_INFO_DIR:/home/.../TOPDIR/out/astar-parrot/staging_dir/target/pkginfo
$(1):pkg_test
73~77行显示,$(PKG_INFO_DIR)目录下存在pkg_test.missing文件的时候,就打印库依赖错误,并把依赖的库显示出来。
$ cat out/astar-parrot/staging_dir/target/pkginfo/pkg_test.missing libstdc++.so.6
显然,需要探究pkg_test.missing文件是怎么产生的。
TOPDIR/scripts/gen-dependencies.sh
第8行,SELF=${0##*/}:
$0:当前脚本的文件名;
${var##*str}语法:从左向右截取var,保留最后一个str后的字符串。
示例:
$ ./scripts/gen-dependencies.sh
则SELF的值为gen-dependencies.sh
第9行,READELF="${READELF:-readelf}":
${var:-DEFAULT}语法:如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值。
第12行,TARGETS=$*:
$*:传递给脚本或函数的所有参数。根据编译过程的Log,这里:
TARGETS=/home/.../TOPDIR/out/astar-parrot/compile_dir/target/pkg_test/ipkg-sunxi/pkg_test
第21行,find $TARGETS -type f -a -exec file {} \;:对$TARGETS目录下的文件执行"file"命令。
根据我们的demo简化该命令就是:
$ file $TARGETS/test_bin
$TARGETS/test_bin: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), not stripped
第22行,经过sed过滤后,结果:$TARGETS/test_bin
第23行,readelf -d:读取并显示test_bin这个bin文件的dynamic section
$ readelf -d $TARGETS/test_bin Dynamic section at offset 0x4008 contains 23 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libstdc++.so.6] 0x00000001 (NEEDED) Shared library: [libgcc_s.so.1] 0x00000001 (NEEDED) Shared library: [libc.so] 0x0000000c (INIT) 0x10a20 0x0000000d (FINI) 0x13840 0x00000019 (INIT_ARRAY) 0x24000 0x0000001b (INIT_ARRAYSZ) 4 (bytes) 0x0000001a (FINI_ARRAY) 0x24004 0x0000001c (FINI_ARRAYSZ) 4 (bytes) 0x00000004 (HASH) 0x1012c 0x00000005 (STRTAB) 0x10628 0x00000006 (SYMTAB) 0x102a8 0x0000000a (STRSZ) 528 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x240e8 0x00000002 (PLTRELSZ) 344 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x108c8 0x6ffffffe (VERNEED) 0x108a8 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x10838 0x00000000 (NULL) 0x0
第24行,以上结果经过awk过滤:
libstdc++.so.6
libgcc_s.so.1
libc.so
第25行,再经过sort排序(-u去除重复项):
libc.so
libgcc_s.so.1
libstdc++.so.6
可见,经过gen-dependencies.sh脚本,我们需要打包到镜像里的bin/lib文件的依赖库就获取到了。
至此,第一个问题解决。
继续分析TOPDIR/build/package-ipkg.mk脚本。
$ cat out/astar-parrot/staging_dir/target/pkginfo/pkg_test.provides libc.so libgcc_s.so.1 libgomp.so libgomp.so.1 libgomp.so.1.0.0 $ grep -xF "libc.so" out/astar-parrot/staging_dir/target/pkginfo/pkg_test.provides libc.so $ grep -xF "libgcc_s.so.1" out/astar-parrot/staging_dir/target/pkginfo/pkg_test.provides libgcc_s.so.1 $ grep -xF "libstdc++.so.6" out/astar-parrot/staging_dir/target/pkginfo/pkg_test.provides <-- grep不到 $ echo $? 1
第70行,grep语句:在pkg_test.provides文件中搜索各个依赖库,如果不存在(grep命令返回非0)就把依赖库的名字写到pkg_test.missing文件中。
至此,CheckDependencies工作就做完了。pkg_test.provides这个文件是谁又是怎么产生的?
$(IDEPEND_$(1))的值:libc
第186~193行,patsubst:字符串替换函数,这里是把”libc“替换成”PKG_INFO_DIR/libc.provides“,即:
/home/.../TOPDIR/out/astar-parrot/staging_dir/target/pkginfo/libc.provides
pkg_test.provides文件内容来源:
1、/home/.../TOPDIR/out/astar-parrot/compile_dir/target/pkg_test/ipkg-sunxi/pkg_test目录下的lib*.so*和*.ko文件(第186行)
2、libc.provides文件(第187行)
3、pkg_test的Makefile中extra_provides配置项(第192行)
那么,我们只要在上述来源中任意一个添加缺失的库文件(名),即可解决问题。
下面验证一下。
第一种解决方式:
$ touch TOPDIR/package/pkg_test/libstdc++.so.6 $ cat Makefile define Build/Prepare $(CP) libstdc++.so.6 $(PKG_BUILD_DIR)/ <--- 新增 endef define Package/$(PKG_NAME)/install $(CP) $(PKG_BUILD_DIR)/libstdc++.so.6 $(1) <--- 新增 endef
亲测有效。但是这种方式太“野”了,不够official。最官方的做法是在 Makefile 文件中加 DEPENDS 描述:
define Package/$(PKG_NAME) DEPENDS:+=libxxx endef
第二种解决方式:libc.provides文件中添加libstdc++.so.6
亲测有效。缺点:libc.provides是动态生成的,下次clean编译镜像的时候就失效了,需要重新更改该文件。
第三种解决方式:在Makefile中添加extra_provides描述段
define Package/$(PKG_NAME)/extra_provides echo "libstdc++.so.6"; endef
亲测有效。对于我的应用环境,这种方式最好。
PS:
在把自己的bin/lib文件编译到镜像之后,发现bin文件被更改了!如下图比较:
原因在第197行有答案:$(RSTRIP) $$(IDIR_$(1))
STRIP="/home/.../TOPDIR/out/host/bin/sstrip"
STRIP_KMOD="/home/.../TOPDIR/scripts/strip-kmod.sh"
PATCHELF="/.../TOPDIR/out/host/bin/patchelf"
/home/.../TOPDIR/scripts/rstrip.sh /home/.../TOPDIR/out/astar-parrot/compile_dir/target/pkg_test/ipkg-sunxi/pkg_test
strip的作用是删除掉目标文件中的符号及调试信息,从而达到给目标文件“瘦身”的目的。
如果需要禁用strip功能,参考“OpenWrt取消strip或者重新设置strip参数的方法”。