前言
上一篇《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最核心的两点:
- 初始化工作都放到Agent_OnAttach的回调函数中,不要另外再执行agentinit了,
- 变量直接在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
完