]> xenbits.xensource.com Git - unikraft/unikraft.git/commitdiff
lib/ukdebug, arch/arm: Add main loop to handle incoming packets
authorMarc Rittinghaus <marc.rittinghaus@unikraft.io>
Mon, 19 Aug 2024 08:34:37 +0000 (10:34 +0200)
committerUnikraft Bot <monkey@unikraft.io>
Fri, 13 Sep 2024 20:51:57 +0000 (20:51 +0000)
The main loop of the gdb stub receives packets, calls the appropriate
handlers and responds to the gdb host. The loop is entered after a
debug trap has been caught.  The code in `lib/ukdebug/arch/*` implements
the trap handling for the ARM64 and x86_64 architectures.

Checkpatch-Ignore: COMPLEX_MACRO
Checkpatch-Ignore: UNNECESSARY_PARENTHESES
Signed-off-by: Marc Rittinghaus <marc.rittinghaus@unikraft.io>
Reviewed-by: Michalis Pappas <michalis@unikraft.io>
Reviewed-by: Simon Kuenzer <simon@unikraft.io>
Approved-by: Simon Kuenzer <simon@unikraft.io>
GitHub-Closes: #1479

arch/arm/arm64/include/uk/asm/arch.h
lib/ukdebug/Config.uk
lib/ukdebug/Makefile.uk
lib/ukdebug/arch/arm64/gdbsup.c [new file with mode: 0644]
lib/ukdebug/arch/x86_64/gdbsup.c [new file with mode: 0644]
lib/ukdebug/gdbstub.c
lib/ukdebug/gdbstub.h [new file with mode: 0644]

index cdf8fda81a0c3da5fc22c3ebf1db30569c07dea4..c1542b536fc5a1ca46fd8697c0b8b2b715b0625e 100644 (file)
 #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)
index 6be82c0a84161ac75e1293e68319dbd8edfcab8a..57ad7621011e5c039a26e4ff2944e6c2ab17fd8c 100644 (file)
@@ -124,6 +124,8 @@ endif
 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.
 
index f8d462b92250e93a6b1c559ff48eea9eaa4a12e8..fcaf988b248f988e4f733ef43099e2b473f7b46b 100644 (file)
@@ -3,6 +3,9 @@ $(eval $(call addlib_s,libukdebug,$(CONFIG_LIBUKDEBUG)))
 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__
 
@@ -13,6 +16,7 @@ LIBUKDEBUG_SRCS-y += $(LIBUKDEBUG_BASE)/hexdump.c
 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
diff --git a/lib/ukdebug/arch/arm64/gdbsup.c b/lib/ukdebug/arch/arm64/gdbsup.c
new file mode 100644 (file)
index 0000000..e9f3fb9
--- /dev/null
@@ -0,0 +1,112 @@
+/* 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);
diff --git a/lib/ukdebug/arch/x86_64/gdbsup.c b/lib/ukdebug/arch/x86_64/gdbsup.c
new file mode 100644 (file)
index 0000000..915dd0d
--- /dev/null
@@ -0,0 +1,49 @@
+/* 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);
index bb9ab57517a5606b21ade621aa780fc3a173bdaa..0c4e4204ac08bd32747a45b2ccf2aecc30783950 100644 (file)
@@ -5,15 +5,28 @@
  * 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;
@@ -31,6 +53,15 @@ int gdb_dbg_getc(void)
        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;
@@ -268,3 +299,105 @@ static __ssz gdb_recv_packet(char *buf, __sz len)
        /* 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;
+}
diff --git a/lib/ukdebug/gdbstub.h b/lib/ukdebug/gdbstub.h
new file mode 100644 (file)
index 0000000..af5731d
--- /dev/null
@@ -0,0 +1,19 @@
+/* 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__ */