boot: Add gdb support and documentation

This will finally allow debugging issues in systemd without resorting to
Print() calls all over the place.
This commit is contained in:
Jan Janssen 2021-12-19 18:05:44 +01:00 committed by Zbigniew Jędrzejewski-Szmek
parent ff97eb4aac
commit 948d085e89
6 changed files with 158 additions and 0 deletions

View file

@ -330,3 +330,43 @@ To debug systemd components other than PID 1, set "program" to the full path of
debug and set "processId" to "${command:pickProcess}". Now, when starting the debugger, VSCode will ask you
the PID of the process you want to debug. Run `systemctl show --property MainPID --value <component>` in the
container to figure out the PID and enter it when asked and VSCode will attach to that process instead.
# Debugging systemd-boot
During boot, systemd-boot and the stub loader will output a message like `systemd-boot@0x0A,0x0B`,
providing the location of the text and data sections. These location can then be used to attach
to a QEMU session (provided it was run with `-s`) with these gdb commands:
```
(gdb) file build/src/boot/efi/systemd-bootx64.efi
(gdb) add-symbol-file build/src/boot/efi/systemd_boot.so 0x0A -s .data 0x0B
(gdb) set architecture i386:x86-64
(gdb) target remote :1234
```
This process can be automated by using the `debug-sd-boot.sh` script in the tools folder. If run
without arguments it will provide usage information.
If the debugger is too slow to attach to examine an early boot code passage, we can uncomment the
call to `debug_break()` inside of `efi_main()`. As soon as the debugger has control we can then run
`set variable wait = 0` or `return` to continue. Once the debugger has attached, setting breakpoints
will work like usual.
To debug systemd-boot in an IDE such as VSCode we can use a launch configuration like this:
```json
{
"name": "systemd-boot",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/src/boot/efi/systemd-bootx64.efi",
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"miDebuggerServerAddress": ":1234",
"setupCommands": [
{ "text": "shell mkfifo /tmp/sdboot.{in,out}" },
{ "text": "shell qemu-system-x86_64 [...] -s -serial pipe:/tmp/sdboot" },
{ "text": "shell ${workspaceFolder}/tools/debug-sd-boot.sh ${workspaceFolder}/build/src/boot/efi/systemd-bootx64.efi /tmp/sdboot.out systemd-boot.gdb" },
{ "text": "source /tmp/systemd-boot.gdb" },
]
}
```

View file

@ -2353,6 +2353,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
InitializeLib(image, sys_table);
init_usec = time_usec();
debug_hook(L"systemd-boot");
/* Uncomment the next line if you need to wait for debugger. */
// debug_break();
err = BS->OpenProtocol(image,
&LoadedImageProtocol,

View file

@ -181,6 +181,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
EFI_STATUS err;
InitializeLib(image, sys_table);
debug_hook(L"systemd-stub");
/* Uncomment the next line if you need to wait for debugger. */
// debug_break();
err = BS->OpenProtocol(
image,

View file

@ -738,3 +738,20 @@ UINT64 get_os_indications_supported(void) {
return osind;
}
#ifdef EFI_DEBUG
__attribute__((noinline)) void debug_break(void) {
/* This is a poor programmer's breakpoint to wait until a debugger
* has attached to us. Just "set variable wait = 0" or "return" to continue. */
volatile BOOLEAN wait = TRUE;
while (wait)
/* Prefer asm based stalling so that gdb has a source location to present. */
#if defined(__i386__) || defined(__x86_64__)
asm volatile("pause");
#elif defined(__aarch64__)
asm volatile("wfi");
#else
BS->Stall(5000);
#endif
}
#endif

View file

@ -159,3 +159,13 @@ static inline void *PHYSICAL_ADDRESS_TO_POINTER(EFI_PHYSICAL_ADDRESS addr) {
}
UINT64 get_os_indications_supported(void);
#ifdef EFI_DEBUG
void debug_break(void);
extern UINT8 _text, _data;
/* Report the relocated position of text and data sections so that a debugger
* can attach to us. See debug-sd-boot.sh for how this can be done. */
# define debug_hook(identity) Print(identity L"@0x%x,0x%x\n", &_text, &_data)
#else
# define debug_hook(identity)
#endif

85
tools/debug-sd-boot.sh Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
if [[ $# -lt 2 ]]; then
echo "Usage: ${0} TARGET INPUT [GDBSCRIPT]"
echo "Debug systemd-boot/stub in QEMU."
echo
echo "TARGET should point to the EFI binary to be examined inside the"
echo "build directory (systemd-boot\$ARCH.efi or linux\$arch.efi.stub)."
echo
echo "INPUT should point to the QEMU serial output pipe. This is used to"
echo "extract the location of the symbols. For this to work, QEMU must"
echo "be run with '-s -serial pipe:PATH'. Note that QEMU will append"
echo ".in/.out to the path, while this script expects the out pipe directly."
echo
echo "If GDBSCRIPT is empty, gdb is run directly attached to the boot"
echo "loader, otherwise a script is generated in the given path that allows"
echo "attaching manually like this:"
echo " (gdb) source GDBSCRIPT"
echo " (gdb) target remote :1234"
echo
echo "Exmaple usage:"
echo " mkfifo /tmp/sdboot.{in,out}"
echo " qemu-system-x86_64 [...] -s -serial pipe:/tmp/sdboot"
echo " ./tools/debug-sd-boot.sh ./build/src/boot/efi/systemd-bootx64.efi \\"
echo " /tmp/sdboot.out"
exit 1
fi
binary=$(realpath "${1}")
if [[ "${1}" =~ systemd-boot([[:alnum:]]+).efi ]]; then
target="systemd-boot"
symbols=$(realpath "$(dirname "${1}")/systemd_boot.so")
elif [[ "${1}" =~ linux([[:alnum:]]+).efi.stub ]]; then
target="systemd-stub"
symbols=$(realpath "$(dirname "${1}")/linux${BASH_REMATCH[1]}.elf.stub")
else
echo "Cannot detect EFI binary '${1}'."
exit 1
fi
case "${BASH_REMATCH[1]}" in
ia32) arch="i386";;
x64) arch="i386:x86-64";;
aa64) arch="aarch64";;
arm|riscv64) arch="${BASH_REMATCH[1]}";;
*)
echo "Unknown EFI arch '${BASH_REMATCH[1]}'."
exit 1
esac
# system-boot will print out a line like this to inform us where gdb is supposed to
# look for .text and .data section:
# systemd-boot@0x0,0x0
while read -r line; do
if [[ "${line}" =~ ${target}@(0x[[:xdigit:]]+),(0x[[:xdigit:]]+) ]]; then
text="${BASH_REMATCH[1]}"
data="${BASH_REMATCH[2]}"
break
fi
done < "${2}"
if [[ -z "${text}" || -z "${data}" ]]; then
echo "Could not determine text and data location."
exit 1
fi
if [[ -z "${3}" ]]; then
gdb_script=$(mktemp /tmp/debug-sd-boot.XXXXXX.gdb)
trap 'rm -f "${gdb_script}"' EXIT
else
gdb_script="${3}"
fi
echo "file ${binary}
add-symbol-file ${symbols} ${text} -s .data ${data}
set architecture ${arch}" > "${gdb_script}"
if [[ -z "${3}" ]]; then
gdb -x "${gdb_script}" -ex "target remote :1234"
else
echo "GDB script written to '${gdb_script}'."
fi