修復金山雲KSYStreamer 在Android P以上機型Native Crash

問題

在升級了Target API28之後,發現我們一直使用的金山雲的推流SDK在部分Android 9以上的手機離開開播頁的時候會不明的出現Crash,我的小米8上面是固定第二次的時候Crash,一些手機第一次就Crash了,崩潰日誌如下:

Abort message: 'FORTIFY: pthread_mutex_destroy called on a destroyed mutex (0xe60a6638)'
r0 00000000 r1 00004484 r2 00000006 r3 00000008
r4 00004484 r5 00004484 r6 ffc9d79c r7 0000010c
r8 00000000 r9 e60ce000 r10 ffc9d830 r11 e60ce000
ip ffc9d738 sp ffc9d788 lr e65b5139 pc e65ace56
backtrace:
#00 pc 0001ce56 /system/lib/libc.so (abort+58)
#1 pc 00064a63 /system/lib/libc.so (__fortify_fatal(char const*, ...)+26)
#2 pc 00064265 /system/lib/libc.so (HandleUsingDestroyedMutex(pthread_mutex_t*, char const*)+20)
#3 pc 00064919 /system/lib/libc.so (pthread_mutex_destroy+128)
#4 pc 000c969f /data/app/com.ksyun.live.demo-GHznaGLhejtsX-hu5CDo8w==/lib/arm/libksylive.so (AudioFilterBase::destroyFifo()+6)
#5 pc 000c96f3 /data/app/com.ksyun.live.demo-GHznaGLhejtsX-hu5CDo8w==/lib/arm/libksylive.so (AudioFilterBase::~AudioFilterBase()+14)
#6 pc 000ca27f /data/app/com.ksyun.live.demo-GHznaGLhejtsX-hu5CDo8w==/lib/arm/libksylive.so (AudioResample::~AudioResample()+50)
#7 pc 000ca291 /data/app/com.ksyun.live.demo-GHznaGLhejtsX-hu5CDo8w==/lib/arm/libksylive.so (AudioResample::~AudioResample()+4)

這可咋整啊?金山雲都已經停止維護了,找作者也找不到啊!Github上面也有人在報這個問題(https://github.com/ksvc/KSYStreamer_Android/issues/297),不過也沒有人能解決。自研的推流工具也還沒有Ready。。。無奈只能自己逆向去嘗試解決了:

這個報錯是說我們重複銷燬了一個互斥鎖。掛在了Native層,但是很奇怪是爲啥之前不會掛,但是現在就掛了?只能去讀系統源碼了。

查看系統源代碼如下:

在這裏插入圖片描述
可以發現在紅框部分就是檢測這個mutex是否有銷燬的邏輯了。我們進去看看這裏到底做了啥:
在這裏插入圖片描述
從這可以看到,如果mutex的狀態是0xffff就代表是已被銷燬,而HandleUsingDestroyedMutex則會在AndroidP以上拋出錯誤,最終導致程序的異常退出。

錯誤原因找到了,那我們怎麼知道金山雲到底做了啥導致的這個錯誤呢?我們先用IDA工具去分析一下Crash的地方:

在這裏插入圖片描述
之前報錯的地方是:

#4 pc 000c969f /data/app/com.ksyun.live.demo-GHznaGLhejtsX-hu5CDo8w==/lib/arm/libksylive.so (AudioFilterBase::destroyFifo()+6)

說明離崩潰的地址是離destroyFifo的基地址偏移了6字節,也就是在

 BL              audio_utils_fifo_deinit

這裏掛掉了,看看內容如何:
在這裏插入圖片描述
這裏就是直接調用了pthread_mutex_destroy。元兇找到了是這裏,一個mutex被重複destroy,但是爲什麼第一次沒有Crash?但是第二次Crash了?從我過往的經驗看,一般是變量定義的時候沒有初始化導致的。這裏銷燬的mutex也正好是在audio_utils_fifo_init裏面 初始化的。所以我猜測這個崩潰應該就是audio_utils_fifo_init沒有調用。抱着刨根問底的精神,我接下來就去調試看看是不是真的是這個原因Crash的。

調試過程

一開始我本來想用IDA的動態調試在我手機上直接去給函數打斷點,無奈調試的時候總是卡死(小米的手機是真爛),懶得折騰,就用Hook工具去做調試了,由於這裏要用Inline Hook,愛奇藝的xhook不能搞定,我這裏就用的Dobby這個框架來做的Hook,代碼如下:

static int my_audio_utils_fifo_init(unsigned int *a1, unsigned int a2, unsigned int a3, unsigned int a4) {
    int ret = old_audio_utils_fifo_init(a1, a2, a3, a4);
    __android_log_print(ANDROID_LOG_DEBUG, "mytag", "my_audio_utils_fifo_init");
    return  ret;
}
int  my_audio_utils_fifo_deinit(int a1) {
    __android_log_print(ANDROID_LOG_DEBUG, "mytag", "my_audio_utils_fifo_deinit");
    return 0;
}
 
void dobbyHook() {
    DobbyHook((void *) &audio_utils_fifo_init, (void *) my_audio_utils_fifo_init,
              (void **) &old_audio_utils_fifo_init);
    DobbyHook((void *) &audio_utils_fifo_deinit, (void *) my_audio_utils_fifo_deinit,
              (void **) &old_audio_utils_fifo_deinit);
}

由於old_audio_utils_fifo_deinit會導致Crash,所以我直接返回了0,不執行這一句了。接下來我們運行看看:

在這裏插入圖片描述
果然是沒有init就去做了deinit!這個也就驗證了我剛纔的猜想。這裏提一下,在C裏面定義一個變量的時候,如果沒有初始化,那就是一個在內存塊上隨機的值,具體取決於之前這個地址的內容是啥,所以爲什麼我的小米8第一次沒有Crash,但是第二次Crash了。因爲它第一次的內存的內容沒有賦值爲0x…FFFF,所以也就不會認爲是被Destroy的mutex,而第二次運氣不好,恰好分配的這塊內存的值就是0x…FFFF,也就導致的這裏的Crash。

解決問題

這個問題其實看到這個崩潰就比較好解決。我想到了兩種方案:

  1. Native Hook 這種方案成本最低,而且有很多現成的開源框架可以使用。我最終解決這個問題也是用的這個方法:
    我這裏直接用Native hook工具在Android P以上的手機把pthread_mutex_destroy改爲先檢查一下狀態是否沒有destroy,再去做destroy。我這裏就用了愛奇藝的xhook來寫了一個hook函數搞定了:
    在這裏插入圖片描述
    在這裏插入圖片描述
  2. 直接修改libksylive.so的機器代碼,在audio_fifo_utils_deinit或者j_pthread_mutex_destroy裏面加入destroy mutex的保護
    我原本是想直接去用Arm彙編修改libksylive.so的,無奈彙編水平過低,改了之後總是格式有問題,最近也沒時間去弄了。最終還是用的Xhook的方案解決上線了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章