]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: Add gdb support and documentation
authorJan Janssen <medhefgo@web.de>
Sun, 19 Dec 2021 17:05:44 +0000 (18:05 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 4 Jan 2022 16:23:01 +0000 (17:23 +0100)
This will finally allow debugging issues in systemd without resorting to
Print() calls all over the place.

docs/HACKING.md
src/boot/efi/boot.c
src/boot/efi/stub.c
src/boot/efi/util.c
src/boot/efi/util.h
tools/debug-sd-boot.sh [new file with mode: 0755]

index e194df64cfcddf858b014a6baa5aeac04dc5405a..117ed9253313be6ed5735fe239c82adc900f968a 100644 (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" },
+    ]
+}
+```
index 0360d2a0bff795fcca0f9b026f9a7d94ca1e094c..2c5bd913d2786f78394d7a1f1cfaab675dec6579 100644 (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,
index 0b1f276c4167683f17bc4967bf7ba6402787e0af..22554a4860443d4b66f19a1875fb069cb1f4f1a9 100644 (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,
index 76e4eef1eb5de2b425c5ddc3a3d6bd0f14725c7e..dbd7301d71787fdb5e9858abfd925656d58313b2 100644 (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
index b40f05eaef09b3d4554a4b6bf4838bfb93dc8ec6..fa88ab62cb6cf8733117ec8d559311292ab1ef3e 100644 (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
diff --git a/tools/debug-sd-boot.sh b/tools/debug-sd-boot.sh
new file mode 100755 (executable)
index 0000000..1af97c1
--- /dev/null
@@ -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