前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android内存篇(二)---JVMTI在Anroid8.1下的使用

Android内存篇(二)---JVMTI在Anroid8.1下的使用

作者头像
Vaccae
发布2022-05-25 09:04:38
5550
发布2022-05-25 09:04:38
举报
文章被收录于专栏:微卡智享

前言

上一篇《Android内存篇(一)---使用JVMTI监控应用》中已经介绍了Android的JVMTI内存监控,文章最后我也提到了,虽然代码中anroid8.0通过反射开启JVMTI的监控,但是项目中的代码并不能用,在JNI里C++报空指针的问题,也是因为自己的产品中用的Android设备正好是8.1的,实际使用时发现的这个问题,所以就有了这篇针对Android8,1的JVMTI使用

Android8.1运行错误

首先建了一个Android8.1的虚拟机,然后我们直接在虚拟机上运行JVMTI的Demo。

A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 22296 (e.memorymonitor), pid 22296 (e.memorymonitor)

可以看到直接报错signal 11,这个一般是C++中空指针造成。那我们就看一下代码,上面日志报错前的面LOG是输出了SetEventCallbacks,然后就报错了,那我们定位一下代码

代码语言:javascript
复制
extern "C"
JNIEXPORT void JNICALL
Java_pers_vaccae_memorymonitor_Monitor_attachInit(JNIEnv *env, jobject thiz, jstring path) {
    ALOGI("attachInit");

    const char *_path = env->GetStringUTFChars(path, NULL);

    ALOGI("mPackageName:%s", mPackageName.c_str());

    memoryFile = new MemoryFile(_path);

    //开启JVMTI事件监听
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.VMObjectAlloc = &objectAlloc;
    callbacks.MethodEntry = &methodEntry;

    ALOGI("SetEventCallbacks");
    //设置回调函数
    int error = mJvmtiEnv->SetEventCallbacks(&callbacks, sizeof(callbacks));
    ALOGI("返回码:%d\n", error);

    //开启监听
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, nullptr);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr);

    env->ReleaseStringUTFChars(path, _path);

    ALOGI("attachInit Finished");
}

从代码中可以看到,是执行了attachInit函数后,输出的SetEventCallbacks,下一步是执行完SetEventCallbacks后会输出得到的返回值,在日志中并没有输出反回值,那说明是执行SetEventCallbacks出现的异常,那我们就来看看mJvmtiEvent的有没有问题。

在代码中加入判断mJvmtiEnv是不是空的,然后再运行

输出的日志上面显示mJvmtiEnv是空的,那就找这个指针什么时候赋值的,从代码中可以看到,是开启JVMTI的agent时回调给赋值。

上图中可以看到,回调中指针赋值,并且下面的GetPotentialapabilities和AddCapabilities也都正常执行,说明赋值的是没有问题。

那我们从调用上来看,执行完初始化后,执行完attactAgent后接着执行的agentInit,唯一不同的就是Android8.1是采用反射的方式调用的,所以这里可以直接得出一个结论:通过反射回调后的方法给指针赋值,正常调用时是找不到指针指到的地址。后面做了几个测试后,也验证了这一结果,一个静态函数反射回调后改变值,正常输出还是原值,在反射中设置的函数回调可以正常显示到反射回调后得到的值。找到原因后,那我们就要改造代码了,这块改动比较大,所以把项目整个复制过来,重新修改。

代码实现

核心代码

改造Android8.1下能用的JVMTI最核心的两点:

  1. 初始化工作都放到Agent_OnAttach的回调函数中,不要另外再执行agentinit了,
  2. 变量直接在Agent_OnAttach中定义死,不能通过外面参数再修改了

native-lib.cpp

代码语言:javascript
复制
#pragma once

#include <jni.h>
#include <string>
#include <android/log.h>
#include <chrono>
#include "jvmti.h"
#include "MemoryFile.h"

#define LOG_TAG "jvmti"

#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

jvmtiEnv *mJvmtiEnv;
MemoryFile *memoryFile;
jlong tag = 0;

std::string mPackageName;

//查找过滤
jboolean findFilterObjectAlloc(const char *name) {
    std::string tmpstr = name;
    int idx;
    //先判断甩没有Error,有Error直接输出
    idx = tmpstr.find("OutOfMemory");
    if (idx == std::string::npos) {
        idx = tmpstr.find("vaccae");
        if (idx == std::string::npos)//不存在。
        {
            return JNI_FALSE;
        } else {
            return JNI_TRUE;
        }
    } else {
        return JNI_TRUE;
    }
}

//查找过滤
jboolean findFilterMethod(const char *name) {
    std::string tmpstr = name;
    int idx;
    //先判断甩没有Error,有Error直接输出
    idx = tmpstr.find("OutOfMemory");
    if (idx == std::string::npos) {
        idx = tmpstr.find("ryb/medicine/module_inventory");
        if (idx == std::string::npos)//不存在。
        {
            return JNI_FALSE;
        } else {
            return JNI_TRUE;
        }
    } else {
        return JNI_TRUE;
    }
}

// 获取当时系统时间
std::string GetCurrentSystemTime() {
    //auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    auto now = std::chrono::system_clock::now();
    //通过不同精度获取相差的毫秒数
    uint64_t dis_millseconds =
            std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()
            -
            std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count() * 1000;
    time_t tt = std::chrono::system_clock::to_time_t(now);
    struct tm *ptm = localtime(&tt);
    char date[60] = {0};
    sprintf(date, "%d-%02d-%02d %02d:%02d:%02d.%03d",
            (int) ptm->tm_year + 1900, (int) ptm->tm_mon + 1, (int) ptm->tm_mday,
            (int) ptm->tm_hour, (int) ptm->tm_min, (int) ptm->tm_sec, (int) dis_millseconds);
    return move(std::string(date));
}

//调用System.Load()后会回调该方法
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    //ALOGI("JNI_OnLoad");
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    //ALOGI("JNI_OnLoad Finish");
    return JNI_VERSION_1_6;
}

void JNICALL objectAlloc(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread,
                         jobject object, jclass object_klass, jlong size) {
    //给对象打tag,后续在objectFree()内可以通过该tag来判断是否成对出现释放
    tag += 1;
    jvmti_env->SetTag(object, tag);

    //获取线程信息
    jvmtiThreadInfo threadInfo;
    jvmti_env->GetThreadInfo(thread, &threadInfo);

    //获得 创建的对象的类签名
    char *classSignature;
    jvmti_env->GetClassSignature(object_klass, &classSignature, nullptr);


    if (findFilterObjectAlloc(classSignature)) {
        //写入日志文件
        char str[500];
        const char *format = "%s: object alloc {Thread:%s Class:%s Size:%lld Tag:%lld} \r\n";
        ALOGI(format, GetCurrentSystemTime().c_str(), threadInfo.name, classSignature, size, tag);
        sprintf(str, format, GetCurrentSystemTime().c_str(), threadInfo.name, classSignature, size,
                tag);
        //ALOGI("file:%s", MemoryFile::m_path.c_str());

        MemoryFile::Write(str, sizeof(char) * strlen(str));
        //}
        jvmti_env->Deallocate((unsigned char *) classSignature);
    }
}

void JNICALL methodEntry(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jmethodID method) {
    jclass clazz;
    char *signature;
    char *methodName;

    //获得方法对应的类
    jvmti_env->GetMethodDeclaringClass(method, &clazz);
    //获得类的签名
    jvmti_env->GetClassSignature(clazz, &signature, nullptr);
    //获得方法名字
    jvmti_env->GetMethodName(method, &methodName, nullptr, nullptr);


    if (findFilterObjectAlloc(signature)) {
        //写日志文件
        char str[500];
        char *format = "%s: methodEntry {%s %s} \r\n";
        ALOGI(format, GetCurrentSystemTime().c_str(), signature, methodName);
        sprintf(str, format, GetCurrentSystemTime().c_str(), signature, methodName);

        MemoryFile::Write(str, sizeof(char) * strlen(str));
    }

    jvmti_env->Deallocate((unsigned char *) methodName);
    jvmti_env->Deallocate((unsigned char *) signature);
}


//初始化工作
extern "C"
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
    int error;
    //准备JVMTI环境
    vm->GetEnv((void **) &mJvmtiEnv, JVMTI_VERSION_1_2);

    std::string path = "/data/user/0/pers.vaccae.memorymonitor/files/log/" +
                       GetCurrentSystemTime() + ".log";
    //初始化
    MemoryFile::Init(path.c_str());

    //开启JVMTI的能力
    jvmtiCapabilities caps;
    mJvmtiEnv->GetPotentialCapabilities(&caps);
    mJvmtiEnv->AddCapabilities(&caps);

    //开启JVMTI事件监听
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    //callbacks.VMObjectAlloc = &objectAlloc;
    callbacks.MethodEntry = &methodEntry;

    ALOGI("SetEventCallbacks");
    //设置回调函数
    error = mJvmtiEnv->SetEventCallbacks(&callbacks, sizeof(callbacks));
    ALOGI("返回码:%d\n", error);

    //开启监听
//    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, nullptr);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr);


    ALOGI("Agent_OnAttach Finish");
    return JNI_OK;
}

Moniter中也直接执行attachAgent函数,去掉了agentinit。

实现效果

改完后,我们来看一下运行结果

重新运行后,可以看到jvmti中写入了方法OutOfMemoryError的记录,因为我在MainActivity中加入了Try Catch,所以异常也捕获到了。捕获OOM的方法可以看《Android中关于OOM的捕获的方法》。

相应的日志文件也写入成功了,我们拷贝出来打开再看一下

日志也都存到文件里了,这样Android8.1的JVMTI日志监控也可以实现了。

源码地址

https://github.com/Vaccae/AndroidJVMTIDemo.git

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • Android8.1运行错误
      • 代码实现
        • 核心代码
          • 实现效果
            • 源码地址
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档