From 2854fcc5d1f4ba2100424b408e414e819a0a8bb2 Mon Sep 17 00:00:00 2001 From: mrmees <38006194+mrmees@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:28:47 -0500 Subject: [PATCH] android: fall back to visibly initialized callbacks on Android 16 --- lib/android.js | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/android.js b/lib/android.js index 11daebe..4745ab0 100644 --- a/lib/android.js +++ b/lib/android.js @@ -2160,6 +2160,13 @@ function instrumentArtFixupStaticTrampolines () { ]; const api = getApi(); const art = api.module; + const { artNterpEntryPoint, artQuickToInterpreterBridge } = api; + const quickCodeOffset = getArtMethodSpec(api.vm).offset.quickCode; + + const synchronizeReplacements = function () { + artController.replacedMethods.synchronize(quickCodeOffset, artNterpEntryPoint, artQuickToInterpreterBridge); + }; + for (const [name, pattern] of patterns) { const base = art.findSymbolByName(name); if (base === null) { @@ -2168,17 +2175,38 @@ function instrumentArtFixupStaticTrampolines () { const matches = Memory.scanSync(base, 8192, pattern); if (matches.length === 0) { - return; + continue; } - const { artNterpEntryPoint, artQuickToInterpreterBridge } = api; - const quickCodeOffset = getArtMethodSpec(api.vm).offset.quickCode; - Interceptor.attach(matches[0].address, function () { - artController.replacedMethods.synchronize(quickCodeOffset, artNterpEntryPoint, artQuickToInterpreterBridge); - }); + Interceptor.attach(matches[0].address, synchronizeReplacements); return; } + + // Android 16 ART refactored this callback flow and older byte signatures no longer + // match reliably. Fall back to the callback methods that are still present. + if (getAndroidApiLevel() >= 36) { + const fallbackSymbols = [ + '_ZN3art11ClassLinker26VisiblyInitializedCallback11MakeVisibleEPNS_6ThreadE', + '_ZN3art11ClassLinker26VisiblyInitializedCallback3RunEPNS_6ThreadE', + '_ZN3art11ClassLinker26VisiblyInitializedCallback22MarkVisiblyInitializedEPNS_6ThreadE' + ]; + + let hooked = false; + fallbackSymbols.forEach(name => { + const address = art.findSymbolByName(name); + if (address === null) { + return; + } + + Interceptor.attach(address, synchronizeReplacements); + hooked = true; + }); + + if (hooked) { + return; + } + } } function ensureArtKnowsHowToHandleReplacementMethods (vm) {