#!/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 "Example 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 "${1%efi}elf")
elif [[ "${1}" =~ linux([[:alnum:]]+).efi.stub ]]; then
    target="systemd-stub"
    symbols=$(realpath "${1%efi.stub}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/stub will print out a line like this to inform us where it was loaded:
#        systemd-boot@0xC0DE
while read -r line; do
    if [[ "${line}" =~ ${target}@(0x[[:xdigit:]]+) ]]; then
        loaded_base="${BASH_REMATCH[1]}"
        break
    fi
done <"${2}"

if [[ -z "${loaded_base}" ]]; then
    echo "Could not determine loaded image base."
    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

cat >"${gdb_script}" <<EOF
file ${binary}
symbol-file ${symbols} -o ${loaded_base}
set architecture ${arch}
EOF

if [[ -z "${3}" ]]; then
    gdb -x "${gdb_script}" -ex "target remote :1234"
else
    echo "GDB script written to '${gdb_script}'."
fi
