Skip to content

android: Update replacement methods during CMC compaction pause - #413

Open
31n6 wants to merge 1 commit into
frida-packages:mainfrom
31n6:android-cmc-compaction-pause
Open

31n6 wants to merge 1 commit into
frida-packages:mainfrom
31n6:android-cmc-compaction-pause

Conversation

@31n6

@31n6 31n6 commented Nov 5, 2026

Copy link
Copy Markdown

Refs #387, #394, frida/frida#3805

In my repro on Android 16 (CMC), the DecodeGcMasksOnly crash from #387 goes away with the access flags mask fix from 302a588, which is on main but not released yet. With that fix, the same repro hits the crash below. With both, the #387 symptom is gone in my repro.

With the userfaultfd based CMC collector (used on this Android 16 device), the thread stack walk during compaction skips VisitDeclaringClass(), because CMC updates ArtMethods in LinearAlloc separately. Replacement ArtMethods are malloc'd, so their declaring_class_ keeps the pre-compaction address while ThreadFlipVisitor walks the stack. For a native frame, ART reads the shorty through that class and crashes in ReferenceMapVisitor::VisitFrame(). The existing sync on Heap::CollectGarbageInternal leave runs after the flip, which is too late.

Hook MarkCompact::CompactionPause(), which runs with all threads suspended before any flip visitor. On leave, pass each replacement's declaring_class_ to MarkCompact::VisitRoots(), so the collector updates it to the post-compaction address.

Tested on Samsung S24, Android 16 (BP4A.251205.006), arm64, Frida 17.25.2, with a stock-app repro. With explicit GCs, unpatched main crashed in 18 of 18 runs and this patch in 0 of 10. With allocation-driven (young) GCs only, main crashed in 5 of 5 runs and this patch in 0 of 5. eslint lib reports no problems.

Not tested on a device running the CC collector. The CC sync hooks are unchanged, and CompactionPause() only runs when MarkCompact is the active collector, so on CC the new hook is installed but never called.

The CMC collector does not visit the declaring class of methods on the
stack while compacting, and our malloc'd replacement ArtMethods are not
in LinearAlloc, so their declaring_class_ goes stale before threads walk
their stacks. Let MarkCompact::VisitRoots() update them on leave of
CompactionPause(), while all threads are still suspended.
@31n6

31n6 commented Nov 5, 2026

Copy link
Copy Markdown
Author

I missed this when I opened this PR: #410 fixes the same root cause in a different way.

  • android: Clone replacements into LinearAlloc #410 allocates each replacement in LinearAlloc, using Runtime::CreateCalleeSaveMethod(). CMC then updates its declaring_class_ like any other ArtMethod. No GC hook is needed.
  • This PR keeps replacements malloc'd. It updates their declaring_class_ on leave of MarkCompact::CompactionPause(), before any thread walks its stack.

I tested #410 on the same device with the same repro. It fixes the crash too: 0/10 runs crashed with explicit GCs and 0/5 with allocation only, while main crashed 2/2 in each mode in the same session. The details are in my comment on #410.

How the two approaches compare:

  • android: Clone replacements into LinearAlloc #410 does not depend on when the GC runs, and does not take the methods lock with all threads suspended.
  • This PR leaks nothing, and does not depend on CreateCalleeSaveMethod() allocating in a LinearAlloc that CMC visits. It does depend on two non-exported symbols, CompactionPause() and VisitRoots().

I'm fine with whichever approach you prefer. If you go with #410, I'll close this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant