利用Android9.0虚拟机的JVMTI技术实现一些黑科技

        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)

产品运行时错误监测及调试

热加载等等。。

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值