mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-11 05:17:44 +03:00
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:
parent
ff97eb4aac
commit
948d085e89
@ -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" },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
85
tools/debug-sd-boot.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user