#define ID_AA64MMFR2_EL1_CNP_SHIFT _AC(0, U)
#define ID_AA64MMFR2_EL1_CNP_MASK _AC(0xf, ULL)
+/* MDSCR_EL1: Monitor Debug System Control Register */
+#define MDSCR_EL1_SS UK_BIT(0)
+#define MDSCR_EL1_KDE UK_BIT(13)
+
/* RGSR_EL1: Random Allocation Tag Seed Register */
#define RGSR_EL1_SEED_SHIFT _AC(8, U)
#define RGSR_EL1_SEED_MASK _AC(0xffff, UL)
#define SCTLR_EL1_ATA_BIT (_AC(1, UL) << 43)
#define SCTLR_EL1_DSSBS_BIT (_AC(1, UL) << 44)
+/* SPSR: Saved Program Status Register */
+#define SPSR_EL1_SS UK_BIT(21)
+#define SPSR_EL1_D UK_BIT(9)
+
/* TCR_EL1 - Translation Control Register */
#define TCR_EL1_DS_SHIFT 59
#define TCR_EL1_DS_BIT (_AC(1, UL) << TCR_EL1_DS_SHIFT)
config LIBUKDEBUG_GDBSTUB
bool "GDB stub"
depends on (ARCH_X86_64 || ARCH_ARM_64)
+ select LIBUKNOFAULT
+ select LIBISRLIB
help
The GDB stub allows connecting GDB to Unikraft via a serial console.
CINCLUDES-$(CONFIG_LIBUKDEBUG) += -I$(LIBUKDEBUG_BASE)/include
CXXINCLUDES-$(CONFIG_LIBUKDEBUG) += -I$(LIBUKDEBUG_BASE)/include
+LIBUKDEBUG_ASINCLUDES-y += -I$(LIBUKDEBUG_BASE)/arch/$(CONFIG_UK_ARCH)
+LIBUKDEBUG_CINCLUDES-y += -I$(LIBUKDEBUG_BASE)/arch/$(CONFIG_UK_ARCH)
+
LIBUKDEBUG_CFLAGS-y += -D__IN_LIBUKDEBUG__
LIBUKDEBUG_CXXFLAGS-y += -D__IN_LIBUKDEBUG__
LIBUKDEBUG_SRCS-$(CONFIG_LIBZYDIS) += $(LIBUKDEBUG_BASE)/asmdump.c
LIBUKDEBUG_SRCS-$(CONFIG_LIBUKDEBUG_GDBSTUB) += $(LIBUKDEBUG_BASE)/gdbstub.c|isr
+LIBUKDEBUG_SRCS-$(CONFIG_LIBUKDEBUG_GDBSTUB) += $(LIBUKDEBUG_BASE)/arch/$(CONFIG_UK_ARCH)/gdbsup.c|isr
LIBUKDEBUG_SRCS-$(CONFIG_LIBUKDEBUG_TRACEPOINTS) += $(LIBUKDEBUG_BASE)/trace.c
LIBUKDEBUG_SRCS-$(CONFIG_LIBUKDEBUG_TRACEPOINTS) += $(LIBUKDEBUG_BASE)/trace.ld
--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* Copyright (c) 2021, Karlsruhe Institute of Technology. All rights reserved.
+ * Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors.
+ * Licensed under the BSD-3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ */
+
+#include "gdbsup.h"
+#include "../../gdbstub.h"
+
+#include <uk/arch/traps.h>
+#include <uk/arch/lcpu.h>
+#include <uk/essentials.h>
+#include <uk/nofault.h>
+#include <uk/bitops.h>
+#include <uk/compiler.h>
+
+#define BRK_OPCODE_MASK 0xffe0001fUL
+#define BRK_OPCODE 0xd4200000UL
+
+static void gdb_arch_enable_single_step(struct __regs *regs)
+{
+ __sz mdscr = SYSREG_READ(mdscr_el1);
+
+ mdscr |= MDSCR_EL1_SS;
+ mdscr |= MDSCR_EL1_KDE;
+
+ SYSREG_WRITE(mdscr_el1, mdscr);
+
+ regs->spsr_el1 |= SPSR_EL1_SS;
+}
+
+static void gdb_arch_disable_single_step(struct __regs *regs)
+{
+ __sz mdscr = SYSREG_READ(mdscr_el1);
+
+ mdscr &= ~MDSCR_EL1_SS;
+ mdscr &= ~MDSCR_EL1_KDE;
+
+ SYSREG_WRITE(mdscr_el1, mdscr);
+
+ regs->spsr_el1 &= ~SPSR_EL1_SS;
+}
+
+/* We get here via traps raised by the platform
+ * TODO: Once the crash screen PR is merged, crashes can land us here
+ * too, if the "enter debugger on crash" feature is enabled.
+ *
+ * TODO: Arm needs extra configuration for hardware breakpoints. At present,
+ * we only support software breakpoints so there is no need to configure
+ * hardware breakpoints yet. See D2.7 and D2.8.4 of DDI0487K.
+ */
+static int gdb_arch_dbg_trap(int errnr, struct __regs *regs)
+{
+ int r;
+
+ gdb_arch_disable_single_step(regs);
+
+ r = gdb_dbg_trap(errnr, regs);
+ if (r < 0) {
+ return r;
+ } else if (r == GDB_DBG_STEP) { /* Single step */
+ gdb_arch_enable_single_step(regs);
+
+ regs->spsr_el1 &= ~SPSR_EL1_D;
+ }
+
+ return 0;
+}
+
+static int gdb_arch_check_brk(unsigned long pc)
+{
+ __u32 opcode;
+
+ if (uk_nofault_memcpy((void *)&opcode, (void *)pc, sizeof(opcode),
+ UK_NOFAULTF_NOPAGING) != sizeof(opcode))
+ return -EFAULT;
+
+ return ((opcode & BRK_OPCODE_MASK) != BRK_OPCODE);
+}
+
+static int gdb_arch_debug_handler(void *data)
+{
+ int have_brk, r;
+ struct ukarch_trap_ctx *ctx = (struct ukarch_trap_ctx *)data;
+
+ r = gdb_arch_dbg_trap(5 /* SIGTRAP */, ctx->regs);
+ if (unlikely(r < 0))
+ return r;
+
+ /* If we return from an brk trap, we have to explicitly skip the
+ * corresponding brk instruction. Otherwise, we will not make
+ * any progress and break again. However, software breakpoints
+ * temporarily overwrite instructions with brk so that we are also
+ * returning from an brk trap in this case but must not skip any
+ * instructions as GDB will have restored the original instruction by
+ * now. If the current PC is still brk, then it is compiled in and
+ * must be skipped. Otherwise, just continue at the current PC.
+ */
+ have_brk = gdb_arch_check_brk(ctx->regs->elr_el1);
+ if (unlikely(have_brk < 0))
+ return have_brk;
+
+ if ((ESR_EC_FROM(ctx->esr) == ESR_EL1_EC_BRK64) && (have_brk == 0)) {
+ ctx->regs->elr_el1 += 4; /* instructions are all 4 bytes wide */
+ ctx->regs->spsr_el1 &= ~SPSR_EL1_SS;
+ }
+
+ return UK_EVENT_HANDLED;
+}
+
+UK_EVENT_HANDLER(UKARCH_TRAP_DEBUG, gdb_arch_debug_handler);
--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* Copyright (c) 2021, Karlsruhe Institute of Technology. All rights reserved.
+ * Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors.
+ * Licensed under the BSD-3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ */
+
+#include "../../gdbstub.h"
+
+#include <uk/arch/traps.h>
+#include <uk/bitops.h>
+
+#ifndef X86_EFLAGS_TF
+#define X86_EFLAGS_TF UK_BIT(8)
+#endif
+
+/* We get here via traps raised by the platform
+ * TODO: Once the crash screen PR is merged, crashes can land us here
+ * too, if the "enter debugger on crash" feature is enabled.
+ */
+static int gdb_arch_dbg_trap(int errnr, struct __regs *regs)
+{
+ int r;
+
+ /* Unset trap flag, i.e., continue */
+ regs->eflags &= ~X86_EFLAGS_TF;
+
+ r = gdb_dbg_trap(errnr, regs);
+ if (r < 0) {
+ return r;
+ } else if (r == GDB_DBG_STEP) { /* Single step */
+ regs->eflags |= X86_EFLAGS_TF;
+ }
+
+ return 0;
+}
+
+static int gdb_arch_debug_handler(void *data)
+{
+ int r;
+ struct ukarch_trap_ctx *ctx = (struct ukarch_trap_ctx *)data;
+
+ if ((r = gdb_arch_dbg_trap(5 /* SIGTRAP */, ctx->regs)) < 0)
+ return r;
+ else
+ return UK_EVENT_HANDLED;
+}
+
+UK_EVENT_HANDLER(UKARCH_TRAP_DEBUG, gdb_arch_debug_handler);
* You may not use this file except in compliance with the License.
*/
+#include "gdbstub.h"
+#include <uk/plat/lcpu.h>
+
#include <uk/assert.h>
#include <uk/essentials.h>
#include <uk/arch/limits.h>
+#include <uk/isr/string.h>
#include <errno.h>
#include <stddef.h>
+struct gdb_excpt_ctx {
+ struct __regs *regs;
+ int errnr;
+};
+
+#define GDB_BUF_SIZE 2048
+static char gdb_recv_buffer[GDB_BUF_SIZE];
+
#define GDB_PACKET_RETRIES 5
+#define GDB_STR_A_LEN(str) str, (sizeof(str) - 1)
#define GDB_CHECK(expr) \
do { \
__ssz __r = (expr); \
return (int)__r; \
} while (0)
+typedef int (*gdb_cmd_handler_func)(char *buf, __sz buf_len,
+ struct gdb_excpt_ctx *g);
+
+struct gdb_cmd_table_entry {
+ gdb_cmd_handler_func f;
+ const char *cmd;
+ __sz cmd_len;
+};
+
int gdb_dbg_putc(char b __unused)
{
return -ENOTSUP;
return -ENOTSUP;
}
+static int gdb_starts_with(const char *buf, __sz buf_len,
+ const char *prefix, __sz prefix_len)
+{
+ if (unlikely(buf_len < prefix_len))
+ return 0;
+
+ return (memcmp_isr(buf, prefix, prefix_len) == 0);
+}
+
static char gdb_checksum(const char *buf, __sz len)
{
char c = 0;
/* We ran out of space */
return -ENOMEM;
}
+
+/* ? */
+static int gdb_handle_stop_reason(char *buf __unused, __sz buf_len __unused,
+ struct gdb_excpt_ctx *g)
+{
+ GDB_CHECK(gdb_send_signal_packet(g->errnr));
+
+ return 0;
+}
+
+static struct gdb_cmd_table_entry gdb_cmd_table[] = {
+ { gdb_handle_stop_reason, GDB_STR_A_LEN("?") }
+};
+
+#define NUM_GDB_CMDS (sizeof(gdb_cmd_table) / \
+ sizeof(struct gdb_cmd_table_entry))
+
+static int gdb_main_loop(struct gdb_excpt_ctx *g)
+{
+ __ssz r;
+ __sz i, l;
+
+ GDB_CHECK(gdb_send_signal_packet(g->errnr));
+
+ do {
+ r = gdb_recv_packet(gdb_recv_buffer, sizeof(gdb_recv_buffer));
+ if (unlikely(r < 0)) {
+ break;
+ } else if (r == 0) {
+ /* We received an empty packet */
+ continue;
+ }
+
+ for (i = 0; i < NUM_GDB_CMDS; i++) {
+ l = gdb_cmd_table[i].cmd_len;
+
+ if (!gdb_starts_with(gdb_recv_buffer, r,
+ gdb_cmd_table[i].cmd, l)) {
+ continue;
+ }
+
+ r = gdb_cmd_table[i].f(gdb_recv_buffer + l, r - l,
+ g);
+
+ break;
+ }
+
+ if (i == NUM_GDB_CMDS) {
+ /* Send empty packet to signal GDB that
+ * we do not support the command
+ */
+ GDB_CHECK(gdb_send_empty_packet());
+
+ r = 0; /* Ignore unsupported commands */
+ }
+ } while (r == 0);
+
+ UK_ASSERT(r < 0 || r == GDB_DBG_CONT || r == GDB_DBG_STEP);
+
+ return (int)r;
+}
+
+int gdb_dbg_trap(int errnr, struct __regs *regs)
+{
+ struct gdb_excpt_ctx g = {regs, errnr};
+ static int nest_cnt;
+ unsigned long irqs;
+ int r;
+
+ /* TODO: SMP support
+ * If we have SMP support, we must freeze all other CPUs here and
+ * resume them before returning. However, it might be that another
+ * CPU tries to enter the debugger at the same time. We therefore
+ * need to protect the debugger with a spinlock and try to lock it.
+ * If the current CPU does not get the lock, a different CPU is
+ * already in the debugger. The unsuccessful CPU has to wait for the
+ * lock to become available, but also remain responsive to the freeze
+ * that the first CPU initiates.
+ *
+ * Until we have SMP support, it is safe to just disable interrupts.
+ * We might re-enter nevertheless, for example, due to an UK_ASSERT
+ * in the debugger code itself or if we set a breakpoint in code
+ * called by the debugger.
+ */
+#ifdef UKPLAT_LCPU_MULTICORE
+#warning The GDB stub does not support multicore systems
+#endif
+
+ irqs = ukplat_lcpu_save_irqf();
+ if (nest_cnt > 0) {
+ ukplat_lcpu_restore_irqf(irqs);
+ return GDB_DBG_CONT;
+ }
+
+ nest_cnt++;
+ r = gdb_main_loop(&g);
+ nest_cnt--;
+
+ ukplat_lcpu_restore_irqf(irqs);
+
+ return r;
+}
--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* Copyright (c) 2021, Karlsruhe Institute of Technology. All rights reserved.
+ * Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors.
+ * Licensed under the BSD-3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ */
+
+#ifndef __UKDEBUG_GDBSTUB_H__
+#define __UKDEBUG_GDBSTUB_H__
+
+#include <uk/config.h>
+#include <uk/arch/lcpu.h>
+
+#define GDB_DBG_CONT 0x0001 /* Continue execution */
+#define GDB_DBG_STEP 0x0002 /* Single-step one instruction */
+
+int gdb_dbg_trap(int errnr, struct __regs *regs);
+
+#endif /* __UKDEBUG_GDBSTUB_H__ */