diff options
-rw-r--r-- | docs/HACKING.md | 40 | ||||
-rw-r--r-- | src/boot/efi/boot.c | 3 | ||||
-rw-r--r-- | src/boot/efi/stub.c | 3 | ||||
-rw-r--r-- | src/boot/efi/util.c | 17 | ||||
-rw-r--r-- | src/boot/efi/util.h | 10 | ||||
-rwxr-xr-x | tools/debug-sd-boot.sh | 85 |
6 files changed, 158 insertions, 0 deletions
diff --git a/docs/HACKING.md b/docs/HACKING.md index e194df64cf..117ed92533 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -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" }, + ] +} +``` diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 0360d2a0bf..2c5bd913d2 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -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, diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 0b1f276c41..22554a4860 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -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, diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index 76e4eef1eb..dbd7301d71 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -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 diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index b40f05eaef..fa88ab62cb 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -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 diff --git a/tools/debug-sd-boot.sh b/tools/debug-sd-boot.sh new file mode 100755 index 0000000000..1af97c18ec --- /dev/null +++ b/tools/debug-sd-boot.sh @@ -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 |