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

news2024/11/23 0:33:05

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/667729.html

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

相关文章

正运动即将亮相2023年深圳激光展,助力个性化激光智能制造!

■展会名称&#xff1a; 第⼗六届深圳国际激光与智能装备、光子技术博览会&#xff08;以下简称“深圳激光展”&#xff09; ■展会日期 2023年6月27日-29日 ■展馆地点 深圳国际会展中心&#xff08;宝安新馆&#xff09; ■展位号 9D115 激光加工是一种基于光热效应的…

STM32的中断系统详解(嵌入式学习)

中断系统 1. 基本概念2. 中断的意义3. 中断处理过程处理过程过程详述 4. 中断体系结构5. NVIC概念主要功能 6. EXTI概念主要功能结构框图中断和事件的区别 7. 总结 1. 基本概念 中断是处理器中的一种机制&#xff0c;用于响应和处理突发事件或紧急事件。当发生中断时&#xff…

每日学术速递6.9

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Segment Anything in High Quality 标题&#xff1a;以高质量分割任何内容 作者&#xff1a;Lei Ke, Mingqiao Ye, Martin Danelljan, Yifan Liu, Yu-Wing Tai, Chi-Keung Tang, …

Reids分布式锁详细介绍原理和实现

Reids 分布式锁 问题描述 1、单体单机部署的系统被演化成分布式集群系统后 2、由于分布式系统多线程、多进程并且分布在不同机器上&#xff0c;这将使原单机部署情况下的并发控制锁策略失效 3、单纯的Java API 并不能提供分布式锁的能力 4、为了解决这个问题就需要一种跨J…

abd shell后,getevent退出方法

abd shell后&#xff0c;getevent退出方法 输入 exit 然后回车退出

一种很新的交互式智能标注技术

随着人工智能应用的大规模落地&#xff0c;数据标注市场在高速增长的同时&#xff0c;也面临着标注成本的挑战。据IDC报告显示&#xff1a;数据标注在AI应用开发过程中所耗费的时间占到了25%&#xff0c;部分医学类应用一条数据的标注成本甚至高达20元。数据精度的高要求、强人…

RocketMQ 环境搭建

环境&#xff1a;linux&#xff08;centos&#xff09; 或 windos&#xff1b; jdk 1.8 场景&#xff1a;rocket入门学习 时间&#xff1a;2023-04-20 吐槽&#xff1a;可能是本人学习能力不足&#xff0c;想使用docker搭建rocketmq 一直失败&#xff0c;可能是我想使用的比较新…

正排倒排,并不是 MySQL 的排序的全部!

引言 一个悠闲的上午&#xff0c;小航送了我&#xff0c;一袋坚果&#xff0c;他看我吃的正香&#xff0c;慢慢问道&#xff1a;”温哥&#xff0c;mysql的排序&#xff0c;有什么要注意的吗&#xff0c;不就是正排倒排吗&#xff1f;” 我一听他问我的问题&#xff0c;顿感坚…

软件测试简历如何包装?

首先明确的包装简历不等于欺骗&#xff0c;只是把你的最好一面展示出来&#xff0c;给别人一个好的映像&#xff1b;&#xff08;就相当于相亲&#xff0c;哈哈&#xff09; 无论如何包装简历&#xff0c;注意简历上的东西一定要会、一定要会、一定要会&#xff08;面试官一般…

Java框架-Spring

文章目录 1、你了解Spring IOC吗&#xff1f;2、SpringIOC的应用&#xff1f;3、SpringIOC的getBean方法的解析&#xff1f;4、面试题5、你了解Spring AOP吗&#xff1f;6、事务ACID特性7、事务传播 1、你了解Spring IOC吗&#xff1f; IoC&#xff08;Inversion of control&a…

C++编程启蒙-2——你适合学习编程吗?

英语差&#xff0c;数学孬&#xff0c;照样可以学好编程。但&#xff0c;如果你逻辑思维差&#xff0c;动力能力弱&#xff0c;那么学习编程真的会难上加难。本课用来帮助读者实现对逻辑思维与动手能力的自我判断&#xff0c;并给出了实际测试方案。 英语差&#xff0c;数学孬&…

15个常见的AI绘画网站推荐

无论你是专业的艺术家还是对人工智能绘画感兴趣的普通人&#xff0c;AI绘画网站都可以为你提供新的创作灵感和艺术体验&#xff0c;给艺术界带来更多的创新和可能性。以下是15个常见的AI绘画网站的介绍。 即时 AI 灵感 「即时 AI 灵感」是通过文字描述等方式生成精致图像的AI…

QGIS实现shape、geojson数据的矢量切片教程

能够实现矢量切片的办法有很多&#xff0c;可以使用geoserver&#xff0c;可以使用qgis&#xff0c;当然也可以自己写代码实现。这篇文章我们来介绍一下如何使用qgis完成shape数据的矢量切片。 首先我们还是要准备一份矢量数据。矢量数据的格式是shape文件或者是geojson文件都…

IDEA下载安装与使用

IDEA下载、安装与概述、使用 IDEA全称InteliJ IDEA&#xff0c;是用于Java语言开发的集成环境&#xff0c;它是业界公认的目前用于Java程序开发最好的工具 集成环境&#xff1a;把代码编写、编译、执行、调试等多种功能综合到一起的开发工具 1 IDEA的下载 官网链接&#xf…

第一章 基础算法(二)——高精度,前缀和与差分

文章目录 高精度运算高精度加法高精度减法高精度乘法高精度除法 前缀和二维前缀和 差分二维差分 高精度练习题791. 高精度加法792. 高精度减法793. 高精度乘法794. 高精度除法 前缀和练习题795. 前缀和796. 子矩阵的和 差分练习题797. 差分798. 差分矩阵 高精度运算 两个大数做…

Day37

思维导图 练习 1> 编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 a、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 b、和当前用户说“hello 用户名” c、显示您的机器名 hostname d、显示上一级目录中的所有文件的列表 e、显示…

Git 多账号多仓库配置 SSH

前言 在我们使用 Git 中&#xff0c;有时候会遇到多账号多仓库的情况&#xff0c;比如公司的 GitLab 和 GitHub&#xff0c;以及自己的 GitHub&#xff0c;这时候我们就需要配置多个 SSH 密钥来区分不同的账号和仓库 生成 SSH 密钥 根据你注册仓库的邮箱生成 SSH 密钥&#…

Kubeadm方式搭建K8s集群 1.27.0版本

目录 一、集群规划 二、系统初始化准备(所有节点同步操作) 三、安装并配置cri-docker插件 四、安装kubeadm&#xff08;所有节点同步操作&#xff09; 五、初始化集群 六、Node节点添加到集群 七、安装网络组件Calico 八、测试codedns解析可用性 一、集群规划 环境规划…

Qt 定时器

定时器事件---timerEvent 定时器启动 startTimer(); 注意&#xff1a;定时器参数要是全局或者静态变量。 定时器结束 没有结束函数 定时器标志---timerId int timerIdstartTimer(); startTimer()返回定时器标志 需设置为类的成员--类内使用 定时器使用 通过定时器事件…

设计模式介绍

设计模式的分类 总体来说设计模式分为三大类&#xff1a; 创建型模式&#xff0c;共五种&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式&#xff0c;共七种&#xff1a;适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式…