浅谈安卓离线内存分析项目

前言

上一篇文章《Usap64 进程莫名死锁问题分析》中使用到分析技术 art-parser 工具是我未开源项目中的一个最重要组成部分。可能因为文章的选题有趣,也因此我的掘金账号粉丝数突涨,让我意想不到的是登上了周热榜一。

其中也有注意到这项工具,并对此感兴趣。这里也浅谈下关于 art-parser 这个工具的由来背景,以及它未来会不会开源等。

前世今生

毕业于 2016 年,当时最新的 AOSP 版号为 7.0,直接跳过了 Dalvik 虚拟机时代,诸如预编译 OAT 文件等也都在这个时期发生,正是如此第一次接触到 Android 应用程序发生 Native Crash 问题,堆栈显示最后 #0 帧处为 boot.oat 文件上,面对这类问题,当时简直是灾难一般。

早期的NativeCrash 复制代码
#00 pc 00561a48 /data/dalvik-cache/arm/system@[email protected]
#01 pc 71cc8f87 /data/dalvik-cache/arm/system@[email protected] (offset 0x1fb5000)

查阅 AOSP 源代码了解到它是一个 ELF 格式的文件,要解决这样的问题,需要了解许多其它相关知识,如 Java Code 与 ART 预编译生成 Machine Code 的关系,ART Java 对象模型,Tombstone 机制以及 Coredump 文件等。随着版本更新,Tombstone 显示的堆栈也更加的详细以及通过 GDB 来解析 Coredump 文件也能够显示更多信息。

现在的OAT文件 复制代码
(gdb) bt
#0  __epoll_pwait ()
#1  0x0000007df00f6a50 in android::Looper::pollInner (this=<optimized out>, timeoutMillis=3671)
#2  0x0000007df00f6930 in android::Looper::pollOnce (this=0xb400007d67f387c0, timeoutMillis=3671, outFd=0x0, outEvents=0x0, outData=0x0)
#3  0x0000007de88beec0 in android::Looper::pollOnce (this=0xfffffffffffffffc, timeoutMillis=<optimized out>)
#4  android::NativeMessageQueue::pollOnce (env=0xb400007d67e8ce00, pollObj=<optimized out>, timeoutMillis=<optimized out>, this=<optimized out>)
#5  android::android_os_MessageQueue_nativePollOnce (env=0xb400007d67e8ce00, obj=<optimized out>, ptr=-5476376608838249344, timeoutMillis=-305220912)
#6  0x00000000716e7098 in android::os::MessageQueue::nativePollOnce (this=...)
#7  0x0000000071af21dc in android::os::MessageQueue::next (this=...)
#8  0x0000000071aef934 in android::os::Looper::loopOnce (me=..., ident=<optimized out>, thresholdOverride=<optimized out>)
#9  0x0000000071aef840 in android::os::Looper::loop ()

(gdb) frame 7
#7  0x0000000071af21dc in android::os::MessageQueue::next (this=...)

(gdb) p this
$1 = (android::os::MessageQueue &) @0x13a80000: <incomplete type>

尽管现在有更多信息可以输出,但也无法直接展开 0x13a80000 这个对象的数据结构,在过去,我都是基于 GDB 的脚本功能来编写相关的解析功能,方便是挺方便的,但是这样的脚本无法满足我的需求,更多是局限于分析 Native Crash 问题上。

ini 复制代码
art-parser> p 0x13a80000
Size: 0x28
Padding: 0x1
Object Name: android.os.MessageQueue
  iFields of android.os.MessageQueue
    [0x24] boolean mBlocked = 0x1
    [0x8] android.util.SparseArray mFileDescriptorRecords = 0x0
    [0xc] java.util.ArrayList mIdleHandlers = 0x171e5cd0
    [0x10] android.os.Message mMessages = 0x1301f848
    [0x20] int mNextBarrierToken = 0x0
    [0x14] android.os.MessageQueue$IdleHandler[] mPendingIdleHandlers = 0x171e5d30
    [0x18] long mPtr = 0xb400007d45f01480
    [0x25] boolean mQuitAllowed = 0x0
    [0x26] boolean mQuitting = 0x0
  iFields of java.lang.Object
    [0x0] java.lang.Class shadow$_klass_ = 0x70098050
    [0x4] int shadow$_monitor_ = 0x40001130

而在安卓上发生纯粹的 Native Crash 时,分析 Coredump 转储文件,现有的工具有 GDB、LLDB 等,基本能够满足绝大部分开发者。但是将更多的问题转变为分析 Ramdump、Coredump 时需面临的难点,但优点在于它如同现场一般,我们几乎能够查询到所有在内存上的数据。

原问题类型 核心数据 离线分析难点
Java OOM 需要对应的程序的 Java Hprof 文件 从 Coredump 转储文件中统计各个 Java 对象的数量占用内存大小。
Java 常见异常 AndroidRuntime 的 Fatal 日志 从 Coredump 转储文件中定位 Java 的调用堆栈,检索具体 Java 对象数据,如某对象成员空指针等信息。
ANR 、Watchdog anr_trace.txt 文件,如耗时问题需记录到具体调用栈
冻屏 范围广,表现多,如问题表现在用户态时,大部分需要 Dumpsys 数据 从 Ramdump 取得 system_server 的 Coredump 转储,从 Coredump 中得到具体的 Dumpsys 数据,计算量大,人工手动计算基本不可能。
...

在互联网技术中,也有类似的如 jmap + gcore 来完成现场保存以及离线提取 hprof 文件。这么棒的解析技术,为什么 Android 的技术栈上会没有呢?于是我最初的设想解决转储问题、提取 hprof 文件以及展开 Java 对象数据结构。

项目结构

随着项目的进行,发现最初的设想无法满足我自身需求,如 Java 堆栈解析,ART 虚拟机寄存器解析,检索对象,统计对象数量等。然而功能实现上虽然并不难,但是 Core 文件的内容也并不是每一个都那么的全,要考虑到许多缺页的情况。比如该 Core 采用的是原生默认参数抓取,那么会缺失的文件页表。部分抓 Core 的机制会缺失重要的 bss 段内存,如 MTK 的项目常常会发生。从 Ramdump 文件中转储 Coredump 文件,可以使用 crash-utility 项目的 gcore 插件来完成,早一些的项目这个过程是没有任何问题的,只是现在的手机项目 zram 的大小占用越来越大,以致于现今要从 Ramdump 中解析用户态的数据较为依赖 zram 的解析。然而 crash-utility 项目中 zram 长久以来都无人维护,也因此 gcore 的转储方案不适用于我们现在的手机项目。

由于这一套解决方案中采用了 crash-utility 来补充内核态解析,彻底打通这个桥梁于是我也给该项目维护更新适配最新的内核。

标题
Fix "rd" command to display data on zram on Linux 5.17 and later github.com/crash-utili...
arm64: Fix "vtop" command to display swap information on Linux 5.19 and later github.com/crash-utili...

Core 从何而来

既然是离线内存分析,那么首当其冲的能力是解决 Core 从何而来的问题。

名称 用途
ram2core 应用于 crash-tools 环境下,从 ramdump 中提取目标进程的 coredump。 配合 zram、shmem 来解析特殊内存。
proc2core 应用于真机 Root 环境下,抓取目标进程 coredump。
OpenCoreSDK 应用于安卓应用程序非 Root 环境下,需开发者集成的开源方案 介绍:《Android 应用程序如何抓取 Coredump》 应用:《结合 OpenCore 分析安卓应用程序 Crash 问题
其它方案 如配置原生参数《如何理解Native Crash问题》 该文在我还没创建掘金前在鸿洋的公众号上投稿过。

Core 文件页修复

从 Ramdump 中转储得到某个进程的 Coredump,往往文件页相关的内存都存在大量的丢失,如果是纯粹的 Native 进程,一般也只有 .so 等动态库文件的需求,而 .so 可在符号表文件里找到,因此都可在 LLDB、GDB 上通过外部映射。

但是非 Native 进程,并且需要解析 Java 数据,那么需求就不一样了,它需要依赖 DexCache 内存,也就是对应的原始 .jar,.apk,.dex 等文件内容,此时要拿到这些文件,要么在真机里刷入对应版本的 ROM,或者是通过解压刷机包的 super.img 获得。

完整的修复过程 复制代码
# crash SYS_COREDUMP/SYS_COREDUMP vmlinux

找到 systemui 进程
crash> ps | grep "systemui"
     2506     827   0  ffffff81615ddc80  IN   0.3 11381528    42780  ndroid.systemui

添加 zram.ko 符号表     
crash> mod -s zram zram.ko
     MODULE       NAME                                  BASE            SIZE  OBJECT FILE
ffffffc0033f2ac0  zram                            ffffffc0033e9000     73728  zram.ko 

添加 zsmalloc.ko 符号表
crash> mod -s zsmalloc zsmalloc.ko
     MODULE       NAME                                  BASE            SIZE  OBJECT FILE
ffffffc0033e12c0  zsmalloc                        ffffffc0033da000     57344  zsmalloc.ko

添加 zram 插件
 crash> extend zram.so
./zram.so: shared object loaded

添加 shmem 插件
 crash> extend shmem.so
./shmem.so: shared object loaded

添加 ram2core 插件
crash> extend ram2core.so
./ram2core.so: shared object loaded

开始对 2506 进程进行转储
crash> ram2core -p 2506 -s zram -m shmem
Write ELF Header
Write Program Headers
Write Segments
>>>> 10% <<<<
>>>> 20% <<<<
>>>> 30% <<<<
>>>> 40% <<<<
>>>> 50% <<<<
>>>> 60% <<<<
>>>> 70% <<<<
>>>> 80% <<<<
>>>> 90% <<<<
Done
Saved [core.2506].
ini 复制代码
art-parser --core-file=core.2506

此时我们拿到的 Core.2506 文件是不完整的,许多内容都无法展示
art-parser> map
Warn: Not found exec dynamic.
Warn: Not found r_debug, You can try command linker.

art-parser> p 0x12c00000
Size: 0x10
Object Name: 
  iFields of 
  iFields of 
    [0xc] byte  = 0x23
    [0x8] byte  = 0xffffff90
  iFields of 
    [0x0] byte  = 0x70
    [0x4] byte  = 0x0
例如前面的两个命令,一个是缺少 app_process64 的文件页、另一是缺少 core-oj.jar 导致无法展示。

此时我们需要填补缺少的文件页,类似的像 GDB 、LLDB 那样通过原始文件映射到运行环境下。

ini 复制代码
将 app_process64 映射到 art-parser 环境下
art-parser> execfn out/app_process64
Load [0] segment [0x5b1754f000, 0x5b17551000) out/app_process64 [0x0]
Load [1] segment [0x5b17551000, 0x5b17552000) out/app_process64 [0x2000]

此时的 link map 才能正常输出
art-parser> map
0x74ef15f0e0 [0x5b1754f000, 0x5b17551000) [out/app_process64] [*] 被映射后
0x74f0403200 [0x74f02d2000, 0x74f0309000) [/system/bin/linker64] [*]
0x74ef15f338 [0x74f02d1000, 0x74f02d2000) [[vdso]] [*]
0x74ef15f590 [0x74e8969000, 0x74e8a28000) [/system/lib64/libandroid_runtime.so] [*]
0x74ef15f7e8 [0x74d6244000, 0x74d6294000) [/system/lib64/libbinder.so] [*]
0x74ef15fa40 [0x74d63d5000, 0x74d63de000) [/system/lib64/libcutils.so] [*]

将其它 .so 映射到 art-parser 环境下
art-parser> sysroot out
Load [0] segment [0x74e8969000, 0x74e8a28000) out/root/system/lib64/libandroid_runtime.so [0x0]
Load [1] segment [0x74e8a28000, 0x74e8b7e000) out/root/system/lib64/libandroid_runtime.so [0xbf000]
Load [0] segment [0x74d6244000, 0x74d6294000) out/root/system/lib64/libbinder.so [0x0]
Load [1] segment [0x74d6294000, 0x74d62f1000) out/root/system/lib64/libbinder.so [0x50000]

将 .dex, .jar, .apk 等依赖的 DexCache 文件映射到 art-parser 环境下
art-parser> dex out
Load [classes.dex] segment [0x74cac0c000, 0x74cac1b000) out/root/system/framework/Booster.jar [0x0]
Load [classes3.dex] segment [0x7426a5c000, 0x7426a8b000) out/root/system_ext/priv-app/SystemUI/SystemUI.apk [0x12b6000]
Load [classes2.dex] segment [0x738f4e6000, 0x738f7c1000) out/root/product/app/SystemUIPlugin/SystemUIPlugin.apk [0x540000]
Load [classes.dex] segment [0x74d9e00000, 0x74d9e04000) out/root/system_ext/framework/framework-pointer-pad.jar [0x0]
Load [classes2.dex] segment [0x73abbb0000, 0x73ac560000) out/root/system_ext/priv-app/SystemUI/SystemUI.apk [0x907000]

此时的 print 输出也算是正常了
art-parser> p 0x12c00000 -b
Size: 0x10
Object Name: java.lang.StringBuilder
  iFields of java.lang.StringBuilder
  iFields of java.lang.AbstractStringBuilder
    [0xc] int count = 0x23
    [0x8] char[] value = 0x12c00090
  iFields of java.lang.Object
    [0x0] java.lang.Class shadow$_klass_ = 0x70615c70
    [0x4] int shadow$_monitor_ = 0x0
Binary:
0x12c00000: 0x70615c70  0x00000000  0x12c00090  0x00000023

修复 Core 文件
art-parser> restore /tmp/restore.2506.core
Saved [/tmp/restore.2506.core].

例如该问题是一个内存泄露导致系统死机问题,我们可以在 Ramdump 中解析出该 App 的所有对象,以及统计出占用的内存大小,以及提取成 hprof 文件在 AS 上分析。

ini 复制代码
art-parser --core-file=/tmp/restore.2506.core --art-path=libart.so

依据Native Size大小进行排序,可看到 HardwareBuffer 占了近 9G 内存大小。
art-parser> top 20 -d -n
Address       Allocations       ShallowSize       NativeSize     ClassName
0x70e8d830           1013            24312        9656024064     android.hardware.HardwareBuffer
0x70d36350           4278           196788         395797260     android.graphics.Bitmap
0x70d3c110           1289            21913           1289000     android.os.BinderProxy
0x70ec5a98            565            20340            282500     android.app.LoadedApk$ServiceDispatcher$InnerConnection
0x70ec58f0            174             6960             87000     android.app.LoadedApk$ReceiverDispatcher$InnerReceiver
0x70ecad68            153             5508             76500     android.database.ContentObserver$Transport
0x70d8bca8            139             4448             71168     android.view.SurfaceControl$Transaction
0x70e0b3c0             80             2560             40000     android.os.Binder
0x72c2b7f8             11              396              5500     android.bluetooth.BluetoothProfileConnector$1
0x71007e78             10              400              5000     android.view.ViewRootImpl$W
0x159aa1a8              9              756              4500     com.android.systemui.qs.external.TileServices
0x710913d8              9              360              4500     android.window.WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper
0x70ecaf20              8              416              4000     android.database.CursorToBulkCursorAdaptor
0x70f474e8              8              288              4000     android.media.AudioManager$2
0x70f47698              8              288              4000     android.media.AudioManager$3
0x70f47840              8              288              4000     android.media.AudioManager$4
0x70f479e8              8              288              4000     android.media.AudioManager$5
0x70ec8e68              7              364              3500     android.content.ContentProvider$Transport
0x70f4aee8              5              180              2500     android.os.Handler$MessengerImpl
0x70f49988              3              108              1500     android.media.session.MediaSessionManager$SessionsChangedWrapper$1

检索 android.hardware.HardwareBuffer 对象
art-parser> search android.hardware.HardwareBuffer
[0] 0x12cc3290 android.hardware.HardwareBuffer
[1] 0x12d80dc8 android.hardware.HardwareBuffer
[2] 0x12e408d8 android.hardware.HardwareBuffer
[3] 0x12e50348 android.hardware.HardwareBuffer
[4] 0x12eac320 android.hardware.HardwareBuffer
[5] 0x12eb8728 android.hardware.HardwareBuffer
[6] 0x12f44bb8 android.hardware.HardwareBuffer
[7] 0x12f62190 android.hardware.HardwareBuffer
[8] 0x12f6d6a8 android.hardware.HardwareBuffer
[9] 0x12fa69e0 android.hardware.HardwareBuffer
[10] 0x12fa6d08 android.hardware.HardwareBuffer
[11] 0x12fcc318 android.hardware.HardwareBuffer
[12] 0x12ff6328 android.hardware.HardwareBuffer
[13] 0x130363f0 android.hardware.HardwareBuffer
[14] 0x130994b8 android.hardware.HardwareBuffer
[15] 0x131405d0 android.hardware.HardwareBuffer
...

输出对象引用关系
art-parser> p 0x12d80dc8 -r
Size: 0x18
Object Name: android.hardware.HardwareBuffer
  iFields of android.hardware.HardwareBuffer
    [0x8] java.lang.Runnable mCleaner = 0x0
    [0xc] dalvik.system.CloseGuard mCloseGuard = 0x12d80e08
    [0x10] long mNativeObject = 0x0
  iFields of java.lang.Object
    [0x0] java.lang.Class shadow$_klass_ = 0x70e8d830
    [0x4] int shadow$_monitor_ = 0x0
References:
    >> 0x12d80cf8 android.window.TaskSnapshot
    >> 0x12d80de0 java.lang.ref.FinalizerReference
    >> 0x12d80e50 sun.misc.Cleaner

进一步转储 hprof 文件,该过程会非常耗时。
art-parser> hprof 6.14.ramdump.systemui.full.hprof

技术体系

该项目属于一项技术兜底,使用有一定门槛,对大部分开发者而言难以理解,也很难会想到可以运用上,包括我在内部也不容易推广。因为遇上需要用到这项技术来分析的问题,那么想必是穷途末路的时候,但是一旦能用上,或者把问题化简为繁时会有奇效。

开源问题

我从设计初就考虑将 art-parser 开放给应用开发者使用的,因此从设计上考虑到开发者没有符号表文件,所以并不依赖符号表文件。在今年的 3 月份时联系过内部提报开源项目,只能说至今未果,因此我也不知道能不能将该项目进行开源。

我还是比较热衷于将此项目开源,类似下边的王者荣耀发生 Java 空指针问题,但却没有报 JE 空指针,而是报 NE 问题,不过使用 art-parser 可以轻松秒杀,想必能帮助到应用开发者解决一些头疼的 NE 问题,尤其是与 Java 相关的。

ini 复制代码
art-parser> bt 31033 
"UnityMain" prio=5 tid=49 Unknown
  | group="main" sCount=0 ucsCount=0 flags=0 obj=0x13401ce8 self=0xa7000056eaefa000
  | sysTid=31033 nice=<unknown> cgrp=<unknown> sched=<unknown> handle=0x6a370bacb0
  | stack=0x6a36fb3000-0x6a36fb7000 stackSize=0x107cb0
  | held mutexes= "mutator lock"
  x0  0x000000000000000b  x1  0xd80000749a199410  x2  0xb9000058eb66ee20  x3  0x0000000000000074  
  x4  0x00000000ffffffff  x5  0x0000000000000001  x6  0x0000000000000000  x7  0x0000000012f2e090  
  x8  0x0000000000000008  x9  0x0b9000058eb66ed0  x10 0x0b9000058eb66ed2  x11 0x0b9000058eb66ed1  
  x12 0x0040000d6001b2a6  x13 0x0000006a370bb000  x14 0xb9000058eb66ed20  x15 0x0040000d6001b222  
  x16 0x0000000000000000  x17 0x0000000000000000  x18 0x0000000000000001  x19 0xa7000056eaefa000  
  x20 0x0200006c00000000  x21 0x0000000000000000  x22 0x0040000d6001b2c6  x23 0x000000000000106e  
  x24 0x0040000d6001b226  x25 0x000000000000000b  x26 0x0000006a370b81b4  x27 0x0000006a370b8188  
  x28 0x0000006a370b81e0  x29 0xb9000058eb66ed40  
  lr  0x005e7474bedd684c  sp  0xb9000058eb66ebe0  pc  0x000000749a1911dc  pst 0x0000000060001000  
  FP[0x58eb66ed40] PC[0x749a1911dc] native: #00 (art::SignalChain::Handler(int, siginfo*, void*)+0x9c) /apex/com.android.art/lib64/libsigchain.so
  FP[0x58eb66ed40] PC[0x74bedd684c] native: #01 () [vdso]
  FP[0x58eb66fff0] PC[0x74bedd684c] native: #02 () [vdso]
  <<maybe handle signal>>
  x0  0x000000000000002d  x1  0x0000000000000000  x2  0x000000000000002d  x3  0x0000000000000074  
  x4  0x00000000ffffffff  x5  0x0000000000000001  x6  0x0000000000000000  x7  0x0000000012f2e090  
  x8  0x3d068dc5bdcab74a  x9  0x3d068dc5bdcab74a  x10 0x2500004feae84b00  x11 0x0000000000000010  
  x12 0x0200006b000d9128  x13 0x0200006bffffffff  x14 0x0000006a370b7a70  x15 0x0040000d6001b222  
  x16 0x0000000000000000  x17 0x0000000000000000  x18 0x0000000000000001  x19 0xa7000056eaefa000  
  x20 0x0000000000000000  x21 0x0000000000000000  x22 0x0000006a73c4bd42  x23 0x000000000000106e  
  x24 0x0000007478c00880  x25 0x0000006a370b8188  x26 0x0000006a370b81b4  x27 0x0000006a370b8188  
  x28 0x0000006a370b81e0  x29 0x0000006a370b81b4  
  lr  0x0000007478c03fc8  sp  0x0000006a370b8160  pc  0x0000007478c03fa8  pst 0x0000000060001000  
  FP[0x6a370b81b4] PC[0x7478c03fa8] native: #00 (17ContentionLogData13AddToWaitTimeEm+0x3f68) /apex/com.android.art/lib64/libart.so
  FP[0x6a370b81b4] PC[0x7478c03fc8] native: #01 (17ContentionLogData13AddToWaitTimeEm+0x3f88) /apex/com.android.art/lib64/libart.so
  QF[0x6a370b8160] PC[0x7478c03fc8] at dex-pc 0x6a73c4bd42 com.tencent.smtt.utils.LogFileUtils.writeDataToStorage  //AM[0x744bdc3808]
  QF[0x6a370b8280] PC[0x7478c09338] at dex-pc 0x6a73c4cb12 com.tencent.smtt.utils.TbsLogClient.writeLogToDisk  //AM[0x744bdc26e0]
  QF[0x6a370b8380] PC[0x7478c0a258] at dex-pc 0x6a73c4ca9a com.tencent.smtt.utils.TbsLogClient.writeLog  //AM[0x744bdc26c0]
  QF[0x6a370b8470] PC[0x7478c0a258] at dex-pc 0x6a73c4c564 com.tencent.smtt.utils.TbsLog.i  //AM[0x9db10b48]
  QF[0x6a370b8570] PC[0x7478c09338] at dex-pc 0x6a73c3f200 com.tencent.smtt.sdk.o.b  //AM[0x744bdb1b18]
  QF[0x6a370b8680] PC[0x7478c0a258] at dex-pc 0x6a73c1c2c0 com.tencent.smtt.sdk.QbSdk.initX5Environment  //AM[0x9db10258]
  QF[0x6a370b8780] PC[0x7478c09338] at dex-pc 0x698d9ef27e com.tencent.up.nb.NBApplication.initTBS  //AM[0x698ddb5708]
  QF[0x6a370b8860] PC[0x7478c0a258] at dex-pc 0x698d9ef1e2 com.tencent.up.nb.NBApplication.init  //AM[0x698ddb56c8]
  QF[0x6a370b8940] PC[0x7478c0a258] at dex-pc 0x698d9fa94a com.tencent.up.nbsdk.EnterManager.initApplication  //AM[0x6a54786210]
  QF[0x6a370b8a40] PC[0x7478c09338] at dex-pc 0x698d9faaba com.tencent.up.nbsdk.EnterManager.preloadPackageByUrl  //AM[0x6a54786270]
  QF[0x6a370b8b30] PC[0x7478c09338] at dex-pc 0x698d9dff60 com.tencent.grobot.XYEnterManager.preLoad  //AM[0x6a54781768]
  QF[0x6a370b9250] PC[0x0000000000] at dex-pc 0x0000000000 java.lang.reflect.Method.invoke(Native method)  //AM[0x6fa90f18]
  QF[0x6a370b9300] PC[0x7478c0a2b4] at dex-pc 0x6a73d730be com.tencent.xplug.Reflector.callByCaller  //AM[0x6a542f91b0]
  QF[0x6a370b93f0] PC[0x7478c0a258] at dex-pc 0x6a73d7309e com.tencent.xplug.Reflector.call  //AM[0x6a542f9190]
  QF[0x6a370b94d0] PC[0x7478c0a258] at dex-pc 0x6a7368cf5e com.tencent.grobot.GRobotDelegateManager.preLoad  //AM[0x6a542f87f8]
  QF[0x6a370b95e0] PC[0x7478c0a258] at dex-pc 0x6a7368c3a6 com.tencent.grobot.GRobot.init  //AM[0x6a542f8490]
  QF[0x6a370ba430] PC[0x0000000000] at dex-pc 0x0000000000 com.unity3d.player.UnityPlayer.nativeRender(Native method)  //AM[0x9db12df8]
  QF[0x6a370ba4e0] PC[0x7478c0a2b4] at dex-pc 0x6a73d7c57a com.unity3d.player.UnityPlayer.c  //AM[0x9db12978]
  QF[0x6a370ba5b0] PC[0x009baf50c8] at dex-pc 0x6a73d7bbf2 com.unity3d.player.UnityPlayer$d$1.handleMessage  //AM[0x744bdb8f28]
  QF[0x6a370ba600] PC[0x00715806b8] at dex-pc 0x747672c4fc android.os.Handler.dispatchMessage  //AM[0x70818a18]
  QF[0x6a370ba630] PC[0x0071583adc] at dex-pc 0x747674fa6c android.os.Looper.loopOnce  //AM[0x70819af8]
  QF[0x6a370ba700] PC[0x0071583604] at dex-pc 0x7476750194 android.os.Looper.loop  //AM[0x70819ad8]
  QF[0x6a370ba750] PC[0x7478c0939c] at dex-pc 0x6a73d7bd2a com.unity3d.player.UnityPlayer$d.run  //AM[0x744bdb4588]
ini 复制代码
  QF[0x6a370b8160] PC[0x7478c03fc8] at dex-pc 0x6a73c4bd42 com.tencent.smtt.utils.LogFileUtils.writeDataToStorage  //AM[0x744bdc3808]
  {
      Virtual registers
      {
        v0 = 0x134c7110    v1 = 0x00000000    v2 = 0x00000000    v3 = 0x370b82c8
        v4 = 0x0000006a    v5 = 0x78c0f544    v6 = 0x136b2340    v7 = 0x12f2e5d0
        v8 = 0x136b2378    v9 = 0x00000000    v10 = 0x00000001
      }
      Physical registers
      {
        x20 = 0x0    x21 = 0x0    x22 = 0x6a73c4bd42    x23 = 0x106e    
        x24 = 0x7478c00880    x25 = 0x6a370b8188    x26 = 0x6a370b81b4    x27 = 0x6a370b8188    
        x28 = 0x6a370b81e0    x29 = 0x6a370b81b4    x30 = 0x7478c03fc8
      }
  }
arduino 复制代码
art-parser> disassemble 0x744bdc3808 -i 0x6a73c4bd42
void com.tencent.smtt.utils.LogFileUtils.writeDataToStorage(java.io.File, java.lang.String, byte[], java.lang.String, boolean) [dex_method_idx=26051]
DEX CODE:
  0x6a73c4bd42: 106e efce 0001           | invoke-virtual {v1}, boolean java.io.File.mkdirs() // method@61390
arduino 复制代码
从字节码可得知调用 v1.mkdirs(),而当前 v1 = 0x0,因此空指针异常。

分析其空指针原因,输出 12 条字节码信息:
art-parser> disassemble 0x744bdc3808 -d 12
void com.tencent.smtt.utils.LogFileUtils.writeDataToStorage(java.io.File, java.lang.String, byte[], java.lang.String, boolean) [dex_method_idx=26051]
DEX CODE:
  0x6a73c4bd20: 001c 0f4e                | const-class v0, com.tencent.smtt.utils.LogFileUtils // type@TypeIndex[3918]
  0x6a73c4bd24: 001d                     | monitor-enter v0
  0x6a73c4bd26: 2071 65c1 0097           | invoke-static {v7, v9}, byte[] com.tencent.smtt.utils.LogFileUtils.encrypt(java.lang.String, java.lang.String) // method@26049
  0x6a73c4bd2c: 070c                     | move-result-object v7
  0x6a73c4bd2e: 0112                     | const/4 v1, #+0
  0x6a73c4bd30: 0738 0004                | if-eqz v7, 0x6a73c4bd38 //+4
  0x6a73c4bd34: 1907                     | move-object v9, v1
  0x6a73c4bd36: 0228                     | goto 0x6a73c4bd3a //+2
  0x6a73c4bd38: 1707                     | move-object v7, v1
  0x6a73c4bd3a: 106e efc3 0006           | invoke-virtual {v6}, java.io.File java.io.File.getParentFile() // method@61379
  0x6a73c4bd40: 010c                     | move-result-object v1
  0x6a73c4bd42: 106e efce 0001           | invoke-virtual {v1}, boolean java.io.File.mkdirs() // method@61390

从字节码上看得知: v1 = v6.getParentFile();
ini 复制代码
art-parser> p 0x136b2340 -b
Size: 0x18
Object Name: java.io.File
  iFields of java.io.File
    [0x8] java.nio.file.Path filePath = 0x0
    [0xc] java.lang.String path = tbslog.txt
    [0x14] int prefixLength = 0x0
    [0x10] java.io.File$PathStatus status = 0x0
  iFields of java.lang.Object
    [0x0] java.lang.Class shadow$_klass_ = 0x6f889e78
    [0x4] int shadow$_monitor_ = 0x0
Binary:
0x136b2340: 0x6f889e78  0x00000000  0x00000000  0x13503e18  
0x136b2350: 0x00000000  0x00000000

由于 path 为 tbslog.txt 没有根目录路径,于是:
File parent = file.getParentFile(); 得到的 parent 对象为 null。
parent.mkdirs(); // 事实上是发生了 Java NullPointerException。

最后能不能开源,我也不知道,只能说尽量争取,毕竟现今在公司里会使用该项目解决一些问题,也仅有几位同事,在这里也只是简单的介绍下,只言片语不易传达,没有真正的使用体会,不知大家对该项目是否感兴趣。

相关推荐
Arrtoria2 分钟前
Kernel2.X的内存世界
linux
程序员岳焱3 分钟前
Java 与 MySQL 性能优化:Linux服务器上MySQL性能指标解读与监控方法
linux·后端·mysql
Jooolin3 分钟前
【编程史】Ubuntu到底是啥?它和Linux又是什么关系?
linux·ubuntu·操作系统
androidwork2 小时前
嵌套滚动交互处理总结
android·java·kotlin
苏州向日葵2 小时前
virtualBox安装ubuntu,常用知识点
linux·运维·ubuntu
阿福不是狗2 小时前
Python使用总结之Linux部署python3环境
linux·开发语言·python
张海森-1688203 小时前
基于sample_aiisp例子,创建3路编码流,记录
linux
飞凌嵌入式3 小时前
基于RK3588,飞凌教育品牌推出嵌入式人工智能实验箱EDU-AIoT ELF 2
linux·人工智能·嵌入式硬件·arm·nxp
fatiaozhang95273 小时前
中兴B860AV1.1强力降级固件包
android·adb·电视盒子·av1·机顶盒rom·魔百盒刷机
橙子199110164 小时前
Kotlin 中的 Object
android·开发语言·kotlin