My Brain on Code

Debugging Android With Jdb

At work I’m a big proponent of vim. Needless to say, I code Android in vim. One thing that many Android developers do is debug their apps using their IDEs built in debugger. Luckily, there is still a way to debug Android apps without an IDE such as Eclipse or Android Studio. What you can use is jdb, a simple command-line debugger for Java classes.

In order to do this, you must first launch your app, find your apps PID, forward a local socket connection to that PID, attach jdb and start debugging.

To demonstrate how all this ties together I created a very simple app. Install that app on a phone or emulator via ant: ant debug install

Once you have it on your phone or emulator, launch the app. Now we need to find the PID of this app. Luckily for us, adb has a command we can execute adb jdwp. This will simply list all PIDs hosting a JDWP transport (the protocol used for communication between jdb and your app). Since we just launched the app, the apps PID will be at the bottom of the list:

$ adb jdwp
58
108
115
114
120
151
157
169
176
192
208
233
243
290

In my case, I need to forward a local socket to PID 290 to start debugging. I can do this by using the command adb forward tcp:8700 jdwp:290. This tells adb to forward a local socket on port 8700 to the app with PID 290 that’s hosting a JDWP transport. Now we can start debugging the simple test app by issuing jdb -attach localhost:8700 -sourcepath /path/to/test/app/src. If all goes well, you’ll have a jdb prompt in which you can start adding breakpoints.

Let’s start by placing a breakpoint when we click the button. Since the OnClickListener is technically a nested class we cannot simply place a breakpoint at line 22 like you’d expect. Instead we must inspect the MainActivity class by issuing:

> class com.test.MainActivity
Class: com.test.MainActivity
extends: android.app.Activity
nested: com.test.MainActivity$1

Ah, there we see the nested class which is our OnClickListener, but just to be sure let’s inspect that as well:

> class com.test.MainActivity$1
Class: com.test.MainActivity$1
extends: java.lang.Object
implements: android.view.View$OnClickListener

Right there we can see it is indeed our OnClickListener for the button and we can now add a breakpoint to the onClick method by issuing stop in com.test.MainActivity$1.onClick. Now when we click our button in the app, the breakpoint will be hit and we can start using jdb to debug the app how we see fit.

We can even create a script to automate launching the app, forwarding the socket and attaching the debugger like so:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
SOURCE_PATH="/path/to/sample/src"
DEBUG_PORT=8700
PACKAGE_NAME=com.test
ACTIVITY=com.test.MainActivity
LAUNCH_CMD="adb shell am start -e debug true -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n $PACKAGE_NAME/$ACTIVITY"
exec $LAUNCH_CMD &
sleep 3 # wait for the app to start
APP_DEBUG_PORT=$(adb jdwp | tail -1);
adb -d forward tcp:$DEBUG_PORT jdwp:$APP_DEBUG_PORT
jdb -attach localhost:$DEBUG_PORT -sourcepath $SOURCE_PATH

NOTE: One caveat to all this is if your app runs in multiple processes, then you’ll have to forward a port for each process the app runs in and run multiple jdb sessions.

Happy debugging!