一、引子
為了把編譯好的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參數的方法”。