感谢文章:blog.csdn.net/tq08g2z/art…
使用适配过Android的leak tracer,不要使用官方的。我自己写的测试demo
注意
尽量将leak tracer编译到要检测内存泄露模块的动态库中,不要单独将leak tracer编译为一个so动态库,否则会导致addr2line无法正确识别出地址符号。
LeakTracer使用教程
在Android平台上检测内存泄露,最终选择使用leak tracer。
通过leak traker,收集到leak结果如下:
# LeakTracer report diff_utc_mono=1685323259.020343 leak, time=1562403.055277, stack=0x7921948b54 0x7921948de4 0x7921c80e00 0x7923f99460 0x792433eee0, size=10485760, data=.................................................. leak, time=1562403.055280, stack=0x7921948b54 0x7921948de4 0x7921c83df4 0x7921c81dc8 0x7921c839e0, size=8, data=....y...
使用addr2line还原调试符号
很明显,我们看到stack都是符号地址,需要通过addr2line工具还原详细的信息
llvm-addr2line.exe -C -f -e libjni.so 0x7921948b54
建议使用ndk版本对应的addr2line,不过我发现其他版本的也可以,比如我使用的是:
ndk\23.1.7779620\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-addr2line.exe
so文件的路径:
app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a\*.so
至于使用哪个so,要看__builtin_frame_address函数所在的动态库模块
- 如果libleaktracer是一个单独的so动态库,那么就应该使用libleaktracer.so(一般都是这种)
- 如果libleaktracer被以静态库的方式引入到另外一个动态库,就应该使用target.so
addr2line显示??::
检查方向:
- 确认so库的路径,以及是否携带调试符号信息
- 确认使用的是项目编译sdk的
addr2line
- addr2line的地址参数,是否和so混用,so里面必须有该地址才能被正常识别。
- 检查你的地址
检查你的地址
这个也是本篇文章我遇到的,0x7921948de4
这种非常大的地址,一看就有问题。
每个进程都有自己独立的进程地址空间。该进程地址空间被划分为一系列连续的虚拟地址空间,每个虚拟地址对应着唯一的物理地址。
在 x86 架构的计算机中,操作系统会为每个进程分配一个虚拟地址空间,该空间从 0x00000000 到 0xFFFFFFFF(或者更少),共 4GB 的大小。
如果要使用 addr2line
工具查询动态链接库(DLL)中某个函数的源代码位置信息,通常需要将查询地址减去该 DLL 的基地址。
这是因为在 Windows 平台上,每个 DLL 都有一个基地址,用于确定该 DLL 中所有函数和变量在虚拟地址空间中的位置,以保证不同 DLL 之间的地址不会重叠冲突。
如果直接使用官方的源码,是没有处理基地址的问题,如果leak tracer以动态库的方式引入到项目,那么stakc地址是有问题的,导致你无论怎么试错,都无法得到正确的调试符号信息。
而处理这个基地址,一个非常重要的函数就是dladdr
。
leak traer所使用到的函数
__builtin_frame_address
void* __builtin_frame_address(unsigned int level);
GCC 内建函数之一,在 C/C++ 中使用时可以获取当前函数的堆栈帧指针(Frame Pointer)。
其中,level
参数表示向上返回堆栈帧的层级数。
如果 level
大于当前函数的堆栈帧层数,返回 NULL
。
有些linux系统返回的内存地址就是VMA地址
dladdr
获得指定地址所在的共享库信息,函数原型如下:
int dladdr( const void *addr, // 要查询的地址 Dl_info *info // 用于存储查询结果的结构体 );
Dl_info
结构体定义如下:
typedef struct { const char* dli_fname; /* 共享库文件名 */ void* dli_fbase; /* 基地址 */ const char* dli_sname; /* 符号名 */ void* dli_saddr; /* 符号地址 */ } Dl_info;
dladdr
函数的返回值是非零表示查询成功,否则表示查询失败。如果查询成功,info
参数中会填充该地址所在的共享库文件名、基地址、符号名称和符号地址等信息。
示例结果:
dli_fbase: 0x791e321000, dli_fname:/data/app/~~M4EQtyRf9F8ogdMdo1pJEg==/com.example.jni-gHJ1rvzAUolWe7FDtjUucA==/base.apk!/lib/arm64-v8a/libjni.so, dli_saddr:0x791e33eb94, dli_sname:_Z12test_addressv
_Unwind_Backtrace
在 Android NDK 中,您可以使用 _Unwind_Backtrace
函数获取当前线程的函数调用栈信息。