Debugging Go Mobile on Android
- Marcus Wu
- Engineering , Software
- November 7, 2023
Introduction
I got started with Go Mobile because my team has been experiencing challenges with mobile development including:
- Difficulty keeping functional parity between iOS and Android
- Extra time needed to implement the same logic in each of the two platforms
- A team of four Engineers was functioning as two teams of two. Two Engineers for iOS and two for Android.
Go Mobile is a promising way to solve that problem. There are other options such as Kotlin Multiplatform Mobile (KMM), but we wanted to keep both iOS and Android Engineers on equal footing and not require iOS Engineers to learn Kotlin.
All seemed to be going well in our experiment until the first time we had to debug a crash in Go. It is much easier if you can attach to the process and inspect values at the time of the crash. We needed to allow VSCode to connect to LLDB on Android to debug Go code.
Setting Up The Emulator
The first step is to get an LLDB server on the target device so that we can remote debug. The Android NDK toolchain provides LLDB servers for various platforms. You will want to choose one that matches your target architecture. In my case it was located at ~/Library/Android/sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/17.0.2/lib/linux/aarch64/lldb-server
. We will copy it to /data/local/tmp
on our device so we can access it later:
adb push ~/Library/Android/sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/17.0.2/lib/linux/aarch64/lldb-server /data/local/tmp
We want to be able to run the lldb-server as the Android package of our app, so we need to copy the executable to where that process has access to it. I will use StructEd as an example:
adb shell run-as com.digitaltorque.structed cp /data/local/tmp/lldb-server /data/data/com.digitaltorque.structed/
If we want to debug something that happens during start-up, we need the app to wait until we have the debugger attached. To do that, we can set a property on Android:
adb shell setprop debug.debuggerd.wait_for_debugger true
Next we forward the port we want use for remote debugging to our computer. I used 23456 here, but it could be a different port:
adb forward tcp:23456 tcp:23456
We can now start our app up (this could be done via the device’s UI as well):
adb shell am start -n com.digitaltorque.structed/com.digitaltorque.structed.MainActivity
We will need our app’s process ID to attach to it, so we can get that now:
adb shell ps -A | grep com.digitaltorque.structed | awk '{ print $2 }'
Finally, we can start the LLDB server to listen on our chosen port:
adb shell run-as com.digitaltorque.structed /data/data/com.digitaltorque.structed/lldb-server --server --listen "*:23456"
Connecting VSCode to LLDB
Open the Extensions marketplace and install CodeLLDB. Next, add a run / debug configuration in VSCode. This can be done via the command pallette (Command + Shift + P on OS X), Then select Debug: Add Configuration...
. It doesn’t matter the specifics of what you add, you just need a launch.json
file to get started.
Edit your launch.json file and add a configuration like this:
{
"name": "Go Mobile Debugging",
"type": "lldb",
"request": "custom",
"initCommands": ["platform select remote-android", "file debug/jni/x86_64/libgojni.so"],
"processCreateCommands": ["platform connect connect://emulator-5554:23456", "attach 22112"]
}
In this example, debug/jni/x86_64/libgojni.so
is the location of the Go shared library generated with gomobile bind
. I have extracted it from the .aar file so that I can reference it here.
emulator-5554
is the serial for the target device. This could be a physical device or an emulator. 23456
references the port number lldb-server is listening to on the device, and 22112
represents the PID of the running app.
At this point, I can select that configuration and press the run button to connect to the LLDB server. Should the app crash, I can look through the threads and the stack frames to see what crashed, in what file, and on what line.
Making it easier
That is a lot of steps to run every time a debugging session is needed. To streamline that process, I wrote up a little Go CLI to help set everything up.
The CLI tool’s code is on github: https://github.com/marcuswu/jniDebug
It can be installed with:
go install github.com/marcuswu/jnidebug@latest
I am still experimenting with this method of debugging – especially with setting breakpoints and examining memory at runtime.
Please let me know if this helps you or if you have come across a better way to do this.