Rosetta on Linux has been great for me, as someone who switched to an ARM Mac and sometimes develops in a VM. This is because my dayjob often involves debugging a proprietary database driver that only has x86 and PowerPC versions available for Linux, not 64-bit ARM. However, while Rosetta can make it easy to run x86 binaries, it’s not as obvious how to debug them. If you naively try running GDB on your x86 program, you get errors like:
warning: `/lib64/ld-linux-x86-64.so.2': Shared library architecture unknown is not compatible with target architecture aarch64. warning: `/lib64/ld-linux-x86-64.so.2': Shared library architecture unknown is not compatible with target architecture aarch64. Warning: Cannot insert breakpoint -1. Cannot access memory at address 0x66ec48
This is because you’re attaching to the Rosetta binary, which causes all sorts of confusion for GDB. Turns out, Rosetta does actually have a way to handle this.
I’ll assume you already have Rosetta set up and working. If you’ve tried running Rosetta directly, you’ll notice there’s an interesting environment variable in its usage message:
$ /media/rosetta/rosetta Usage: rosetta <x86_64 ELF to run> Optional environment variables: ROSETTA_DEBUGSERVER_PORT wait for a debugger connection on given port version: Rosetta-289.7
This isn’t well documented (I’ve seen very few references online), but turns out it’s a GDB server embedded inside of Rosetta. When running your program, simply set that environment variable (i.e.
ROSETTA_DEBUGSERVER_PORT=1234 /path/to/x86). The program will be paused right before execution, and Rosetta will wait on the port you specify for an incoming GDB connection.
You need to make sure that your GDB build supports x86. On Debian, you can install the
gdb-multiarch package, which provides gdb that supports multiple architectures as…
gdb-multiarch. Open another terminal, start the multi-arch gdb, and then set up the environment and connect like this:
(gdb) set architecture i386:x86-64 The target architecture is set to "i386:x86-64". (gdb) file /path/to/x86 Reading symbols from /path/to/x86... (gdb) target remote localhost:1234 Remote debugging using localhost:1234 warning: remote target does not support file transfer, attempting to access files from local filesystem. Reading symbols from /lib64/ld-linux-x86-64.so.2... Reading symbols from /usr/lib/debug/.build-id/e2/5570740d590e5cb7b1a20d86332a8d1bb3b65f.debug... 0x00007ffffffd4090 in _start () from /lib64/ld-linux-x86-64.so.2
We connect to the GDB server, and it breaks right at the C runtime entry point. We can add any breakpoints or any other things into the debug environment, and step through as we wish.
If you’ve used GDB to connect to a remote system, this is basically identical. The only thing we don’t really do that people debugging say, an embedded system would do, is set a sysroot, and this is because GDB will default to the host’s file system. Since Rosetta binaries have an identical view of the host file system as a native binary does (made possible because of multiarch distros), this is fine. (On the other hand, if you did want to debug from another system, you would have to have a copy of the libraries/binaries on the system you’re connecting from, and set the sysroot to that.)