android 如何分析应用的内存(二)

android 如何分析应用的内存(二)

前面对android应用的内存做了一个总体性的概括,那如何查看内存里面的细节呢?

本篇笔记较长,总体内容包括如下两部分:

  1. native部分
    • 寄存器内容是什么。如pc指向何处,sp指向何处
    • 指定地址内容是什么。如变量a对应的内容
    • 线程堆栈内容是什么。如主线程的堆栈,UI线程的堆栈
    • 堆区的对象有哪些。
  2. java部分
    • 线程堆栈有什么内容。
    • 堆中对象分配情况

所有这些,需要使用Debug工具才能查看。部分内容,还需要编程查看。
先来看工具如何使用

工具篇xdd

  1. 如何查看任意内存地址的内容
    使用如下命令
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。则需要对这个程序有一定的了解。
  1. 还可以查看整个映射区域里面的内容
    如前一小节所述,通过/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分成两部分:

  1. gdbserver这部分需要放在android设备端
  2. 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相关的段,则表示含有调试信息

下面是一个正常使用的部分输出结果

  1. 先设置了断点在MidiPort::notifyChannels处
  2. 键入c,运行到断点
  3. step单步,同时显式了断点附近的源码
  4. 使用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环境之后,

  1. 先设置必要符号表的读取路径。
set sysroot out/target/produc/xxx/symbols/
  1. 加载要使用的coredump文件
core-file core.27055
  1. 使用上面介绍的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=)
    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=, 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工程师)

及时调试分成两种情况。

  1. 一启动,就需要调试
  2. 一崩溃,就需要调试

程序一启动,等待gdbserver连接

有两种办法处理这种情况,举例来说,比如停留在程序入口函数main函数的第一行。

  1. 写一个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使其退出循环。

  1. 除了上面的办法以外,我更常用的方法是:给自己发送一个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。

你可能感兴趣的:(android,内存分析,android,gdb,gdb,server,android调试,gdb命令)