From 9f5757ff26d2cf226fd322a5d9e016b683710e47 Mon Sep 17 00:00:00 2001 From: Joshua Selbo Date: Thu, 5 Mar 2026 10:40:02 -0600 Subject: [PATCH] Fix JvmtiAgent native library load when extractNativeLibs=false Debug.attachJvmtiAgent with a bare library name fails when extractNativeLibs=false (the AGP default for minSdk >= 23) because the .so is stored inside the APK rather than extracted to disk. The JVMTI agent loading path in ART uses OpenNativeLibrary with filesystem-only search paths, unlike System.loadLibrary which can load directly from APK via the linker namespace. Resolve the library path via BaseDexClassLoader.findLibrary() first. If the resolved path contains '=' (common in base64-encoded app install directories), copy the library to a temp file since Debug.attachJvmtiAgent rejects paths with '='. If findLibrary returns null, extract the library from classloader resources to a temp file. --- .../android/dx/mockito/inline/JvmtiAgent.java | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java index 84d22e3e..45194df4 100644 --- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java +++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java @@ -20,6 +20,7 @@ import android.os.Debug; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -62,10 +63,58 @@ class JvmtiAgent { + "by a BaseDexClassLoader"); } - Debug.attachJvmtiAgent(AGENT_LIB_NAME, null, cl); + String agentPath = resolveAgentPath(cl); + Debug.attachJvmtiAgent(agentPath, null, cl); nativeRegisterTransformerHook(); } + /** + * Resolve the agent library to a path that Debug.attachJvmtiAgent can load. + * + *

Debug.attachJvmtiAgent rejects paths containing '=' (used as options + * separator). On Android, app install directories may contain '=' in their + * path (e.g. /data/app/~~K6yx...A==/pkg-axiN...Q==/). When findLibrary + * returns such a path, or returns null (extractNativeLibs=false), we copy + * the library to a temp file. + */ + private static String resolveAgentPath(ClassLoader cl) throws IOException { + String path = ((BaseDexClassLoader) cl).findLibrary("dexmakerjvmtiagent"); + + if (path != null && !path.contains("=")) { + return path; + } + + // Copy the library to a temp file with a clean path. + File copiedAgent = File.createTempFile("org.mockito.android.agent", ".so"); + copiedAgent.deleteOnExit(); + + InputStream is; + if (path != null) { + is = new FileInputStream(path); + } else { + // findLibrary returned null — try loading from APK resources + String abi = Build.SUPPORTED_ABIS[0]; + String resourcePath = "lib/" + abi + "/" + AGENT_LIB_NAME; + is = cl.getResourceAsStream(resourcePath); + if (is == null) { + throw new IOException("Could not find " + AGENT_LIB_NAME + + " via findLibrary or classloader resources"); + } + } + + try (OutputStream os = new FileOutputStream(copiedAgent)) { + byte[] buffer = new byte[64 * 1024]; + int numRead; + while ((numRead = is.read(buffer)) != -1) { + os.write(buffer, 0, numRead); + } + } finally { + is.close(); + } + + return copiedAgent.getAbsolutePath(); + } + private native void nativeUnregisterTransformerHook(); @Override