關鍵詞:Android Framework 動態庫 動態連結 Binder
1、事件起因
Android Studio一次更新後發現install App,裝置就重啟了,跑了一遍開機動畫但不是從開機第一屏開始重啟,tombstones內容檢視發現是surfaceflinger
掛在libbinder.so
,那install app做了什麼這個不得而知,理論上有問題應該掛的是PackageManagerService。先不管Android Studio的事情,雖然掛在Binder的庫裡,還是首先懷疑問題出在surfaceflinger
的Binder使用邏輯。
2、原因分析
(以下分析使用RockPi4B還原現場)
tombstone檔案如下
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'rockchip/rk3399_ROCKPI4B_Android11/rk3399_ROCKPI4B_Android11:11/RQ3A.210705.001/eng.kryo.20240128.131540:userdebug/release-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2024-01-28 23:42:32+0800
pid: 11263, tid: 11289, name: Binder:11263_2 >>> /system/bin/surfaceflinger <<<
uid: 1000
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x791c0e6cd0
x0 b4000079ce312fd8 x1 000000005f444d50 x2 000000791d20c9c0 x3 000000791d20c940
x4 0000000000000010 x5 0000000000000018 x6 b400007a7e312e50 x7 b4000079ce312fd8
x8 000000791c0e6c50 x9 0000000010000000 x10 0000000000000001 x11 0000000000000002
x12 0000000000000000 x13 0000007baff5e020 x14 0001096dce96e5c4 x15 0000000029aaaaf0
x16 0000007bafd6c420 x17 0000007bafd29e30 x18 000000791c592000 x19 000000791d20c940
x20 0000000000000010 x21 000000791d20c9c0 x22 b4000079ce312fd8 x23 000000005f444d50
x24 000000791d20d000 x25 0000000000000000 x26 ffffffff000003e8 x27 b400007a7e313014
x28 0000000000000000 x29 000000791d20c8d0
lr 0000007bafd167bc sp 000000791d20c8c0 pc 0000007bafd1685c pst 0000000080000000
backtrace:
#00 pc 000000000004985c /system/lib64/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+228) (BuildId: d5e42e998e9031430bee87f595521231)
#01 pc 00000000000524a8 /system/lib64/libbinder.so (android::IPCThreadState::executeCommand(int)+1032) (BuildId: d5e42e998e9031430bee87f595521231)
#02 pc 0000000000051fec /system/lib64/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+156) (BuildId: d5e42e998e9031430bee87f595521231)
#03 pc 000000000005282c /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+60) (BuildId: d5e42e998e9031430bee87f595521231)
#04 pc 0000000000078e10 /system/lib64/libbinder.so (android::PoolThread::threadLoop()+24) (BuildId: d5e42e998e9031430bee87f595521231)
#05 pc 000000000001567c /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+260) (BuildId: c081ab14bd4aef44c9c459d77d8c9b48)
#06 pc 0000000000014f14 /system/lib64/libutils.so (thread_data_t::trampoline(thread_data_t const*)+412) (BuildId: c081ab14bd4aef44c9c459d77d8c9b48)
#07 pc 00000000000b0c08 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+64) (BuildId: 0a481e8df134382e9d3effff2fce8b74)
#08 pc 00000000000505d0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 0a481e8df134382e9d3effff2fce8b74)
使用addr2line工具檢視程式碼奔潰處原始碼,aosp編譯時會在out目錄下生成symbols目錄,會額外儲存一份帶符號的so檔案方便回溯問題,把崩潰處的地址4985c
傳入
aarch64-linux-android-addr2line -e out/target/product/rk3399_ROCKPI4B_Android11/symbols/system/lib64/libbinder.so 4985c
#frameworks/native/libs/binder/Binder.cpp:188
定位到Binder.cpp 188行處的原始碼:
status_t BBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
data.setDataPosition(0);
status_t err = NO_ERROR;
switch (code) {
case PING_TRANSACTION:
err = pingBinder();
break;
case EXTENSION_TRANSACTION:
err = reply->writeStrongBinder(getExtension());
break;
case DEBUG_PID_TRANSACTION:
err = reply->writeInt32(getDebugPid());
break;
default:
err = onTransact(code, data, reply, flags); // 188行處
break;
}
//... ...
}
還是目前很難看出崩潰原因,tombstone給出的是SEGV_MAPERR錯誤,推測與訪存有關,再結合objdump -S 檢視libbinder.so的反彙編:
... ...
49840: 1400001a b 498a8 <_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j@@Base+0x130>
49844: f94002c8 ldr x8, [x22]
49848: aa1603e0 mov x0, x22
4984c: 2a1703e1 mov w1, w23
49850: aa1503e2 mov x2, x21
49854: aa1303e3 mov x3, x19
49858: 2a1403e4 mov w4, w20
4985c: f9404108 ldr x8, [x8,#128]
49860: d63f0100 blr x8
... ...
可以看到4985c
處的彙編指令是透過x8暫存器偏移+128位元組進行訪存(暫存器相對定址) x8內容0x791c0e6c50加128正好是出錯記憶體地址0x791c0e6cd0,取得的記憶體結果再存回x8
tombstone也會把程序的pmap資訊列印,由於地址空間佈局隨機化(ASLR)機制,可能每次執行結果的地址都不一樣:
memory map (836 entries): (fault address prefixed with --->)
... ...
00000079'1afe2000-00000079'1bc05fff --- 0 c24000
00000079'1bc06000-00000079'1bc07fff rw- 0 2000
00000079'1bc08000-00000079'1bfe1fff --- 0 3da000
00000079'1bfe2000-00000079'1bfe2fff --- 0 1000
00000079'1bfe3000-00000079'1c0defff rw- 0 fc000 [anon:stack_and_tls:11290]
00000079'1c0df000-00000079'1c0dffff --- 0 1000
--->Fault address falls at 00000079'1c0e6cd0 between mapped regions
00000079'1c113000-00000079'1c591fff --- 0 47f000
00000079'1c592000-00000079'1c593fff rw- 0 2000
00000079'1c594000-00000079'1d112fff --- 0 b7f000
00000079'1d113000-00000079'1d113fff --- 0 1000
... ...
出現錯誤的地址是個空洞,也就是出現空指標,透過對比彙編程式碼和C++程式碼可以得知x0x3(w0w3)正好是傳遞了4個引數,ldr x8, [x8,#128]
準備賦值給x8暫存器onTransact
函式型別的函式指標,然後blr x8
跳轉到x8執行子程式,說明沒有找到onTransact這個函式地址。
以上分析也不好判斷是前面這個x8暫存器本身出了問題,在執行過程中被意外修改,還是訪問這塊記憶體有毛病被修改了,但還是不要懷疑是binder庫本身的問題,binder是系統核心功能,有問題早就掛在其他地方了。好訊息是這個問題是必現的,我們有足夠的試錯機會。
透過重新搜尋surfaceflinger相關程式碼,發現MTK為其定製了一個庫libsurface_ext.so
,擴充套件了一些功能,並且實現了一個名為SurfaceExtService的binder服務新增道ServiceManager裡:
extern "C" void createSurfaceExtService() {
const sp<IServiceManager> sm = defaultServiceManager();
if (sm == nullptr) {
LOGE("Can't get ServiceManager");
} else {
sp<IBinder> binder = sm->checkService(String16(SERVICE_NAME));
if (binder != nullptr) {
LOGW("SurfaceExtService added");
} else {
sp<SurfaceExtService> sfext = new SurfaceExtService();
sm->addService(String16(SERVICE_NAME), sfext, false);
LOGI("SurfaceExtService add");
}
}
}
透過宏註釋編譯程式碼,去掉SurfaceExtService服務,重新編譯push,再次透過Android Studio安裝應用,surfaceflinger不出現奔潰。這下問題能夠縮小到libsurface_ext.so
與binder
相關功能。
3、解決問題
反覆看了巨久的程式碼,並不能找到問題,後面突發靈感,還原始碼重新編譯執行surfaceflinger,透過cat /proc/[pid]/maps |grep libsurface_ext
命令檢視surfaceflinger的記憶體對映,發現libsurface_ext.so並不在記憶體對映中。
搜尋程式碼發現其動態載入so的位置:
SurfaceExtServiceHelper.cpp
//
// Created by kryo on 1/28/24.
//
#include <log/log.h>
#include <dlfcn.h>
void createSurfaceExtServiceProxy() {
void* soHandle = dlopen("libsurface_ext.so", RTLD_LAZY);
if (soHandle) {
void (*createSurfaceExtPtr)();
createSurfaceExtPtr = (decltype(createSurfaceExtPtr))(dlsym(soHandle, "createSurfaceExtService"));
if (NULL == createSurfaceExtPtr) {
dlclose(soHandle);
soHandle = nullptr;
ALOGE("createSurfaceExtService not found");
} else {
createSurfaceExtPtr();
dlclose(soHandle); // ?
ALOGD("createSurfaceExtPtr()");
}
} else {
soHandle = nullptr;
ALOGE("lib_surface_ext.so not found");
}
}
程式碼邏輯dlopen載入libsurface_ext.so庫,然後使用dlsym搜尋符號createSurfaceExtService
拿到函式指標並呼叫(看起來和Java反射有點類似),日誌createSurfaceExtPtr()也成功列印。
乍一看沒有問題,但是為啥maps裡面並沒有libsurface_ext.so,仔細一看createSurfaceExtPtr()呼叫後又呼叫了dlclose()
,去掉這個呼叫,重新編譯push,問題不再復現,之前繞了一大圈沒找到bug,原來被這塊給坑了。
回溯下之前的onTransact呼叫出錯的問題,x8暫存器訪問的記憶體所在的區間就是libsurface_ext.so載入的記憶體對映,後面dlclose又把so庫解除安裝了,回頭再看tombstone檔案memory map段也沒有這個so
這個庫使用了binder,有重寫BBinder::onTransact方法:
status_t BBinder::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/);
推測進行Binder呼叫時透過vtable(虛表指標偏移128位元組)訪問該庫Binder物件找被重寫的onTransact函式指標,再跳轉到函式去執行,結果獲取函式指標時訪問的不存在,導致程序crash掉!
呼叫dumpsys
、service list
等可以觸發surfaceflinger的binder呼叫,均能復現該問題,應該也是Android Studio安裝app能復現的原因。
總結
對於程式碼動態新增binder功能的so庫時,我們儘量儲存開啟的so控制代碼,並在所有業務結束後才能透過dlclose()關閉控制代碼,以防止因so庫的引用計數為0時被系統從記憶體中解除安裝,且對於surfaceflinger這種永不退出的程序來說沒有必要呼叫關閉,這個鍋要甩給MTK開發。
Reference
-
Linux 動態連結過程中的【重定位】底層原理
-
《程式設計師的自我修養:連結、裝載與庫》