I answered a questions in Zhihu about "What's the implementation of Xposed Framework on ART Runtime". I realize that this is an interesting question because I did a research on hooking/interception years ago. Therefore, I then went through the code and here is a breakdown notes on the Xposed Framework.

First, the Xposed Framework contains many sub-repositories. I will explain three core components in detail. They are Xposed, XposedInstaller, android_art.

Essentially, the Xposed framework use hooking techniques to replace method pointer inside runtime. The basic steps of hooking in the Xposed framework is following:

  1. XposedInstaller replace /system/bin/app_process with customized app_process. For ART runtime XposedInstaller will also replace customize ART runtime.
  2. XposedInstaller invoke xposed::initialize() in customized app_process to start hooking
  3. customize ART runtime (Xposed/android_art) is patched with a EnableXposedHook() method
  4. EnableXposedHook() will switch method pointer by swapping ArtMethod pointer in ClassLinker in ART Runtime

XposedInstaller Repository

install() method in InstallerFragment.java class will do the main task. Let's go through some codes.

The following code snippet will backup original app_process, copy customized app_process and set proper file owner and user mod.

private boolean install() {
// deleted lines ...
    if (installMode == INSTALL_MODE_NORMAL) {
        // Normal installation
        messages.add(getString(R.string.file_mounting_writable, "/system"));
        if (mRootUtil.executeWithBusybox("mount -o remount,rw /system", messages) != 0) {
            messages.add(getString(R.string.file_mount_writable_failed, "/system"));
            messages.add(getString(R.string.file_trying_to_continue));
        }

        if (new File("/system/bin/app_process.orig").exists()) {
            messages.add(getString(R.string.file_backup_already_exists, "/system/bin/app_process.orig"));
        } else {
            if (mRootUtil.executeWithBusybox("cp -a /system/bin/app_process /system/bin/app_process.orig", messages) != 0) {
                messages.add("");
                messages.add(getString(R.string.file_backup_failed, "/system/bin/app_process"));
                return false;
            } else {
                messages.add(getString(R.string.file_backup_successful, "/system/bin/app_process.orig"));
            }

            mRootUtil.executeWithBusybox("sync", messages);
        }

        messages.add(getString(R.string.file_copying, "app_process"));
        if (mRootUtil.executeWithBusybox("cp -a " + appProcessFile.getAbsolutePath() + " /system/bin/app_process", messages) != 0) {
            messages.add("");
            messages.add(getString(R.string.file_copy_failed, "app_process", "/system/bin"));
            return false;
        }
        if (mRootUtil.executeWithBusybox("chmod 755 /system/bin/app_process", messages) != 0) {
            messages.add("");
            messages.add(getString(R.string.file_set_perms_failed, "/system/bin/app_process"));
            return false;
        }
        if (mRootUtil.executeWithBusybox("chown root:shell /system/bin/app_process", messages) != 0) {
            messages.add("");
            messages.add(getString(R.string.file_set_owner_failed, "/system/bin/app_process"));
            return false;
        }

    }
// deleted lines ...
}

Xposed Repository

For ART runtime Xposed repository provides a libxposed_art.cpp containing XposedBridge_hookMethodNative() method. This method will invoke artMethod->EnableXposedHook method in customized ART Runtime. Let's see the code.

void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
            jobject, jint, jobject javaAdditionalInfo) {
    // Detect usage errors.
    if (javaReflectedMethod == nullptr) {
#if PLATFORM_SDK_VERSION >= 23
        ThrowIllegalArgumentException("method must not be null");
#else
        ThrowIllegalArgumentException(nullptr, "method must not be null");
#endif
        return;
    }

    // Get the ArtMethod of the method to be hooked.
    ScopedObjectAccess soa(env);
    ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);

    // Hook the method
    artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}

android_art Repository

Let's read the code of EnableXposedHook method.

void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
  if (UNLIKELY(IsXposedHookedMethod())) {
    // Already hooked
    return;
  } else if (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();
  ArtMethod* backup_method = cl->AllocArtMethodArray(soa.Self(), 1);
  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* reflect_method;
  if (IsConstructor()) {
    reflect_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);
  } else {
    reflect_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);
  }
  reflect_method->SetAccessible<false>(true);

  // Save extra information in a separate structure, stored instead of the native method
  XposedHookInfo* hookInfo = reinterpret_cast<XposedHookInfo*>(calloc(1, sizeof(XposedHookInfo)));
  hookInfo->reflectedMethod = soa.Vm()->AddGlobalRef(soa.Self(), reflect_method);
  hookInfo->additionalInfo = soa.Env()->NewGlobalRef(additional_info);
  hookInfo->originalMethod = backup_method;
  SetEntryPointFromJni(reinterpret_cast<uint8_t*>(hookInfo));

  ThreadList* tl = Runtime::Current()->GetThreadList();
  soa.Self()->TransitionFromRunnableToSuspended(kSuspended);
  tl->SuspendAll("Hooking method");
  {
    MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
    tl->ForEach(StackReplaceMethod, this);
  }
  tl->ResumeAll();
  soa.Self()->TransitionFromSuspendedToRunnable();

  SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
  SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);

  // Adjust access flags
  SetAccessFlags((GetAccessFlags() & ~kAccNative & ~kAccSynchronized) | kAccXposedHookedMethod);
}

The hooking includes these steps:

  1. backup original ArtMethod pointer
  2. store the hooking method pointer, additional information and backup method pointer to hookInfo structure
  3. replace the entrypoint of original method by invoking SetEntryPointFromJni() method

In the end, the original method will be replace with new one.

In summary, the Xposed framework will replace the ArtMethod pointer with the new code. Note that the hooking can only be done in framework layer above ART runtime. This means that any native method written by C/C++ (NDK) still cannot be hooked. Furthermore, some functionalities in Android are only implemented in native code. Therefore, there are still several limitations for the Xposed framework. But, I guess it's enough for some people to create interesting modules. Thanks for reading. Happy hacking!