Morello GDB is a fork of GDB adding support for Morello, including the pure-capability ABI. This is a short guide to get started with Morello GDB.
Please note that although Morello GDB offers most of the features required for a comfortable experience debugging pure-capability applications, some edge cases may not be handled correctly.
Some terminology:
Morello GDB is hosted as a vendor branch in the upstream binutils-gdb repository: https://sourceware.org/git/?p=binutils-gdb.git;a=shortlog;h=refs/heads/users/ARM/morello-binutils-gdb-master
In order to use Morello GDB together with the latest Morello Linux kernel, it is currently required to build it from source. This is done exactly in the same way as upstream GDB; no Morello toolchain is required. Instructions for the most common situations are included below. First clone the repo:
git clone -b users/ARM/morello-binutils-gdb-master https://sourceware.org/git/binutils-gdb.git
cd binutils-gdb
For debugging applications directly on the Morello board, the native build approach is recommended as it is the most straightforward and provides the richest feature set. For debugging applications that have been built on a separate host machine, the “host GDB + cross GDBserver” approach may be preferable.
Note: to speed up the build, pass -j<N>
to make
, where <N>
is the number of cores on the machine.
Regardless of the build environment, the following arguments are relevant for ./configure
:
--prefix <path>
to choose where the files should be installed.--disable-werror
to prevent warnings from aborting the build (useful when building with a recent compiler).When building on an AArch64 environment (for instance a Morello board), the default configuration can be used to build GDB. For the best experience, it is recommended to install the following optional dependencies (on Debian/Ubuntu): apt install libsource-highlight-dev guile-3.0-dev
./configure
make all-gdb
make install-gdb
When building on an x86 environment, first make sure to install an AArch64 GNU toolchain (on Debian/Ubuntu: apt install g++-aarch64-linux-gnu
). In order to avoid issues arising from a mismatch between the cross-compilation and target environments, it is recommended to build GDB statically in this case:
./configure --host=aarch64-linux-gnu --target=aarch64-linux-gnu LDFLAGS=-static --disable-threading --without-guile --disable-source-highlight
make all-gdb
make install-gdb
It is assumed that the cross-compilation environment is minimal and does not include libraries such as Guile and source-highlight, the use of which is therefore disabled. Some functionalities will not be available as a result.
In certain situations, such as debugging on FVP, it may be more comfortable to run GDB itself on the host machine, and connect to a GDBserver instance running on the target. In that case, first build GDB for the host (see “Native GDB build” regarding optional dependencies):
./configure --target=aarch64-linux-gnu
make all-gdb
make install-gdb
Then build GDBserver (statically for maximum compatibility):
git clean -xf # Necessary after building GDB for the host
./configure --host=aarch64-linux-gnu --target=aarch64-linux-gnu LDFLAGS=-static --disable-threading --disable-gdb --disable-inprocess-agent
make all-gdbserver
make install-gdbserver
Let’s call the application that we wish to debug myapp
. To get the debug session started, there are two scenarios to consider: native GDB, and host GDB + cross GDBserver.
This is the scenario where GDB itself is running on the target. If GDB was built natively (“Native GDB build” above), then PATH_TO_GDB
is <install_prefix>/bin
.
Alternatively, if GDB was built on the host (“Cross GDB build” above), then it should first be copied to PATH_TO_GDB
on the target. To speed up the transfer, it is recommended to strip it first. Example steps using SCP to transfer the binary:
aarch64-linux-gnu-strip -o gdb_stripped -s <install_prefix>/bin/gdb
scp gdb_stripped <target>:PATH_TO_GDB/gdb
The debug session is then started on the target using:
PATH_TO_GDB/gdb --args ./myapp <args>
At this point the application itself is not running yet. Use one of the standard GDB commands (run
or start
) to do so.
This is the scenario where GDBserver is running on the target, while GDB is running on the host and connects to the GDBserver instance (“Host GDB + cross GDBserver build” above). First copy GDBserver to PATH_TO_GDBSERVER
on the target, for instance:
scp <install_prefix>/bin/gdbserver <target>:PATH_TO_GDBSERVER/gdbserver
Then start the debug session on the target via GDBserver:
PATH_TO_GDBSERVER/gdbserver 127.0.0.1:5039 ./myapp <args>
The application is now running, but stopped on the first instruction. Back to the host, we now need to start GDB and then connect to the GDBserver instance:
<install_prefix>/bin/aarch64-linux-gnu-gdb
(gdb) target extended-remote <target>:5039
Note: on FVP, the port GDBserver listens on needs to be forwarded to the host. Assuming user-mode networking is used over virtio net, this can be done by passing for instance -C board.virtio_net.hostbridge.userNetPorts=5039=5039
to FVP. In that case the target to connect to is 127.0.0.1:5039
.
To end the debugging session and exit GDBserver, the easiest option is to use the following command once connected:
(gdb) monitor exit
Alternatively, use pkill gdbserver
on the target.
Generally speaking, debugging Morello applications, including pure-capability binaries, is done using the usual GDB commands, such as continue
, next
, print
, etc. For more information, please refer to the GDB manual. This section provides additional information regarding Morello-specific features.
Capabilities can be displayed using the print
(p
) command, like any other type. For instance, one may print the argv
capability in the main()
function of a pure-capability application:
(gdb) p argv
$1 = (char * __capability * __capability) 0xfffffffff630 [rwxRWE,0x0-0x1000000000000] 0xfffffffff630
By default, capabilities are printed in the so-called simplified format (see this wiki page). Alternatively, Morello GDB can display a verbose decoding of the capability:
(gdb) set print compact-capabilities 0
(gdb) p argv
$2 = (char * __capability * __capability) {tag = 1, address = 0xfffffffff630, permissions = {[ Global Executive User0 MutableLoad BranchUnseal System StoreLocalCap StoreCap LoadCap Execute Store Load ] range = [0x0 - 0x1000000000000)}} 0xfffffffff630
Finally, the raw 128-bit value (excluding the tag) can be printed using:
(gdb) p/x argv
$3 = 0xfcd1c000000300050000fffffffff630
Capability registers can be printed like any other register:
(gdb) p $pcc
$1 = (__capability ) 0x2116ac [rxRE,0x200200-0x227280] 0x2116ac <main+144>
The entire register set, including capability registers and other Morello registers such as CCTLR, can be printed with info registers
.
GDB automatically detects the type of expressions when printing, which notably allows to display capabilities stored in memory:
(gdb) p argv[0]
$1 = 0xfffffffffc7b [rwxRWE,0x0-0x1000000000000] 0xfffffffffc7b "../bin/morello-auxv"
To manually inspect capabilities stored in memory, an arbitrary address can be cast to a pointer to a capability and then dereferenced:
(gdb) p *(void * __capability *)($sp + 16)
$1 = (void * __capability) 0xfffffffff59c [rwxRWE,0xfffffffff59c-0xfffffffff5a0] 0xfffffffff59c
The Morello Linux kernel currently has two modes. By default, capabilities cannot be written via ptrace, in order to avoid violations of the capability model. However, to improve the debugging experience, it is possible to enable capability writes via ptrace, by running the following command on the target (as root):
sysctl cheri.ptrace_forge_cap=1
In this mode, arbitrary capabilities may be injected in the debugged process, and it should therefore only be enabled when weakening certain security properties of the system is acceptable.
Note that injected capabilities are still required to point within the userspace boundaries. In other words, their upper bound cannot exceed the top of the user address space (typically 2^48
).
Provided that capability forging is enabled, the set
command may be used to set capability variables and registers like any other:
(gdb) p $pcc
$1 = (__capability ) 0x2116ac [rxRE,0x200200-0x227280] 0x2116ac <main+144>
(gdb) set $c0 = $pcc
(gdb) p $c0
$2 = (__capability ) 0x2116ac [rxRE,0x200200-0x227280] 0x2116ac <main+144>
Variables and standard registers can only be set to an existing capability (stored in another variable, for instance). In other words, it is not directly possible to modify them or set them to an arbitrary literal value.
Pseudo-capability registers give access to capability registers as arbitrary 129-bit values, by representing them as a struct with three fields: .l
, the lower 64 bits (typically the address); .u
, the upper 64 bits; and .t
, the tag. They are named in the same way as the regular capability registers, prefixed with p
. For instance, printing C0 through the pseudo-capability view:
(gdb) p/x $pc0
$1 = {
l = 0x2116ac,
u = 0xb090c0000e570044,
t = 0x1
}
This is most useful when it comes to modifying the capability. For instance, the address can be incremented by 4, without untagging the capability, using:
(gdb) set $pc0.l += 4
(gdb) p $c0
$1 = (__capability ) 0x2116b0 [rxRE,0x200200-0x227280] 0x2116b0 <main+148>
As noted earlier, the resulting capability is required to be a valid user capability (bounds in the user range). If this is not the case, it will be untagged.
Note: there is currently no simple way to modify the capability metadata - a new encoding needs to be computed and the upper 64 bits (.u
) updated accordingly.
Morello GDB currently has very limited support for writing capabilities to memory. Doing so while preserving the tag can be done using a maintenance command, which takes four arguments (in that order): the address to write to, and then the three “components” of the capability to write (tag, upper 64 bits, lower 64 bits). For instance, to write PCC at the address in SP:
(gdb) maintenance cap_in_memory $sp $ppcc.t $ppcc.u $ppcc.l
Morello GDB is able to call functions in pure-capability applications with the usual commands (see the corresponding section in the GDB manual). However, because doing so requires setting capability registers, the cheri.ptrace_forge_cap
sysctl needs to be enabled (see previous section).
As an example, a fresh capability to the heap can be obtained using:
(gdb) p $p = malloc(16)
$1 = (void * __capability) 0xfffff7ff2280 [rwRW,0xfffff7ff2280-0xfffff7ff2290] 0xfffff7ff2280
Note: Morello GDB does not currently support calling variadic functions (such as printf()
) in purecap.
Morello GDB is able to disassemble Morello instructions, and automatically detects the instruction set (A64 or C64).
Morello GDB automatically detects the ABI of the application. To show the detected ABI, use:
(gdb) show aarch64 abi
The current AArch64 ABI is "auto" (currently "AAPCS64-cap").
AAPCS64-cap
is the pure-capability ABI, while AAPCS64
is the regular arm64 ABI.