Java生态中有一些非常规的技术,它们能达到一些特别的效果。这些技术的实现原理不去深究的话一般并不是广为人知。这种技术通常被称为黑科技。而这些黑科技中的绝大部分底层都是通过JVMTI实现的。形象地说,JVMTI是Java虚拟机提供的一整套后门。通过这套后门可以对虚拟机方方面面进行监控,分析。甚至干预虚拟机的运行
但是Android的ART虚拟机是没有这个功能的,幸运的是从Android9.0开始,Android ART已经加入了JVMTI的相关功能了。目录位于art/openjdkjvmti下,编译生成的是libopenjdkjvmtid.so这个so文件。其中核心文件是jvmti.h文件,里面定义了一些核心方法,和结构体。
jvmti 环境的结构体 _jvmtiEnv
struct _jvmtiEnv {
const struct jvmtiInterface_1_ *functions;
#ifdef __cplusplus
jvmtiError Allocate(jlong size,unsigned char** mem_ptr) {
return functions->Allocate(this, size, mem_ptr);
}
jvmtiError Deallocate(unsigned char* mem) {
return functions->Deallocate(this, mem);
}
...
jvmtiEventCallbacks jvmti的事件的回调接口的结构体,jvmtiEventCallbacks
typedef struct {
.....
jvmtiEventBreakpoint Breakpoint;
/* 63 : Field Access */
jvmtiEventFieldAccess FieldAccess;
/* 64 : Field Modification */
jvmtiEventFieldModification FieldModification;
/* 65 : Method Entry */
jvmtiEventMethodEntry MethodEntry;
/* 66 : Method Exit */
jvmtiEventMethodExit MethodExit;
/* 67 : Native Method Bind */
jvmtiEventNativeMethodBind NativeMethodBind;
/* 68 : Compiled Method Load */
jvmtiEventCompiledMethodLoad CompiledMethodLoad;
/* 69 : Compiled Method Unload */
jvmtiEventCompiledMethodUnload CompiledMethodUnload;
/* 70 : Dynamic Code Generated */
jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;
/* 71 : Data Dump Request */
.....
} jvmtiEventCallbacks;
里面有几个重要的方法,
RetransformClasses 插桩class和RedefineClasses 重新定义class,插桩和重新定义,是不是可以通过这两个方法动态修改运行时的文件,从而实现比如热修复功能等呢?
确实是这样的,我们不重复造轮子了,
参考dodola的https://github.com/AndroidAdvanceWithGeektime/JVMTI_Sample开源项目,我们看下,怎么实现的这个功能,以及部分原理。
(一)先看效果是什么样子的,页面很简单,首先点击启动JVMTI_Agent,然后点击修改Activity Class,最后点击启动Activity,
看下打印,在Activity启动的时候,
02-17 15:42:34.658 31986 31986 D JVMTIHelper: _____________________android.app.Activity#onCreate(android.os.Bundle)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: java.lang.Exception
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at com.dodola.jvmtilib.JVMTIHelper.printEnter(JVMTIHelper.java:72)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.Activity.onCreate(Unknown Source:3)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at com.dodola.jvmti.Main2Activity.onCreate(Main2Activity.java:10)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.Activity.performCreate(Activity.java:7136)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.Activity.performCreate(Activity.java:7127)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2907)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3062)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1819)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.os.Handler.dispatchMessage(Handler.java:106)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.os.Looper.loop(Looper.java:206)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at android.app.ActivityThread.main(ActivityThread.java:6683)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at java.lang.reflect.Method.invoke(Native Method)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:501)
02-17 15:42:34.658 31986 31986 D JVMTIHelper: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
在android.app.Activity.onCreate(Unknown Source:3) 之后调用了
com.dodola.jvmtilib.JVMTIHelper.printEnter(JVMTIHelper.java:72)
这个方法,从而实现了对Activity的onCreate方法的一个插桩操作,神奇吧!
(二) 源码简易分析一下
1 点击启动JVMTI_Agent的时候,
调用
JVMTIHelper.init(MainActivity.this);
然后调用
Debug.attachJvmtiAgent(agentLibSo.getAbsolutePath(),null, classLoader);
看下这个方法原型,第一个参数是library,也就是包含agent代理的so文件的路径,第二个参数是配置选项,第三个参数是类加载器。
/**
* Attach a library as a jvmti agent to the current runtime, with the given classloader
* determining the library search path.
* <p> * Note: agents may only be attached to debuggable apps. Otherwise, this function will
* throw a SecurityException.
*
* @param library the library containing the agent.
* @param options the options passed to the agent.
* @param classLoader the classloader determining the library search path.
*
* @throws IOException if the agent could not be attached.
* @throws SecurityException if the app is not debuggable.
*/
public static void attachJvmtiAgent(@NonNull String library, @Nullable String options,
@Nullable ClassLoader classLoader) throws IOException {
Preconditions.checkNotNull(library);
Preconditions.checkArgument(!library.contains("="));
if (options == null) {
VMDebug.attachAgent(library, classLoader);
} else {
VMDebug.attachAgent(library + "=" + options, classLoader);
}
}
我们这里第二个参数是null,所以调用
VMDebug.attachAgent(library, classLoader);
VMDebug类位于libcore/dalvik/src/main/java/dalvik/system/VMDebug.java
/**
* Attaches an agent to the VM.
*
* @param agent The path to the agent .so file plus optional agent arguments.
* @param classLoader The classloader to use as a loading context.
*/
public static void attachAgent(String agent, ClassLoader classLoader) throws IOException {
nativeAttachAgent(agent, classLoader);
}
然后调用art/runtime/native/dalvik_system_VMDebug.cc中的
VMDebug_nativeAttachAgent
static void VMDebug_nativeAttachAgent(JNIEnv* env, jclass, jstring
agent, jobject classloader) {
if (agent == nullptr) {
ScopedObjectAccess soa(env);
ThrowNullPointerException("agent is null");
return;
}
if (!Dbg::IsJdwpAllowed()) {
ScopedObjectAccess soa(env);
ThrowSecurityException("Can't attach agent, process is not
debuggable.");
return;
}
std::string filename;
{
ScopedUtfChars chars(env, agent);
if (env->ExceptionCheck()) {
return;
}
filename = chars.c_str();
}
Runtime::Current()->AttachAgent(env, filename, classloader);
}
也就是通过Art虚拟机的运行时去AttachAgent,第二个参数即是我们传入的library路径。
忽略一些中间步骤,最终调用art/runtime/ti/agent.cc#PopulateFunctions
void Agent::PopulateFunctions() {
onload_ = reinterpret_cast<AgentOnLoadFunction>(FindSymbol
(AGENT_ON_LOAD_FUNCTION_NAME));
if (onload_ == nullptr) {
VLOG(agents) << "Unable to find 'Agent_OnLoad' symbol in " << this;
}
onattach_ = reinterpret_cast<AgentOnLoadFunction>(FindSymbol
(AGENT_ON_ATTACH_FUNCTION_NAME));
if (onattach_ == nullptr) {
VLOG(agents) << "Unable to find 'Agent_OnAttach' symbol in " << this;
}
onunload_ = reinterpret_cast<AgentOnUnloadFunction>(FindSymbol
(AGENT_ON_UNLOAD_FUNCTION_NAME));
if (onunload_ == nullptr) {
VLOG(agents) << "Unable to find 'Agent_OnUnload' symbol in " << this;
}
}
通过从我们加载的library文件中去FindSymbol,然后执行。
我们看看开源代码中这个Symbol,他用了哪一个,原来用了这个Agent_OnAttach方法,然后看看他做了什么,初始化了一个jvmtiEnv的环境,然后设置了一些callback回调。
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char
*options, void *reserved) {
jvmtiEnv *jvmti_env = CreateJvmtiEnv(vm);
if (jvmti_env == nullptr) {
return JNI_ERR;
}
localJvmtiEnv = jvmti_env;
SetAllCapabilities(jvmti_env);
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = &ClassTransform;
callbacks.VMObjectAlloc = &ObjectAllocCallback;
callbacks.NativeMethodBind = &JvmTINativeMethodBind;
callbacks.GarbageCollectionStart = &GCStartCallback;
callbacks.GarbageCollectionFinish = &GCFinishCallback;
int error = jvmti_env->SetEventCallbacks(&callbacks,
sizeof(callbacks));
SetEventNotification(jvmti_env, JVMTI_ENABLE,
JVMTI_EVENT_GARBAGE_COLLECTION_START);
SetEventNotification(jvmti_env, JVMTI_ENABLE,
JVMTI_EVENT_GARBAGE_COLLECTION_FINISH);
SetEventNotification(jvmti_env, JVMTI_ENABLE,
JVMTI_EVENT_NATIVE_METHOD_BIND);
SetEventNotification(jvmti_env, JVMTI_ENABLE,
JVMTI_EVENT_VM_OBJECT_ALLOC);
SetEventNotification(jvmti_env, JVMTI_ENABLE,
JVMTI_EVENT_OBJECT_FREE);
SetEventNotification(jvmti_env, JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK);
ALOGI("==========Agent_OnAttach=======");
return JNI_OK;
}
这里设置了一个event_callbacks,在执行jvmti操作时候,会回调这个callback
static jvmtiError SetEventCallbacks(jvmtiEnv* env,
const jvmtiEventCallbacks* callbacks,
jint size_of_callbacks) {
ENSURE_VALID_ENV(env);
if (size_of_callbacks < 0) {
return ERR(ILLEGAL_ARGUMENT);
}
if (callbacks == nullptr) {
ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks.reset();
return ERR(NONE);
}
// Lock the event_info_mutex_ while we replace the callbacks.
ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(env);
art::WriterMutexLock lk(art::Thread::Current(), art_env->event_info_mutex_);
std::unique_ptr<ArtJvmtiEventCallbacks> tmp(new ArtJvmtiEventCallbacks());
// Copy over the extension events.
tmp->CopyExtensionsFrom(art_env->event_callbacks.get());
// Never overwrite the extension events.
size_t copy_size = std::min(sizeof(jvmtiEventCallbacks),
static_cast<size_t>(size_of_callbacks));
copy_size = art::RoundDown(copy_size, sizeof(void*));
// Copy non-extension events.
memcpy(tmp.get(), callbacks, copy_size);
// replace the event table.
art_env->event_callbacks = std::move(tmp);
return ERR(NONE);
}
OK,这就是大体分析了初始化过程,忽略了很多Art层的代码。
2 点击修改Activity class,
JVMTIHelper.retransformClasses(new Class[]{Activity.class});
然后调用我们加载的library文件中的
extern "C" JNIEXPORT void JNICALL retransformClasses(JNIEnv *env,
jclass clazz,
jobjectArray
classes) {
jsize numTransformedClasses = env->GetArrayLength(classes);
jclass *transformedClasses = (jclass *) malloc(
numTransformedClasses * sizeof(jclass));
for (int i = 0; i < numTransformedClasses; i++) {
transformedClasses[i] = (jclass) env->NewGlobalRef(env->
GetObjectArrayElement(classes, i));
} ALOGI("==============retransformClasses ===============");
jvmtiError error = localJvmtiEnv->RetransformClasses(
numTransformedClasses,transformedClasses);
for (int i = 0; i < numTransformedClasses; i++) {
env->DeleteGlobalRef(transformedClasses[i]);
}
free(transformedClasses);
}
然后调用了
localJvmtiEnv->RetransformClasses(numTransformedClasses, transformedClasses);
其中第一个参数是插桩类的数量,第二个参数是插桩类,这就进入了Art中jvmti的世界,忽略一些步骤
在这之前回调了上文的callbacks,我们重点看下,ClassTransform,这个方法通过在class load时候,如果是android/app/Activity类,则对遍历这个类的所有方法,找到onCreate方法,然后在这个方法里添加了几条指令(
OP_INVOKE_STATIC),去执行一个JVMTIHelper类的静态方法public static void printEnter(...)
callbacks.ClassFileLoadHook = &ClassTransform;
static void
ClassTransform(jvmtiEnv *jvmti_env,
JNIEnv *env,
jclass classBeingRedefined,
jobject loader,
const char *name,
jobject protectionDomain,
jint classDataLen,
const unsigned char *classData,
jint *newClassDataLen,
unsigned char **newClassData) {
if (!strcmp(name, "android/app/Activity")) {
if (loader == nullptr) {
ALOGI("==========bootclassloader=============");
}
ALOGI("==========ClassTransform %s=======", name);
struct Allocator : public Writer::Allocator {
virtual void *Allocate(size_t size) { return ::malloc(size); }
virtual void Free(void *ptr) { ::free(ptr); }
};
Reader reader(classData, classDataLen);
reader.CreateClassIr(0);
std::shared_ptr<ir::DexFile> dex_ir = reader.GetIr();
ir::Builder b(dex_ir);
for (auto &method : dex_ir->encoded_methods) {
std::string type = method->decl->parent->Decl();
ir::String *methodName = method->decl->name;
std::string prototype = method->decl->prototype->Signature();
ALOGI("==========ClassTransform method %s=======", methodName->c_str());
if (!strcmp("onCreate", methodName->c_str()) &&
!strcmp(prototype.c_str(), "(Landroid/os/Bundle;)V")) {
ALOGI("==========Method modify %s %s=======", methodName->c_str(),
prototype.c_str());
CodeIr c(method.get(), dex_ir);
int originalNumRegisters = method->code->registers -
method->code->ins_count;//v=寄存器p+v数量减去入参p,此例子中v=3 p=2
int numAdditionalRegs = std::max(0, 1 - originalNumRegisters);
int thisReg = numAdditionalRegs + method->code->registers
- method->code->ins_count;
ALOGI("origin:%d addreg:%d", originalNumRegisters, numAdditionalRegs);
if (numAdditionalRegs > 0) {
c.ir_method->code->registers += numAdditionalRegs;
}
ir::Type *stringT = b.GetType("Ljava/lang/String;");
ir::Type *activityT = b.GetType("Landroid/app/Activity;");
ir::Type *jvmtiHelperT = b.GetType("Lcom/dodola/jvmtilib/JVMTIHelper;");
ir::Type *voidT = b.GetType("V");
std::stringstream ss;
ss << method->decl->parent->Decl() << "#" << method->decl->name->c_str() << "(";
bool first = true;
if (method->decl->prototype->param_types != NULL) {
for (const auto &type : method->decl->prototype->param_types->types) {
if (first) {
first = false;
} else {
ss << ",";
}
ss << type->Decl().c_str();
}
}
ss << ")";
std::string methodDescStr = ss.str();
ir::String *methodDesc = b.GetAsciiString(methodDescStr.c_str());
//该例子中不影响原寄存器数量
lir::Instruction *fi = *(c.instructions.begin());
VReg *v0 = c.Alloc<VReg>(0);
VReg *v1 = c.Alloc<VReg>(1);
VReg *v2 = c.Alloc<VReg>(2);
VReg *thiz = c.Alloc<VReg>(thisReg);
addInstr(c, fi, OP_MOVE_OBJECT, {v0, thiz});
addInstr(c, fi, OP_CONST_STRING,
{v1, c.Alloc<String>(methodDesc, methodDesc->orig_index)});//对于插入到前面的指令来说
addCall(b, c, fi, OP_INVOKE_STATIC, jvmtiHelperT, "printEnter", voidT,
{activityT, stringT},
{0, 1});
c.Assemble();
Allocator allocator;
Writer writer2(dex_ir);
jbyte *transformed(
(jbyte *) writer2.CreateImage(&allocator,
reinterpret_cast<size_t *>(newClassDataLen)));
jvmti_env->Allocate(*newClassDataLen, newClassData);
std::memcpy(*newClassData, transformed, *newClassDataLen);
break;
}
}
}
}
会调用到art/openjdkjvmti/ti_redefine.cc中
jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env,
art::Runtime* runtime,
art::Thread* self,
const std::vector<ArtClassDefinition>& definitions,
std::string* error_msg) {
DCHECK(env != nullptr);
if (definitions.size() == 0) {
// We don't actually need to do anything. Just return OK.
return OK;
}
// Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
// are going to redefine.
art::jit::ScopedJitSuspend suspend_jit;
// Get shared mutator lock so we can lock all the classes.
art::ScopedObjectAccess soa(self);
Redefiner r(env, runtime, self, error_msg);
for (const ArtClassDefinition& def : definitions) {
// Only try to transform classes that have been modified.
if (def.IsModified()) {
jvmtiError res = r.AddRedefinition(env, def);
if (res != OK) {
return res;
}
}
}
return r.Run();
}
最后执行了一个Run方法,因为我们通过callbacks可以对这个系统类的进行一些修改,所以通过执行一个UpdateClass方法,然后执行
UpdateMethods和UpdateFields方法,对Art运行时的这些数据结构进行更新。
void Redefiner::ClassRedefinition::UpdateClass(
art::ObjPtr<art::mirror::Class> mclass,
art::ObjPtr<art::mirror::DexCache> new_dex_cache,
art::ObjPtr<art::mirror::Object> original_dex_file) {
DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0);
UpdateMethods(mclass, class_def);
UpdateFields(mclass);
3 点击启动Activity
最后启动一个新的Activity,走到onCreate的时候,会调用里面的JVMTIHelper类的静态方法public static void printEnter(...)
我们这样就是实现了对Activity类的插桩操作。
(三)JVMTI的其他应用
使用JVMTI对class文件加密
使用JVMTI实现应用性能监控(APM)
产品运行时错误监测及调试
热加载等等。。