使用Android Native Hook技术解决VLC播放器闪退的问题

文章目录

  • 1.概述
  • 2.问题描述
  • 3.问题分析
  • 4.问题解决
  • 5.总结

1.概述

在做公司的一个TOB的需求时,发现调起Unity提供的3D播放器播放网络在线视频时闪退了,然后就拉着相关部门的人一起分析问题,最后定位到是VLC里面用到的系统日志打印函数在部分的系统上会出问题,于是各部门的同事就开始想各种解决方案,当时主要是两个部门的同事提出了两种解决方案,一方面是系统部门的人提出直接在系统上改,因为ROM是我们自己的所以可以改系统的代码。禁用掉日志打印函数中引发闪退的部分,但是这样就会导致其他APP使用这个日志打印函数时就无法获取到日志了。但也不失为一个解决办法。第二种解决办法是C++ 部门提出的,直接将VLC的源码下载下来,修改打印日志的函数。这种方法其实也行,就是比较耗费时间和精力,导致一直没有实施,最后就是系统部门的人改了一个版本验证没有闪退后,也不敢进ROM,怕影响其他APP。于是最后我就提出了使用native hook的技术去解决这个问题,本文就是介绍如何使用native hook技术解决这个问题。

2.问题描述

本问题主要是当打开播放器,传递网络视频链接播放的时候就会闪退,闪退的堆栈如下所示:

 FORTIFY: %n not allowed on Android
2024-03-04 18:02:50.263 15889-15947 AndroidRuntime          xxx.xxx.xxx.vrplayer            
 E  FATAL EXCEPTION: UnityMain
 Process: xxx.xxx.xxx.vrplayer, PID: 15889
 java.lang.Error: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
 Version '2019.4.36f1 (660c164b2fc5)', Build type 'Release', Scripting Backend 'il2cpp', CPU 'arm64-v8a'
 Build fingerprint: 'xxx/xxx/xxx:10/QKQ1.211001.001/02241336:user/release-keys'
 Revision: '0'
 ABI: 'arm64'
 Timestamp: 2024-03-04 18:02:50+0800
 pid: 15889, tid: 16063, name: VlcObject  >>> xxx.xxx.xxx.vrplayer <<<
 uid: 1000
 signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
     x0  0000000000000000  x1  0000000000003ebf  x2  0000000000000006  x3  0000007956eedd90
     x4  0000000000000000  x5  0000000000000000  x6  0000000000000000  x7  0000000000000018
     x8  00000000000000f0  x9  becc2cee9a43a307  x10 0000000000000001  x11 0000000000000000
     x12 fffffff0fffffbdf  x13 0000000065e59c4a  x14 0002348d9b6c4800  x15 00005b4371aa1524
     x16 00000079e94118c0  x17 00000079e93eda60  x18 0000000000000000  x19 0000000000003e11
     x20 0000000000003ebf  x21 00000000ffffffff  x22 00000000ffffffff  x23 0000007956eee954
     x24 0000007956eee170  x25 000000000000001d  x26 000000000000006e  x27 0000000000000000
  x28 0000007956ef0008  x29 0000007956eede30
  sp  0000007956eedd70  lr  00000079e939f0c4  pc  00000079e939f0f0
 
                                                                                                    backtrace:
 #00 pc 00000000000830f0  /apex/com.android.runtime/lib64/bionic/libc.so (abort+160) (BuildId: 35b174c1ce3028d241142fca9906cd01)
 #01 pc 00000000000c4984  /apex/com.android.runtime/lib64/bionic/libc.so (helpers::wcsconv(wchar_t*, int)) (BuildId: 35b174c1ce3028d241142fca9906cd01)
 #02 pc 00000000000c3f38  /apex/com.android.runtime/lib64/bionic/libc.so (__vfprintf+10956) (BuildId: 35b174c1ce3028d241142fca9906cd01)
 #03 pc 00000000000e171c  /apex/com.android.runtime/lib64/bionic/libc.so (snprintf+232) (BuildId: 35b174c1ce3028d241142fca9906cd01)
 #04 pc 00000000008bdabc  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (vasnprintf+3036) (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #05 pc 00000000008bce50  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (rpl_snprintf+132) (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #06 pc 00000000008ecee8  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #07 pc 00000000008ecdd8  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (gnutls_x509_trust_list_add_trust_dir+68) (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #08 pc 00000000009275e0  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (gnutls_x509_trust_list_add_system_trust+76) (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #09 pc 000000000063ca28  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #10 pc 000000000074d190  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (vlc_module_load+1076) (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #11 pc 0000000000785dd4  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (vlc_tls_ClientCreate+60) (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #12 pc 000000000061f3b0  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #13 pc 00000000006262d8  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #14 pc 00000000006263d0  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)
 #15 pc 000000000061eac4  /data/app/xxx.xxx.xxx.vrplayer-UO0jeUwKH6BDD6eJ_Zrx6A==/base.apk (BuildId: fbdb95762b44ab3a3cd3722aba5ebc064ca8abd7)

堆栈做了脱敏处理,应该不影响问题分析。

3.问题分析

从上面的堆栈中,我们可以看到这个问题的一行日志如下:
在这里插入图片描述
结合日志,我们发现并不是我们打印的日志,最有可能是第三方库VLC或者是系统打印的,但是VLC我们暂时没有源码,更何况VLC是Unity插件中引用的,我们无法准确的知道对应的代码版本,所以就先从系统分析起,到系统源码中去搜索这行代码。幸运的是,我们在系统源码中找到了这行代码:
在这里插入图片描述上图代码的地址
知道是系统打印的日志后就好办了,我们接着去找下都是谁调用了这个函数,这里需要看堆栈了。我们从堆栈中可以看到如下两个函数:
在这里插入图片描述
然后我们又可以根据日志中的信息:
在这里插入图片描述
推断出调用上面两个函数打印日志的是vlc播放器模块,接下来我们要想解决这个问题,主要有3种办法,第一种,系统修改,屏蔽掉这个日志函数。第二种。修改VLC源码。第三种就是本文介绍的从应用测通过native hook技术去修改。

4.问题解决

通过我的亲身经历也证明了,从系统侧改不现实,因为这是应用侧的库中的原因导致的闪退问题,系统侧没有责任也没有义务去为了一个应用冒着风险去修改这个地方,影响太大。第二,从VLC源码中修改重新编译,这也不现实,因为修改源码意味着需要花时间和精力去熟悉VLC源码,这需要公司投入人力,而且收益不高,并且VLC后续如果更新还得继续去维护,代价太大。所以我提出了native hook的技术。所谓的native hook,就是劫持函数的调用。让原本调用A函数的操作改为调用我自己定义的B函数。这样就绕过了A函数的执行。对应到当前的问题中就是,A函数调用了系统的日志打印函数,会引起闪退,那么我们就hook住A函数,让VLC不去调用A函数,去调用我们的B函数,这样就不会引起应用闪退了。接下来就看下如何去实现对应的函数hook.

通过分析日志我们发现,引起应用闪退的函数主要有两个。
在这里插入图片描述
所以我们只要hook住这两个函数,就可以避免这个native crash。知道了原理后,我们就可以开始hook代码的编写了。这里我们使用的是字节跳动开源的bhook库来hook native的函数调用。主要代码如下所示:

static int my_vfprintf_proxy(FILE *fp, const char *path, va_list va) {
    debug("my_vfprintf_proxy", path, BYTEHOOK_RETURN_ADDRESS());
    LOG("zhongxj:my_vfprintf_proxy = %d,return fd: %d", 1129);
    BYTEHOOK_POP_STACK();
    return 1129;
}

static int my_rpl_snprintf_proxy(char * a, size_t s, const char * cc, ...) {
   // debug("my_rpl_snprintf_proxy", path, BYTEHOOK_RETURN_ADDRESS());
    LOG("zhongxj:my_vfprintf_proxy = %d,return fd: %d", 904);
    BYTEHOOK_POP_STACK();
    return 904;
}

static int hacker_hook_rpl_snprintf() {
    if (NULL != rplsnprintf_stub) return -1;
    void *rpl_snprintf_proxy;
    rpl_snprintf_proxy = (void *) my_rpl_snprintf_proxy;


    rplsnprintf_stub =
            bytehook_hook_single("libvlcjni.so",
                                 NULL,
                                 "rpl_snprintf",
                                 rpl_snprintf_proxy,
                                 rplsnprintf_hooked_callback,
                                 NULL);
    return 0;
}


static int hacker_hook_vasnprintf(JNIEnv *env, jobject thiz) {
    (void) env, (void) thiz;

    if (NULL != vfprintf_stub) return -1;
    void *vfprintf_proxy;
    vfprintf_proxy = (void *) my_vfprintf_proxy;

    vfprintf_stub =
            bytehook_hook_single("libvlcjni.so",
                                 NULL,
                                 "vasnprintf",
                                 vfprintf_proxy,
                                 vfprintf_hooked_callback,
                                 NULL
            );

    int res = hacker_hook_rpl_snprintf();
    LOG("zhongxj:hook rpl res: %d",res);
    return 0;
}



static int hacker_unhook(JNIEnv *env, jobject thiz) {
    (void) env, (void) thiz;
    if (NULL != vfprintf_stub) {
        bytehook_unhook(vfprintf_stub);
        vfprintf_stub = NULL;
    }

    if(NULL != rplsnprintf_stub){
        bytehook_unhook(rplsnprintf_stub);
        rplsnprintf_stub = NULL;
    }

    return 0;
}

然后在Java层使用对应的jni函数,在打开播放器的地方hook上面的两个函数vasnprintf,rpl_snprintf这样当VLC库调用这两个函数的时候,就会被hook住,然后调用我们的my_vfprintf_proxy,my_vfprintf_proxy函数,这样就避免了调用系统的函数导致闪退,然后我们的播放器停止退出的时候可以释放掉我们的hook点就可以了。有人会觉得这种native hook的技术好像不是啥好技术,可能会有害,但其实不是,bhook库被字节跳动广泛用于抖音,火山引擎,飞书等,所以就放心用吧,用得好的话收益很大的。

5.总结

本文主要介绍了使用native hook的方案解决第三方库中的函数调用引起的闪退,这里只是提供了这个思路和大体实现,具体的细节还需要读者自己添加上,比如何时需要hook,hook的时机等。解决这次问题的过程中,我也深深感受到了知识的力量。所以我们应该多读书,多看别人的经验。很多时候这种解决问题的方法是AI和搜索引擎无法提供的,很遗憾我即将30了才明白这个道理,希望年轻的读者,对编程感兴趣的一定要多学习,多看书,多动手。下篇文章我打算出一个bhook的使用方法示例。希望能帮到新手朋友。敬请期待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/428326.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flask入门二(Flask的CBV、模版语法、请求和响应、session执行流程分析、Flask闪现、请求拓展、g对象)

文章目录 一、Flask的CBV1.CBV的写法2.CBV的执行流程3.endpoint 的使用4.CBV中得methods作用5.CBV加装饰器 二、模版语法1.渲染变量2.变量的循环3.逻辑判断 三、请求和响应四、session执行流程分析1.基本使用2.执行流程3.Django中session的执行流程 五、Flask闪现1.作用2.案例3…

【Unity】Node.js安装与配置环境

引言 我们在使用unity开发的时候&#xff0c;有时候会使用一些辅助工具。 Node.js就是开发中&#xff0c;经常会遇到的一款软件。 1.下载Node.js 下载地址&#xff1a;https://nodejs.org/en 2.安装Node.js ①点击直接点击Next下一步 ②把协议勾上&#xff0c;继续点击…

【论文精读】I-JEPA

摘要 计算机视觉中&#xff0c;常采用基于不变性和基于生成的方法进行自监督学习。对比学习&#xff08;CL&#xff09;是典型的基于不变性的方法&#xff0c;通过预训练方法优化编码器&#xff0c;使其能生成同一图像的两个或多个视图的相似嵌入&#xff0c;其中图像视图通常由…

格两例12345

osu/Lucky Roll gaming 周末osu有道题&#xff1a;lcg已知低位 def lcg(s, a, b, p):return (a * s b) % pp getPrime(floor(72.7)) a randrange(0, p) b randrange(0, p) seed randrange(0, p) print(f"{p }") print(f"{a }") print(f"{b …

关于python函数参数传递

参数传递 在 python 中&#xff0c;类型属于对象&#xff0c;对象有不同类型的区分&#xff0c;变量是没有类型的&#xff1a; 在下面的代码示例重&#xff0c;[1,2,3] 是 List 类型&#xff0c;“qayrup” 是 String 类型&#xff0c;而变量 a 是没有类型&#xff0c;它仅仅…

java找工作之Mybatis(入门及xml配置相关)

Mybatis 学习Mybatis就要学会查看官网&#xff0c;官网地址如下&#xff1a;<MyBatis中文网 > 1、简介 1.1什么是Mybatis MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取…

图形系统开发实战课程:进阶篇(上)——9.空间算法(一)

图形开发学院&#xff5c;GraphAnyWhere 课程名称&#xff1a;图形系统开发实战课程&#xff1a;进阶篇(上)课程章节&#xff1a;“图形样式”原文地址&#xff1a;https://www.graphanywhere.com/graph/advanced/2-9.html 第九章 空间算法&#xff08;一&#xff09; \quad 在…

计算机专业必看的十部电影

计算机专业必看的十部电影 1. 人工智能2. 黑客帝国3. 盗梦空间4. 社交网络5. Her6. 模仿游戏7. 斯诺登8. 头号玩家9. 暗网10. 网络迷踪 计算机专业必看的十部电影&#xff0c;就像一场精彩盛宴&#xff01; 《黑客帝国》让你穿越虚拟世界&#xff0c;感受高科技的魅力《模仿游戏…

小红关鸡(双指针)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K Special Judge, 64bit IO Format: %lld 题目描述 有nnn个鸡窝排成一排&a…

#WEB前端(CCS常用属性,补充span、div)

1.实验&#xff1a; 复合元素、行内元素、块内元素、行内块元素 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; span为行内元素&#xff1a;不可设置宽高&#xff0c;实际占用控件决定分布空间。 div为块内元素&#xff1a;占满整行&#xff0c;可以设置宽高 img为行内块元…

Windows 2012 设置 nginx 开机自启动(适用于windows2012/10)

Windows 2012 设置 nginx 开机自启动&#xff08;适用于windows2012/10&#xff09;https://www.cnblogs.com/xuegqcto/articles/7521483.html 在windows server 2012上安装nginx&#xff0c;同时配置开机自启动服务&#xff08;推荐使用“Windows Service Wrapper”工具&…

前后端分离项目服务器部署

文章目录 前言准备工作安装jdk1.8安装nginx安装库解压、编译nginx并安装nginx 命令测试nginx 安装mysql卸载mariadb用root用户登录系统&#xff0c;增加mysql用户和组准备数据目录初始化MySQL将mysql加入到服务中编辑配置文件&#xff0c;保存退出启动mysql配置环境变量设置开机…

20 个不同的 Python 函数实例

Python 是一种广泛使用的高级编程语言&#xff0c;其函数是 Python 编程中至关重要的概念之一。函数是一段可以重复使用的代码块&#xff0c;可以接收输入参数并返回输出结果。使用函数能够提高代码的可读性、可维护性和重用性。 基础知识 在 Python 中&#xff0c;函数使用关…

C语言初阶—数组

数组是一组相同类型元素的集合。 在C99标准之前&#xff0c;数组的大小必须是常量或常量表达式。 在C99标准之后&#xff0c;数组的大小可以是变量&#xff0c;可以支持变长数组&#xff0c;但变长数组不能初始化。 不完全初始化&#xff0c;剩余的元素默认初始化为0 。 数组访…

c++_leetcode_寻找峰值

目录 一、寻找峰值的示例 二、官方实现代码及解释 1、官方测试结果&#xff1a; 2、代码解释&#xff1a; 3、解题思路&#xff1a; 三、我的暴力解决 1、测试一&#xff1a; 2、测试二&#xff1a; 3、最终“暴力求解”代码&#xff1a; 4、官网提交测试通过&#xf…

终极排序(快排,归并,库函数)

一、快速排序 1、确定分界点&#xff1a;q [ l ] , q [ ( l r ) / 2 ] , q [ r ] ,或者其它区间之中的随机数。&#xff08;左 l 右 r &#xff09; 2、调整区间&#xff1a;&#xff08;较难理解的部分&#xff09; &#xff08;1&#xff09;、暴力做法 …

基于Siamese网络的zero-shot意图分类

原文地址&#xff1a;Zero-Shot Intent Classification with Siamese Networks 通过零样本意图分类有效定位域外意图 2021 年 9 月 24 日 意图识别是面向目标对话系统的一项重要任务。意图识别(有时也称为意图检测)是使用标签对每个用户话语进行分类的任务&#xff0c;该标签…

云手机的境外舆情监控应用——助力品牌公关

在当今数字化时代&#xff0c;社交媒体已成为品牌传播和互动的主要平台。随之而来的是海量的信息涌入&#xff0c;品牌需要及时了解并应对海外社交媒体上的舆情变化。本文将介绍如何通过云手机进行境外舆情监控&#xff0c;更好地帮助企业公关及时作出决策。 1. 境外舆情监控与…

图像实现曲面屏效果

图像实现曲面屏效果 双线性插值 双线性插值是一种常用的图像插值方法&#xff0c;用于在图像中两个相邻像素之间进行插值&#xff0c;以获取介于它们之间某个位置的像素值。在透视变换等情况下&#xff0c;由于原始图像的像素点与目标图像的像素点位置不完全重合&#xff0c;…

HTML入门:推荐1款免费好用的web开发工具

前言 你好&#xff0c;我是云桃桃。 在过去的 10 年里&#xff0c;我一直专注于 web 前端开发领域&#xff0c;积累了丰富的经验和知识。 所以&#xff0c;接下来&#xff0c;我会把自己所学所做给体系化输出&#xff0c;我将持续与你分享关于 HTML、CSS、JavaScript&#x…