1. 程式人生 > >一個令人蛋疼的NDK鏈接錯誤

一個令人蛋疼的NDK鏈接錯誤

static ash main.c .cpp 編譯過程 android 回憶 支持 項目結構

背景

我們APP的引擎包engine.so。包括了A、B、C三個project。但每次都是源代碼形式編譯,導致svn上存在多份同樣代碼拷貝。

很不科學。

。。核心的Bproject由我維護。整個SO編譯project由多個人維護。

於是乎偶進行了一次升級:將B源代碼從soproject中解耦:將B打成一個靜態庫,然後編譯So的時候鏈接靜態庫。

開始行動

基本思路:將B的源代碼包到一個guide_b外殼project中。ndk-build生成guide_b.so 的同一時候誘導生成libB.a靜態庫,然後這個libB.a能夠公布。 android的編譯文件夾實在蛋疼。eclipse下設置路徑難用的非常。還是習慣命令行下的ndk-build。可是ndk-build的前提是:當前路徑下必須有一個jni文件夾,且它裏面有一個Android.mk文件,以及srcxx子文件夾。裏面放了源碼

為了遵循android ndk編譯這個蛋疼的規定,並且又不破壞Bproject的項目結構(舊的支持xcode和vs編譯),在build文件夾下添加一個android子文件夾。創建Android.mk文件,然後通過python腳本將所需源代碼文件復制到android/jni下,全部這些操作通過一個run.bat批處理腳本串聯,build完畢以後刪掉拷貝的源代碼和編譯中間結果。 技術分享
將python引入編譯很靈活。

踩雷了

拿到編譯出的libB.a,放入engine.so編譯project中,改動mk文件,頭部增加靜態庫預編譯段,
include $(CLEAR_VARS)

LOCAL_MODULE := BModule

LOCAL_SRC_FILES := libB.a
  
include $(PREBUILT_STATIC_LIBRARY)
在so編譯部分載入BModule模塊:LOCAL_STATIC_LIBRARIES := BModule 編譯so很順利。可是拿到APPproject中。傻眼了build以後的包僅僅有地圖文字,沒有底圖了。

淚奔了,libB.a和engine.so的編譯過程都自我感覺很之良好啊。。。尼瑪。僅僅能不斷自我打擊。暗示一定什麽環節出問題了。。

沒有crash,文字標註還有,可是底圖一直渲染不成功。

。。


排雷的過程

1)將B的源代碼放入SO編譯project,終於so包沒問題。僅僅能懷疑自己的libB.a編譯有問題或者鏈接有問題咯,於是進行第一個嘗試:

不直接進行源代碼編譯。而是通過ndk自帶的arm-linux-androideabi-ar.exe工具,將源代碼編譯時產生的一系列.o文件。手工編譯成.a。然後鏈接這個.a,發現build的so包還是有問題。

source =>	*.o	=>	engine.so

*.o	=>	libB.a	=>	engine.so
	ar
上述兩種路徑:第一條表示源代碼編譯,ok;第二條是源代碼編譯的中間結果.o文件。手工通過ar打包成 libB.a。然後鏈接libB.a,就有問題。

真是見鬼了。!。

2)躲只是了,僅僅能source中添加log,第一次build成libB.a,然後第二次build成engine.so。最後復制到androidproject中。build APK。

source =>	libB.a	=>	engine.so	=>	apk.

整個蛋疼的定位過程得益於windows的批處理腳本。能夠實現半自己主動化。

不斷反復這個過程,不斷調整log精度。終於定位究竟圖瓦片繪制失敗的問題:坐標轉化函數GetGeoRect的結果錯誤。導致繪制時候取不到數據

定位問題的解決辦法

尼瑪,疑問重重:源代碼編譯沒問題。build成靜態庫,然後再鏈接就有問題。代碼沒有修改,為何單單這個核心函數出問題呢? 進一步驗證: GetGeoRect是一個類靜態函數。寫一個main.cpp,測試libB.a中的該函數是否正確,顯式傳入制定的參數,build成可運行程序,然後推到手機上運行,詳細參見http://blog.csdn.net/ryfdizuo/article/details/28891649 結果函數運行結果正確。

。說明libB.a內這些函數本身沒有問題。

問題出在so包的鏈接階段

跟組內一經驗豐富的哥們討論,那天恰好周五。下班前還是沒結果。。

。晚上回去後回一直在回憶編譯的整個過程。想起他無心的一句話:“是不是可能有反復的定義啥的“。最終想到了一個問題,Aproject裏面Bproject的兩個頭文件。當時為了解耦其它人將兩個頭文件反復拷貝了一份,(明顯觸犯了DRY原則)例如以下, yy.h中包括了靜態函數的GetGeoRect定義,vv.h中包括了render_config_t結構體定義,而GetGeoRect中使用了render_config_t結構體。

技術分享

我近期一次B模塊升級。更新了vv.h中的render_config_t結構體,內部添加了一個256的char數組。

附圖新舊vv.h頭文件裏的render_config_t結構體:

舊的:技術分享新的:技術分享

第二天周六,按耐不住奔到公司,更新A模塊中的vv.h頭文件,build出的so包最終正確了。

總算是找到問題所在了:

A和Bproject中的vv.h和yy.h文件反復。B中vv.h文件近期被更新過。

1)當A、Bproject均採用源代碼編譯時,終於SO中的GetGeoRect函數內部使用了最新的render_config_t結構體布局(編譯器可能依據文件的改動時間等等作為比對條件吧),因此底圖繪制正確。

2)可是當Bprojectbuild成靜態庫libB.a時。此時build成SO時,GetGeoRect函數定義採用了Aproject中源代碼(編譯器可能更加信賴源代碼吧)。因此render_config_t也採用了舊的內存布局。因此當調用SO執行時。傳入GetGeoRect函數的render_config_t的對象採用最新的內存布局,可是內部實際上是按舊的結構體解析和執行,當然結果就全然錯了。


一句話總結問題

文件反復拷貝 =》 導致兩個地方文件更新不同步 =》導致同一個結構體有兩份定義。兩種內存布局 =》 導致SO中的全局函數中,libB.a中沒有被反復定義的函數採用了新的結構體布局。被反復的函數則採用了舊的內存布局 =》終於結果:傳入GetGeoRect函數之前的結構體是新布局,函數內部按舊布局解析,全部參數錯亂。

附錄


靜態庫,僅僅是.o文件的集合打包。 動態庫文件:沒有作用域區分,全部函數都以唯一的全局函數形式存在,C++的函數會被name-mangling處理,假設我們希望直接通過函數名獲取so中的函數地址,則使用extern C包裹防止函數名被改動。通過objdump工具能夠驗證。



一個令人蛋疼的NDK鏈接錯誤