I have been recently doing some remote apps debugging on Linux-based systems i.e host is on Ubuntu and target machine is an Android-based device and I got to learn some of the gdb remote debugging features along the way so I wanted to write a quick post about it for newbies who could struggle or for those who’are so used to Visual Studio & Windows platform like myself!
Let me get some basic terminology out of the way:
- Host machine: The machine on which you’re debugging your application. It’s also where you generally keep the unstripped binaries.
- Target device: The device which your app that you want to debug runs on. That could be any kind of computing platform such as an Android tablet.
- Executable: This is the binary of your app which we’ll later use to attach gdb to it.
- gdbserver: This is what we’re gonna use to let our host & target machines communicate. It must be installed on the target device beforehand.
The simple way we’re gonna realize the remote debugging is simply this:
- Debug-build your app with optimizations off, assers enabled, etc.
- Run gdbserver on target machine to connect app being run to the host machine gdb
- Run gdb on host machine to attach it to our app running on target device
- Debug happily!
Now let’s imagine that you have already debug-built your app on your host machine to run it on your target device, let’s go with an Android device for our example. If your target device is a tiny embedded system or doesn’t have much of storage which can’t hold your debug-built app, you can unstrip the binary before pushing it to the device:
strip my_binary my_stripped_binary
adb push my_stripped_binary data/local/data/my_app/ # Push it to Android device
We’re ready to go!
On our target Android device:
gdbserver :DEBUG_PORT my_stripped_binary app_arguments
Here, we cd into the app directory and call gdbserver with DEBUG_PORT through which we’re going to remote debug, our binary and its arguments. DEBUG_PORT should be an open port so if you’re in an office network or such, check what’s available!
On our host machine:
file my_binary # The unstripped debug-built binary
target remote localhost:DEBUG_PORT
b main # Set a breakpoint in main() function
b my_function_to_debug.cpp:100 # Here we set a breakpoint in a FILE:LINE pattern
continue # We found the problem, continue running app
If you try to debug an app now, you’ll hopefully find out that your apps on different machines connect to each other through gdb/gdbserver pair yet we cannot really see anything meaningful because we’re missing an essential piece of info here; gdbinit file.
At any time, to see which libraries have been loaded by gdb, enter following in gdb console:
info sharedlibrary # Lists all libraries the app refers to
This file basically describes under what kind of debugging configuration we’re gonna run our app. You can find lots of info about it on Google but main things your .gdbinit file should cover are where your debug-built libraries are so gdb can point us out to corresponding source, assembly style, which signals to ignore or stop at, etc. I generally create slightly different .gdbinit files for each app and put the one under $HOME/ which I currently want to use with gdb. Otherwise, you need to put it alongside your app on target device. I intentionally don’t want to go much into details of .gdbinit file here because for me, it’s how I figured out much of the gdb stuff so I think if you tackle with its usage, what flag does what and all, you’ll get the gist of gdb remote debugging pretty quickly. Otherwise, shoot me a question here and we’ll see what the problem is, maybe!
That kind of does it here. As you may guess, there’s much more to learn and also much more which can go wrong but gdb is heavily used so it’s well-covered all over the internet in fine details.
Any suggestion and corrections are more than welcome! Thank you for your time!