当前位置:首页 > 科技  > 软件

你的debug包在Android 14变卡了吗

来源: 责编: 时间:2024-04-16 08:33:16 114观看
导读一、背景我的App怎么这么卡,谁在代码里下毒了!有一天突然发现debug包运行变的特别卡顿,经过下面的简单测试发现debug包在Android 14上出了问题。 图片二、问题排查纪录常规手段排查使用了systrace以及内部的debug包 trac

一、背景

我的App怎么这么卡,谁在代码里下毒了!Aup28资讯网——每日最新资讯28at.com

有一天突然发现debug包运行变的特别卡顿,经过下面的简单测试发现debug包在Android 14上出了问题。 Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

二、问题排查纪录

Aup28资讯网——每日最新资讯28at.com

常规手段排查

Aup28资讯网——每日最新资讯28at.com

使用了systrace以及内部的debug包 trace工具dutrace进行排查。Aup28资讯网——每日最新资讯28at.com

结论:CPU空闲,主线程无明显阻塞,看上去就是纯方法执行耗时。Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

发现怀疑点

Aup28资讯网——每日最新资讯28at.com

第一步排查过程中没有特别大的收获,但是我用dutrace工具排查时发现了一个异常现象。这里简单介绍一下dutrace的实现原理:Aup28资讯网——每日最新资讯28at.com

dutrace是利用inline hook在artmethod的执行前后加上atrace的点再通过perfetto ui工具展示。有以下优点:Aup28资讯网——每日最新资讯28at.com

    1. 支持线下分析函数执行流程,函数耗时。Aup28资讯网——每日最新资讯28at.com

    2. 在分析函数调用流程下:Aup28资讯网——每日最新资讯28at.com

        a. 可以查看整个过程的函数调用(包括framework函数);Aup28资讯网——每日最新资讯28at.com

        b. 能够指定监控的函数和线程有效过滤无用trace;Aup28资讯网——每日最新资讯28at.com

        c. 动态配置不需要重新打包。Aup28资讯网——每日最新资讯28at.com

    3. 可使用现成的UI分析工具,有系统关键线程的函数调用,例如渲染耗时、线程锁,GC 耗时等,还有 I/O 操作、CPU 负载等事件。Aup28资讯网——每日最新资讯28at.com

流程图流程图Aup28资讯网——每日最新资讯28at.com

在对artmethod执行前后进行hook时 这里涉及到处理art方法解释执行的三种情况。Aup28资讯网——每日最新资讯28at.com

ART Runtime 解释器

  1. The C++ interpreter,也就是传统的基于switch结构的解释器,一般仅在调试环境、方法跟踪、指令不支持或者在字节码发生异常情况下(例如failed structured-locking verification)才走该分支。
  2. The mterp fast interpreter,核心是引入了handler table做指令映射,并通过手写汇编以实现指令间的快速切换,提高了解释器性能。
  3. Nterp是Mterp的再次优化。Nterp省去了managed code stacks的维护,采用了和Native方法一样的栈帧结构,并且译码和翻译执行全程都由汇编代码实现,进一步拉进解释器和compiled code的性能差距。

在这边我发现了一个异常现象,就是Android 14的解释执行居然都用的switch解释执行方式。我又重新去测试了几个Android 版本的解释执行方式。Android 12走的mterp,Android 13走的是nterp,当进行调试的时候才会走到switch, 理论上Android 14应该也走nterp才对,怎么会走了最慢的switch呢。以下按顺序是12、13、14版本的方法执行backtrace。Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

排查怀疑点

Aup28资讯网——每日最新资讯28at.com

开始怀疑是解释执行导致的卡顿了,翻了下源码 art/runtime/interpreter/mterp/nterp.cc 中确实有变动 如果是javaDebuggable 就不走nterp了。接下来尝试去证明是是这个问题导致的。Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

isJavaDebuggable 是runtime.cc中的 RuntimeDebugState runtime_debug_state_ 中控制的。我们可以找到runtime的实例然后通过偏移量修改过runtime_debug_state_属性,看了下源码还可以通过_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE 进行设置。 Aup28资讯网——每日最新资讯28at.com

void Runtime::SetRuntimeDebugState(RuntimeDebugState state) {  if (state != RuntimeDebugState::kJavaDebuggableAtInit) {    // We never change the state if we started as a debuggable runtime.    DCHECK(runtime_debug_state_ != RuntimeDebugState::kJavaDebuggableAtInit);  }  runtime_debug_state_ = state;}

我通过上述方式去进行尝试验证 把测试包的 isJavaDebuggable 设置为false 依然卡顿,把生产包的isJavaDebuggable设置为true,变得稍微卡了点。于是我推翻了自己解释执行方式导致卡顿的猜想。 Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

排查native耗时

Aup28资讯网——每日最新资讯28at.com

怀疑nativie方法执行耗时, 再次尝试用simpleperf定位问题。Aup28资讯网——每日最新资讯28at.com

结论:基本都是解释执行代码中的堆栈耗时,没有其他特殊堆栈。Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

定位到DEBUG_JAVA_DEBUGGABLE

Aup28资讯网——每日最新资讯28at.com

那就想着从debuggable的源头入手,逐步缩小范围定位影响变量。Aup28资讯网——每日最新资讯28at.com

AndroidManifest中的debuggable影响系统system进程启动我们进程中的一个runtimeFlags。Aup28资讯网——每日最新资讯28at.com

frameworks/base/core/java/android/os/Process.java 中的start方法 其中第6个参数就是runtimeFlags而如果是debuggableFlag runtimeFlags会被添加以下一些flag 那就先缩小标签范围。Aup28资讯网——每日最新资讯28at.com

if (debuggableFlag) {                runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;                runtimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;                runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;                // Also turn on CheckJNI for debuggable apps. It's quite                // awkward to turn on otherwise.                runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;                // Check if the developer does not want ART verification                if (android.provider.Settings.Global.getInt(mService.mContext.getContentResolver(),                        android.provider.Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE, 1) == 0) {                    runtimeFlags |= Zygote.DISABLE_VERIFIER;                    Slog.w(TAG_PROCESSES, app + ": ART verification disabled");                }            }

需要修改我们进程的启动参数。那就需要去hook system进程了。这边涉及到手机root,安装hook框架的一些操作,然后通过hook Process的start去做一些参数修改。Aup28资讯网——每日最新资讯28at.com

hookAllMethods(        Process.class,        "start",        new XC_MethodHook() {            @Override            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {                final String niceName = (String) param.args[1];                final int uid = (int) param.args[2];                final int runtimeFlags = (int) param.args[5];                XposedBridge.log("process_xx " + runtimeFlags);                if (isDebuggable(niceName, user)) {                    param.args[5] = runtimeFlags&~DEBUG_JAVA_DEBUGGABLE;                    XposedBridge.log("process_xx " + param.args[5]);                }            }        });

这次还是有一些明显的结果的。测试包 runtimeflags 移除DEBUG_JAVA_DEBUGGABLE后不卡了。而生产包包括应用市场上的应用加上DEBUG_JAVA_DEBUGGABLE标记后全部都变卡了。那就可以证明是DEBUG_JAVA_DEBUGGABLE这个变量引起的。Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

定位到DeoptimizeBootImage

Aup28资讯网——每日最新资讯28at.com

继续源码观察DEBUG_JAVA_DEBUGGABLE带来的影响。Aup28资讯网——每日最新资讯28at.com

if ((runtime_flags & DEBUG_JAVA_DEBUGGABLE) != 0) {    runtime->AddCompilerOption("--debuggable");    runtime_flags |= DEBUG_GENERATE_MINI_DEBUG_INFO;    runtime->SetRuntimeDebugState(Runtime::RuntimeDebugState::kJavaDebuggableAtInit);    {      // Deoptimize the boot image as it may be non-debuggable.      ScopedSuspendAll ssa(__FUNCTION__);      runtime->DeoptimizeBootImage();    }    runtime_flags &= ~DEBUG_JAVA_DEBUGGABLE;    needs_non_debuggable_classes = true;  }

这里有逻辑是DEBUG_JAVA_DEBUGGABLE带来的影响点,SetRuntimeDebugState之前已经测试过了。也不是DEBUG_GENERATE_MINI_DEBUG_INFO带来的影响,那是runtime->DeoptimizeBootImage()?于是我用debugable为false的包通过_ZN3art7Runtime19DeoptimizeBootImageEv主动去调用了DeoptimizeBootImage方法,然后复现了!Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

原因分析

Aup28资讯网——每日最新资讯28at.com

DeoptimizeBootImage 将bootImage中AOT代码方法转换为java可调试。重新初始化方法入口点,走到解释执行,而不使用AOT代码。追溯到Instrumentation::InitializeMethodsCode方法,还是到了CanUseNterp(method) CanRuntimeUseNterp这个点。也是Android 13可以用nterp,android 14只能走switch了。Aup28资讯网——每日最新资讯28at.com

我再次hook代码,让CanRuntimeUseNterp 直接return true, 但是还是卡。我发现即使我hook了。下面的这些方法还是走到了switch解释执行。反过来想一想是因为我hook已经滞后了DeoptimizeBootImage已经执行了,当调用到基础方法的时候都是switch执行了。Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

我用Android 13 debugable true的包进行测试先hook CanRuntimeUseNterp return false,然后再执行DeoptimizeBootImage,复现卡顿 。Aup28资讯网——每日最新资讯28at.com

初步定位:bootimage中的方法 Android 13走的nterp而Android 14走的switch  bootimage里面的方法特别基础和零碎所以导致方法switch执行耗时严重。Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

验证是系统问题

Aup28资讯网——每日最新资讯28at.com

如果是系统问题,那大家都应该遇到的,不只我们App有这个问题, 于是我找到了几个小伙伴帮忙验证debug包这个问题。果然都有这个问题,同一个包安装在Android 14 和 Android 13上体验完全不一致。Aup28资讯网——每日最新资讯28at.com

1反馈问题

Aup28资讯网——每日最新资讯28at.com

在issuetracker上已经有人反馈android 14 debug包慢了 https://issuetracker.google.com/issues/311251587。但是还没有结果,于是我补上了我定位到的问题。Aup28资讯网——每日最新资讯28at.com

图片图片Aup28资讯网——每日最新资讯28at.com

顺便也提了个issue https://issuetracker.google.com/issues/328477628Aup28资讯网——每日最新资讯28at.com

三、临时解决

在等Google回复的同时,也同时在思考App层可以有什么办法去规避这个问题,让debug包的体验也回归丝滑,比如如何去重新optimize bootimage中的方法。抱着这个想法又去学习了一下art的代码,发现Android 14新增了一个UpdateEntrypointsForDebuggable方法,这个方法会去按照规则重新设置方法的执行方式比如aot和nterp,那我在这之前把CanRuntimeUseNterp hook了返回true 再去调用UpdateEntrypointsForDebuggable不就会重新走到nterp了吗。  Aup28资讯网——每日最新资讯28at.com

void Instrumentation::UpdateEntrypointsForDebuggable() {  Runtime* runtime = Runtime::Current();  // If we are transitioning from non-debuggable to debuggable, we patch  // entry points of methods to remove any aot / JITed entry points.  InstallStubsClassVisitor visitor(this);  runtime->GetClassLinker()->VisitClasses(&visitor);}

按照上面的思路尝试了一波,果然变得流畅很多!!!Aup28资讯网——每日最新资讯28at.com

其实上面的解决方案还有遗留问题。对比debugable为false的包还是有些卡顿。我也发现了bootImage中的方法已经走到nterp上了,但是apk中的大部分代码还是走到了switch解释执行上,于是我改变思路。我在调用UpdateEntrypointsForDebuggable前先把RuntimeDebugState设置成非debugable,调用之后再把RuntimeDebugState设置会debugable不就行了吗。最后的代码如下,hook框架使用了https://github.com/bytedance/android-inline-hook。Aup28资讯网——每日最新资讯28at.com

Java_test_ArtMethodTrace_bootImageNterp(JNIEnv *env,                                                      jclass clazz) {    void *handler = shadowhook_dlopen("libart.so");    instance_ = static_cast<void **>(shadowhook_dlsym(handler, "_ZN3art7Runtime9instance_E"));    jobject    (*getSystemThreadGroup)(void *runtime) =(jobject (*)(void *runtime)) shadowhook_dlsym(handler,                                                                                          "_ZNK3art7Runtime20GetSystemThreadGroupEv");    void    (*UpdateEntrypointsForDebuggable)(void *instrumentation) = (void (*)(void *i)) shadowhook_dlsym(            handler,            "_ZN3art15instrumentation15Instrumentation30UpdateEntrypointsForDebuggableEv");    if (getSystemThreadGroup == nullptr || UpdateEntrypointsForDebuggable == nullptr) {        LOGE("getSystemThreadGroup  failed ");        shadowhook_dlclose(handler);        return;    }    jobject thread_group = getSystemThreadGroup(*instance_);    int vm_offset = findOffset(*instance_, 0, 4000, thread_group);    if (vm_offset < 0) {        LOGE("vm_offset not found ");        shadowhook_dlclose(handler);        return;    }    void (*setRuntimeDebugState)(void *instance_, int r) =(void (*)(void *runtime,                                                                    int r)) shadowhook_dlsym(            handler, "_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE");    if (setRuntimeDebugState != nullptr) {        setRuntimeDebugState(*instance_, 0);    }    void *instrumentation = reinterpret_cast<void *>(reinterpret_cast<char *>(*instance_) +                                                     vm_offset - 368 );    UpdateEntrypointsForDebuggable(instrumentation);    setRuntimeDebugState(*instance_, 2);    shadowhook_dlclose(handler);    LOGE("bootImageNterp success");}

Aup28资讯网——每日最新资讯28at.com

四、最后

最近在社区上也看到了高通工程师的一篇文章,他在我定位到的问题的基础上做了更详细的分析,确认了Google会在Android 15上修复这个问题,如果是海外版本的Android 14设备,Google计划通过com.android.artapex模块的更新来修复这个问题。但是国内由于网络的问题,Google的推送无法工作,因此需要各个手机厂家来主动合入这两笔改动。[1]Aup28资讯网——每日最新资讯28at.com

如果大家需要临时解决debugable包的卡顿的问题也可以通过上述方式解决。 Aup28资讯网——每日最新资讯28at.com

Aup28资讯网——每日最新资讯28at.com

参考文章:Aup28资讯网——每日最新资讯28at.com

[1] https://juejin.cn/post/7353106089296789556Aup28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-83784-0.html你的debug包在Android 14变卡了吗

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 详解@Stomp/Stompjs在Vue3中的应用与实践

下一篇: 通过JS获取你当前的网络状况?建议大家学一学~

标签:
  • 热门焦点
  • 一加Ace2 Pro真机揭晓 钛空灰配色质感拉满

    一加Ace2 Pro真机揭晓 钛空灰配色质感拉满

    终于,在经过了几波预热之后,一加Ace2 Pro的外观真机图在网上出现了。还是博主数码闲聊站曝光的,这次的外观设计还是延续了一加11的方案,只是细节上有了调整,例如新加入了钛空灰
  • 太卷!Redmi MAX 100英寸电视便宜了:12999元买Redmi史上最大屏

    太卷!Redmi MAX 100英寸电视便宜了:12999元买Redmi史上最大屏

    8月5日消息,从小米商城了解到,Redmi MAX 100英寸巨屏电视日前迎来官方优惠,到手价12999元,比发布价便宜了7000元,在大屏电视市场开卷。据了解,Redmi MAX 100
  • 轿车从天而降电动车主被撞身亡 超速抢道所致:现场视频让网友吵翻

    轿车从天而降电动车主被撞身亡 超速抢道所致:现场视频让网友吵翻

    近日,上海青浦区法院判决轿车从天而降电动车主被撞身亡案,轿车车主被判有期徒刑一年。案件显示当时男子驾驶轿车在上海某路段行驶,前车忽然转弯提速超车,
  • 让我们一起聊聊文件的操作

    让我们一起聊聊文件的操作

    文件【1】文件是什么?文件是保存数据的地方,是数据源的一种,比如大家经常使用的word文档、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存数据,它既可以保
  • 如何通过Python线程池实现异步编程?

    如何通过Python线程池实现异步编程?

    线程池的概念和基本原理线程池是一种并发处理机制,它可以在程序启动时创建一组线程,并将它们置于等待任务的状态。当任务到达时,线程池中的某个线程会被唤醒并执行任务,执行完任
  • 得物宠物生意「狂飙」,发力“它经济”

    得物宠物生意「狂飙」,发力“它经济”

    作者|花花小萌主近日,得物宣布正式上线宠物鉴别,通过得物App内的&ldquo;在线鉴别&rdquo;,可找到鉴别宠物的选项。通过上传自家宠物的部位细节,就能收获拥有专业资质认证的得物鉴
  • ESG的面子与里子

    ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • 华为发布HarmonyOS 4:更好玩、更流畅、更安全

    华为发布HarmonyOS 4:更好玩、更流畅、更安全

    在8月4日的华为开发者大会2023(HDC.Together)大会上,HarmonyOS 4正式发布。自2019年发布以来,HarmonyOS一直以用户为中心,经历四年多的发展HarmonyOS已
  • 北京:科技教育体验基地开始登记

    北京:科技教育体验基地开始登记

      北京“科技馆之城”科技教育体验基地登记和认证工作日前启动。首批北京科技教育体验基地拟于2023年全国科普日期间挂牌,后续还将开展常态化登记。  北京科技教育体验基
Top