浅谈安卓离线内存分析项目 前言 前世今生 项目结构 Core

前言

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

作者榜.jpg 文章热榜.jpg

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

前世今生

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

1
2
早期的NativeCrash复制代码#00 pc 00561a48 /data/dalvik-cache/arm/system@framework@boot.art
#01 pc 71cc8f87 /data/dalvik-cache/arm/system@framework@boot.oat (offset 0x1fb5000)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
现在的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 问题上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 的转储方案不适用于我们现在的手机项目。

UML 图 (9).jpg
由于这一套解决方案中采用了 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 获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
完整的修复过程复制代码# 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].
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 那样通过原始文件映射到运行环境下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 上分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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

技术体系

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

开源问题

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}
}
1
2
3
4
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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。

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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%