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

2022-05-25 09:04:38 浏览数 (1)

前言

上一篇《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("返回码:%dn", 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-d-d d:d:d.d",
            (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} rn";
        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} rn";
        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("返回码:%dn", 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

0 人点赞