summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/HACKING.md40
-rw-r--r--src/boot/efi/boot.c3
-rw-r--r--src/boot/efi/stub.c3
-rw-r--r--src/boot/efi/util.c17
-rw-r--r--src/boot/efi/util.h10
-rwxr-xr-xtools/debug-sd-boot.sh85
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