夜雨聆风学习资料网

ARTICLE · 1006378

工具源码分析——Xposed原理

工具源码分析——Xposed原理

这是我第一次分析工具的原理并阅读源码,也是第一次接触 Zygote 进程,感觉自己开发能力很欠缺,尤其是阅读ArtMethod时,每一行代码都看不懂,更别说合起来看了。但好处是,自己对看工具源码这件事不恐惧了。

因为有些地方是AI生成的,而且源码我不太看得懂,如果有错误,麻烦在评论区指出来。

虽然在家里,但是最近每天都很忙,只能抽时间学习

一、源码分析

AOSP源码版本: android-8.0.0_r28

android 系统

这是经典的android架构图,Zygote进程是init创建的其中一个进程,用于创建安卓运行时虚拟机artDalvik、创建提供系统服务的System Server进程、以及孵化用户进程。

加载Zygote进程是init进程的作用之一:

FirstStageMain() / SecondStageMain() / SetupSelinux() 这种明确的两阶段拆分是 Android 10 (Q, API 29) 左右才引入的。在 Android 8.0 中,init 进程只有一个统一的入口。

1091  parser.ParseConfig("/init.rc");          // ← 解析 /init.rc

init.rc 是启动脚本资源文件,放在同仓库的 system/core/rootdir/ 下。

init 解析 rc 文件时,service zygote 这一行后面的所有 token 会被完整地保存到 args_ 中。详情请看system/core/init/service.cppapp_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,使 Zytoge fork 的进程会自带这些 Hook。加载所有已激活的模块,并将代码注入到 Zygote 中。

用户在 hook 函数时,Xposd 完成了以下事情:

首先封装一个对象additionalInfo, 保存该方法的回调集合,参数类型以及返回类型。 然后调用hookMethodNative方法,获取该方法的ArtMethod对象,并进行 Hook。

Hook原理:Xposed 修改了 Java 函数的执行流: 查找函数的 ArtMethod 方法 ->函数运行到 Quick 入口 -> 函数被劫持到 Xposed c++ 代理函数 -> 函数执行结果返回到 Java 。具体细节如下:

  1. 创建 hook_info结构体,包括 Java 层的 Method 对象、additionalInfo以及原方法的备份。
  2. Xposed 将hook_info结构体放在 ART 虚拟机执行方法的JNI入口(entry_point_from_jni_字段),这是因为 Xposed 修改后的函数执行流程不会将 JNI入口解析为 JNI函数。之后,通过执行ArtMethod类的GetEntryPointFromJniPtrSize()函数完成hook_info的反查。
  3. 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(falsefalse, 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();staticinlineconstvoidGetQuickProxyInvokeHandler(){returnreinterpret_cast<constvoid*>(art_quick_proxy_invoke_handler);}

这个函数被放在汇编代码里,不同架构的存放位置不一样。

ARM 32
art/runtime/arch/arm/quick_entrypoints_arm.S
ARM64
art/runtime/arch/arm64/quick_entrypoints_arm64.S
x86
art/runtime/arch/x86/quick_entrypoints_x86.S
x86_64
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 success

artQuickProxyInvokeHandler 在 runtime/entrypoints/quick/quick_trampoline_entrypoints.cc 中,将 java 代码代理方法的调用 转化为 Xposed 代理方法 的调用。

该方法分为以下几个步骤:

  1. Xposed 兼容性检查
  2. 暂停线程挂起
  3. 栈验证与 JNI 环境准备
  4. 将接收者转为 JNI 局部引用
  5. 核心参数解析
  6. 移除实例方法的this指针
  7. 进入 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

相关学习资料

返回首页浏览学习资料