android 如何分析应用的内存(二)
前面对android应用的内存做了一个总体性的概括,那如何查看内存里面的细节呢?
本篇笔记较长,总体内容包括如下两部分:
- native部分
- 寄存器内容是什么。如pc指向何处,sp指向何处
- 指定地址内容是什么。如变量a对应的内容
- 线程堆栈内容是什么。如主线程的堆栈,UI线程的堆栈
- 堆区的对象有哪些。
- java部分
- 线程堆栈有什么内容。
- 堆中对象分配情况
所有这些,需要使用Debug工具才能查看。部分内容,还需要编程查看。
先来看工具如何使用
工具篇xdd
- 如何查看任意内存地址的内容
使用如下命令
adb shell xxd -s 0x7fe1128000 -l 4 /proc/2696/mem
## 1. xxd是android提供的一个十六进制查看工具
## 2. -s 0x7fe1128000 表示从0x7fe1128000处偏移开始查看
## 3. -l 4 表示,读取多长.单位字节
## 4. /proc/2696/mem 则为pid为2696进程对应的内存
## 至于读出来的4个字节的内容,应该怎么解释,是大端存储,还是小端存储,亦或是
## 表示一个int,还是表示4个char。则需要对这个程序有一定的了解。
- 还可以查看整个映射区域里面的内容
如前一小节所述,通过/proc/pid/maps找到指定的区域。然后使用如下的命令,将整个区域dump出来
adb shell xxd -s 0x7fe1128000 -l $((0x7fe1927000 - 0x7fe1128000)) /proc/2696/mem > 2696-stack.log
## 1. xxd是android提供的一个十六进制查看工具
## 2. -s 0x7fe1128000 表示从0x7fe1128000处偏移开始查看
## 3. -l $((0x7fe1927000 - 0x7fe1128000)) 先计算0x7fe1927000
## 与0x7fe1128000之间的差值。0x7fe1927000和0x7fe1128000是
## 在/proc/pid/maps文件中读取的堆栈内存区域的起始地址和结束地址
## 然后用-l选项标记。此处表示,读取多长.
## 4. /proc/2696/mem 则为pid为2696进程对应的内存
使用vscode打开2696-stack.log。可以看到如下信息
// 省略一部分
7fe1926b00: 0000 0000 0000 0050 4154 483d 2f73 6269 .......PATH=/sbi
7fe1926b10: 6e3a 2f73 7973 7465 6d2f 7362 696e 3a2f n:/system/sbin:/
7fe1926b20: 7379 7374 656d 2f62 696e 3a2f 7379 7374 system/bin:/syst
7fe1926b30: 656d 2f78 6269 6e3a 2f76 656e 646f 722f em/xbin:/vendor/
7fe1926b40: 6269 6e3a 2f76 656e 646f 722f 7862 696e bin:/vendor/xbin
7fe1926b50: 0044 4f57 4e4c 4f41 445f 4341 4348 453d .DOWNLOAD_CACHE=
7fe1926b60: 2f64 6174 612f 6361 6368 6500 414e 4452 /data/cache.ANDR
7fe1926b70: 4f49 445f 424f 4f54 4c4f 474f 3d31 0041 OID_BOOTLOGO=1.A
7fe1926b80: 4e44 524f 4944 5f52 4f4f 543d 2f73 7973 NDROID_ROOT=/sys
7fe1926b90: 7465 6d00 414e 4452 4f49 445f 4153 5345 tem.ANDROID_ASSE
7fe1926ba0: 5453 3d2f 7379 7374 656d 2f61 7070 0041 TS=/system/app.A
7fe1926bb0: 4e44 524f 4944 5f44 4154 413d 2f64 6174 NDROID_DATA=/dat
7fe1926bc0: 6100 414e 4452 4f49 445f 5354 4f52 4147 a.ANDROID_STORAG
7fe1926bd0: 453d 2f73 746f 7261 6765 0045 5854 4552 E=/storage.EXTER
7fe1926be0: 4e41 4c5f 5354 4f52 4147 453d 2f73 6463 NAL_STORAGE=/sdc
7fe1926bf0: 6172 6400 4153 4543 5f4d 4f55 4e54 504f ard.ASEC_MOUNTPO
7fe1926c00: 494e 543d 2f6d 6e74 2f61 7365 6300 424f INT=/mnt/asec.BO
7fe1926c10: 4f54 434c 4153 5350 4154 483d 2f73 7973 OTCLASSPATH=/sys
7fe1926c20: 7465 6d2f 6672 616d 6577 6f72 6b2f 636f tem/framework/co
7fe1926c30: 6d2e 7175 616c 636f 6d6d 2e71 7469 2e63 m.qualcomm.qti.c
7fe1926c40: 616d 6572 612e 6a61 723a 2f73 7973 7465 amera.jar:/syste
7fe1926c50: 6d2f 6672 616d 6577 6f72 6b2f 5150 6572 m/framework/QPer
7fe1926c60: 666f 726d 616e 6365 2e6a 6172 3a2f 7379 formance.jar:/sy
7fe1926c70: 7374 656d 2f66 7261 6d65 776f 726b 2f63 stem/framework/c
7fe1926c80: 6f72 652d 6f6a 2e6a 6172 3a2f 7379 7374 ore-oj.jar:/syst
7fe1926c90: 656d 2f66 7261 6d65 776f 726b 2f63 6f72 em/framework/cor
7fe1926ca0: 652d 6c69 6261 7274 2e6a 6172 3a2f 7379 e-libart.jar:/sy
7fe1926cb0: 7374 656d 2f66 7261 6d65 776f 726b 2f63 stem/framework/c
7fe1926cc0: 6f6e 7363 7279 7074 2e6a 6172 3a2f 7379 onscrypt.jar:/sy
7fe1926cd0: 7374 656d 2f66 7261 6d65 776f 726b 2f6f stem/framework/o
7fe1926ce0: 6b68 7474 702e 6a61 723a 2f73 7973 7465 khttp.jar:/syste
7fe1926cf0: 6d2f 6672 616d 6577 6f72 6b2f 626f 756e m/framework/boun
7fe1926d00: 6379 6361 7374 6c65 2e6a 6172 3a2f 7379 cycastle.jar:/sy
7fe1926d10: 7374 656d 2f66 7261 6d65 776f 726b 2f61 stem/framework/a
7fe1926d20: 7061 6368 652d 786d 6c2e 6a61 723a 2f73 pache-xml.jar:/s
7fe1926d30: 7973 7465 6d2f 6672 616d 6577 6f72 6b2f ystem/framework/
7fe1926d40: 6c65 6761 6379 2d74 6573 742e 6a61 723a legacy-test.jar:
7fe1926d50: 2f73 7973 7465 6d2f 6672 616d 6577 6f72 /system/framewor
7fe1926d60: 6b2f 6578 742e 6a61 723a 2f73 7973 7465 k/ext.jar:/syste
7fe1926d70: 6d2f 6672 616d 6577 6f72 6b2f 6672 616d m/framework/fram
7fe1926d80: 6577 6f72 6b2e 6a61 723a 2f73 7973 7465 ework.jar:/syste
7fe1926d90: 6d2f 6672 616d 6577 6f72 6b2f 7465 6c65 m/framework/tele
7fe1926da0: 7068 6f6e 792d 636f 6d6d 6f6e 2e6a 6172 phony-common.jar
7fe1926db0: 3a2f 7379 7374 656d 2f66 7261 6d65 776f :/system/framewo
7fe1926dc0: 726b 2f76 6f69 702d 636f 6d6d 6f6e 2e6a rk/voip-common.j
7fe1926dd0: 6172 3a2f 7379 7374 656d 2f66 7261 6d65 ar:/system/frame
7fe1926de0: 776f 726b 2f69 6d73 2d63 6f6d 6d6f 6e2e work/ims-common.
7fe1926df0: 6a61 723a 2f73 7973 7465 6d2f 6672 616d jar:/system/fram
7fe1926e00: 6577 6f72 6b2f 6f72 672e 6170 6163 6865 ework/org.apache
7fe1926e10: 2e68 7474 702e 6c65 6761 6379 2e62 6f6f .http.legacy.boo
7fe1926e20: 742e 6a61 723a 2f73 7973 7465 6d2f 6672 t.jar:/system/fr
7fe1926e30: 616d 6577 6f72 6b2f 616e 6472 6f69 642e amework/android.
7fe1926e40: 6869 646c 2e62 6173 652d 5631 2e30 2d6a hidl.base-V1.0-j
7fe1926e50: 6176 612e 6a61 723a 2f73 7973 7465 6d2f ava.jar:/system/
7fe1926e60: 6672 616d 6577 6f72 6b2f 616e 6472 6f69 framework/androi
7fe1926e70: 642e 6869 646c 2e6d 616e 6167 6572 2d56 d.hidl.manager-V
7fe1926e80: 312e 302d 6a61 7661 2e6a 6172 3a2f 7379 1.0-java.jar:/sy
7fe1926e90: 7374 656d 2f66 7261 6d65 776f 726b 2f74 stem/framework/t
7fe1926ea0: 636d 6966 6163 652e 6a61 723a 2f73 7973 cmiface.jar:/sys
7fe1926eb0: 7465 6d2f 6672 616d 6577 6f72 6b2f 7465 tem/framework/te
7fe1926ec0: 6c65 7068 6f6e 792d 6578 742e 6a61 723a lephony-ext.jar:
7fe1926ed0: 2f73 7973 7465 6d2f 6672 616d 6577 6f72 /system/framewor
7fe1926ee0: 6b2f 5766 6443 6f6d 6d6f 6e2e 6a61 723a k/WfdCommon.jar:
7fe1926ef0: 2f73 7973 7465 6d2f 6672 616d 6577 6f72 /system/framewor
7fe1926f00: 6b2f 6f65 6d2d 7365 7276 6963 6573 2e6a k/oem-services.j
7fe1926f10: 6172 0053 5953 5445 4d53 4552 5645 5243 ar.SYSTEMSERVERC
7fe1926f20: 4c41 5353 5041 5448 3d2f 7379 7374 656d LASSPATH=/system
7fe1926f30: 2f66 7261 6d65 776f 726b 2f73 6572 7669 /framework/servi
7fe1926f40: 6365 732e 6a61 723a 2f73 7973 7465 6d2f ces.jar:/system/
7fe1926f50: 6672 616d 6577 6f72 6b2f 6574 6865 726e framework/ethern
7fe1926f60: 6574 2d73 6572 7669 6365 2e6a 6172 3a2f et-service.jar:/
7fe1926f70: 7379 7374 656d 2f66 7261 6d65 776f 726b system/framework
7fe1926f80: 2f77 6966 692d 7365 7276 6963 652e 6a61 /wifi-service.ja
7fe1926f90: 723a 2f73 7973 7465 6d2f 6672 616d 6577 r:/system/framew
7fe1926fa0: 6f72 6b2f 636f 6d2e 616e 6472 6f69 642e ork/com.android.
7fe1926fb0: 6c6f 6361 7469 6f6e 2e70 726f 7669 6465 location.provide
7fe1926fc0: 722e 6a61 7200 414e 4452 4f49 445f 534f r.jar.ANDROID_SO
7fe1926fd0: 434b 4554 5f7a 7967 6f74 653d 3900 2f73 CKET_zygote=9./s
7fe1926fe0: 7973 7465 6d2f 6269 6e2f 6170 705f 7072 ystem/bin/app_pr
7fe1926ff0: 6f63 6573 7336 3400 0000 0000 0000 0000 ocess64.........
## 第一列是地址。第二列到第九列是对应的内存内容。第十列是内容的字符化显示
## 上图是堆栈的最底部
## 从最右侧可以看到这是这个程序加载时传递的PATH路径。
## 整个dump出来的区域都可以查看,只不过这些数据多而杂,实际意义并不大。
工具篇gdb
android 可以使用gdb工具,进行原生代码的调试。在android中,gdb分成两部分:
- gdbserver这部分需要放在android设备端
- gdbclient这部分放在pc端。gdbserver和gdbclient之间通过网络通信。下面是调试步骤
第一步:找到gdbserver然后,push进android 设备
NDK目录/prebuilt/对应的设备ABI/gdbserver/gdbserver 如下:
adb push gdbserver /data/local/
## 赋予可执行权限
adb shell chmod -R 777 /data/local/gdbserver
## 调试已经运行的应用
adb shell /data/local/gdbserver/gdbserver :5039 --attach pid
## 其中 :5039表示使用的端口号,这个端口号,将会和gdbclient进行交互。
## pid为想要调试的应用的pid
## 若是要重新调试一个还未运行的应用,可以如下:
gdbserver :5039 executable
第二步:设置端口转发命令
adb forward tcp:5039 tcp:5039
## 将设备里面的5039号端口,转发到本地的5039号端口
第三步:找到gdbclient,然后使用它
NDK目录中,gdbclient的名字就叫gdb。在如下位置
NDK目录/prebuilt/对应的平台/bin/gdb
gdb
## 输入gdb就会进入gdb的调试界面。
第四步:告诉gdb你使用的库的符号表的位置。如下
set solib-absolute-prefix /绝对路径/xx.so
set solib-search-path /绝对路径/lib
## set solib-search-path后面的调用会覆盖前面的调用,如果有多个路径,
## 每个路径之间用冒号隔开
第五步:连接远程的android设备
target remote :5039
## 连接本地5039端口
第六步:debbug时暂停其他线程
set scheduler-locking off
## on 打开, off关闭
- 如何避免,调试带来的anr
下面是我找到的一些方法,但是对于我的代码目前是没有用的。我直接修改了aosp的源码,将相应的ANR去掉了,若是应用程序开发者,可使用android模拟器达到同样的效果
## 需要给调试的应用,添加:android:debugable="true"
settings put global debug_app cn.findpiano.piano
setprop debug.anr_disable 1
- 如何设置段点
## 在文件处设置断点.在file.c的第42行处设置断点
break file.c:42
## 在特定的函数处设置断点
break function_name
## 在特定的地址处设置断点
break *0x12345678
## 然后键入continue,继续运行到下一个断点。
- 如何查看所有断点
info breakpoints
## 删除断点,禁用断点,启用断点
delete 数字
disbale 数字
enable 数字
注意:如果你正在调试的app,对应的so库,没有符号表 和调试信息段,则可能无法设置断点,因此,最好在你的Android.mk中的相应位置,加上-g选项
同时,关闭Android应用的剥离操作。如下
buildTypes {
debug {
minifyEnabled false
shrinkResources false
}
}
packagingOptions{
doNotStrip "*/arm64-v8a/*.so"
}
- 如何单步
step ## 单步并进入,简写s
next ## 单步并跳过,简写n
finish ## 跳出当前函数,简写fin
- 如何查看已经加载的共享库
info shared
- 如何加载特定的共享库的符号表
symbol-file /path/to/your/library.so
- 如何查看所有已经加载的符号表
info files
## 查看某个特定的库
## 可以看到是否有调试信息
info sharedlibrary yourlibrary.so
- 如何打印当前线程的堆栈
bt
thread apply all bt ## 打印所有线程的调用栈
- 如何查看所有线程
info threads
- 如何切换线程
thread 线程的id
- 如何查看当前堆栈的所有本地变量
info locals
- 如何查看当前函数的参数
info args
- 如何切换栈帧
frame 帧id
- 如何设置源码位置
directory 目录
- 如何查看有哪些源文件
info sources
info sources 正则表达式 ## 按照正则表达是寻找这些个源文件
- 如何显示代码
list ## 显示当前停止处附近的几行
list number ## 显示特定行
list number, number +n ## 显示从number开始的,后面的n行
list function_name ## 显示特定的函数
注意:在查看代码之前,一定要加载符号表,可以使用symbol-file命令
也可以使用set debug-file-directory /path/to/debug/files命令
- 如何查看历史命令
show commands n ## n表示显示几个
- 如何打印某个对象
print 对象名 ## 如print this->mMidiDevice
print/x 对象名 ## 十六进制输出
## /斜杠后面还可以有
## x: 以十六进制格式打印
## d: 以十进制格式打印
## u: 以无符号十进制格式打印
## o: 以八进制格式打印
## t: 以二进制格式打印
- 如何查看程序的内存映射关系
info proc mappings
- 如何查看所有寄存器的值
info registers
print $sp ## 打印sp寄存器的值
- 如何打印某个内存地址
print address
- 如何查看全局变量
info variables
- 如何查看虚拟函数表
info vtbl 对象
- 如何查看so库中,是否含有调试信息
使用ndk中的命令,NDK目录/toolchains/pc平台-版本号/prebuilt/pc相关的abi/bin/*readelf如下:
C:\Users\wanbiao>C:\Users\wanbiao\AppData\Local\Android\Sdk\ndk-bundle\toolchains\x86_64-4.9\prebuilt\windows-x86_64\bin\x86_64-linux-android-readelf.exe -S E:\work-space\FindAndroidPianoApp\piano\build\intermediates\stripped_native_libs\OfficialDebug\out\lib\arm64-v8a\libfindmidiserver.so
There are 34 section headers, starting at offset 0x1154890:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
## 省略不必要的信息
00000000000000b1 0000000000000001 MS 0 0 1
[25] .debug_loc PROGBITS 0000000000000000 00258301
000000000000438e 0000000000000000 0 0 1
[26] .debug_abbrev PROGBITS 0000000000000000 0025c68f
0000000000024f2a 0000000000000000 0 0 1
[27] .debug_info PROGBITS 0000000000000000 002815b9
0000000000872925 0000000000000000 0 0 1
[28] .debug_ranges PROGBITS 0000000000000000 00af3ede
0000000000051470 0000000000000000 0 0 1
[29] .debug_str PROGBITS 0000000000000000 00b4534e
00000000003269e9 0000000000000001 MS 0 0 1
[30] .debug_line PROGBITS 0000000000000000 00e6bd37
00000000001272dd 0000000000000000 0 0 1
[31] .symtab SYMTAB 0000000000000000 00f93018
00000000000a0f50 0000000000000018 33 22310 8
[32] .shstrtab STRTAB 0000000000000000 01033f68
0000000000000163 0000000000000000 0 0 1
[33] .strtab STRTAB 0000000000000000 010340cb
00000000001207be 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
## 如果看到含有debug相关的段,则表示含有调试信息
下面是一个正常使用的部分输出结果
- 先设置了断点在MidiPort::notifyChannels处
- 键入c,运行到断点
- step单步,同时显式了断点附近的源码
- 使用info locals,打印本地变量,即变量ret的值
(gdb) break MidiPort::notifyChannels
Breakpoint 1 at 0x76ab0e0db8 (2 locations)
(gdb) c
Continuing.
[New Thread 32470.4135]
[New Thread 32470.4136]
[New Thread 32470.4137]
[New Thread 32470.4138]
[Switching to Thread 32470.32546]
Thread 36 "RxCachedThreadS" hit Breakpoint 1, 0x00000076ab10b5d4 in MidiPort::notifyChannels()@plt ()
from target:/data/app/cn.findpiano.piano-yycCgVpGa-NSEDhJxH6Fzw==/lib/arm64/libfindmidiserver.so
(gdb) step
Single stepping until exit from function _ZN8MidiPort14notifyChannelsEv@plt,
which has no line number information.
MidiPort::notifyChannels (this=0x30436964696d2f64)
at E:/work-space/FindAndroidPianoApp/rom.sdk/src/main/cpp/findmidiserver/core\MidiPort.cpp:218
warning: Source file is more recent than executable.
218 void MidiPort::notifyChannels() {
(gdb) info locals
_l = {
__m_ = @0x3900000039}
ret = 1853042550
(gdb)
coredump (这部分仅针对Framework工程师)
我们还可以使用gdb,直接对corefile进行调试。
不同的Android平台,在使能coredump功能的时候,操作步骤不尽相同。具体平台就不再做过多介绍。下面举例说明,如何调试corefile
假设有一个corefile名为core.27055
使用格式如下:
gdb 可执行文件
关键点在如何找到正确的gdb执行文件。这个gdb应该运行在PC端,并且还能够解析android的coredump。
按照常理来讲,应该在prebuilts中,但是在NDK目录中,并没有找到合适的gdb。
如果使用上面提到的
NDK目录/prebuilt/对应的平台/bin/gdb
则会提示
no core file handler recognizes format
显然这个gdb并不合适。
真正合适的gdb在android系统的源码/prebuilts目录下。但是不幸的是,似乎每个android版本放的下级目录依然没有固定,因此在使用时,可根据目录关键字进行查看。
在目前高通某个版本中的目录为:/prebuilts/gdb/linux-86/bin/gdb.
举例如下:
./prebuilts/gdb/linux-86/bin/gdb ./out/target/product/xxx/symbols/system/bin/app_process64
## 运行对应的gdb,后一个参数为可执行文件。本次举例使用的core.27055是
## 一个应用程序的coredump,因此它的可执行文件为app_process64
## 上述app_process64的路径,为源码编译之后生成的路径
进入gdb环境之后,
- 先设置必要符号表的读取路径。
set sysroot out/target/produc/xxx/symbols/
- 加载要使用的coredump文件
core-file core.27055
- 使用上面介绍的gdb调试方法,即可进行调试,如打印当前堆栈。如下
(gdb) bt
#0 __epoll_pwait () at bionic/libc/arch-arm64/syscalls/__epoll_pwait.S:9
#1 0x0000007a528b9c34 in epoll_pwait (fd=-4, events=0x7fc46c41d8, max_events=16, timeout=-1, ss=<optimized out>)
at bionic/libc/bionic/epoll_pwait.cpp:42
#2 0x0000007a51056e54 in android::Looper::pollInner (this=0x79c32df380, timeoutMillis=-1) at system/core/libutils/Looper.cpp:242
#3 0x0000007a51056d38 in android::Looper::pollOnce (this=0x79c32df380, timeoutMillis=-1, outFd=0x0, outEvents=0x0, outData=0x0)
at system/core/libutils/Looper.cpp:210
#4 0x0000007a4f834848 in android::android_os_MessageQueue_nativeDestroy (env=0x7fc46c41d8, clazz=<optimized out>, ptr=16)
at frameworks/base/core/jni/android_os_MessageQueue.cpp:185
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
在正确设置,sysroot之后,gdb能够显示,堆栈停留在哪个文件,第几行等有用的信息。
及时调试 (这部分仅针对Framework工程师)
及时调试分成两种情况。
- 一启动,就需要调试
- 一崩溃,就需要调试
程序一启动,等待gdbserver连接
有两种办法处理这种情况,举例来说,比如停留在程序入口函数main函数的第一行。
- 写一个while循环,等待gdbserver的接入。如下
bool wait_for_self = android::base::GetBoolProperty("debug.debuggerd.wait_for_self", false);
while(wait_for_self){
ALOGI("please use gdbserver to attach this program");
}
// 在这里,使用debug.debuggerd.wait_for_self来控制本程序,
// 直到gdbserver连接上。
// 一旦gdbserver连接上,就修改wait_for_self使其退出循环。
- 除了上面的办法以外,我更常用的方法是:给自己发送一个SIGSTOP信号。如下;
bool wait_for_self = android::base::GetBoolProperty("debug.debuggerd.wait_for_self", false);
if(wait_for_self){
ALOGI("please use gdbserver to attach this program");
raise(SIGSTOP);
}
// 一旦gdbserver连接上之后,就可以直接使用单步调试命令进行调试。
在这个小节里面介绍的两种办法,可以适用于任何你想要停留的位置。
注意:我们将使用此小节介绍的方法,来调试后面遇到的debug.debuggerd.wait_for_gdb没有反应的问题,请往后看
程序一崩溃,等待gdbserver连接
android的应用,可能出现:一打开就崩溃,无法使用gdbserver的–attach进行调试。此时。可以让程序停留在崩溃处,并等待gdbserver的连接。步骤如下:
## android 6.0以下
adb shell setprop debug.db.uid 10000
## 这句话告诉系统,所有UID <= 10000的 程序崩溃,都会等待gdbserver的连接
## android 11 以下
adb shell setprop debug.debuggerd.wait_for_gdb true
## 告诉系统,程序崩溃的时候,等待gdb的连接
## android 11之后
adb shell setprop debug.debuggerd.wait_for_debugger true
## 告诉系统,程序崩溃时候,等待调试器的连接
下面举例说明,例子为高通8.1系统中,按上述步骤,运行下列命令
adb shell setprop debug.debuggerd.wait_for_gdb true
可恶的是,当程序崩溃时,它并没有停下来,等待gdbserver的连接。
为了弄清楚为何如此,现在使用上文介绍的gdb进行调试
全局搜索debug.debuggerd.wait_for_gdb出现在crash_dump.cpp中。故使用上一小段《程序一启动,等待gdbserver连接》的知识,在crash_dump.cpp的main方法的第一行,放入如下代码
bool wait_for_self = android::base::GetBoolProperty("debug.debuggerd.wait_for_self", false);
if(wait_for_self){
ALOGI("please use gdbserver to attach this program");
raise(SIGSTOP);
}
编译成功,并push到/system/bin/目录下。故意制造程序的崩溃,此时crash_dump停在了main函数的第一行。
按照上面介绍的单步执行命令。一步一步下去。
突然,收到如下的提示信息。
Program terminated with signal SIGALRM, Alarm clock.
那么猜测,中断debug.debuggerd.wait_for_gdb执行的,很可能是SIGALRM信号。换句话说,还未等crash_dump运行到debug.debuggerd.wait_for_gdb的处理逻辑,程序便因为收到SIGALRM信号而退出。
故,在crash_dump中拦截这个信号,如下:
//新增信号处理函数
void handle_sigalrm(int a){
ALOGI("receive a signal %d",a);
}
//在main函数中注册,SIGALRM的信号处理函数
signal(SIGALRM,handle_sigalrm);
再次编译,并制造应用崩溃,此时应用停了一下,并在log中打印如下信息
05-31 18:15:41.069 3468 3468 I : ***********************************************************
05-31 18:15:41.069 3468 3468 I : * Process 2051 has been suspended while crashing.
05-31 18:15:41.069 3468 3468 I : * To attach gdbserver and start gdb, run this on the host:
05-31 18:15:41.069 3468 3468 I : *
05-31 18:15:41.069 3468 3468 I : * gdbclient.py -p 2051
05-31 18:15:41.069 3468 3468 I : *
05-31 18:15:41.069 3468 3468 I : ***********************************************************
此时表示,应用2051进入了等待gdbserver连接的状态中。
至此,gdb的调试已经记录完成。不仅是应用程序开发者,还是framework开发者,亦或是驱动开发者。
对于本篇gdb的使用和思路,也完全具有参考价值。
前面两篇工具篇,解决了,native部分的前三个子问题。分别是:
1. native部分
- 寄存器内容是什么。如pc指向何处,sp指向何处 (使用工具)
- 指定地址内容是什么。如变量a对应的内容 (使用工具)
- 线程堆栈内容是什么。如主线程的堆栈,UI线程的堆栈 (使用工具)
- 堆区的对象有哪些。
对于堆区的对象应该怎么查看和收集呢?需要等到LLDB工具介绍完之后,统一处理。
java部分在native介绍完之后,再介绍。
下一篇依然是工具篇里面的lldb。