TWiStErRob

Detailed walkthrough

Getting a full Thread Dump on Android

The Android operating system does a thread dump whenever it receives SIGQUIT signal. However you need an unlocked device to send that signal. Fear not, there’s a workaround to get the exact same thread dump with a little more effort on a production device with a debuggable application.

Background

There are a few sources on the Internet on how to make a thread dump in Android, but most of them don’t work or are lacking the intrinsic locking information, here’s an example:

If you have a developer / rooted device, you can ask the Dalvik VM to dump thread stacks by sending a SIGQUIT to the app process you’re interested in. How to make Java Thread Dump in Android?

If you try to send a signal on a non-rooted/non-dev device you’ll get:

me@laptop$ adb shell "ps | grep twister"
USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
u0_a504   10904 278   914108 23164 ffffffff 00000000 S net.twisterrob.app

me@laptop$ adb shell kill -s SIGQUIT 10904
/system/bin/sh: kill: 10904: Operation not permitted

An answer to How to stop an android application from adb without force-stop or root? suggests to use run-as:

me@laptop$ adb shell run-as net.twisterrob.app kill -3 10904
run-as: Package 'net.twisterrob.app' is unknown

while the OP states that force-stop and am kill is ruled out, we could use them here:

me@laptop$ adb shell am force-stop net.twisterrob.app
me@laptop$ adb shell am kill --user all net.twisterrob.app

…the first really kills the app, and the second does nothing, in any case there’s no thread dump.

Solution

A solution for the above issues follows, based on the fact that you have control over your own Android process from the app’s code.

Step 0: Fake a UI lock-down

To have something to validate that we have the right information I suggest to lock on a known object. So later we’ll see who/where and when locked. I put the following into my main Activity’s onCreate:

// we want to see what "this" is in a thread dump
synchronized (this) {
	try {
		wait();
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

Step 1: Always available debugger break entry point

If you want a thread dump you probably have a deadlock in your code in which case it might not be trivial to know when and where to stop the code execution. You’ll need to start a background thread in your app so that you can stop the execution any time and be able to mess around in immediate mode too.

There’s a Pause Program action available in IntelliJ IDEA’s Debug view:
"Pause Program" action in IDEA's Debug View
but you can’t execute any code in that mode:

Target VM is not paused by breakpoint request. Evaluation of methods is not possible in this mode

So to have a live breakable point, put the following snippet somewhere in your app:

new Thread("Debug-Breaker") {
	@Override public void run() {
		while (true) {
			try {
				Thread.sleep(1); // breakpoint here
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}.start();

Anywhere is ok that you know will surely execute before you need the thread dump. Even an button’s event handler will work. I choose to put mine in:

public class App extends Application {
	@Override public void onCreate() {

Don’t forget to register it in your manifest: <application android:name=".App".

Compile and run your app. If you did step 0 as I did, your app will be plain black/white and do nothing at this point, because wait() blocked the UI thread.

Step 2: Create thread dump

Go to your IDE and do Run > Attach debugger to Android process
Choose Process dialog
and select your app’s process and then go to the place where you have Thread.sleep(1); and put a breakpoint on it:
Breakpoint on line `Thread.sleep`
The breakpoint should be hit immediately:
app pause at breakpoint

Now there’s a context you can execute code in, go to Run > Evaluate Expression... and enter:

android.os.Process.sendSignal(android.os.Process.myPid(), android.os.Process.SIGNAL_QUIT)

In the Android LogCat view you’ll see the following information printed:

04-13 16:36:21.971  10904-10954/net.twisterrob.app I/Process﹕ Sending signal. PID: 10904 SIG: 3
04-13 16:36:21.971  10904-10909/net.twisterrob.app I/dalvikvm﹕ threadid=3: reacting to signal 3
04-13 16:36:22.061  10904-10909/net.twisterrob.app I/dalvikvm﹕ Wrote stack traces to '/data/anr/traces.txt'

The app may be still running at this point, but it doesn’t matter any more. Feel free to detach debugger and/or stop it on the phone.

Step 3: Acquire thread dump

Now it should be a simple pull from your device like this, but hey it’s not that simple…

me@laptop$ adb pull /data/anr/traces.txt
failed to copy '/data/anr/traces.txt' to './traces.txt': Permission denied

for some reason I wasn’t able to copy the file, but I can read it… so it’s simple after all:

me@laptop$ adb shell "cat /data/anr/traces.txt" > traces.txt

Step 4: Read thread dump

Now if you open traces.txt on you machine and scroll from the bottom you should find your app:

----- pid 10904 at 2015-04-13 17:16:40 -----
Cmd line: net.twisterrob.app

... lots of stack traces of threads ...

----- end 10904 -----

Here’s the UI thread’s state, remember in Step 0 I synchronized on this, see line #6 (starting with “waiting on”):

"main" prio=5 tid=1 WAIT
    | group="main" sCount=1 dsCount=0 obj=0x418ccea0 self=0x417c7388
    | sysTid=13183 nice=-11 sched=0/0 cgrp=apps handle=1074684244
    | state=S schedstat=( 0 0 0 ) utm=7 stm=5 core=3
    at java.lang.Object.wait(Native Method)
    - waiting on <0x42aeb7a8> (a net.twisterrob.app.MainActivity)
    at java.lang.Object.wait(Object.java:364)
    at net.twisterrob.app.MainActivity.onCreate(MainActivity.java:45)
    at android.app.Activity.performCreate(Activity.java:5426)
    ...

Summary

The above steps should give you access to thread dumps for any application you develop from your real device. The full thread dumps contain locking information like the waiting on line above which helps to diagnose deadlocks and synchronization issues. If you just want to see where your app is at the current time I suggest you use the Threads view of the SDK’s DDMS monitor, or Thread.getAllStackTraces() programmatically or while debugging.

Go to top