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

来源:https://www.cnblogs.com/kryo/p/18116327
-Advertisement-
Play Games

關鍵詞:Android Framework 動態庫 動態鏈接 Binder 1、事件起因 Android Studio一次更新後發現install App,設備就重啟了,跑了一遍開機動畫但不是從開機第一屏開始重啟,tombstones內容查看發現是surfaceflinger掛在libbinder. ...


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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本篇文章主要介紹了GaussDB(DWS)性能調優涉及到的優化器和系統級GUC參數,通過合理配置這些GUC參數,能夠充分利用好CPU、記憶體、磁碟IO和網路IO等資源,提升語句的執行性能和GaussDB(DWS)集群的整體性能。 ...
  • 本文詳細記錄一次在Mac中安裝MySQL Server的過程,安裝環境如下: MacOS 14.4 x86, core i7 在MySQL資料庫實驗環境下通常都要安裝其MySQL Server,安裝方式五花八門,最簡單的有通過系統包管理工具一鍵安裝,例如apt和yum等,這種安裝方法會使得MySQL ...
  • 第4章 Hadoop文件參數配置 實驗一:hadoop 全分佈配置 1.1 實驗目的 完成本實驗,您應該能夠: 掌握 hadoop 全分佈的配置 掌握 hadoop 全分佈的安裝 掌握 hadoop 配置文件的參數意義 1.2 實驗要求 熟悉 hadoop 全分佈的安裝 瞭解 hadoop 配置文件 ...
  • 概述 在主從複製中,一般有一個主資料庫(Master)和一個或多個從資料庫(Slave),主資料庫負責接收和處理寫操作,從資料庫複製主資料庫的日誌文件,將寫操作在自身的資料庫重演,從而實現數據的同步 複製類型 STATEMENT:把主資料庫執行的 sql 複製到從資料庫,是預設類型 ROW:直接把數 ...
  • 一、CustomDialog CustomDialog組件是一種自定義對話框,可以通過開發人員根據特定的要求定製內容和佈局。它允許開發人員創建一個完全可定製的對話框,可以顯示任何類型的內容,例如文本、圖像、表單和按鈕。 CustomDialog通常用於在執行任務之前向用戶提供額外的信息或輸入選項 ...
  • 一、TextInput/TextArea TextInput和TextArea組件通常用於收集用戶輸入的文本數據。 TextInput組件通常用於單行文本的輸入,它允許用戶通過一個游標來輸入文字,並支持多種樣式和佈局選項來提高用戶體驗。例如,在用戶輸入錯誤時可以顯示錯誤消息或在用戶輸入時自動完成 ...
  • Android 音視頻開發 - VideoView 本篇文章主要介紹下Android 中的VideoView. 1: VideoView簡介 VideoView是一個用於播放視頻的視圖組件,可以方便地在應用程式中播放本地或網路上的視頻文件。 VideoView可以直接在佈局文件中使用,也可以在代碼中 ...
  • Android音視頻開發 - MediaMetadataRetriever 相關 MediaMetadataRetriever 是android中用於從媒體文件中提取元數據新的類. 可以獲取音頻,視頻和圖像文件的各種信息,如時長,標題,封面等. 1:初始化對象 private MediaMetada ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...