I describe:
- How to build Android emacs with debug symbols.
- How to install it on the Android device.
- How to start lldb-server on the Android device, how to configure lldb, and connect it to lldb-server.
- Some simple lldb commands, to get you up to speed.
- A potential pitfall, when looking at global variables in emacs.
- Some things I have not needed to enable in lldb.
Some useful references for lldb are listed.
Although the article is kind of long, I consider the described debugging method to be quick and easy. It is just important to get the details right.
What I describe here requires that Termux is installed on Android, and that Emacs and Termux have been configured to have permission to access each other.
lldb is the offically supported debugger for Android, and it has been so for a long time. I consider it to be a very good and capable debugger. lldb has two parts:
- lldb, which runs on the host computer, and is the user interface.
- lldb-server, which runs on Android, and controls the process to be debugged.
lldb-server can run in several modes:
- “lldb-server platform” for cooperating with lldb
- In this mode lldb-server can either serve just one lldb connection, or serve several connections (–server), potentially in parallel.
- “lldb-server gdbserver” for cooperating with gdb
Clone the emacs repo, if you have not done this already:
- git clone –single-branch https://github.com/emacs-mirror/emacs.git –depth 1
Change directory to the emacs repo.
- make clean
- ANDROID_CFLAGS=”-g3 -O0” ./configure <options>…
Make any desired changes to source files:
- make all
If you later on make more changes to source files, I recommend that you rebuild using the following commands:
- make clean
- make all
Use make clean unless you are confident that it is not needed. To verify that it is not needed:
- Insert some statements in a source file, say alloc.c.
- Rebuild without “make clean”, and install the rebuilt apk file.
- In the debugger, connected to the rebuilt Android emacs:
- List a function in alloc.c, that comes after the inserted statements:
- “l Fcons”
- The displayed source lines should have the Fcons definition line in the middle of the listing.
- Compare with listing a function in a module that you have not updated: “l some_other_function”.
- List a function in alloc.c, that comes after the inserted statements:
If both cases list the specified function in the middle of the listing, there is probably no need to use “make clean”.
You need to have developer options enabled on the Android device, and permission to connect via USB. Connect the host to the Android device with an USB cable. To check that the connection works do:
- adb devices
- adb shell
The first time you try to access the Android device via USB, you will have to grant the host access permission, on the Android device.
Now install the Android emacs APK on the Android device:
- adb install -r java/*.apk
Start emacs on the Android device using the Android launcher. It is possible to start it from the host too, using adb:
- adb shell am start -D -n “org.gnu.emacs/org.gnu.emacs.EmacsActivity”
lldb-server is part of Termux package lldb:
- pkg install lldb
Now start lldb-server:
- lldb-server platform –listen “0.0.0.0:7564” –server
This starts lldb-server in a mode compatible with lldb, and the server can handle several lldb connections, potentially in parallel. To stop the server just hit C-c.
On the host computer one must now set up port forwarding:
- adb forward “tcp:7564” “tcp:7564”
This only has to be done once in a host login session.
lldb has a configuration file, ~/lldbinit. Here is how my ~/.lldbinit looks:
- settings set target.load-cwd-lldbinit true
- settings set target.inline-breakpoint-strategy always
- process handle -p true -n false -s false SIGUSR1
- process handle -p true -n false -s false SIGIO
The settings for SIGUSR1 and SIGIO are necessary, otherwise lldb will stop emacs as soon as you do something to the app window, and also when I/O happens.
There is an .lldbinit in the emacs repo src subdirectory, and I consider it to be useful. Thats why I have set:
- settings set target.load-cwd-lldbinit true
Without this setting lldb will refuse to read that .lldbinit file.
The line “settings set target.inline-breakpoint-strategy always” is for handling the case that a .c, or .cpp, file includes another .c or .cpp file. I do not know if it is needed for the case of emacs, but it does not seem to cause any problems to have it set.
There is probably an lldb available in your host linux package manager, that is what I am using. There is also an lldb available in NDK: toolchains/llvm/prebuilt/linux-x86_64/bin/lldb. However, that one must be started via toolchains/llvm/prebuilt/linux-x86_64/bin/lldb.sh, otherwise it will complain about several missing dependencies. That lldb seems to have been built with support for reading object file sections compressed with LZMA, while at least my host lldb seems to lack such support. However such support appears to not be essential.
Run “adb shell ps | grep emacs” to show the process id for Android emacs. I will call it $emacspid below.
Change dir to the emacs repo src subdirectory:
- lldb
- platform select remote-android
- platform connect connect://R52TC05Y0BF:7564
- The R52… is what “adb devices” shows.
- process attach -p $emacspid
- image list libemacs.so
- This is just a sanity check. Expected output is something as follows:
- [ 0] 3BEBBEFC 0x0000006d76e1d000 home/jw.lldb/module_cache/remote-android/.cache/3BEBBEFC/libemacs.so
- This is just a sanity check. Expected output is something as follows:
Emacs is paused. One can now issue various lldb commands and for example set a break point. To resume emacs give command:
- continue
Documentation commands:
- help
- apropos
There is lots of online help in lldb. For example try “help platform”. Show the value of variable “pure_size” (works even for static variables):
- target variable pure_size
- Can be shortened to “ta v pure_size”
Show global variables in current file:
- target variable
Show variables in current stack frame:
- frame variable
Show the value of variable “bar” in current stack frame:
- frame variable bar
- Can be shortened to “fr v bar”
One can search for symbol, function and file names, using regular expressions:
- ta v -r .*gc_threshold.*
List beginning of function Fcons:
- l Fcons
Set a breakpoint on line 12 in file test.c:
- b test.c:12
In alloc.c I declared some debug variables as follows:
- DEFVAR_INT (“jw-debug-so-and-so”, jw_debug_so_and_so,
doc: * Documentation of what its for. *);
This enabled me to show the variable in emacs, with command “C-h v”. Very nice. But when I tried “ta v jw_debug_so_and_so”, lldb said that it could not find the variable. It turned out that it was part of the array “globals”. I could list the contents of this big array with the command:
- ta v -A globals
lldb has been around for a long time. If one googles for various use cases, one can encounter statements about necessary workarounds. However several of these workarounds appear to no longer be needed, at least in the case described above. Here are some examples:
- I have not needed to extract debug symbols from shared libraries into separate files.
- I have not needed to deal with sysroot.
- I have not needed to copy the Android system libraries to the host, and specify the top of that directory tree as sysroot.
- I have not needed to specify target.exec-search-paths, such as:
- settings append target.exec-search-paths “/home/jw/Downloads/emacs-30/emacs/cross/src”
References:
- The LLDB Debugger: https://lldb.llvm.org/
- lldb-server: https://lldb.llvm.org/man/lldb-server.html
- lldb cheat sheet: https://www.nesono.com/sites/default/files/lldb%20cheat%20sheet.pdf
- LLDB Debugger commands: https://gist.github.com/wanyakun/314690093ea195d749fca6869ebf200e
- lldb source mapping: https://werat.dev/blog/debugging-lldb-with-source-stepping/