記一次dlopen使用問題導致Framework重啟,tombstones、pmap與反彙編分析(上)

Kryo發表於2024-04-05

關鍵詞: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

x8reg

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.sobinder相關功能。

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掉!

呼叫dumpsysservice list等可以觸發surfaceflinger的binder呼叫,均能復現該問題,應該也是Android Studio安裝app能復現的原因。

總結

對於程式碼動態新增binder功能的so庫時,我們儘量儲存開啟的so控制代碼,並在所有業務結束後才能透過dlclose()關閉控制代碼,以防止因so庫的引用計數為0時被系統從記憶體中解除安裝,且對於surfaceflinger這種永不退出的程序來說沒有必要呼叫關閉,這個鍋要甩給MTK開發。

Reference

  • Linux 動態連結過程中的【重定位】底層原理

  • 《程式設計師的自我修養:連結、裝載與庫》

相關文章