ARTICLE · 1006378
工具源码分析——Xposed原理
这是我第一次分析工具的原理并阅读源码,也是第一次接触 Zygote 进程,感觉自己开发能力很欠缺,尤其是阅读
ArtMethod时,每一行代码都看不懂,更别说合起来看了。但好处是,自己对看工具源码这件事不恐惧了。因为有些地方是AI生成的,而且源码我不太看得懂,如果有错误,麻烦在评论区指出来。
虽然在家里,但是最近每天都很忙,只能抽时间学习
一、源码分析
AOSP源码版本: android-8.0.0_r28
android 系统
这是经典的android架构图,Zygote进程是init创建的其中一个进程,用于创建安卓运行时虚拟机art或Dalvik、创建提供系统服务的System Server进程、以及孵化用户进程。
加载Zygote进程是init进程的作用之一:
FirstStageMain()/SecondStageMain()/SetupSelinux()这种明确的两阶段拆分是 Android 10 (Q, API 29) 左右才引入的。在 Android 8.0 中,init 进程只有一个统一的入口。
1091 parser.ParseConfig("/init.rc"); // ← 解析 /init.rcinit.rc 是启动脚本资源文件,放在同仓库的 system/core/rootdir/ 下。
init 解析 rc 文件时,service zygote 这一行后面的所有 token 会被完整地保存到 args_ 中。详情请看system/core/init/service.cpp。
app_main 负责初始化 ART/Dalvik 虚拟机并加载 ZygoteInit 类,从而创建出 Zygote 进程。
init (解析 .rc 文件) │ execv("/system/bin/app_process64", args) ▼app_process main() ← app_main.cpp │ ├─ 1. 创建 AppRuntime(继承 AndroidRuntime,承载 ART 虚拟机) ├─ 2. 解析命令行参数 ├─ 3. setArgv0() 改写进程名(/system/bin/app_process64 → zygote) │ ├─ Zygote 模式 ──────────────► runtime.start("com.android.internal.os.ZygoteInit", ...) │ ├─ startVm() 启动 ART 虚拟机 │ └─ JNI 调用 ZygoteInit.main() │ → fork 出 SystemServer + 所有应用进程 │ └─ Application 模式 ─────────► runtime.start(className, ...) 直接运行指定 Java 类的 main() (CLASSPATH 下指定的 jar,如 cmd/am/pm 等 shell 工具)这是解析命令行参数的代码:根据是否有--zygote决定是否进入Zygote 模式 。
runtime.start负责创建java虚拟机,并且执行传入类中的main 方法。
if (zygote) {344 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);345 } elseif (className) {346 runtime.start("com.android.internal.os.RuntimeInit", args, zygote);347 } rovo89:xposed
用自己的话总结一遍 Xposed 流程:
Xposed 替换 app_process文件,通过AndroidRuntime::start()函数启动 Xposed 自定义的 Zytoge 进程,调用类里面的main函数。main函数完成了这几件事情:系统资源加载的逻辑进行 Hook , 使Xposed 能替换字符串等 APK 资源。 对系统核心方法hook,使 Zytogefork的进程会自带这些 Hook。加载所有已激活的模块,并将代码注入到 Zygote 中。
用户在 hook 函数时,Xposd 完成了以下事情:
首先封装一个对象additionalInfo, 保存该方法的回调集合,参数类型以及返回类型。 然后调用hookMethodNative方法,获取该方法的ArtMethod对象,并进行 Hook。
Hook原理:Xposed 修改了 Java 函数的执行流: 查找函数的 ArtMethod 方法 ->函数运行到 Quick 入口 -> 函数被劫持到 Xposed c++ 代理函数 -> 函数执行结果返回到 Java 。具体细节如下:
创建 hook_info结构体,包括 Java 层的 Method 对象、additionalInfo以及原方法的备份。Xposed 将 hook_info结构体放在 ART 虚拟机执行方法的JNI入口(entry_point_from_jni_字段),这是因为 Xposed 修改后的函数执行流程不会将JNI入口解析为JNI函数。之后,通过执行ArtMethod类的GetEntryPointFromJniPtrSize()函数完成hook_info的反查。Xposed 代理函数通过调用 CallStaticObjectMethod函数完成 hook,流程如下:
首先执行 beforeHookedMethod回调然后通过 originalMethod调用原始方法最后执行 afterHookedMethod回调返回结果
另外,由于 Xposed 使用偏移量解析 ArtMethod 类,如果类的结构发生改变,那么 Xposed 就要额外维护一个偏移量。
以下是 Xposed 流程的详细分析:
通过背诵八股文,我们知道:Xposed 通过替换系统的 app_process,让 Zygote 在启动应用进程时自动注入 Xposed 框架代码。这是通过修改 Android.mk实现的。
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) LOCAL_SRC_FILES := app_main2.cpp # Android5.0 + LOCAL_MULTILIB := both LOCAL_MODULE_STEM_32 := app_process32_xposed LOCAL_MODULE_STEM_64 := app_process64_xposedelse LOCAL_SRC_FILES := app_main.cpp LOCAL_MODULE_STEM := app_process_xposedendif如果xposed成功加载,则使用runtimeStart运行类de.robv.android.xposed.XposedBridge。
if (zygote) { isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv); runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote); } elseif (className) { isXposedLoaded = xposed::initialize(false, false, className, argc, argv); runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote); }// #define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge"有一个问题:Xposed 是如何通过runtime.Start调用xposedBridge项目中的类呢?
结论是:只要 FindClass 能找到这个类(且类中存在 static void main(String[] args) 方法),runtime.start 就能启动它。 类可以位于 boot classpath,也可以位于 CLASSPATH 指定的 jar/apk 中 。
从 xposed::initalize 步进,这个函数用来加载jar文件:
#define XPOSED_JAR "/system/framework/XposedBridge.jar"
上面的图片相当于执行这段代码,将jar文件写入 CLASSPATH 字符串中。在之后的startVm里,ART 用 CLASSPATH 的内容 new 出了一个 PathClassLoader 实例。
setenv("CLASSPATH", "/system/framework/XposedBridge.jar", 1); // 前插Xposed 中app_process执行流程如下:
app_process (Xposed) main()│├─ ① setenv("CLASSPATH", "/system/framework/XposedBridge.jar")│ 只是写入环境块,此刻零加载│└─ ② runtime.start(...) │ ├─ startVm() → JNI_CreateJavaVM │ ├─ 读 BOOTCLASSPATH → 装配 boot classpath(供 BootClassLoader 用) │ ├─ 读 CLASSPATH → 写入系统属性 java.class.path │ └─ VM 初始化时调用 libcore 的工厂方法: │ │ // libcore: java/lang/ClassLoader.java │ static ClassLoader createSystemClassLoader(){ │ String classPath = System.getProperty("java.class.path"); │ returnnew PathClassLoader("", classPath, │ BootClassLoader.getInstance()); │ } │ │ ↑ 关键一行:这个实例被 VM 持有为“系统类加载器” │ (即 ClassLoader.getSystemClassLoader() 的返回值) │ ├─ env->FindClass("de/robv/.../XposedBridge") │ 无调用帧 → 用系统类加载器查找 │ → 此刻才真正打开 jar 里的 dex、defineClass → 命中 └─ 调用 XposedBridge.main()rovo89:xposedBridge
这是xposedBridge的main函数。
protectedstaticvoidmain(String[] args){try {if (!hadInitErrors()) { initXResources(); SELinuxHelper.initOnce(); SELinuxHelper.initForProcess(null); runtime = getRuntime(); XPOSED_BRIDGE_VERSION = getXposedVersion();if (isZygote) {// XposedInit.hookResources();// 系统级别的核心类和方法进行 Hook XposedInit.initForZygote(); }//它会读取模块列表,并将每个模块的 APK 加载到 Zygote 进程的内存中 XposedInit.loadModules(); } }hookResources的目的是劫持应用的 Resources 对象,用自定义的 XResources 子类替换系统默认的 Resources 。 由于不同的版本的Andorid API 不一样,所以不同版本中,获取关键类和 Hook 的方式不尽相同。
以 Android 8.0举例子,以下是hookResources的代码:通过hookAllMethods 函数确保Xposed 模块可以透明地修改应用软件的行为。
if (Build.VERSION.SDK_INT <= 18) { classGTLR = ActivityThread.class; classResKey = Class.forName("android.app.ActivityThread$ResourcesKey"); } else {// 获取关键类 classGTLR = Class.forName("android.app.ResourcesManager"); classResKey = Class.forName("android.content.res.ResourcesKey"); }if (Build.VERSION.SDK_INT >= 24) { hookAllMethods(classGTLR, "getOrCreateResources", new XC_MethodHook() {// hook after 函数 @Overrideprotectedvoid afterHookedMethod(MethodHookParam param) throws Throwable {// At least on OnePlus 5, the method has an additional parameter compared to AOSP.finalint activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);finalint resKeyIdx = getParameterIndexByType(param.method, classResKey);// 从 `ResourcesKey` 中获取资源目录 `mResDir` String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir"); XResources newRes = cloneToXResources(param, resDir);if (newRes == null) {return; }// 根据是否有 Activity Token,将新资源添加到对应的弱引用列表中 Object activityToken = param.args[activityTokenIdx]; synchronized (param.thisObject) { ArrayList<WeakReference<Resources>> resourceReferences;if (activityToken != null) { Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken); resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources"); } else { resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences"); } resourceReferences.add(new WeakReference(newRes)); } } }); }hookAllMethod 函数,顾名思义,遍历 hookClass 中声明的所有方法,找出所有方法名匹配的方法,调用hookMethod。
publicstatic Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback){ Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();for (Member method : hookClass.getDeclaredMethods())if (method.getName().equals(methodName)) unhooks.add(hookMethod(method, callback));return unhooks; }hookMethod做了这些事:存储回调函数,获取方法元数据,调用Native方法实现hook。
// 存储回调函数CopyOnWriteSortedSet<XC_MethodHook> callbacks;synchronized (sHookedMethodCallbacks) { callbacks = sHookedMethodCallbacks.get(hookMethod);if (callbacks == null) { callbacks = new CopyOnWriteSortedSet<>();// hookMethod 被hook的方法 callbacks 回调集合 sHookedMethodCallbacks.put(hookMethod, callbacks); newMethod = true; }}callbacks.add(callback);// 获取方法元数据if (runtime == RUNTIME_ART) {// Android 5.0+ (ART运行时) slot = 0; // ART 下不再需要 slot parameterTypes = null; // Native 层会自己获取 returnType = null;} elseif (hookMethod instanceof Method) {// Dalvik 运行时 - 普通方法 slot = getIntField(hookMethod, "slot"); // 方法在 vtable 中的索引 parameterTypes = ((Method) hookMethod).getParameterTypes(); returnType = ((Method) hookMethod).getReturnType();} else {// Dalvik 运行时 - 构造函数 slot = getIntField(hookMethod, "slot"); parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes(); returnType = null;}// 调用Native方法实现hookAdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);additionalInfo 封装了该方法的回调集合,参数类型以及返回类型。
voidXposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod, jobject, jint, jobject javaAdditionalInfo){// Detect usage errors.ScopedObjectAccess soa(env);if (javaReflectedMethod == nullptr) {#if PLATFORM_SDK_VERSION >= 23 ThrowIllegalArgumentException("method must not be null");#else ThrowIllegalArgumentException(nullptr, "method must not be null");#endifreturn; }// Get the ArtMethod of the method to be hooked. ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);// Hook the method artMethod->EnableXposedHook(soa, javaAdditionalInfo);}这个 ArtMethod为什么有EnableXposedHook方法呢,因为早在编译期,/system/lib64/libart.so文件就被替换掉了。
rovo89:XposedTools
在build.pl文件中,完整的构建流程是 编译 → 收集 → 生成属性文件 → 打包 → 签名 。
subcompile($$;$) {my @targets = qw(xposed); # 基础目标if ($sdk < 21) {# Dalvik 运行时push @targets, qw/libxposed_dalvik/; } else {# ART 运行时push @targets, qw/libxposed_art/;push @targets, qw/libart libart-compiler .../;push @makefiles, qw(art/Android.mk); } Xposed::compile(...);}get_compiled_files 定义了要打包的文件,以arm64 sdk>=21 为例,打包以下文件:
/system/bin/app_process64_xposed # 64位 Zygote/system/lib/libxposed_art.so # ART Hook 库/system/lib/libart.so # 替换的 ART 库/system/lib/libart-compiler.so # 替换的编译器/system/bin/dex2oat # 替换的 DEX 编译器/system/bin/oatdump # 替换的 OAT 分析工具rovo89:android_art
android_art项目存储着修改之后的art虚拟机。
EnableXposedHook用于将ArtMethod替换为 Xposed 的 Hook 版本
voidArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info){if (UNLIKELY(IsXposedHookedMethod())) {// Already hookedreturn; } elseif (UNLIKELY(IsXposedOriginalMethod())) {// This should never happen ThrowIllegalArgumentException(StringPrintf("Cannot hook the method backup: %s", PrettyMethod(this).c_str()).c_str());return; }// Create a backup of the ArtMethod object// 备份原方法auto* cl = Runtime::Current()->GetClassLinker();auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader()); ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc); backup_method->CopyFrom(this, cl->GetImagePointerSize()); backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);// Create a Method/Constructor object for the backup ArtMethod object// 创建备份方法的反射 mirror::AbstractMethod* reflected_method;if (IsConstructor()) { reflected_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method); } else { reflected_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method); } reflected_method->SetAccessible<false>(true);// Save extra information in a separate structure, stored instead of the native method// hook_info: Java 层的 Method 对象,additional_info,原方法的备份 XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo))); hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method); hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info); hook_info->original_method = backup_method;// 暂停虚拟机执行ScopedThreadSuspension sts(soa.Self(), kSuspended); jit::ScopedJitSuspend sjs;gc::ScopedGCCriticalSection gcs(soa.Self(), gc::kGcCauseXposed, gc::kCollectorTypeXposed);ScopedSuspendAll ssa(__FUNCTION__); cl->InvalidateCallersForMethod(soa.Self(), this); jit::Jit* jit = art::Runtime::Current()->GetJit();if (jit != nullptr) { jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method); }// 替换方法入口: JNI入口指向 hook_info,快速编译入口点设置为 Xposed 的代理处理函数 SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*)); SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler()); SetCodeItemOffset(0);// Adjust access flags.constuint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict; SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);MutexLock mu(soa.Self(), *Locks::thread_list_lock_); Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);}下面这段代码是替换了native 方法 和 非 native 方法 的入口,是hook的 核心函数:
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());SetCodeItemOffset(0);介绍一下 ART 虚拟机调用方法的流程。对于非Native方法:
Xposed hook 前
Java 调用方 │ ▼1. 查找 ArtMethod 对象 (从类中获取该方法对应的 ArtMethod 实例) │ ▼2. 读取 Quick 入口 (entry_point_from_quick_compiled_code_) ┌─────────────────────────────────────────────────────────┐ │ 对于非 native 方法,此入口指向: │ │ • 若已 AOT 编译:指向编译后的本地机器码 │ │ • 若未编译:指向解释器入口 (interpreter bridge) │ │ • 若 JIT 编译过:指向 JIT 生成的代码 │ └─────────────────────────────────────────────────────────┘ │ ▼3. 执行方法体 ┌─────────────────────────────────────────────────────────┐ │ 直接执行 Java 字节码 / 机器码 │ │ (参数已在寄存器/栈中,无需额外转换) │ └─────────────────────────────────────────────────────────┘ │ ▼4. 返回结果给 Java 调用方Xposed hook后:
Java 调用方 │ ▼1. 查找 ArtMethod 对象 (对象本身未变,但内部字段已被修改) │ ▼2. 读取 Quick 入口 (entry_point_from_quick_compiled_code_) ┌─────────────────────────────────────────────────────────┐ │ 已被 Xposed 修改: │ │ → 指向 Xposed 的 C++ 代理函数 │ │ (GetQuickProxyInvokeHandler) │ └─────────────────────────────────────────────────────────┘ │ ▼3. 执行 Xposed C++ 代理函数 ┌─────────────────────────────────────────────────────────┐ │ a. 通过 JNI 入口读取 hook_info (数据槽) │ │ └── 虽然非 native 方法原本不用 JNI 入口, │ │ 但 Xposed 将其借用来存储上下文 │ │ │ │ b. 调用 Java 层回调:beforeHookedMethod │ │ └── 执行所有注册的 XC_MethodHook 的 before 逻辑 │ │ │ │ c. 决定是否执行原始方法 │ │ ├── 若执行:通过 hook_info 中的 original_method │ │ │ └── 调用备份的原始 ArtMethod 的 Quick 入口 │ │ │ → 执行原始 Java 代码 │ │ └── 若不执行:跳过原始逻辑 │ │ │ │ d. 调用 Java 层回调:afterHookedMethod │ │ └── 执行所有注册的 XC_MethodHook 的 after 逻辑 │ └─────────────────────────────────────────────────────────┘ │ ▼4. 返回结果给 Java 调用方对于Native方法:
Xposed hook 前:
Java 调用方 │ ▼1. 查找 ArtMethod 对象 │ ▼2. 读取 Quick 入口 (entry_point_from_quick_compiled_code_) │ └── 对于 native 方法,这个入口指向一个特定的 JNI 桥接函数 (JNI Bridge) ▼3. 执行 JNI 桥接函数 │ └── 该函数负责将 Java 参数转换为 C/C++ 类型 ▼4. 从 ArtMethod 中读取 JNI 入口 (entry_point_from_jni_) │ └── 这个指针指向实际的 C/C++ 实现函数 ▼5. 跳转并执行 C/C++ 代码 │ ▼6. 返回结果给 Java 调用方Xposed hook 后
Java 调用方 │ ▼1. 查找 ArtMethod 对象 │ └── 此时 ArtMethod 已被修改 ▼2. 读取 Quick 入口 (entry_point_from_quick_compiled_code_) │ └── 已被 Xposed 修改,**直接指向 Xposed 的 C++ 代理函数** (GetQuickProxyInvokeHandler) ▼3. 执行 Xposed C++ 代理函数 │ ├── 通过 JNI 入口 (已被 Xposed 借用来存储数据) 取出 XposedHookInfo │ ├── 调用 Java 层的 beforeHookedMethod 回调 │ ├── 决定是否调用原始方法 │ │ ├── 若调用:通过 XposedHookInfo 中的 original_method 备份,执行原始 C/C++ 代码 │ │ └── 若不调用:直接跳过 │ └── 调用 Java 层的 afterHookedMethod 回调 ▼4. 返回结果给 Java 调用方经过hook后,art执行native函数和非native函数时,执行流都不会经过entry_point_from_jni_ ,所以xposed将 hook_info存入entry_point_from_jni_,也就是函数的 JNI 入口。
分析 Xposed 的代理函数做了什么,首先要定位art_quick_proxy_invoke_handler 。
// Return the address of quick stub code for handling transitions into the proxy invoke handler.extern"C"voidart_quick_proxy_invoke_handler();staticinlineconstvoid* GetQuickProxyInvokeHandler(){returnreinterpret_cast<constvoid*>(art_quick_proxy_invoke_handler);}这个函数被放在汇编代码里,不同架构的存放位置不一样。
art/runtime/arch/arm/quick_entrypoints_arm.S | |
|---|---|
art/runtime/arch/arm64/quick_entrypoints_arm64.S | |
art/runtime/arch/x86/quick_entrypoints_x86.S | |
art/runtime/arch/x86_64/quick_entrypoints_x86_64.S |
ENTRY art_quick_proxy_invoke_handler SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_X0 mov x2, xSELF // pass Thread::Current mov x3, sp // pass SP bl artQuickProxyInvokeHandler // (Method* proxy method, receiver, Thread*, SP) ldr x2, [xSELF, THREAD_EXCEPTION_OFFSET] cbnz x2, .Lexception_in_proxy // success if no exception is pending RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME // Restore frame fmov d0, x0 // Store result in d0 incase it was float or double ret // return on successartQuickProxyInvokeHandler 在 runtime/entrypoints/quick/quick_trampoline_entrypoints.cc 中,将 java 代码代理方法的调用 转化为 Xposed 代理方法 的调用。
该方法分为以下几个步骤:
Xposed 兼容性检查 暂停线程挂起 栈验证与 JNI 环境准备 将接收者转为 JNI 局部引用 核心参数解析 移除实例方法的this指针 进入 Xposed 代理分支,调用 InvokeXposedHandleHookedMethod/ 进入其他代理分支,调用InvocationHandler
extern"C"uint64_tartQuickProxyInvokeHandler( ArtMethod* proxy_method, mirror::Object* receiver, Thread* self, ArtMethod** sp)SHARED_REQUIRES(Locks::mutator_lock_){constbool is_xposed = proxy_method->IsXposedHookedMethod();if (!is_xposed) { DCHECK(proxy_method->IsRealProxyMethod()) << PrettyMethod(proxy_method); DCHECK(receiver->GetClass()->IsProxyClass()) << PrettyMethod(proxy_method); }// Ensure we don't get thread suspension until the object arguments are safely in jobjects.constchar* old_cause = self->StartAssertNoThreadSuspension("Adding to IRT proxy object arguments");// Register the top of the managed stack, making stack crawlable. DCHECK_EQ((*sp), proxy_method) << PrettyMethod(proxy_method); self->VerifyStack();// Start new JNI local reference state. JNIEnvExt* env = self->GetJniEnv();ScopedObjectAccessUnchecked soa(env);ScopedJniEnvLocalRefState env_state(env);// Create local ref. copies of proxy method and the receiver.constbool is_static = proxy_method->IsStatic(); jobject rcvr_jobj = is_static ? nullptr : soa.AddLocalReference<jobject>(receiver);// Placing arguments into args vector and remove the receiver. ArtMethod* non_proxy_method = proxy_method->GetInterfaceMethodIfProxy(sizeof(void*)); CHECK(is_xposed || !non_proxy_method->IsStatic()) << PrettyMethod(proxy_method) << " " << PrettyMethod(non_proxy_method);std::vector<jvalue> args;uint32_t shorty_len = 0;constchar* shorty = non_proxy_method->GetShorty(&shorty_len);BuildQuickArgumentVisitor local_ref_visitor(sp, is_static, shorty, shorty_len, &soa, &args); local_ref_visitor.VisitArguments();if (!is_static) { DCHECK_GT(args.size(), 0U) << PrettyMethod(proxy_method); args.erase(args.begin()); }// InvokeXposedHandleHookedMethod 将控制权交给 Xposed的钩子调度中心 if (is_xposed) { jmethodID proxy_methodid = soa.EncodeMethod(proxy_method); self->EndAssertNoThreadSuspension(old_cause); JValue result = InvokeXposedHandleHookedMethod(soa, shorty, rcvr_jobj, proxy_methodid, args); local_ref_visitor.FixupReferences();return result.GetJ(); }// Convert proxy method into expected interface method.// 如果不是 Xposed代理,执行被代理的原始方法 ArtMethod* interface_method = proxy_method->FindOverriddenMethod(sizeof(void*)); DCHECK(interface_method != nullptr) << PrettyMethod(proxy_method); DCHECK(!interface_method->IsRealProxyMethod()) << PrettyMethod(interface_method); self->EndAssertNoThreadSuspension(old_cause); jobject interface_method_jobj = soa.AddLocalReference<jobject>( mirror::Method::CreateFromArtMethod(soa.Self(), interface_method));// All naked Object*s should now be in jobjects, so its safe to go into the main invoke code// that performs allocations. JValue result = InvokeProxyInvocationHandler(soa, shorty, rcvr_jobj, interface_method_jobj, args);// Restore references which might have moved. local_ref_visitor.FixupReferences();return result.GetJ();}如果不是 Xposed 代理,那么用户编写的代理代码会像这样:
publicinterfaceProxyInterface{voiddoSomething(String message);}publicclassRealTargetimplementsProxyInterface{@OverridepublicvoiddoSomething(String message){ System.out.println("真实对象执行: " + message); }}// 用户代码publicclassMyInvocationHandlerimplementsInvocationHandler{private Object target;publicMyInvocationHandler(Object target){this.target = target; }@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable { System.out.println("=== 前置增强 ===");// 调用真实对象的方法 Object result = method.invoke(target, args); System.out.println("=== 后置增强 ===");return result; }}publicclassMain{publicstaticvoidmain(String[] args){// 创建真实目标对象 RealTarget realTarget = new RealTarget();// 创建代理对象 ProxyInterface proxy = (ProxyInterface) Proxy.newProxyInstance( Main.class.getClassLoader(),newClass[]{ProxyInterface.class},newMyInvocationHandler(realTarget) );// 调用代理方法 proxy.doSomething("hello world"); }}运行结果:
=== 前置增强 ===真实对象执行: hello world=== 后置增强 ===结合上面的源码:ART 发现 proxy 是代理对象,在InvokeProxyInvocationHandler会通过 JNI 调用 Java 层的 Proxy.invoke()函数 。
JValue InvokeProxyInvocationHandler(ScopedObjectAccessAlreadyRunnable& soa, constchar* shorty, jobject rcvr_jobj, jobject interface_method_jobj,std::vector<jvalue>& args){ DCHECK(soa.Env()->IsInstanceOf(rcvr_jobj, WellKnownClasses::java_lang_reflect_Proxy));// Build argument array possibly triggering GC. soa.Self()->AssertThreadSuspensionIsAllowable(); jobjectArray args_jobj = nullptr;const JValue zero;int32_t target_sdk_version = Runtime::Current()->GetTargetSdkVersion();// Do not create empty arrays unless needed to maintain Dalvik bug compatibility.if (args.size() > 0 || (target_sdk_version > 0 && target_sdk_version <= 21)) { args_jobj = soa.Env()->NewObjectArray(args.size(), WellKnownClasses::java_lang_Object, nullptr);if (args_jobj == nullptr) { CHECK(soa.Self()->IsExceptionPending());return zero; }for (size_t i = 0; i < args.size(); ++i) {if (shorty[i + 1] == 'L') { jobject val = args.at(i).l; soa.Env()->SetObjectArrayElement(args_jobj, i, val); } else { JValue jv; jv.SetJ(args.at(i).j); mirror::Object* val = BoxPrimitive(Primitive::GetType(shorty[i + 1]), jv);if (val == nullptr) { CHECK(soa.Self()->IsExceptionPending());return zero; } soa.Decode<mirror::ObjectArray<mirror::Object>* >(args_jobj)->Set<false>(i, val); } } }// Call Proxy.invoke(Proxy proxy, Method method, Object[] args). jvalue invocation_args[3]; invocation_args[0].l = rcvr_jobj; invocation_args[1].l = interface_method_jobj; invocation_args[2].l = args_jobj; jobject result = soa.Env()->CallStaticObjectMethodA(WellKnownClasses::java_lang_reflect_Proxy, WellKnownClasses::java_lang_reflect_Proxy_invoke, invocation_args);// Unbox result and handle error conditions.if (LIKELY(!soa.Self()->IsExceptionPending())) {if (shorty[0] == 'V' || (shorty[0] == 'L' && result == nullptr)) {// Do nothing.return zero; } else { StackHandleScope<1> hs(soa.Self());autoh_interface_method(hs.NewHandle(soa.Decode<mirror::Method*>(interface_method_jobj)));// This can cause thread suspension.size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); mirror::Class* result_type = h_interface_method->GetArtMethod()->GetReturnType(true/* resolve */, pointer_size); mirror::Object* result_ref = soa.Decode<mirror::Object*>(result); JValue result_unboxed;if (!UnboxPrimitiveForResult(result_ref, result_type, &result_unboxed)) { DCHECK(soa.Self()->IsExceptionPending());return zero; }return result_unboxed; } } else {// In the case of checked exceptions that aren't declared, the exception must be wrapped by// a UndeclaredThrowableException. mirror::Throwable* exception = soa.Self()->GetException();if (exception->IsCheckedException()) { mirror::Object* rcvr = soa.Decode<mirror::Object*>(rcvr_jobj); mirror::Class* proxy_class = rcvr->GetClass(); mirror::Method* interface_method = soa.Decode<mirror::Method*>(interface_method_jobj); ArtMethod* proxy_method = rcvr->GetClass()->FindVirtualMethodForInterface( interface_method->GetArtMethod(), sizeof(void*));auto virtual_methods = proxy_class->GetVirtualMethodsSlice(sizeof(void*));size_t num_virtuals = proxy_class->NumVirtualMethods();size_t method_size = ArtMethod::Size(sizeof(void*));// Rely on the fact that the methods are contiguous to determine the index of the method in// the slice.int throws_index = (reinterpret_cast<uintptr_t>(proxy_method) -reinterpret_cast<uintptr_t>(&virtual_methods.At(0))) / method_size; CHECK_LT(throws_index, static_cast<int>(num_virtuals)); mirror::ObjectArray<mirror::Class>* declared_exceptions = proxy_class->GetThrows()->Get(throws_index); mirror::Class* exception_class = exception->GetClass();bool declares_exception = false;for (int32_t i = 0; i < declared_exceptions->GetLength() && !declares_exception; i++) { mirror::Class* declared_exception = declared_exceptions->Get(i); declares_exception = declared_exception->IsAssignableFrom(exception_class); }if (!declares_exception) { soa.Self()->ThrowNewWrappedException("Ljava/lang/reflect/UndeclaredThrowableException;",nullptr); } }return zero; }}流程如下:
Native 层 │ ├─ args (vector<jvalue>) ← 从栈上提取的原始参数 │ ↓ ├─ 【步骤1:装箱】 │ ├─ 引用类型 → 直接放入 Object[] │ └─ 基本类型 → BoxPrimitive() 装箱 │ ↓ ├─ args_jobj (Object[]) │ ↓ ├─ 【步骤2:JNI 调用】 │ ↓ CallStaticObjectMethodA ├─ java.lang.reflect.Proxy.invoke(proxy, method, args) │ ↓Java 层 │ ├─ Proxy.invoke() │ ↓ h.invoke(proxy, method, args) ├─ InvocationHandler.invoke() │ ↓ 执行业务逻辑 ├─ 返回 result (Object) │ ↓Native 层 │ ├─ 【步骤3:拆箱】 │ ├─ void/null → 返回 zero │ ├─ 引用类型 → 直接返回 jobject │ └─ 基本类型 → UnboxPrimitiveForResult() 拆箱 │ ↓ ├─ 【步骤4:异常处理】 │ ├─ 无异常 → 返回结果 │ └─ 有异常 → 检查声明 / 包装 UndeclaredThrowableException │ ↓ └─ 返回 JValue 给 artQuickProxyInvokeHandler如果函数是 Xposed 代理函数,在第七步就会调用InvokeXposedHandleHookedMethod。该方法与InvokeProxyInvocationHandler的区别主要如下:
// 调用 Xposed 框架的回调方法jobject result = soa.Env()->CallStaticObjectMethodA( ArtMethod::xposed_callback_class, // 类: XposedBridge ArtMethod::xposed_callback_method, // 方法: handleHookedMethod invocation_args);// 获取 XposedHookInfoconst XposedHookInfo* hook_info = soa.DecodeMethod(method)->GetXposedHookInfo();jvalue invocation_args[5];invocation_args[0].l = hook_info->reflected_method; // 被 Hook 的原始 Methodinvocation_args[1].i = 1; // 固定值 1(originalMethodId)invocation_args[2].l = hook_info->additional_info; // Xposed 额外信息invocation_args[3].l = rcvr_jobj; // this 对象(或 null)invocation_args[4].l = args_jobj; // 参数数组jobject result = soa.Env()->CallStaticObjectMethodA( ArtMethod::xposed_callback_class, // 类: de.robv.android.xposed.XposedBridge ArtMethod::xposed_callback_method, // 方法: handleHookedMethod invocation_args);接下来应该还需要分析 ArtMethod ,但我不想看了,因为前面的源码我都一知半解,如果详细分析,还涉及到 Class 加载机制等Java原理。留给下次分析吧
二:待完成
类加载的原理还不熟悉: BaseDexClassLoader 以及对应的三个子类:InMemoryDexClassLoader, DexClassLoader PathClassLoader 。以及 ClassLoader 对应的子类 BootClassLoader。 可以看这篇,不过我看不懂 https://bbs.kanxue.com/thread-289025.htm java中的代理有很多种,但我还不理解
**Retrofit 等库的接口代理**- **原理**:Retrofit 使用 Java 动态代理(`Proxy.newProxyInstance()`),但它代理的是**接口**,调用时动态分发到 HTTP 请求。 - **与 ART 的关系**:**这其实就是第 2 种(普通 Java 动态代理)**,所以会经过 `artQuickProxyInvokeHandler`,并且走 `InvokeProxyInvocationHandler` 回调到 Retrofit 的 `InvocationHandler`。**Android 系统服务中的 Binder 代理**- **原理**:`Binder` 框架中的 `BinderProxy` 是 Native 层的代理对象,Java 层对应的是 `android.os.BinderProxy`。- **与 ART 的关系**:`BinderProxy` 是一个**真实类**(不是运行时生成的 `$Proxy`),它的方法调用走的是 JNI 的 Native 方法。 - **代码路径**:调用 `BinderProxy.transact()` 直接进入 Native 层,**完全不经过** `artQuickProxyInvokeHandler`。ArtMethod 原理
参考资料 / 来源
Xposed ,Xposed Bridge , android_art AOSP 源码 https://bbs.kanxue.com/thread-289025.htm https://bbs.kanxue.com/thread-267048-1.htm