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

news2025/2/9 11:22:55

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

接上文,介绍六大板块中的第二个————malloc hook

上一篇的自定义分配函数,常常只能解决当前库中的分配,而不能跟踪整个app中的分配。

为此,android的libc库,从Android 9.0开始引入了malloc hook技术

这个技术定义了四个全局变量,这个变量指向对应的函数。这四个全局变量是:

void* (*volatile __malloc_hook)(size_t, const void*);
void* (*volatile __realloc_hook)(void*, size_t, const void*);
void (*volatile __free_hook)(void*, const void*);
void* (*volatile __memalign_hook)(size_t, size_t, const void*);

他们的对应关系如下:

hookfunction
__malloc_hookmalloc
__malloc_hookcalloc
__realloc_hookrealloc
__free_hookfree
__memalign_hookmemalign
__memalign_hookposix_memalign
__memalign_hookaligned_alloc

注意:在32位系统中,有两个已经不在推荐使用的函数,
pvalloc和valloc对应的hook为__memalign_hook。

再次注意:malloc_usable_size这个函数,目前还没有与之对应的hook可以使用

这些变量可以在任何时刻被修改,但是他们不是线程安全的,因此,一定要在适当的时候,修改这些变量。否则,可能会导致程序崩溃

启动Bionic中的hook功能

对于Framework工程师,有两种方法打开这个功能

  1. 通过setprop如下
## 首先,需要关掉Android的系统服务,这样可以追踪app中调用系统服务的分配
adb shell stop
## 然后,设置libc.debug.hooks.enable为1,打开hook功能
adb shell setprop libc.debug.hooks.enable 1
## 最后重新启动Android系统中的系统服务
adb shell start
  1. 只在当前的shell环境中,启用这个功能如下
## 进入shell环境
adb shell
## 使能shell变量LIBC_HOOKS_ENABLE为1,打开hook功能
export LIBC_HOOKS_ENABLE=1
## 运行一个本地程序,ls,这样就会跟踪ls的内存分配情况
ls

对于APP工程师,依然有两种方式打开这个功能,这两种方式都是通过设定LIBC_HOOKS_ENABLE=1环境变量来实现

  1. 通过命令行中设置wrap.app属性传递
adb shell setprop wrap.<APP> '"LIBC_HOOKS_ENABLE=1"'

举例如下:

## 要设置的app的包名为com.google.android.googlequicksearchbox 
adb shell setprop wrap.com.google.android.googlequicksearchbox '"LIBC_HOOKS_ENABLE=1 logwrapper"'

原理简述:系统在启动app的时候,会查看是否有wrap.app这个属性,如果有且不会空,则会将这个属性的值,作为一个shell命令,这个shell命令的后面,还会跟上应用的包名。比如上例会在一个shell中运行如下命令

LIBC_HOOKS_ENABLE=1 logwrapper com.google.android.googlequicksearchbox

然后在同一个shell环境中,启动应用。在上面的例子中,我们设置了一个环境变量LIBC_HOOKS_ENABLE=1,然后再将com.google.android.googlequicksearchbox中未输出到log系统中的log重定向到了log系统中。

  1. 通过在app中增加wrap.sh脚本达到.

这个方法在后续的调试中,还会大量登场,因此,下面通过一个小节详细介绍这部分内容。

如何在app中使用wrap.sh

通过wrap.app来增加简单的shell命令,是非常可行的。但当命令复杂起来,用shell脚本就更加合适了。

  1. 新建一个shell脚本,命名为wrap.sh
  2. 将wrap.sh放入src/main/resources/lib/*中

在这里插入图片描述

当打包之后,wrap.sh将会出现在apk的lib/arm64-v8a/目录下

  1. 修改apk的编译选项。
    在Android的manifest文件中的application标签,打开调试
android:debuggable="true"

同时还需要在build.gradle中将 useLegacyPackaging设置为true。这句话的意思是:是否要使用传统的对so库进行压缩的打包方式。如果没有设置,当minSdk>=23时,将不会压缩,且会保持页对齐方式进行打包。

如下:

packagingOptions {
    pickFirst 'lib/arm64-v8a/libc++_shared.so'
    exclude 'META-INF/*'
    doNotStrip "*/arm64-v8a/*.so"
    jniLibs {
        useLegacyPackaging true
    }
}
  1. 测试,为了查看wrap.sh是否生效,我们在wrap.sh中增加了一个环境变量如下:
#!/system/bin/sh
export wanbiaowrapsh=1
exec "$@"

在这个脚本中,我们定义了一个环境变量wanbiaowrapsh并将其值置为1并使用exec命令,执行程序

除此之外在app的适当,读取环境变量是否成功如下:

val env = System.getenv()
for (envName in env.keys) {
    Timber.w("Midimanager %s=%s%n", envName, env[envName])
}

运行app,查看log,可得如下输出。
在这里插入图片描述
可见,环境变量已经能够被正确的读取了

注意:在wrap.sh的脚本中,换行符,一定要是LF,而不能是CRLF。如果是后者将会导致应用被卡主,而无法正常运行

注意:wrap.sh的使用条件,最低为Android 8.1

实现bionic中的hook

有了前面打开bionic的功能。接下来我们将实现上面介绍的四个hook。

在这一部分中,我们依然会使用上一小节中,定义的AllPtr和Debug对象,记录所有的分配调用栈和时间。

接下来是每个函数的实现。

//因为hook函数不是线程安全的,因此在修改他们的时候,加了如下的锁
static std::recursive_mutex mutexMalloc;
static std::recursive_mutex mutexFree;
static std::recursive_mutex mutexRealloc;
static std::recursive_mutex mutexMemalign;

//在上一小节中定义的两个函数,分别保存和删除ptr
extern void addToAllptr(void *ptr,std::size_t sz);
extern void popFromAllptr(void *ptr);

//分别保存,原始的分配函数和释放函数
const auto origin_malloc = __malloc_hook;
const auto origin_free = __free_hook;
const auto origin_realloc = __realloc_hook;
const auto origin_Memalign = __memalign_hook;

//新的malloc分配函数
void *new_malloc(size_t size, const void * caller){
    std::unique_lock<std::recursive_mutex> _l(mutexMalloc);
    //使用原始分配函数,进行分配
    auto ptr = origin_malloc(size,caller);
    //为了让addToAllptr函数能够正常使用malloc,恢复原位
    __malloc_hook = origin_malloc;
    addToAllptr(ptr,size);
    __malloc_hook = new_malloc;
    return ptr;//注意要返回,当没有返回值时,有些编译器可能不会提示错误
}

//新的free释放函数
void new_free(void* ptr, const void* caller){
    std::unique_lock<std::recursive_mutex> _l(mutexFree);
    //使用原始的释放函数,进行释放
    origin_free(ptr,caller);
    //为了让popFromAllptr能够正常使用free,恢复原位
    __free_hook = origin_free;
    popFromAllptr(ptr);
    __free_hook = new_free;
}

//剩下两个没有做任何修改,仅仅是演示使用
void* new_realloc(void* ptr, size_t size, const void* caller){
    //nothing to do
    return origin_realloc(ptr,size,caller);
}

void* new_memalign(size_t size, size_t size1, const void* caller){
    //nothing to do
    return origin_Memalign(size,size1,caller);
}

除了在上面进行修改以外,还需要在启动的时候,将对应的hook函数变量改成新的值,因此在加载so库的时候,做此操作。如下:

jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
    //分别将对应的hook函数,修改成新的值
    {
        std::unique_lock<std::recursive_mutex> _l(mutexMalloc);
        __malloc_hook = new_malloc;
    }
    {
        std::unique_lock<std::recursive_mutex> _l(mutexFree);
        __free_hook = new_free;
    }
    {
        std::unique_lock<std::recursive_mutex> _l(mutexRealloc);
        __realloc_hook = new_realloc;
    }
    {
        std::unique_lock<std::recursive_mutex> _l(mutexMemalign);
        __memalign_hook = new_memalign;
    }

    return JNI_VERSION_1_6;
}

这样当so库加载完成之后,就能够正确的处理我们自定义的hook函数了。

在进行测试之前,还需要进行一点额外的操作。

上一小节中,定义了一个void printStackTrace(char *buffer, int size);函数。现在再定义一个函数,void printStackTrace();将对应的堆栈信息直接输出到log系统中。如下

void Debug::printStackTrace() {
        const auto maxStackDeep = 50;
        intptr_t stackBuf[maxStackDeep];
        char outBuf[1024*maxStackDeep];
        memset(outBuf, 0, sizeof(outBuf));
        dumpBacktraceIndex(outBuf, stackBuf, captureBacktrace(stackBuf, maxStackDeep));
        ALOGD("-----start-----");
        for(int i=0;i<maxStackDeep;i++){
            auto startLine = outBuf+i*1024;
            if(strlen(startLine) > 0){
                ALOGD("%s", outBuf+i*1024);
            }
        }
        ALOGD("-----end-----");
    }

上面函数,将堆栈层数,调整到了50层。

开始测试

因为在后续的文章中,还会使用到wrap.sh。所以本次测试直接使用wrap.sh。内容如下,可直接复制使用

#!/system/bin/sh
export LIBC_HOOKS_ENABLE=1
exec "$@"

打包并运行,可在log系统中观测到类似如下的log。

-----start-----
2023-06-14 13:20:02.045 19720-19720 Find_Debug              pid-19720                            D  #0: 0x792227647c  0x2747c  _ZN4Find5Debug16captureBacktraceEPlm      /data/app/~~KRrGK7sLlqLxlvtobHnUfg==/com.example.test_malloc-Ot3c1Kcp1YMgiDUYl4TFjA==/lib/arm64/libtest_malloc.so

//省略若干                                                                         
                                                                                                    #38: 0x799d80a258  0x20a258              /apex/com.android.art/lib64/libart.so
2023-06-14 13:20:02.045 19720-19720 Find_Debug              pid-19720                            D  -----end-----

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

从log可以看到,几乎所有的分配都被捕获到了,而不仅仅是当前代码库中的分配。这对于framework工程师来讲,有很高的参考价值。

同样的,也将void printStackTrace(char *buffer, int size);函数的堆栈增加到50层。此处不在贴图,原理和上面一样。

接下来就是按照上一小节中的方法,使用shell脚本,直接按照时间过滤。得到久未释放的指针。然后查看其调用栈,获取具体的分配情况,以鉴别是否内存泄漏

至此。malloc hook介绍完毕。这个方法,要求在Android 9.0以上的版本才可以使用。为了能够在Android 9.0以下的版本中使用,在下一小节,我们将介绍malloc的另外的功能————malloc调试和libc的回调,以及DDMS图形工具进行查看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646092.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试工程师的核心价值是什么?23年“我“要进阶高级测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 与产品、研发相比…

面向制造业的IT管理

制造业的数字化和工业4.0技术的应用&#xff0c;使制造商能够优化生产&#xff0c;转变价值链&#xff0c;创造卓越的客户体验。随着行业采用新的技术&#xff0c;如人工智能(AI)、工业物联网(IIOT)和其他策略&#xff0c;制造商需要找到有效的方法来监控和管理他们的生产环境、…

一文彻底搞懂 Softmax 函数,数学原理分析和 PyTorch 验证

文章目录 1. Softmax 的定义2. Softmax 使用 e 的幂次的作用2.1 代码验证2.2 数学原理分析 3. 解决 Softmax 的数值溢出问题3.1 什么是数值溢出&#xff1f;3.2 解决数值上溢问题&#xff1a; x i − m a x ( x ) x_i-max(x) xi​−max(x)3.3 解决数值下溢问题&#xff1a;log_…

Python篇——数据结构与算法(第六部分:哈希表)

目录 1、直接寻址表 2、直接寻址表缺点 3、哈希 4、哈希表 5、解决哈希冲突 6、拉链法 7、常见哈希函数 8、哈希表的实现 8.1迭代器iter&#xff08;&#xff09;和__iter__ 8.2str&#xff08;&#xff09;和repr&#xff08;&#xff09; 8.3代码实现哈希表 8.4哈…

【数据库】Mysql数据库管理

文章目录 引言一、Mysql数据库管理1. 库和表2. 常用的数据类型3. char和varchar区别 二、SQL语句1. SQL语句分类2. 查看数据库结构3. DDL数据定义语言3.1 创建新的数据库3.2 创建新的表3.3 删除指定数据表3.4 删除指定数据库 4. DML数据操控语言4.1 向数据表中插入新的内容4.2 …

连以太网接口和串口傻傻分不清?看完本文就懂了

概要 路由器是一种网络设备&#xff0c;它的主要功能是在不同的网络之间转发数据包&#xff0c;实现网络互联。路由器根据数据包的目的地址&#xff0c;选择最佳的路径&#xff0c;将数据包发送到下一跳。路由器可以连接不同的网络类型&#xff0c;如以太网、帧中继、PPP等。 …

ChatGPT读PDF、生成思维导图的几种方案

大家好&#xff0c;我是可夫小子&#xff0c;《小白玩转ChatGPT》专栏作者&#xff0c;关注AIGC、读书和自媒体。 日常办公&#xff0c;我们离不开pdf文档读取&#xff0c;思维导图制作&#xff0c;那么ChatGPT能够给我们什么帮助呢&#xff1f; 通常的方法是&#xff1a;我们…

14、Nginx---缓存服务

一、缓存类型 1、服务器端缓存 2、代理缓存 3、客户端缓存 代理缓存的原理&#xff1a; 二、代理缓存配置语法 2.1、代理缓存路径 proxy_cache_path path [levelslevels] [use_temp_pathon|off] keys_zonename:size [inactivetime] [max_sizesize] [manager_filesnumber] [mana…

如何让你的allure报告测试步骤更清晰,更具吸引力?

引言 在软件测试中&#xff0c;清晰的测试步骤对于团队的协作和问题跟踪至关重要&#xff0c;Allure报告是一种强大的工具&#xff0c;能够将测试结果以直观和易于理解的方式呈现给您的团队和客户。 想要让Allure报告更具吸引力和可读性吗&#xff1f;那就不要错过我的精彩建…

MIT6.024学习笔记(三)——图论(2)

科学是使人变得勇敢的最好途径。——布鲁诺 文章目录 通信网络问题二叉树型直径路由器规模路由器数量拥挤程度 二维数组型直径路由器规模路由器数量拥挤程度 蝴蝶型直径路由器规模路由器数量拥挤程度 benes型直径路由器规模路由器数量拥挤 通信网络问题 在通信网络中&#xff…

Redis基础知识(安装基础指令等)

Redis 基础知识 相关资料 官网: https://redis.io/中文地址: http://redis.cn/下载地址: https://redis.io/download 为什么需要Redis 企业需求 高并发 高可用 高性能 海量用户 关系型数据库(如MySQL)-问题 性能瓶颈&#xff1a;磁盘IO 性能低下 扩展瓶颈&#xff1a…

Java基础小项目——【源码】控制台的类似BOSS招聘的一个应聘者用户和公司用户的就业项目【应聘+招聘】

目录 引出题目要求--云就业平台相关的java基础知识项目分层设计 核心业务图解源码总结 引出 类似BOSS招聘的一个应聘者用户和公司用户的就业项目&#xff0c;控制台项目 题目要求–云就业平台 类似BOSS招聘的一个应聘者用户和公司用户的就业项目 第3章 应用系统功能介绍 3…

【Jetpack】使用 Room Migration 升级数据库并导出 Schema 文件 ( Schema 文件简介 | 生成 Schema 文件配置 | 生成 Schema 文件过程 )

文章目录 一、Schema 文件简介二、生成 Schema 文件配置三、生成 Schema 文件过程1、数据库版本 1 - 首次运行应用2、数据库版本 1 升级至 数据库版本 2 - 第二次运行应用3、数据库版本 2 升级至 数据库版本 3 - 第三次运行应用 一、Schema 文件简介 使用 Room Migration 升级数…

Windows Subsystem for Android (WSA) 下载:在 Windows 11 上运行 Android 应用 (June 2023)

适用于 Android™️ 的 Windows 子系统&#xff0c;2023 年 6 月更新 请访问原文链接&#xff1a;https://sysin.org/blog/wsa/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 适用于 Android™️ 的 Windows 子系统使你的 Wi…

Linux系统之ifconfig命令的基本使用

Linux系统之ifconfig命令的基本使用 一、ifconfig命令介绍1. ifconfig简介2. ifconfig注意事项3. ifconfig命令特点 二、ifconfig命令的使用方法1. 查看ifconfig的帮助信息2. ifconfig的使用帮助 三、安装ifconfig命令工具1. 安装net-tools软件包2. 查看ifconfig工具的版本 四、…

至暗时刻,显卡销量腰斩,NVIDIA提前掏出2000元档4060救场

不知道大家有没有感觉&#xff0c;自从 RTX 40 系显卡面世后&#xff0c;玩家们对于装机热情却是反常理的不增反降。 以往每代新显卡出来&#xff0c;哪次不是掀起一阵装机热潮。 然而这次小忆听到最多的声音就是&#xff1a;手里 750Ti 还能再战、GTX 1060 永远滴神等。 当然…

pandas链式操作与SettingWithCopyWarning详解

1.SettingWithCopyWarning问题 SettingWithCopyWarning是pandas中一个经典问题&#xff0c;也是pandas库中位数不多的坑之一。关于这个问题&#xff0c;我们先看下面的一个例子。 import pandas as pddef t1():data {name: [a, b, c, d, e, f],num: [1, 2, 3, 4, 5, 6],ss: …

Linux系统命令与网络、磁盘参数和日志监控

文章目录 1、grep搜索命令2、wc命令3、 uptime机器启动时间负载4、ulimit用户资源5、scp远程拷贝6、dos2unix和unix2dos7、sed 1、grep搜索命令 grep命令用于在文件中搜索&#xff0c;并显示匹配效果 # 1、在指定文件查找&#xff0c;查找int main grep int main server.c# 2…

接口自动化测试丨如何处理 Header cookie

Cookie&#xff08;复数形态&#xff1a;Cookies&#xff09;是某些网站为了辨别用户身份而储存在用户本地终端上的数据。在接口测试过程中&#xff0c;如果网站采取了 Cookie 认证的方式&#xff0c;那么发送的请求需要附带 Cookie&#xff0c;才会得到正常的响应的结果。接口…

C#中List<T>的排序相关的使用方法总结

C#中List<>的排序相关的使用方法 list的排序一般使用Sort和LINQ的Orderby方法&#xff0c;本文主要介绍其如何使用。 &#x1f32e;1.Sort和实现Comparable接口 此方式需要类去实现IComparable接口 public class OrderTest {[Test]public void OraderTest(){List<E…