修复金山云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的方案解决上线了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章