Skip to content

android: Fix perform() hang when attaching during app startup - #414

Open
8lank wants to merge 1 commit into
frida-packages:mainfrom
8lank:perform-attach-during-startup
Open

8lank wants to merge 1 commit into
frida-packages:mainfrom
8lank:perform-attach-during-startup

Conversation

@8lank

@8lank 8lank commented Nov 10, 2026

Copy link
Copy Markdown

Java.perform() can wait forever when Frida attaches to an app that is still starting.

Cause

When there is no Application yet, perform() queues the callback, and _performPendingVmOpsWhenReady() decides when to run it:

  1. It calls ActivityThread.currentApplication() once.
  2. If that returns null, it hooks handleBindApplication() and getPackageInfo(), and runs the queue from whichever is called first.

handleBindApplication() calls getPackageInfo() early, and only later sets the Application that currentApplication() returns. An attach that lands between the two sees null in step 1, then hooks two methods that have already run. Neither runs again, so the callback never runs and no error is reported.

We hit this when attaching to apps right after starting them.

Change

After it installs the hooks, _performPendingVmOpsWhenReady() also checks currentApplication() every 50 ms, until it returns an Application or a hook fires. The existing initialized flag makes sure the queue runs once, whichever path gets there first.

Test

PerformTest.performRunsWhenNoHookFires covers it. The test runner is not an app process, so the test:

  • sets Java._cachedIsAppProcess, so perform() takes the app path;
  • replaces ActivityThread.currentApplication(), so it returns null until 100 ms after perform(), then a stand-in Application.

Nothing in the runner calls the two hooked methods. That is the same state as an attach that came too late for them. Without the fix, getNextMessage() gives up after 5 s and the test fails.

I could not run the suite: tests.yml fails on main today (59 of 64 tests failed on API 29 x86_64 in the latest push run, because the scripts send nothing back), and I don't have the NDK setup for the native runner. Instead I ran the test's steps as a script in a real app, on an Android 16 x86_64 emulator with Frida 17.19.0, 5 rounds each:

Bridge currentApplication() null for 100 ms perform() ran
With this fix yes 5 of 5, within 1.4 s
Without yes 1 of 5; the other 4 never ran before a 15 s timeout
Without no 5 of 5

The one run without the fix that passed was the first, while the app was still starting, so a later getPackageInfo() call probably fired the hook.

Note

In a process that never gets an Application, the check keeps running every 50 ms. perform() already waited forever there; only the timer is new.

🤖 Generated with Claude Code

When there is no Application yet, perform() waits for a hook on
handleBindApplication() or getPackageInfo(). An attach that lands after
the app made both calls, but before it set its Application, never sees
either hook fire, so the callback never runs. Also check
currentApplication() every 50 ms, until it returns an Application or a
hook fires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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