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

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

接上文

细节部分包括如下

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

其中介绍了native部分的前三个。

这些都需要使用工具才能查看,部分内容还需要写代码才能查看,因此先介绍了xdd工具,
接着介绍了gdb工具,接下来介绍lldb。

前一篇文章中,介绍的gdb,是GCC项目的调试器。
从android NDKr13中推荐使用LLVM项目,到NDK r18全面拥抱LLVM项目之后

android更推荐使用的调试器为LLVM项目的LLDB.

工具篇LLDB

本小节主要为两大内容:

  1. lldb的命令行调试
  2. lldb在AS上面的调试

前者更加适用于Framework工程师,MMI工程师

后者更加适用于Android应用工程师,但我也推荐Android应用工程师阅读本小节内容。
理由有三:

  1. 部分应用因为不知名原因,导致AS调试经常断连
  2. 部分应用使用了第三方库,第三方库中一些SIGxx,可能导致AS没办法正确显示。
  3. 不支持AS进行lldb调试的设备。如何查看请看,LLDB的GUI篇

LLDB的命令行

在进行调试之前,依然要根据前一篇文章所提及的步骤,对so库进行处理

1. 在合适的地方,加上-g选项,打开调试。可以是Android.mk也可以是build.gradle,还可以是CMake,依据自己的项目编译脚本而定
2. 去掉apk里面的编译优化,如下:
buildTypes {
        debug {
            minifyEnabled false
            shrinkResources false
        }
    }
packagingOptions{
        doNotStrip "*/arm64-v8a/*.so"
    }

跟gdb一样,LLDB依然有server和client两部分。

server运行在android平台,client运行在PC平台

第一步:将server拷贝到手机中
NDK中的工具链文件夹中,已经将LLDB放入其中,位置如下:
NDK目录/llvm/prebuilt/对应平台/lib64/clang/版本号/lib/linux/androidABI/lldb-server

注意:在不同版本的NDK包中,可能路径不一样,可升级NDK包进行查看

将其push到手机中如下:

## push到/data/local
adb push lldb-server /data/local/
## 赋予可执行权限
adb shell chmod 777 /data/local/lldb-server

第二步:运行server

./lldb-server p --listen "*:5039" --server
## 其中p表示,使用platform命令,即pc端可使用platform进行连接
## 除了p以外,还可以使用g。表示使用gdb远程协议,此处不做介绍

下面介绍lldb-server的p模式下的命令参数

--server:运行在服务器模式,这样可以操作多个连接。如果没有这个选项,仅仅接受一个连接,并在完成
    之后自动退出
--listen <host>:<port>:监听的主机名字和端口,如果端口位置为0,则会使用一个随机端口。上面命
    令的星号,表示所有主机,端口为5039.即,使用一个可用主机的5039号端口。
--socketf-file <path>:将正在监听的端口号写入这个文件中,当--listen指定的端口号为0时有用。
--log-file <path>:将log输出到指定的文件中,如果没有,则输出到stderr

第三部:设置端口转发命令

adb forward tcp:5039 tcp:5039
## 同gdb一样,需要设置端口转发命令,将android端的5039转发到pc端的5039

第四步:使用client命令连接server

lldb的client命令在
NDK目录/toolchains/llvm/prebuilt/pc平台/bin/lldb

注意:在不同版本的NDK包中,可能路径不一样,可升级NDK包进行查看
运行如下命令

.\lldb.cmd
## 进入lldb的命令行界面。
## 注意,同目录下还会有一个lldb.exe命令,该命令为lldb.cmd要使用的命令,直接运行它可能会出现
## 一些python路径找不到的情况 。因此,在lldb.cmd中进行了正确的配置。

## 同样的,如果实在linux环境下,应该运行的是lldb.sh而不是直接运行lldb(包括mac)

第五步:在lldb中选择正确的平台插件

platform list 
## 查看支持的平台有哪些,输出如下:
(lldb) platform list
Available platforms:
host: Local Windows user platform plug-in.
remote-linux: Remote Linux user platform plug-in.
remote-freebsd: Remote FreeBSD user platform plug-in.
remote-netbsd: Remote NetBSD user platform plug-in.
remote-openbsd: Remote OpenBSD user platform plug-in.
remote-ios: Remote iOS platform plug-in.
remote-macosx: Remote Mac OS X user platform plug-in.
host: Local Mac OS X user platform plug-in.
remote-windows: Remote Windows user platform plug-in.
remote-gdb-server: A platform that uses the GDB remote protocol as the communication transport.
remote-android: Remote Android user platform plug-in.

选择正确的平台插件,我们调试Android,就应该选择remote-android.如果不选择,则为本机的平台(此处为windows)

platform select remote-android
## 运行之后,它会提示,如下:
(lldb) platform select remote-android
  Platform: remote-android
 Connected: no
 ##此处显示还未连接,故下一步就是进入连接

第六步:连接远端server

platform connect connect://P1008N19120251:5039
## 其中P1008N19120251表示adb devices列出的sn号
## 5039是对应的端口号,输出如下:
(lldb) platform connect connect://P1008N19120251:5039
  Platform: remote-android
    Triple: aarch64-unknown-linux-android
OS Version: 27 (3.18.71-perf)
  Hostname: localhost
 Connected: yes
WorkingDir: /data/local
    Kernel: #1 SMP PREEMPT Tue Aug 30 19:49:21 CST 2022

第七步:与具体的程序进行连接

process attach --pid 123
## 与pid为123的程序建立连接

process attach --name programName
## 与programName建立连接 (注意Android平台不可用)

process attach --name programName --waitfor
## 一旦programName启动,就和它进行连接(注意Android平台不可用)

一旦连接成功输出大致如下:

(lldb) process attach --pid 20469
Process 20469 stopped
* thread #1, name = 'findpiano.piano', stop reason = signal SIGSTOP
    frame #0: 0x000000704c878be4 libc.so`__epoll_pwait + 8
libc.so`__epoll_pwait:
->  0x704c878be4 <+8>:  cmn    x0, #0x1, lsl #12         ; =0x1000
    0x704c878be8 <+12>: cneg   x0, x0, hi
    0x704c878bec <+16>: b.hi   0x704c829f44              ; __set_errno_internal
    0x704c878bf0 <+20>: ret
  thread #2, name = 'Jit thread pool', stop reason = signal SIGSTOP
//省略若干类似log
  thread #114, name = 'pool-15-thread-', stop reason = signal SIGSTOP
    frame #0: 0x000000704c829b2c libc.so`syscall + 28
libc.so`syscall:
->  0x704c829b2c <+28>: svc    #0
    0x704c829b30 <+32>: cmn    x0, #0x1, lsl #12         ; =0x1000
    0x704c829b34 <+36>: cneg   x0, x0, hi
    0x704c829b38 <+40>: b.hi   0x704c829f44              ; __set_errno_internal

Executable module set to "C:\Users\wanbiao\.lldb\module_cache\remote-android\.cache\27FE2319-A0C9-B3CD-A572-C955E7D6E716\app_process64".

第八步:进入真正的调试阶段

  • 如何设置源码

A,先查看调试表中的源码路径

image lookup -vn functionname
## 查看functionname对应的信息,其中就包括对应的源码路径位置。
参数说明:
-v: 输出详细信息
-n: 后面跟的是待查找的符号名字,如函数名
-a: 后面跟一个地址表达式
-r: 后面跟一个正则表达式,按照正则表达式查找
-t: 后面跟一个类型名,按照类型名查找

注意:在ubunt系统中,-vn 应该分开写成 -v -n

举例如下:(从此处开始,后面的例子,用图片展示)
android 如何分析应用的内存(三)_第1张图片

从图片可以看到,notifyChannel的源文件是

/Users/biaowan/StudioProjects/FindAndroidPianoApp/rom.sdk/src/main/cpp/findmidiserver/core/MidiPort.cpp

这是我在Mac上面的编译源文件。

B,将上面找到的路径,映射到本地的源文件路径:

settings set target.source-map 源码中的路径  本地路径
## 设置源码中的路径,映射为本地路径
 settings show target.source-map
 ## 查看源码中的路径映射关系
source list -f xxx.cpp
## 查看源文件

注意注意:非常得不幸,lldb在ndk24以下的版本中target.source-map没有作用,而在Ubuntu和MAC上面运行良好
如若发现不能正常工作,可切换到ndk 25.2.9519653版本中。亲测有效,例子如下:
android 如何分析应用的内存(三)_第2张图片

补充知识:如何查看一个so库中,对应的源文件路径都有哪些呢?
在lldb中并没有找到对应的命令,若有人知道,请告诉我。
而我经常使用的命令是:

gdb
## 运行gdb,进入gdb命令行
file xx.so
## 加载xx.so文件
info sources
## 打印所有的可能的源文件

android 如何分析应用的内存(三)_第3张图片

  • 如何设置断点
breakpoint set --file myfile.cpp --line 42
## 再文件的第几行,设置断点

breakpoint set --name myFunction
## 再函数处,设置断点

breakpoint set --address 0x12345678
## 在地址处设置,端点
  • 如何查看断点
breakpoint list

举例如下:
在这里插入图片描述

设置断点的命令依然还有很多,可以查看https://lldb.llvm.org/use/tutorial.html

  • 如何设置watchpoint

因为在我正在使用的版本不支持watchpoint,所以此处不做介绍,后面可能出一篇专门的lldb的文章,到时候会详细介绍,为了达到类似的效果,我们使用条件断点,如下

  • 如何设置条件断点

在断点后面,加上–conditoin “表达式”,举例如下:

breakpoint set --file MidiPort.cpp --line 304 --condition "buff[1]=0x9f"
## 当buff[1]等于0x9f时,断点停止在304行

因为mac和ubuntu的lldb可以使用tab进行补全命令,因此后续的举例将直接在mac或者ubuntu中进行
android 如何分析应用的内存(三)_第4张图片

  • 如何查看线程
thread list
## 列出素有的线程,包括线程id,线程名等等
thread info 线程id
## 查看对应线程的详细信息,包括线程ID,状态,寄存器等
thread backtrace 线程id
## 查看对应线程的调用栈
thread select 线程id
## 切换到对应的线程


android 如何分析应用的内存(三)_第5张图片

  • 如何进行单步调试
step ## 单步执行,进入函数的调用
next ## 单步执行,跳过函数的调用
finish ## 在当前函数中,执行到返回语句
thread step-in ## 在当前线程中,执行单步调试,进入函数中
thread step-over ## 在当前线程中,执行单步调试,跳过函数调用
thread step-out ## 在当前线程中,执行单步调试,从当前函数返回
  • 如何读取变量的值
print (简写p) ## 打印变量
print -f format 变量 ## 以format格式打印变量
## format 有binary、decimal、hex、octal、float、char 等
## -r 表示以原始格式打印

android 如何分析应用的内存(三)_第6张图片

express ## 计算并打印表达式的值
## 它所具有的参数,同print一样,有-f,-r
## 两者之间的区别就是express会计算表达式

android 如何分析应用的内存(三)_第7张图片

  • 如何切换栈帧
thread backtrace 
## 打印当前的线程栈
frame select number
## 切换到number对应的栈帧中
  • 如何查看已经加载的共享库
image list 
## 查看所有的已经加载的共享库
image list -b xx.so
## 查看某个共享库
  • 如何添加符号表
target symbols add /path/to/symbol/file
## 将指定的路径添加到符号表搜索路径中。
  • 如何查看当前堆栈的所有变量
frame variable
## 查看当前堆栈的所有变量
frame variable --show-types <type>
## 只查看特定类型的变量
frame variable myVariable
## 只查看myVariable变量
  • 如何查看寄存器的值
register read
## 查看所有寄存器的值
register read --format hex
## 以十六进制查看
## --format 可以有binary、decimal、hex、float 等
register read sp
## 读取sp寄存器的值
  • 如何查看某个内存地址
memory read address
## 查看某个地址的内容
memory read -c n address
## 查看address开始的n个字节
memroy read -c n address --format format
## 以format格式查看,format的值有binary、decimal、hex、float 
  • 如何修改某个内存的值
memory write address value
## 写入value到某个内存中
  • 如何查看当前线程的全局变量
target variable

LLDB分析coredump

第一步当然是加载corefile。以上一篇的core.27055为例。在加载corefile之前,需要将corefile和
带有符号表的库放在同一目录下。

在gdb中,可以通过set sysroot进行设置。但是lldb中,并未发现有类似的命令。知道的读者可以告诉我,谢谢先

格式如下

lldb.sh  <执行文件>  -c  <corefile>

一旦加载成功,则可以使用上面介绍的命令,进行内存的查看了

LLDB的及使调试技术

LLDB的及时调试,同GDB一样,包括两部分

  1. 在程序启动的时候,就连接上lldb
  2. 在程序崩溃的时候,就连接上lldb

但这两部分的操作同GDB一样,再此不再过多介绍。

到目前为止,所有文章所使用的Android版本最高为8.1,因此对于其后出现的一些调试技术,可能稍微有差异
但这些差异并不是这些技术的落后,而是对这些技术的一层层封装。读者可放心使用

因为LLDB的GUI调试,会有大量的图片产生,因此,将这部分放入下一篇文章中。
在下一篇文章中,将会有两种GUI的使用。分别是AS和VScode

你可能感兴趣的:(android,内存分析,android内存,lldb,命令行,lldb,分析内存,lldb设置断点,lldb查看变量)