From 64286c789ff1fd0c2296c73fa68fc4b4fbe44ee4 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sun, 24 May 2026 18:23:54 +0200 Subject: [PATCH 01/16] Include system assert.h in src/assert.h (macOS / clang compatibility) --- src/assert.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/assert.h b/src/assert.h index 4ca5227c..f232f9ba 100644 --- a/src/assert.h +++ b/src/assert.h @@ -9,6 +9,8 @@ #ifndef ASSERT_H_ #define ASSERT_H_ +#include + #ifndef NDEBUG #include "fault.h" From 59aa75c5d9ffd67fa260546aaffa5202905aba57 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sun, 24 May 2026 18:24:23 +0200 Subject: [PATCH 02/16] Only clear history session if stdin is a terminal Fixes segfault on macOS when stdin is not a terminal (e.g. BATS test setup). See 9c0d6886 for similar bugfix in init. --- src/input.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/input.c b/src/input.c index c6e5fdfd..dce36291 100644 --- a/src/input.c +++ b/src/input.c @@ -186,5 +186,7 @@ int input_is_terminal(void) void input_end(void) { - clear_history(); + if (input_term) { + clear_history(); + } } From 993e7d42bc5725466fdafaf6f7c62cf988d91ef2 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Tue, 5 May 2026 17:48:29 +0200 Subject: [PATCH 03/16] Add .clang-tidy Disable modernize-use-nullptr check (NULL -> nullptr) from C23 --- .clang-tidy | 1 + 1 file changed, 1 insertion(+) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..738f6d53 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1 @@ +Checks: '-modernize-use-nullptr' From 93d9a09d97cdb3a1c42401eef7764890c8063f75 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Thu, 9 Apr 2026 20:26:17 +0200 Subject: [PATCH 04/16] Check for RV64 cpu in RV device filter --- src/device/device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/device.c b/src/device/device.c index 23f55a57..7397494f 100644 --- a/src/device/device.c +++ b/src/device/device.c @@ -151,7 +151,7 @@ static bool dev_match_to_filter(device_t *device, device_filter_t filter) case DEVICE_FILTER_R4K_PROCESSOR: return (strcmp(device->type->name, "dr4kcpu") == 0); case DEVICE_FILTER_RV_PROCESSOR: - return (strcmp(device->type->name, "drvcpu") == 0); + return (strcmp(device->type->name, "drvcpu") == 0) || (strcmp(device->type->name, "drv64cpu") == 0); default: die(ERR_INTERN, "Unexpected device filter"); } From 664a927886de86c996425b5477f9315876fec91a Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sat, 25 Apr 2026 11:51:54 +0200 Subject: [PATCH 05/16] Add get_pc to general CPU API --- src/device/cpu/general_cpu.c | 8 ++++++++ src/device/cpu/general_cpu.h | 4 ++++ src/device/dr4kcpu.c | 6 ++++++ src/device/drv64cpu.c | 6 ++++++ src/device/drvcpu.c | 6 ++++++ 5 files changed, 30 insertions(+) diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index 9ca5650d..c9f5a59d 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -132,6 +132,14 @@ void cpu_reg_dump(general_cpu_t *cpu) cpu->type->reg_dump(cpu->data); } +ptr64_t cpu_get_pc(general_cpu_t *cpu) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + return cpu->type->get_pc(cpu->data); +} + void cpu_set_pc(general_cpu_t *cpu, ptr64_t pc) { if (cpu == NULL) { diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index 13861807..856a9509 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -27,6 +27,8 @@ typedef void (*remove_breakpoint_func_t)(void *, ptr64_t); typedef bool (*convert_addr_func_t)(void *, ptr64_t, ptr36_t *, bool); /** Function type for dumping register content */ typedef void (*reg_dump_func_t)(void *); +/** Function type for getting the program counter of a cpu */ +typedef ptr64_t (*get_pc_func_t)(void *); /** Function type for setting the program counter of a cpu */ typedef void (*set_pc_func_t)(void *, ptr64_t); /** Function type for notifying the processor about a write to a memory location, used for implementing SC atomic*/ @@ -43,6 +45,7 @@ typedef struct { remove_breakpoint_func_t remove_breakpoint; convert_addr_func_t convert_addr; reg_dump_func_t reg_dump; + get_pc_func_t get_pc; set_pc_func_t set_pc; sc_access_func_t sc_access; } cpu_ops_t; @@ -109,6 +112,7 @@ extern bool cpu_convert_addr(general_cpu_t *cpu, ptr64_t virt, ptr36_t *phys, bo */ extern void cpu_reg_dump(general_cpu_t *cpu); +extern ptr64_t cpu_get_pc(general_cpu_t *cpu); extern void cpu_set_pc(general_cpu_t *cpu, ptr64_t pc); /** diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index c3809666..ecb40f95 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -30,12 +30,18 @@ static bool r4k_cpu_convert_addr(r4k_cpu_t *cpu, ptr64_t virt, ptr36_t *phys, bo return r4k_convert_addr(cpu, virt, phys, write, false) == r4k_excNone; } +static ptr64_t r4k_get_pc_wrapper(void *cpu) +{ + return ((r4k_cpu_t *) cpu)->pc; +} + static const cpu_ops_t r4k_cpu = { .interrupt_up = (interrupt_func_t) r4k_interrupt_up, .interrupt_down = (interrupt_func_t) r4k_interrupt_down, .convert_addr = (convert_addr_func_t) r4k_cpu_convert_addr, .reg_dump = (reg_dump_func_t) r4k_reg_dump, + .get_pc = (get_pc_func_t) r4k_get_pc_wrapper, .set_pc = (set_pc_func_t) r4k_set_pc, .sc_access = (sc_access_func_t) r4k_sc_access }; diff --git a/src/device/drv64cpu.c b/src/device/drv64cpu.c index 1b65d500..0a4996da 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -33,6 +33,11 @@ static bool rv64_convert_add_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, boo return rv64_convert_addr((rv_cpu_t *) cpu, virt.ptr, phys, write, false, false) == rv_exc_none; } +static ptr64_t rv64_get_pc_wrapper(void *cpu) +{ + return (ptr64_t) { .ptr = ((rv_cpu_t *) cpu)->pc }; +} + static void rv64_set_pc_wrapper(void *cpu, ptr64_t addr) { // use all 64 bits from addr @@ -46,6 +51,7 @@ static const cpu_ops_t rv_cpu = { .convert_addr = (convert_addr_func_t) rv64_convert_add_wrapper, .reg_dump = (reg_dump_func_t) rv64_reg_dump, + .get_pc = (get_pc_func_t) rv64_get_pc_wrapper, .set_pc = (set_pc_func_t) rv64_set_pc_wrapper, .sc_access = (sc_access_func_t) rv64_sc_access }; diff --git a/src/device/drvcpu.c b/src/device/drvcpu.c index 350adeca..a0589ba7 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -32,6 +32,11 @@ static bool rv32_convert_add_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, boo return rv32_convert_addr((rv_cpu_t *) cpu, virt.lo, phys, write, false, false) == rv_exc_none; } +static ptr64_t rv32_get_pc_wrapper(void *cpu) +{ + return (ptr64_t) { .lo = ((rv_cpu_t *) cpu)->pc, .hi = 0 }; +} + static void rv32_set_pc_wrapper(void *cpu, ptr64_t addr) { // use only low 32-bits from addr @@ -45,6 +50,7 @@ static const cpu_ops_t rv_cpu = { .convert_addr = (convert_addr_func_t) rv32_convert_add_wrapper, .reg_dump = (reg_dump_func_t) rv32_reg_dump, + .get_pc = (get_pc_func_t) rv32_get_pc_wrapper, .set_pc = (set_pc_func_t) rv32_set_pc_wrapper, .sc_access = (sc_access_func_t) rv32_sc_access }; From 914985c2a1eb2d1914d395623ccdb29469b2287e Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sun, 26 Apr 2026 17:51:58 +0200 Subject: [PATCH 06/16] Add general CPU init This is done to unify the initialization of the general CPU type in preparation for it containing the BP list (which will need to be initialized) --- src/device/cpu/general_cpu.c | 10 ++++++++++ src/device/cpu/general_cpu.h | 10 ++++++++++ src/device/dr4kcpu.c | 5 +---- src/device/drv64cpu.c | 5 +---- src/device/drvcpu.c | 5 +---- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index c9f5a59d..70ca5401 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -11,11 +11,21 @@ #include "../../assert.h" #include "../../main.h" +#include "../../utils.h" #include "general_cpu.h" // list of all cpus list_t cpu_list = LIST_INITIALIZER; +general_cpu_t *general_cpu_init(const uint id, void *cpu, const cpu_ops_t *type) +{ + general_cpu_t *gen_cpu = safe_malloc_t(general_cpu_t); + gen_cpu->cpuno = id; + gen_cpu->data = cpu; + gen_cpu->type = type; + return gen_cpu; +} + general_cpu_t *get_cpu(unsigned int no) { general_cpu_t *cpu; diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index 856a9509..52ed48e0 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -58,6 +58,16 @@ typedef struct { void *data; } general_cpu_t; +/** + * @brief Allocates and initializes a general_cpu_t structure + * + * @param id The CPU id + * @param cpu A pointer to the CPU-specific data structure + * @param type A pointer to the cpu_ops_t structure describing the CPU methods + * @return A pointer to the initialized general_cpu_t structure + */ +extern general_cpu_t *general_cpu_init(uint id, void *cpu, const cpu_ops_t *type); + /** * @brief Retrieves the general_cpu_t structure based on the given cpu id */ diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index ecb40f95..326e2d9e 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -60,10 +60,7 @@ static bool dr4kcpu_init(token_t *parm, device_t *dev) r4k_cpu_t *cpu = safe_malloc_t(r4k_cpu_t); r4k_init(cpu, id); - general_cpu_t *gen_cpu = safe_malloc_t(general_cpu_t); - gen_cpu->cpuno = id; - gen_cpu->data = cpu; - gen_cpu->type = &r4k_cpu; + general_cpu_t *gen_cpu = general_cpu_init(id, cpu, &r4k_cpu); add_cpu(gen_cpu); diff --git a/src/device/drv64cpu.c b/src/device/drv64cpu.c index 0a4996da..68cad4cd 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -70,10 +70,7 @@ static bool drv64cpu_init(token_t *parm, device_t *dev) rv64_cpu_t *cpu = safe_malloc_t(rv64_cpu_t); rv64_cpu_init(cpu, id); - general_cpu_t *gen_cpu = safe_malloc_t(general_cpu_t); - gen_cpu->cpuno = id; - gen_cpu->data = cpu; - gen_cpu->type = &rv_cpu; + general_cpu_t *gen_cpu = general_cpu_init(id, cpu, &rv_cpu); add_cpu(gen_cpu); diff --git a/src/device/drvcpu.c b/src/device/drvcpu.c index a0589ba7..95faeb4e 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -70,10 +70,7 @@ static bool drvcpu_init(token_t *parm, device_t *dev) rv32_cpu_t *cpu = safe_malloc_t(rv_cpu_t); rv32_cpu_init(cpu, id); - general_cpu_t *gen_cpu = safe_malloc_t(general_cpu_t); - gen_cpu->cpuno = id; - gen_cpu->data = cpu; - gen_cpu->type = &rv_cpu; + general_cpu_t *gen_cpu = general_cpu_init(id, cpu, &rv_cpu); add_cpu(gen_cpu); From 5f4231624334511886f2881934a5678ab3dd3e29 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Thu, 9 Apr 2026 20:27:29 +0200 Subject: [PATCH 07/16] Update dap to latest protocol version --- src/debug/breakpoint.c | 25 +- src/debug/dap.c | 428 +++++++++++++++++++++------ src/debug/dap.h | 16 +- src/input.c | 3 + src/main.c | 28 +- src/text.c | 2 +- tests/rvtests/unit-tests/main-test.c | 2 +- 7 files changed, 378 insertions(+), 126 deletions(-) diff --git a/src/debug/breakpoint.c b/src/debug/breakpoint.c index fd6b5390..34682172 100644 --- a/src/debug/breakpoint.c +++ b/src/debug/breakpoint.c @@ -232,11 +232,16 @@ static void breakpoint_hit(breakpoint_t *breakpoint) switch (breakpoint->kind) { case BREAKPOINT_KIND_SIMULATOR: - alert("Debug: Hit breakpoint at %#0" PRIx64, breakpoint->pc.ptr); + alert("Debug: Hit simulator breakpoint at %#0" PRIx64, breakpoint->pc.ptr); machine_interactive = true; break; case BREAKPOINT_KIND_DEBUGGER: - gdb_handle_event(GDB_EVENT_BREAKPOINT); + if (dap_enabled) { + alert("Debug: Hit debugger breakpoint at %#0" PRIx64, breakpoint->pc.ptr); + dap_event_hit_code_breakpoint(breakpoint->pc.ptr); + } else if (remote_gdb) { + gdb_handle_event(GDB_EVENT_BREAKPOINT); + } break; default: die(ERR_INTERN, "Unexpected breakpoint kind"); @@ -320,10 +325,6 @@ bool breakpoint_check_for_code_breakpoints(void) } } - if (hit) { - return hit; - } - while (dev_next(&dev, DEVICE_FILTER_RV_PROCESSOR)) { const rv_cpu_t *cpu = get_rv(dev); @@ -334,5 +335,17 @@ bool breakpoint_check_for_code_breakpoints(void) } } + // TODO: implement for rv64 as well + + // while (dev_next(&dev, DEVICE_FILTER_RV_PROCESSOR)) { + // const struct rv64_cpu *cpu = get_rv(dev); + // + // ptr64_t addr = { 0 }; + // addr.lo = cpu->pc; + // if (breakpoint_hit_by_address(cpu->bps, addr)) { + // hit = true; + // } + // } + return hit; } diff --git a/src/debug/dap.c b/src/debug/dap.c index 3fb69679..3aad0092 100644 --- a/src/debug/dap.c +++ b/src/debug/dap.c @@ -13,24 +13,144 @@ #include "../main.h" #include "dap.h" -static int connection_fd = -1; -static unsigned int cpuno_global = 0; // TODO: what is it? - -/** Internal buffer for incoming messages */ -static uint8_t message_buffer[64]; -static const ssize_t message_buffer_len = sizeof(message_buffer); -static ssize_t message_buffer_used = 0; - -typedef enum dap_command_type { - NO_OP = 0, - BREAKPOINT = 1, - CONTINUE = 2, -} dap_command_type_t; +// be64toh is not on macOS +#if defined(__APPLE__) + #include + #define be64toh(x) OSSwapBigToHostInt64(x) + #define htobe64(x) OSSwapHostToBigInt64(x) +#endif -typedef struct __attribute__((__packed__)) dap_command { - uint8_t type; - uint32_t addr; -} dap_command_t; +static int connection_fd = -1; +static unsigned int cpuno_global = 0; // Default CPU device number used + +typedef enum dap_request_type { + /** Request to resume execution. Also used for the initial start. */ + ResumeRequest = 0x01, + /** Request to pause execution. */ + PauseRequest = 0x02, + /** Request to stop execution and exit the simulator. */ + StopRequest = 0x03, + /** Request to step `arg0=count` instructions. */ + StepRequest = 0x04, + + // Breakpoint requests + /** Request to set a code breakpoint at `arg0=address`. */ + SetCodeBreakpointRequest = 0x05, + /** Request to remove a code breakpoint at `arg0=address`. */ + RemoveCodeBreakpointRequest = 0x06, + + /** Request to set a data breakpoint at `arg0=address` with `arg1=kind`. */ + SetDataBreakpointRequest = 0x07, + /** Request to remove a data breakpoint at `arg0=address`. */ + RemoveDataBreakpointRequest = 0x08, + + // Register requests + /** Request to read the value of register `arg0=id`. */ + ReadGeneralRegisterRequest = 0x09, + /** Request to write to register `arg0=id` the value `arg1=value`. */ + WriteGeneralRegisterRequest = 0x0A, + + /** Request to read the value of Control and Status Register (CSR) `arg0=id`. */ + ReadCsrRequest = 0x0B, + /** Request to write to Control and Status Register (CSR) `arg0=id` the value `arg1=value`. */ + WriteCsrRequest = 0x0C, + + /** Request to read the value of the program counter. */ + ReadPCRequest = 0x0D, + /** Request to write `arg0=value` to the program counter. */ + WritePCRequest = 0x0E, + + // Memory requests + /** Request to read physical memory at `arg0=address`. */ + ReadPhysMemoryRequest = 0x0F, + /** Request to write `arg1=data` (8 B) to physical memory at `arg0=address`. */ + WritePhysMemoryRequest = 0x10, + + /** Request to read virtual memory at `arg0=address`. */ + ReadVirtMemoryRequest = 0x11, + /** Request to write `arg1=data` (8 B) to virtual memory at `arg0=address`. */ + WriteVirtMemoryRequest = 0x12, + + /** Request to translate the virtual address `arg0=address` to a physical address. */ + TranslateAddressRequest = 0x13, + + // Interrupt requests + /** Request to raise interrupt `arg0=id`. */ + RaiseInterruptRequest = 0x14, + /** Request to clear interrupt `arg0=id`. */ + ClearInterruptRequest = 0x15, +} dap_request_type_t; + +typedef enum dap_outbound_category { + /** Response to a request frame. */ + ResponseCategory = 0x01, + /** Event frame. */ + EventCategory = 0x02 +} dap_outbound_category_t; + +typedef enum dap_response_status { + /** Response to a successful request. */ + StatusOk = 0x01, + /** Response to a failed request with an unspecified error. */ + StatusUnspecifiedError = 0x02, + /** Response to an unsupported request. */ + StatusUnsupportedRequestError = 0x03 +} dap_response_status_t; + +typedef enum dap_event_type { + /** Event indicating that the simulator has exited. */ + ExitedEvent = 0x01, + /** Event indicating that the simulator has paused execution. */ + StoppedAtEvent = 0x02, +} dap_event_type_t; + +typedef enum dap_stopped_reason { + /** Stopped due to a pause. */ + StoppedReasonPaused = 0x01, + /** Stopped due to hitting a breakpoint. */ + StoppedReasonBreakpoint = 0x02, + /** Stopped due to stepping. */ + StoppedReasonStep = 0x03, + /** Stopped due to an interrupt. */ + StoppedReasonInterrupt = 0x04, + +} dap_stopped_reason_t; + +/** Structure for DAP requests */ +typedef struct dap_request { + dap_request_type_t type; + uint64_t arg0; + uint64_t arg1; +} dap_request_t; + +/** Structure for DAP responses */ +typedef struct dap_response { + dap_response_status_t type; + uint64_t arg0; + uint64_t arg1; +} dap_response_t; + +/** Structure for DAP events */ +typedef struct dap_event { + dap_event_type_t type; + uint64_t arg0; + uint64_t arg1; +} dap_event_t; + +enum { + INBOUND_FRAME_SIZE = 17, /** Size of a single DAP request frame */ + OUTBOUND_FRAME_SIZE = 18, /** Size of a single DAP response frame */ +}; + +/** Length of an inbound frame is always 17 B = 1 B (type) + 8 B (arg0) + 8 B (arg1) */ +static_assert(INBOUND_FRAME_SIZE == sizeof(uint8_t) + 2 * sizeof(uint64_t), "DAP inbound frame must be exactly 17 bytes long"); + +/** Length of an outbound frame is always 17 B = 1 B (category) + 1 B (status/type) + 8 B (arg0) + 8 B (arg1) */ +static_assert(OUTBOUND_FRAME_SIZE == sizeof(uint8_t) + sizeof(uint8_t) + 2 * sizeof(uint64_t), "DAP inbound frame must be exactly 18 bytes long"); + +/** Internal buffer for incoming frames */ +static uint8_t frame_buffer[INBOUND_FRAME_SIZE]; +static ssize_t frame_buffered_bytes = 0; // Number of bytes currently buffered in frame buffer bool dap_init(void) { @@ -80,154 +200,268 @@ bool dap_init(void) return true; } -void dap_close(void) -{ - if (connection_fd == -1) { - io_error("dap_already_closed"); - } - - if (close(connection_fd) == -1) { - io_error("dap_connection_fd"); - } - - connection_fd = -1; - dap_state = DAP_DONE; - machine_halt = true; - alert("DAP connection closed."); -} - -/** Receive bytes from DAP connection, non-blocking +/** Receive bytes from DAP connection * - * This will try to receive `len` bytes and store them in `buf`. + * This will try to receive `INBOUND_FRAME_SIZE` bytes and store + * them in `buf`. * If not enough bytes are available, it will return false and * buffer the received bytes internally until enough bytes are - * available. The buffer provided is not touched in this case. - * @param buf Pointer to buffer for received data. - * @param len Total number of bytes to receive. - * @return True if successful. + * available. The buffer provided is not modified in this case. + * @param buf Pointer to buffer for received data. Only modified if the function returns true. + * @param block Whether to block until bytes are available. + * If false, the function will return immediately if no bytes are available, without buffering. + * @return True if successfully received `INBOUND_FRAME_SIZE` bytes and stored them in `buf`. + * False otherwise. */ -static bool dap_receive_bytes(void *buf, const ssize_t len) +static bool dap_receive_bytes(void* buf, const bool block) { - const ssize_t need_bytes = len - message_buffer_used; - uint8_t *write_buffer = message_buffer + message_buffer_used; - ASSERT(need_bytes <= message_buffer_len); - ASSERT(len <= message_buffer_len); - ASSERT(need_bytes > 0); + const ssize_t need_bytes = INBOUND_FRAME_SIZE - frame_buffered_bytes; + uint8_t* write_buffer = frame_buffer + frame_buffered_bytes; + ASSERT(need_bytes <= INBOUND_FRAME_SIZE); + ASSERT(0 < need_bytes); ASSERT(connection_fd != -1); ASSERT(buf != NULL); - ASSERT(len > 0); + ASSERT(write_buffer + need_bytes <= frame_buffer + INBOUND_FRAME_SIZE); // No overflow check - const ssize_t received = recv(connection_fd, write_buffer, need_bytes, MSG_DONTWAIT); + const int flags = block ? 0 : MSG_DONTWAIT; + const ssize_t received = recv(connection_fd, write_buffer, need_bytes, flags); // Received if (received > 0) { - message_buffer_used += received; + frame_buffered_bytes += received; // Got enough if (received == need_bytes) { - ASSERT(message_buffer_used == len); - memcpy(buf, message_buffer, len); - message_buffer_used = 0; + ASSERT(frame_buffered_bytes == INBOUND_FRAME_SIZE); + memcpy(buf, frame_buffer, INBOUND_FRAME_SIZE); + frame_buffered_bytes = 0; return true; } // Not enough, buffer ASSERT(received < need_bytes); - ASSERT(message_buffer_used < len); - ASSERT(message_buffer_used < message_buffer_len); - return false; - } - - // Closed - if (received == 0) { - dap_close(); + ASSERT(frame_buffered_bytes < INBOUND_FRAME_SIZE); return false; } // No bytes for now - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { return false; } - // Error - io_error("dap_recv"); + // Closed or had errors, exit + + if (received < 0) { + io_error("dap_recv"); + } + dap_state = DAP_DONE; return false; } -/** Receive a DAP command, non-blocking +/** Send bytes to DAP connection, blocking until all successfully sent * - * @param out_cmd Pointer to output command structure. + * This will try to send `OUTBOUND_FRAME_SIZE` bytes from `buf`. + * If not all bytes can be sent at once, it will block and retry + * until all bytes are sent. + * The buffer provided is not modified until the operation is + * successful. + * @param buf Pointer to buffer with data to send. * @return True if successful. */ -static bool dap_receive_command(dap_command_t *out_cmd) +static bool dap_send_bytes(const void* buf) +{ + ssize_t to_send = OUTBOUND_FRAME_SIZE; + const uint8_t* ptr = buf; + while (to_send > 0) { + const ssize_t sent = send(connection_fd, ptr, to_send, 0); + // Error + if (sent < 0) { + if (errno == EINTR) continue; + io_error("dap_send"); + dap_state = DAP_DONE; + return false; + } + // Connection closed + if (sent == 0) { + dap_state = DAP_DONE; + return false; + } + ptr += sent; + to_send -= sent; + } + return true; +} + +/** Receive a DAP request + * + * @param out_cmd Pointer to output request structure. Only modified if the function returns true. + * @param block Whether to block until a full request is available. + * @return True if successfully received a full request and stored it in `out_cmd`. False otherwise. + */ +static bool dap_receive_request(dap_request_t *out_cmd, const bool block) { - uint8_t buffer[sizeof(dap_command_t)] = { 0 }; - if (!dap_receive_bytes(buffer, sizeof(dap_command_t))) { + uint8_t buffer[INBOUND_FRAME_SIZE] = { 0 }; + if (!dap_receive_bytes(buffer, block)) { return false; } out_cmd->type = buffer[0]; - uint32_t netorder_addr; - memcpy(&netorder_addr, buffer + sizeof(out_cmd->type), sizeof(netorder_addr)); - out_cmd->addr = ntohl(netorder_addr); + uint64_t netorder_arg0; + uint64_t netorder_arg1; + memcpy(&netorder_arg0, buffer + sizeof(uint8_t), sizeof(netorder_arg0)); + memcpy(&netorder_arg1, buffer + sizeof(uint8_t) + sizeof(netorder_arg0), sizeof(netorder_arg1)); + + out_cmd->arg0 = be64toh(netorder_arg0); + out_cmd->arg1 = be64toh(netorder_arg1); return true; } -/** Add a DAP breakpoint */ -static void dap_breakpoint_add(const uint32_t addr) +/** Send a DAP response, blocking until successful + * + * @param response Response to send. + * @return True if successful. + */ +static bool dap_send_response(const dap_response_t response) { - ptr64_t virt_address = { 0 }; - virt_address.lo = addr; - rv_cpu_t *cpu = get_cpu(cpuno_global)->data; + uint8_t buffer[OUTBOUND_FRAME_SIZE] = { 0 }; + buffer[0] = ResponseCategory; + buffer[1] = (uint8_t)response.type; - // TODO: Register as DAP debugger breakpoint - const breakpoint_t *breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_SIMULATOR); + const uint64_t netorder_arg0 = htobe64(response.arg0); + const uint64_t netorder_arg1 = htobe64(response.arg1); + memcpy(buffer + 2, &netorder_arg0, sizeof(netorder_arg0)); + memcpy(buffer + 2 + sizeof(netorder_arg0), &netorder_arg1, sizeof(netorder_arg1)); + + return dap_send_bytes(buffer); +} + +/** Send a DAP event, blocking until successful + * + * @param event Event to send. + * @return True if successful. + */ +static bool dap_send_event(const dap_event_t event) +{ + uint8_t buffer[OUTBOUND_FRAME_SIZE] = { 0 }; + buffer[0] = EventCategory; + buffer[1] = (uint8_t)event.type; + + const uint64_t netorder_arg0 = htobe64(event.arg0); + const uint64_t netorder_arg1 = htobe64(event.arg1); + memcpy(buffer + 2, &netorder_arg0, sizeof(netorder_arg0)); + memcpy(buffer + 2 + sizeof(netorder_arg0), &netorder_arg1, sizeof(netorder_arg1)); + + return dap_send_bytes(buffer); +} + +void dap_close(void) +{ + if (connection_fd != -1) { + dap_send_event((dap_event_t){ExitedEvent, 0, 0}); + + if (close(connection_fd) == -1) { + io_error("dap_connection_fd"); + } + connection_fd = -1; + } + + dap_state = DAP_DONE; + machine_halt = true; + alert("DAP connection closed."); +} + +/* Simulator events */ + +void dap_event_hit_code_breakpoint(const uint64_t address) +{ + dap_state = DAP_PAUSED; + dap_send_event((dap_event_t){StoppedAtEvent, address, StoppedReasonBreakpoint}); +} + +/* Handlers */ + +static void dap_handle_resume(void) +{ + dap_state = DAP_RUNNING; + alert("DAP: Resuming execution."); + dap_send_response((dap_response_t){ StatusOk, 0, 0 }); +} + +static void dap_handle_pause(void) +{ + dap_state = DAP_PAUSED; + alert("DAP: Pausing execution."); + dap_send_response((dap_response_t){ StatusOk, 0, 0 }); + const ptr64_t address = cpu_get_pc(get_cpu(cpuno_global)); + dap_send_event((dap_event_t){StoppedAtEvent, address.ptr, StoppedReasonPaused}); +} + +// TODO: implement both set and remove as device-specific (vtable) entries and use that to support all archs +/** Set a DAP code breakpoint */ +static void dap_handle_set_code_breakpoint(const uint64_t addr) +{ + ptr64_t virt_address = { addr }; + rv_cpu_t* cpu = get_cpu(cpuno_global)->data; + + const breakpoint_t* breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_DEBUGGER); if (breakpoint != NULL) { + dap_send_response((dap_response_t){ StatusUnspecifiedError, 0, 0 }); // TODO: more specific err code return; } - breakpoint_t *inserted_breakpoint = breakpoint_init(virt_address, BREAKPOINT_KIND_SIMULATOR); + breakpoint_t *inserted_breakpoint = breakpoint_init(virt_address, BREAKPOINT_KIND_DEBUGGER); list_append(&cpu->bps, &inserted_breakpoint->item); - alert("Added DAP breakpoint at address 0x%x.", addr); + alert("Added DAP code breakpoint at address 0x%x %x.", virt_address.hi, virt_address.lo); + dap_send_response((dap_response_t){ StatusOk, 0, 0 }); } -/** Remove a DAP breakpoint */ -static void dap_breakpoint_remove(const uint32_t addr) +/** Remove a DAP code breakpoint */ +static void dap_handle_remove_code_breakpoint(const uint64_t addr) { - alert("Removing DAP breakpoint from address 0x%x.", addr); + const ptr64_t virt_address = { addr }; + alert("Removing DAP code breakpoint from address 0x%x %x.", virt_address.hi, virt_address.lo); - ptr64_t virt_address; - virt_address.ptr = UINT64_C(0xffffffff00000000) | addr; - r4k_cpu_t *cpu = get_cpu(cpuno_global)->data; + rv_cpu_t* cpu = get_cpu(cpuno_global)->data; - breakpoint_t *breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_SIMULATOR); - if (breakpoint != NULL) { + breakpoint_t* breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_DEBUGGER); + if (breakpoint == NULL) { + alert("No such breakpoint!"); + dap_send_response((dap_response_t){ StatusUnspecifiedError, 0, 0 }); // TODO: more specific err code return; } list_remove(&cpu->bps, &breakpoint->item); safe_free(breakpoint) + dap_send_response((dap_response_t){ StatusOk, 0, 0 }); } void dap_process(void) { - dap_command_t command = { 0 }; + dap_request_t request = { 0 }; - while (dap_receive_command(&command)) { - switch (command.type) { - case NO_OP: + while (dap_receive_request(&request, dap_state == DAP_PAUSED)) { + switch (request.type) { + case ResumeRequest: + dap_handle_resume(); continue; - case BREAKPOINT: - dap_breakpoint_add(command.addr); + case PauseRequest: + dap_handle_pause(); continue; - case CONTINUE: - dap_state = DAP_RUNNING; + case StopRequest: + // Response is handled in dap_close which is always called at the end. + dap_state = DAP_DONE; + return; + case SetCodeBreakpointRequest: + dap_handle_set_code_breakpoint(request.arg0); continue; - default: - alert("Unknown DAP command type %u.", command.type); + case RemoveCodeBreakpointRequest: + dap_handle_remove_code_breakpoint(request.arg0); continue; + default: + alert("Unknown DAP request type %u.", request.type); + dap_send_response((dap_response_t){ StatusUnsupportedRequestError, 0, 0 }); } } } diff --git a/src/debug/dap.h b/src/debug/dap.h index 37c0d0ca..48766ede 100644 --- a/src/debug/dap.h +++ b/src/debug/dap.h @@ -2,9 +2,9 @@ #define MSIM_DAP_H typedef enum { - DAP_READY, // DAP initialized, waiting for connection - DAP_CONNECTED, // DAP connected, ready to process requests - DAP_RUNNING, // DAP running + DAP_INIT, // DAP not connected yet + DAP_RUNNING, // Running the simulation + DAP_PAUSED, // Paused, waiting for requests DAP_DONE // DAP session done } dap_state_t; @@ -15,11 +15,17 @@ extern bool dap_init(void); /** Process new DAP requests * (main DAP callback) - * - * @return Processing decision + * Blocks only when dap_state is DAP_PAUSED, otherwise it will + * return immediately if there are no requests to process. */ extern void dap_process(void); +/** Close DAP connection and cleanup + * + * This will also send an "exited" event to the DAP client, so it should be called when the simulation is exiting. + */ extern void dap_close(void); +extern void dap_event_hit_code_breakpoint(uint64_t address); + #endif // MSIM_DAP_H diff --git a/src/input.c b/src/input.c index dce36291..389c4e29 100644 --- a/src/input.c +++ b/src/input.c @@ -160,6 +160,9 @@ void interactive_control(void) /* User break in readline */ printf("\n"); alert("Quit"); + if (dap_enabled) { + dap_close(); + } input_back(); free(cmdline); exit(ERR_OK); diff --git a/src/main.c b/src/main.c index bd290f00..92ce3dcb 100644 --- a/src/main.c +++ b/src/main.c @@ -70,7 +70,7 @@ bool dap_enabled = false; unsigned int dap_port = 10505; /** DAP state */ -dap_state_t dap_state = DAP_READY; +dap_state_t dap_state = DAP_INIT; /** Enable non-deterministic behaviour */ bool machine_nondet = false; @@ -296,20 +296,17 @@ static bool gdb_startup(void) /** Try to startup DAP connection. * - * @return True if the connection was opened. + * The dap state upon return will be either DAP_CONNECTED if the connection was successful, DAP_DONE otherwise. */ static void dap_startup(void) { if (get_cpu(0) == NULL) { error("Cannot debug without any processor"); + dap_state = DAP_DONE; return; } - if (dap_init()) { - dap_state = DAP_CONNECTED; - } else { - dap_state = DAP_DONE; - } + dap_state = dap_init() ? DAP_PAUSED : DAP_DONE; } /** Run 4096 machine cycles @@ -371,19 +368,13 @@ static void machine_run(void) // DAP if (dap_enabled) { - // Startup DAP if enabled & not connected yet - if (dap_state == DAP_READY) { - dap_startup(); - } - // Process new DAP events - if (dap_state == DAP_CONNECTED) { + if (dap_state != DAP_DONE) { dap_process(); } - // TODO: avoid busy wait - if (dap_state != DAP_RUNNING && dap_state != DAP_DONE) { - continue; + if (dap_state == DAP_DONE) { + machine_halt = true; } } @@ -462,6 +453,11 @@ int main(int argc, char *args[]) alert("Entering interactive mode, type `help' for help."); } + if (dap_enabled) { + // Startup DAP + dap_startup(); + } + /* * Main simulation loop */ diff --git a/src/text.c b/src/text.c index f862eddd..bc6060e6 100644 --- a/src/text.c +++ b/src/text.c @@ -82,7 +82,7 @@ const char txt_help[] = " -V, --version display version info\n" " -i, --interactive enter interactive mode\n" " -t, --trace enter trace mode\n" " -g, --remote-gdb=port enter gdb mode\n" - " -d, --dap[port] enter DAP mode (default: 10505)\n" + " -d, --dap[=port] enter DAP mode (default: 10505)\n" " -n, --non-deterministic enable non-deterministic behaviour\n" " -X, --no-extra-instructions disable MSIM-specific instructions\n"; diff --git a/tests/rvtests/unit-tests/main-test.c b/tests/rvtests/unit-tests/main-test.c index f61fcaa5..7f94d24a 100644 --- a/tests/rvtests/unit-tests/main-test.c +++ b/tests/rvtests/unit-tests/main-test.c @@ -19,7 +19,7 @@ bool remote_gdb_step = false; // DAP debugging bool dap_enabled = false; unsigned int dap_port = 10505; -dap_state_t dap_state = DAP_READY; +dap_state_t dap_state = DAP_INIT; /** General simulator behaviour */ bool machine_nondet = false; From 591e2ffb1e9aef08e148b44c2fa34aa383ef536d Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sun, 26 Apr 2026 16:03:59 +0200 Subject: [PATCH 08/16] Rework code breakpoints for general CPUs (RV32,RV64,r4k) Make code BPs managed by the general_cpu_t and only delegate arch-specific operations to the concrete CPU. Update both GDB and DAP debugging modes to use the new implementation. BP list moved from concrete CPU structs for both unification and due to XLEN problem: breakpoint_check_for_code_breakpoints needs to access the CPU's list of BPs, but if they were directly inside the concrete cpu structure, not only would we have to break the abstraction and trigger them in an architecture-specific way, we would need to include both RV32 and RV64 cpu.h headers. These both define XLEN as 32/64, but it can't be included in one TU at once. --- src/debug/breakpoint.c | 41 +++-------------- src/debug/dap.c | 24 ++++------ src/debug/gdb.c | 39 +++------------- src/device/cpu/general_cpu.c | 57 +++++++++++++++++++++--- src/device/cpu/general_cpu.h | 55 ++++++++++++++++++----- src/device/cpu/mips_r4000/cpu.c | 3 -- src/device/cpu/mips_r4000/cpu.h | 3 -- src/device/cpu/riscv_rv32ima/cpu.c | 3 -- src/device/cpu/riscv_rv32ima/cpu.h | 3 -- src/device/dr4kcpu.c | 71 ++++++++++-------------------- src/device/drv64cpu.c | 12 ++++- src/device/drvcpu.c | 12 ++++- 12 files changed, 160 insertions(+), 163 deletions(-) diff --git a/src/debug/breakpoint.c b/src/debug/breakpoint.c index 34682172..20c8b293 100644 --- a/src/debug/breakpoint.c +++ b/src/debug/breakpoint.c @@ -34,11 +34,6 @@ #include "../assert.h" #include "../device/cpu/general_cpu.h" -#include "../device/cpu/mips_r4000/cpu.h" -#include "../device/cpu/riscv_rv32ima/cpu.h" -#include "../device/device.h" -#include "../device/dr4kcpu.h" -#include "../device/drvcpu.h" #include "../fault.h" #include "../main.h" #include "../utils.h" @@ -303,49 +298,25 @@ breakpoint_t *breakpoint_find_by_address(list_t breakpoints, return NULL; } -/** Search all of the processors +/** Search all the processors * - * Search all of the processors whether any of them is going to + * Search all the processors whether any of them is going to * execute instruction where a code breakpoint is located. All such * breakpoints are fired. * * @return True, if at least one breakpoint has been fired. - * */ bool breakpoint_check_for_code_breakpoints(void) { bool hit = false; - device_t *dev = NULL; - - while (dev_next(&dev, DEVICE_FILTER_R4K_PROCESSOR)) { - r4k_cpu_t *cpu = get_r4k(dev); - if (breakpoint_hit_by_address(cpu->bps, cpu->pc)) { - hit = true; - } - } - - while (dev_next(&dev, DEVICE_FILTER_RV_PROCESSOR)) { - const rv_cpu_t *cpu = get_rv(dev); - - ptr64_t addr = { 0 }; - addr.lo = cpu->pc; - if (breakpoint_hit_by_address(cpu->bps, addr)) { + general_cpu_t* cpu = NULL; + for_each(cpu_list, cpu, general_cpu_t) + { + if (breakpoint_hit_by_address(cpu->bps, cpu_get_pc(cpu))) { hit = true; } } - // TODO: implement for rv64 as well - - // while (dev_next(&dev, DEVICE_FILTER_RV_PROCESSOR)) { - // const struct rv64_cpu *cpu = get_rv(dev); - // - // ptr64_t addr = { 0 }; - // addr.lo = cpu->pc; - // if (breakpoint_hit_by_address(cpu->bps, addr)) { - // hit = true; - // } - // } - return hit; } diff --git a/src/debug/dap.c b/src/debug/dap.c index 3aad0092..a755c604 100644 --- a/src/debug/dap.c +++ b/src/debug/dap.c @@ -398,42 +398,34 @@ static void dap_handle_pause(void) dap_send_event((dap_event_t){StoppedAtEvent, address.ptr, StoppedReasonPaused}); } -// TODO: implement both set and remove as device-specific (vtable) entries and use that to support all archs /** Set a DAP code breakpoint */ static void dap_handle_set_code_breakpoint(const uint64_t addr) { - ptr64_t virt_address = { addr }; - rv_cpu_t* cpu = get_cpu(cpuno_global)->data; + const ptr64_t virt_addr = { addr }; + general_cpu_t* cpu = get_cpu(cpuno_global); - const breakpoint_t* breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_DEBUGGER); - if (breakpoint != NULL) { + if (!cpu_insert_breakpoint(cpu, virt_addr, BREAKPOINT_KIND_DEBUGGER)) { dap_send_response((dap_response_t){ StatusUnspecifiedError, 0, 0 }); // TODO: more specific err code return; } - breakpoint_t *inserted_breakpoint = breakpoint_init(virt_address, BREAKPOINT_KIND_DEBUGGER); - list_append(&cpu->bps, &inserted_breakpoint->item); - alert("Added DAP code breakpoint at address 0x%x %x.", virt_address.hi, virt_address.lo); + alert("Added DAP code breakpoint at address %#0" PRIx64, virt_addr.ptr); dap_send_response((dap_response_t){ StatusOk, 0, 0 }); } /** Remove a DAP code breakpoint */ static void dap_handle_remove_code_breakpoint(const uint64_t addr) { - const ptr64_t virt_address = { addr }; - alert("Removing DAP code breakpoint from address 0x%x %x.", virt_address.hi, virt_address.lo); + const ptr64_t virt_addr = { addr }; + general_cpu_t* cpu = get_cpu(cpuno_global); - rv_cpu_t* cpu = get_cpu(cpuno_global)->data; - - breakpoint_t* breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_DEBUGGER); - if (breakpoint == NULL) { + if (!cpu_remove_breakpoint(cpu, virt_addr, BREAKPOINT_KIND_DEBUGGER)) { alert("No such breakpoint!"); dap_send_response((dap_response_t){ StatusUnspecifiedError, 0, 0 }); // TODO: more specific err code return; } - list_remove(&cpu->bps, &breakpoint->item); - safe_free(breakpoint) + alert("Removed DAP code breakpoint from address %#0" PRIx64, virt_addr.ptr); dap_send_response((dap_response_t){ StatusOk, 0, 0 }); } diff --git a/src/debug/gdb.c b/src/debug/gdb.c index 65390c50..0dfda0cb 100644 --- a/src/debug/gdb.c +++ b/src/debug/gdb.c @@ -715,7 +715,7 @@ static void gdb_reply_event(gdb_event_t event) /** Activate code breakpoint * */ -static void gdb_insert_code_breakpoint(r4k_cpu_t *cpu, ptr64_t addr) +static void gdb_insert_code_breakpoint(general_cpu_t *cpu, ptr64_t addr) { /* * Breakpoint insertion should be done in an idempotent way, @@ -723,34 +723,15 @@ static void gdb_insert_code_breakpoint(r4k_cpu_t *cpu, ptr64_t addr) * we will not insert a new breakpoint and we will not consider * this as faulty behavior. */ - breakpoint_t *breakpoint = breakpoint_find_by_address(cpu->bps, - addr, BREAKPOINT_FILTER_DEBUGGER); - - if (breakpoint != NULL) { - return; - } - - /* Breakpoint not found, thus insert it now. */ - breakpoint_t *inserted_breakpoint = breakpoint_init(addr, BREAKPOINT_KIND_DEBUGGER); - - list_append(&cpu->bps, &inserted_breakpoint->item); + cpu_insert_breakpoint(cpu, addr, BREAKPOINT_KIND_DEBUGGER); } /** Deactivate code breakpoint * */ -static void gdb_remove_code_breakpoint(r4k_cpu_t *cpu, ptr64_t addr) +static void gdb_remove_code_breakpoint(general_cpu_t *cpu, ptr64_t addr) { - breakpoint_t *breakpoint = breakpoint_find_by_address(cpu->bps, - addr, BREAKPOINT_FILTER_DEBUGGER); - - /* Removing non existent breakpoint is not considered as a bug */ - if (breakpoint == NULL) { - return; - } - - list_remove(&cpu->bps, &breakpoint->item); - safe_free(breakpoint); + cpu_remove_breakpoint(cpu, addr, BREAKPOINT_KIND_DEBUGGER); } /** Handle code or memory breakpoint commands from the debugger @@ -801,12 +782,8 @@ static void gdb_breakpoint(char *req, bool insert) return; } - ptr64_t virt; - // Extend the address as the GDB sends the address in 32bits. - virt.ptr = UINT64_C(0xffffffff00000000) | address; - // TODO: implement for both - r4k_cpu_t *cpu = (r4k_cpu_t *) get_cpu(cpuno_global)->data; - // TODO: ASSERT that it really us r4k + general_cpu_t *cpu = get_cpu(cpuno_global); + const ptr64_t virt = { .ptr = address }; if (code_breakpoint) { if (length != 4) { @@ -843,9 +820,7 @@ static void gdb_breakpoint(char *req, bool insert) */ static void gdb_remote_done(bool fail, bool remote_request) { - // TODO: implement for both - r4k_cpu_t *cpu = (r4k_cpu_t *) get_cpu(cpuno_global)->data; - // TODO: ASSERT that it really us r4k + general_cpu_t *cpu = get_cpu(cpuno_global); if (!fail) { gdb_send_reply(remote_request ? GDB_REPLY_OK : GDB_REPLY_WARNING); diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index 70ca5401..e9976f4e 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -14,15 +14,15 @@ #include "../../utils.h" #include "general_cpu.h" -// list of all cpus list_t cpu_list = LIST_INITIALIZER; -general_cpu_t *general_cpu_init(const uint id, void *cpu, const cpu_ops_t *type) +general_cpu_t *general_cpu_init(const unsigned int id, void *cpu, const cpu_ops_t *type) { general_cpu_t *gen_cpu = safe_malloc_t(general_cpu_t); gen_cpu->cpuno = id; gen_cpu->data = cpu; gen_cpu->type = type; + list_init(&gen_cpu->bps); return gen_cpu; } @@ -101,19 +101,64 @@ void cpu_interrupt_down(general_cpu_t *cpu, unsigned int no) cpu->type->interrupt_down(cpu->data, no); } -void cpu_insert_breakpoint(general_cpu_t *cpu, ptr64_t addr, breakpoint_t kind) +bool cpu_normalize_bp_addr(general_cpu_t *cpu, const ptr64_t addr, ptr64_t *out) { if (cpu == NULL) { cpu = get_fallback_cpu(); } - cpu->type->insert_breakpoint(cpu->data, addr, kind); + return cpu->type->normalize_bp_addr(cpu->data, addr, out); } -void cpu_remove_breakpoint(general_cpu_t *cpu, ptr64_t addr) + +bool cpu_insert_breakpoint(general_cpu_t *cpu, const ptr64_t addr, const breakpoint_kind_t kind) { if (cpu == NULL) { cpu = get_fallback_cpu(); } - cpu->type->remove_breakpoint(cpu->data, addr); + + ptr64_t normalized_addr = { 0 }; + if (!cpu->type->normalize_bp_addr(cpu->data, addr, &normalized_addr)) { + if (kind == BREAKPOINT_KIND_SIMULATOR) { + error("Virtual address out of range"); + } + return false; + } + + const breakpoint_t *existing = breakpoint_find_by_address(cpu->bps, normalized_addr, (breakpoint_filter_t) kind); + if (existing == NULL) { + breakpoint_t *bp = breakpoint_init(normalized_addr, kind); + list_append(&cpu->bps, &bp->item); + } + // Only report error for simulator breakpoints, debuggers are left to handle it as they want + else if (kind == BREAKPOINT_KIND_SIMULATOR) { + error("Breakpoint already exists"); + } + return true; +} + +bool cpu_remove_breakpoint(general_cpu_t *cpu, const ptr64_t addr, const breakpoint_kind_t kind) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + + ptr64_t normalized_addr = { 0 }; + if (!cpu->type->normalize_bp_addr(cpu->data, addr, &normalized_addr)) { + if (kind == BREAKPOINT_KIND_SIMULATOR) { + error("Virtual address out of range"); + } + return false; + } + + breakpoint_t *remove = breakpoint_find_by_address(cpu->bps, normalized_addr, (breakpoint_filter_t) kind); + if (remove != NULL) { + list_remove(&cpu->bps, &remove->item); + safe_free(remove); + } + // Only report error for simulator breakpoints, debuggers are left to handle it as they want + else if (kind == BREAKPOINT_KIND_SIMULATOR) { + error("Unknown breakpoint"); + } + return true; } /** diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index 52ed48e0..1ad19165 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -19,10 +19,8 @@ /** Function type for raising and canceling interrupts */ typedef void (*interrupt_func_t)(void *, unsigned int); -/** Function type for inserting breakpoints */ -typedef void (*insert_breakpoint_func_t)(void *, ptr64_t, breakpoint_t); -/** Function type for removing breakpoints */ -typedef void (*remove_breakpoint_func_t)(void *, ptr64_t); +/** Function type for normalizing breakpoint addresses */ +typedef bool (*normalize_bp_addr_func_t)(void *, ptr64_t, ptr64_t *); /** Function type for converting addresses */ typedef bool (*convert_addr_func_t)(void *, ptr64_t, ptr36_t *, bool); /** Function type for dumping register content */ @@ -39,10 +37,9 @@ typedef bool (*sc_access_func_t)(void *, ptr36_t, int); * NULL value means "not implemented" */ typedef struct { - interrupt_func_t interrupt_up; /** Rainse an interrupt */ + interrupt_func_t interrupt_up; /** Raise an interrupt */ interrupt_func_t interrupt_down; /** Cancel an interrupt */ - insert_breakpoint_func_t insert_breakpoint; - remove_breakpoint_func_t remove_breakpoint; + normalize_bp_addr_func_t normalize_bp_addr; /** Normalize a breakpoint address, e.g. to align it to instruction boundary or anything arch-specific */ convert_addr_func_t convert_addr; reg_dump_func_t reg_dump; get_pc_func_t get_pc; @@ -50,14 +47,18 @@ typedef struct { sc_access_func_t sc_access; } cpu_ops_t; -/** Structure describinfg cpu methods */ +/** Structure describing general CPU */ typedef struct { item_t item; unsigned int cpuno; const cpu_ops_t *type; void *data; + list_t bps; // Breakpoints } general_cpu_t; +/** List of all CPUs */ +extern list_t cpu_list; + /** * @brief Allocates and initializes a general_cpu_t structure * @@ -66,7 +67,7 @@ typedef struct { * @param type A pointer to the cpu_ops_t structure describing the CPU methods * @return A pointer to the initialized general_cpu_t structure */ -extern general_cpu_t *general_cpu_init(uint id, void *cpu, const cpu_ops_t *type); +extern general_cpu_t *general_cpu_init(unsigned int id, void *cpu, const cpu_ops_t *type); /** * @brief Retrieves the general_cpu_t structure based on the given cpu id @@ -102,8 +103,40 @@ extern void cpu_interrupt_up(general_cpu_t *cpu, unsigned int no); */ extern void cpu_interrupt_down(general_cpu_t *cpu, unsigned int no); -extern void cpu_insert_breakpoint(general_cpu_t *cpu, ptr64_t addr, breakpoint_t kind); -extern void cpu_remove_breakpoint(general_cpu_t *cpu, ptr64_t addr); +/** + * @brief Normalizes an address for breakpoint insertion + * + * This is used to align the address to instruction boundaries + * or to do any other architecture-specific normalization + * that is required for breakpoint insertion. + * + * @param cpu The cpu for which the address will be normalized + * @param addr The address that will be normalized + * @param out A pointer to the variable where the normalized address will be stored, + * only modified if the function returns true + * @return true if the address was successfully normalized, false otherwise + */ +extern bool cpu_normalize_bp_addr(general_cpu_t *cpu, ptr64_t addr, ptr64_t *out); + +/** + * @brief Inserts a code breakpoint at the given address + * + * @param cpu The cpu on which the breakpoint will be inserted + * @param addr The address at which the breakpoint will be inserted + * @param kind The breakpoint kind + * @return true if the breakpoint was successfully inserted, false otherwise + */ +extern bool cpu_insert_breakpoint(general_cpu_t *cpu, ptr64_t addr, breakpoint_kind_t kind); + +/** + * @brief Removes a code breakpoint at the given address + * + * @param cpu The cpu on which the breakpoint will be removed + * @param addr The address at which the breakpoint will be removed + * @param kind The breakpoint kind, only this kind of breakpoint will be removed + * @return true if the breakpoint was successfully removed, false otherwise + */ +extern bool cpu_remove_breakpoint(general_cpu_t *cpu, ptr64_t addr, breakpoint_kind_t kind); /** * @brief converts an address from virtual to physical memory, not modifying cpu state diff --git a/src/device/cpu/mips_r4000/cpu.c b/src/device/cpu/mips_r4000/cpu.c index e147877e..80b2c77e 100644 --- a/src/device/cpu/mips_r4000/cpu.c +++ b/src/device/cpu/mips_r4000/cpu.c @@ -2653,9 +2653,6 @@ void r4k_init(r4k_cpu_t *cpu, unsigned int procno) cp0_cause(cpu).val = HARD_RESET_CAUSE; cp0_watchlo(cpu).val = HARD_RESET_WATCHLO; cp0_watchhi(cpu).val = HARD_RESET_WATCHHI; - - /* Breakpoints */ - list_init(&cpu->bps); } /** Set the PC register diff --git a/src/device/cpu/mips_r4000/cpu.h b/src/device/cpu/mips_r4000/cpu.h index 44f6948e..eac8d0de 100644 --- a/src/device/cpu/mips_r4000/cpu.h +++ b/src/device/cpu/mips_r4000/cpu.h @@ -625,9 +625,6 @@ typedef struct r4k_cpu { uint64_t tlb_invalid; uint64_t tlb_modified; uint64_t intr[INTR_COUNT]; - - /* breakpoints */ - list_t bps; } r4k_cpu_t; /** Opcode numbers diff --git a/src/device/cpu/riscv_rv32ima/cpu.c b/src/device/cpu/riscv_rv32ima/cpu.c index f34a41b8..c6c902e2 100644 --- a/src/device/cpu/riscv_rv32ima/cpu.c +++ b/src/device/cpu/riscv_rv32ima/cpu.c @@ -97,9 +97,6 @@ void rv32_cpu_init(rv32_cpu_t *cpu, unsigned int procno) rv32_tlb_init(&cpu->tlb, DEFAULT_RV_TLB_SIZE); cpu->priv_mode = rv_mmode; - - /* Breakpoints */ - list_init(&cpu->bps); } /** diff --git a/src/device/cpu/riscv_rv32ima/cpu.h b/src/device/cpu/riscv_rv32ima/cpu.h index 417b4737..3343a6db 100644 --- a/src/device/cpu/riscv_rv32ima/cpu.h +++ b/src/device/cpu/riscv_rv32ima/cpu.h @@ -57,9 +57,6 @@ typedef struct rv32_cpu { /** Translation Lookaside Buffer used for caching translated addresses */ rv32_tlb_t tlb; - /** breakpoints **/ - list_t bps; - } rv32_cpu_t; /** Basic CPU routines */ diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index 326e2d9e..67912b2d 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -25,7 +25,23 @@ #include "device.h" #include "dr4kcpu.h" -static bool r4k_cpu_convert_addr(r4k_cpu_t *cpu, ptr64_t virt, ptr36_t *phys, bool write) +static bool r4k_cpu_normalize_bp_addr(void *cpu, const ptr64_t addr, ptr64_t *out) +{ + (void) cpu; + const uint64_t aligned_addr = ALIGN_DOWN(addr.ptr, 4); + + // Virtual address out of range + if (!virt_range(aligned_addr)) { + return false; + } + + // Extend the address as the user will not enter it in 64bit mode + // when the emulated CPU is 32bit. + *out = (ptr64_t) { .ptr = UINT64_C(0xffffffff00000000) | aligned_addr }; + return true; +} + +static bool r4k_cpu_convert_addr_wrapper(r4k_cpu_t *cpu, ptr64_t virt, ptr36_t *phys, bool write) { return r4k_convert_addr(cpu, virt, phys, write, false) == r4k_excNone; } @@ -38,9 +54,11 @@ static ptr64_t r4k_get_pc_wrapper(void *cpu) static const cpu_ops_t r4k_cpu = { .interrupt_up = (interrupt_func_t) r4k_interrupt_up, .interrupt_down = (interrupt_func_t) r4k_interrupt_down, + .normalize_bp_addr = (normalize_bp_addr_func_t) r4k_cpu_normalize_bp_addr, - .convert_addr = (convert_addr_func_t) r4k_cpu_convert_addr, + .convert_addr = (convert_addr_func_t) r4k_cpu_convert_addr_wrapper, .reg_dump = (reg_dump_func_t) r4k_reg_dump, + .get_pc = (get_pc_func_t) r4k_get_pc_wrapper, .set_pc = (set_pc_func_t) r4k_set_pc, .sc_access = (sc_access_func_t) r4k_sc_access @@ -269,27 +287,11 @@ static bool dr4kcpu_goto(token_t *parm, device_t *dev) /** Break command implementation * + * Insert a code breakpoint at the specified address. */ static bool dr4kcpu_break(token_t *parm, device_t *dev) { - r4k_cpu_t *cpu = get_r4k(dev); - uint64_t _addr = ALIGN_DOWN(parm_uint_next(&parm), 4); - - if (!virt_range(_addr)) { - error("Virtual address out of range"); - return false; - } - - ptr64_t addr; - // Extend the address as the user will not enter it in 64bit mode - // when the emulated CPU is 32bit. - addr.ptr = UINT64_C(0xffffffff00000000) | _addr; - - breakpoint_t *bp = breakpoint_init(addr, - BREAKPOINT_KIND_SIMULATOR); - list_append(&cpu->bps, &bp->item); - - return true; + return cpu_insert_breakpoint(dev->data, (ptr64_t) { .ptr = parm_uint_next(&parm) }, BREAKPOINT_KIND_SIMULATOR); } /** Bd command implementation @@ -297,7 +299,7 @@ static bool dr4kcpu_break(token_t *parm, device_t *dev) */ static bool dr4kcpu_bd(token_t *parm, device_t *dev) { - r4k_cpu_t *cpu = get_r4k(dev); + general_cpu_t *cpu = dev->data; breakpoint_t *bp; printf("[address ] [hits ] [kind ]\n"); @@ -320,32 +322,7 @@ static bool dr4kcpu_bd(token_t *parm, device_t *dev) */ static bool dr4kcpu_br(token_t *parm, device_t *dev) { - r4k_cpu_t *cpu = get_r4k(dev); - uint64_t addr = ALIGN_DOWN(parm_uint_next(&parm), 4); - - if (!virt_range(addr)) { - error("Virtual address out of range"); - return false; - } - - bool fnd = false; - breakpoint_t *bp; - for_each(cpu->bps, bp, breakpoint_t) - { - if (bp->pc.ptr == addr) { - list_remove(&cpu->bps, &bp->item); - safe_free(bp); - fnd = true; - break; - } - } - - if (!fnd) { - error("Unknown breakpoint"); - return false; - } - - return true; + return cpu_remove_breakpoint(dev->data, (ptr64_t) { .ptr = parm_uint_next(&parm) }, BREAKPOINT_KIND_SIMULATOR); } /** Done diff --git a/src/device/drv64cpu.c b/src/device/drv64cpu.c index 68cad4cd..a08c0b24 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -27,7 +27,14 @@ #include "cpu/riscv_rv64ima/debug.h" #include "drv64cpu.h" -static bool rv64_convert_add_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, bool write) +static bool rv64_cpu_normalize_bp_addr(void *cpu, const ptr64_t addr, ptr64_t *out) +{ + (void) cpu; + out->ptr = ALIGN_DOWN(addr.ptr, 4); + return true; +} + +static bool rv64_convert_addr_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, bool write) { // use all 64 bits from virt return rv64_convert_addr((rv_cpu_t *) cpu, virt.ptr, phys, write, false, false) == rv_exc_none; @@ -48,7 +55,8 @@ static const cpu_ops_t rv_cpu = { .interrupt_up = (interrupt_func_t) rv64_interrupt_up, .interrupt_down = (interrupt_func_t) rv64_interrupt_down, - .convert_addr = (convert_addr_func_t) rv64_convert_add_wrapper, + .normalize_bp_addr = (normalize_bp_addr_func_t) rv64_cpu_normalize_bp_addr, + .convert_addr = (convert_addr_func_t) rv64_convert_addr_wrapper, .reg_dump = (reg_dump_func_t) rv64_reg_dump, .get_pc = (get_pc_func_t) rv64_get_pc_wrapper, diff --git a/src/device/drvcpu.c b/src/device/drvcpu.c index 95faeb4e..5af34183 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -26,7 +26,14 @@ #include "cpu/riscv_rv32ima/debug.h" #include "drvcpu.h" -static bool rv32_convert_add_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, bool write) +static bool rv32_cpu_normalize_bp_addr(void *cpu, const ptr64_t addr, ptr64_t *out) +{ + (void) cpu; + out->ptr = ALIGN_DOWN(addr.ptr, 4); + return true; +} + +static bool rv32_convert_addr_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, bool write) { // use only low 32-bits from virt return rv32_convert_addr((rv_cpu_t *) cpu, virt.lo, phys, write, false, false) == rv_exc_none; @@ -47,7 +54,8 @@ static const cpu_ops_t rv_cpu = { .interrupt_up = (interrupt_func_t) rv32_interrupt_up, .interrupt_down = (interrupt_func_t) rv32_interrupt_down, - .convert_addr = (convert_addr_func_t) rv32_convert_add_wrapper, + .normalize_bp_addr = (normalize_bp_addr_func_t) rv32_cpu_normalize_bp_addr, + .convert_addr = (convert_addr_func_t) rv32_convert_addr_wrapper, .reg_dump = (reg_dump_func_t) rv32_reg_dump, .get_pc = (get_pc_func_t) rv32_get_pc_wrapper, From 0f92889d7617d6c5e33181994579403998274dc3 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Fri, 1 May 2026 01:29:42 +0200 Subject: [PATCH 09/16] Add get/set for general registers to general CPU API --- src/device/cpu/general_cpu.c | 16 ++++++++++++++++ src/device/cpu/general_cpu.h | 26 ++++++++++++++++++++++++++ src/device/dr4kcpu.c | 20 ++++++++++++++++++++ src/device/drv64cpu.c | 20 ++++++++++++++++++++ src/device/drvcpu.c | 20 ++++++++++++++++++++ 5 files changed, 102 insertions(+) diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index e9976f4e..2e322a88 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -187,6 +187,22 @@ void cpu_reg_dump(general_cpu_t *cpu) cpu->type->reg_dump(cpu->data); } +bool cpu_get_reg(general_cpu_t *cpu, unsigned int regno, uint64_t *out_value) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + return cpu->type->get_reg(cpu->data, regno, out_value); +} + +bool cpu_set_reg(general_cpu_t *cpu, unsigned int regno, uint64_t value) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + return cpu->type->set_reg(cpu->data, regno, value); +} + ptr64_t cpu_get_pc(general_cpu_t *cpu) { if (cpu == NULL) { diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index 1ad19165..4c8be52c 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -25,6 +25,10 @@ typedef bool (*normalize_bp_addr_func_t)(void *, ptr64_t, ptr64_t *); typedef bool (*convert_addr_func_t)(void *, ptr64_t, ptr36_t *, bool); /** Function type for dumping register content */ typedef void (*reg_dump_func_t)(void *); +/** Function type for getting the value of a general register of a cpu */ +typedef bool (*get_reg_func_t)(void *, unsigned int, uint64_t *); +/** Function type for setting the value of a general register of a cpu */ +typedef bool (*set_reg_func_t)(void *, unsigned int, uint64_t); /** Function type for getting the program counter of a cpu */ typedef ptr64_t (*get_pc_func_t)(void *); /** Function type for setting the program counter of a cpu */ @@ -42,6 +46,8 @@ typedef struct { normalize_bp_addr_func_t normalize_bp_addr; /** Normalize a breakpoint address, e.g. to align it to instruction boundary or anything arch-specific */ convert_addr_func_t convert_addr; reg_dump_func_t reg_dump; + get_reg_func_t get_reg; + set_reg_func_t set_reg; get_pc_func_t get_pc; set_pc_func_t set_pc; sc_access_func_t sc_access; @@ -155,6 +161,26 @@ extern bool cpu_convert_addr(general_cpu_t *cpu, ptr64_t virt, ptr36_t *phys, bo */ extern void cpu_reg_dump(general_cpu_t *cpu); +/** + * @brief Gets the value of a general register of the cpu + * + * @param cpu the processor pointer + * @param regno the index of the register to get + * @param out_value a pointer to the variable where the register value will be stored, only modified if the function returns true + * @return true if the register value was successfully retrieved, false otherwise + */ +extern bool cpu_get_reg(general_cpu_t *cpu, unsigned int regno, uint64_t *out_value); + +/** + * @brief Sets the value of a general register of the cpu + * + * @param cpu the processor pointer + * @param regno the index of the register to set + * @param value the value that will be set to the register + * @return true if the register value was successfully set, false otherwise + */ +extern bool cpu_set_reg(general_cpu_t *cpu, unsigned int regno, uint64_t value); + extern ptr64_t cpu_get_pc(general_cpu_t *cpu); extern void cpu_set_pc(general_cpu_t *cpu, ptr64_t pc); diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index 67912b2d..8849dcc9 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -46,6 +46,24 @@ static bool r4k_cpu_convert_addr_wrapper(r4k_cpu_t *cpu, ptr64_t virt, ptr36_t * return r4k_convert_addr(cpu, virt, phys, write, false) == r4k_excNone; } +static bool r4k_get_reg_wrapper(void *cpu, unsigned int regno, uint64_t *out_val) +{ + if (regno >= R4K_REG_COUNT) { + return false; + } + *out_val = ((r4k_cpu_t *) cpu)->regs[regno].val; + return true; +} + +static bool r4k_set_reg_wrapper(void *cpu, unsigned int regno, uint64_t val) +{ + if (regno >= R4K_REG_COUNT) { + return false; + } + ((r4k_cpu_t *) cpu)->regs[regno].val = val; + return true; +} + static ptr64_t r4k_get_pc_wrapper(void *cpu) { return ((r4k_cpu_t *) cpu)->pc; @@ -59,6 +77,8 @@ static const cpu_ops_t r4k_cpu = { .convert_addr = (convert_addr_func_t) r4k_cpu_convert_addr_wrapper, .reg_dump = (reg_dump_func_t) r4k_reg_dump, + .get_reg = (get_reg_func_t) r4k_get_reg_wrapper, + .set_reg = (set_reg_func_t) r4k_set_reg_wrapper, .get_pc = (get_pc_func_t) r4k_get_pc_wrapper, .set_pc = (set_pc_func_t) r4k_set_pc, .sc_access = (sc_access_func_t) r4k_sc_access diff --git a/src/device/drv64cpu.c b/src/device/drv64cpu.c index a08c0b24..ecb2b9a8 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -40,6 +40,24 @@ static bool rv64_convert_addr_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, bo return rv64_convert_addr((rv_cpu_t *) cpu, virt.ptr, phys, write, false, false) == rv_exc_none; } +static bool rv64_get_reg_wrapper(void *cpu, unsigned int regno, uint64_t *out_val) +{ + if (regno >= RV64_REG_COUNT) { + return false; + } + *out_val = ((rv_cpu_t *) cpu)->regs[regno]; + return true; +} + +static bool rv64_set_reg_wrapper(void *cpu, unsigned int regno, uint64_t val) +{ + if (regno >= RV64_REG_COUNT) { + return false; + } + ((rv_cpu_t *) cpu)->regs[regno] = val; + return true; +} + static ptr64_t rv64_get_pc_wrapper(void *cpu) { return (ptr64_t) { .ptr = ((rv_cpu_t *) cpu)->pc }; @@ -59,6 +77,8 @@ static const cpu_ops_t rv_cpu = { .convert_addr = (convert_addr_func_t) rv64_convert_addr_wrapper, .reg_dump = (reg_dump_func_t) rv64_reg_dump, + .get_reg = (get_reg_func_t) rv64_get_reg_wrapper, + .set_reg = (set_reg_func_t) rv64_set_reg_wrapper, .get_pc = (get_pc_func_t) rv64_get_pc_wrapper, .set_pc = (set_pc_func_t) rv64_set_pc_wrapper, .sc_access = (sc_access_func_t) rv64_sc_access diff --git a/src/device/drvcpu.c b/src/device/drvcpu.c index 5af34183..22f5c817 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -39,6 +39,24 @@ static bool rv32_convert_addr_wrapper(void *cpu, ptr64_t virt, ptr36_t *phys, bo return rv32_convert_addr((rv_cpu_t *) cpu, virt.lo, phys, write, false, false) == rv_exc_none; } +static bool rv32_get_reg_wrapper(void *cpu, unsigned int regno, uint64_t *out_val) +{ + if (regno >= RV_REG_COUNT) { + return false; + } + *out_val = ((rv_cpu_t *) cpu)->regs[regno]; + return true; +} + +static bool rv32_set_reg_wrapper(void *cpu, unsigned int regno, uint64_t val) +{ + if (regno >= RV_REG_COUNT) { + return false; + } + ((rv_cpu_t *) cpu)->regs[regno] = val; + return true; +} + static ptr64_t rv32_get_pc_wrapper(void *cpu) { return (ptr64_t) { .lo = ((rv_cpu_t *) cpu)->pc, .hi = 0 }; @@ -58,6 +76,8 @@ static const cpu_ops_t rv_cpu = { .convert_addr = (convert_addr_func_t) rv32_convert_addr_wrapper, .reg_dump = (reg_dump_func_t) rv32_reg_dump, + .get_reg = (get_reg_func_t) rv32_get_reg_wrapper, + .set_reg = (set_reg_func_t) rv32_set_reg_wrapper, .get_pc = (get_pc_func_t) rv32_get_pc_wrapper, .set_pc = (set_pc_func_t) rv32_set_pc_wrapper, .sc_access = (sc_access_func_t) rv32_sc_access From d300adab65c51eade00247dbc4c90d5a9d10a276 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Fri, 1 May 2026 20:03:23 +0200 Subject: [PATCH 10/16] Add architecture getter to general CPU API This allows to differentiate the general CPU based on its arch, used for debugging. Alternative would be to use filters or names as in device.h, but this: 1) requires the whole device, not just CPU 2) is fragile as it is based on string names 3) would need a new filter as the existing doesn't differentiate between RV32 and RV64 --- src/device/cpu/general_cpu.c | 8 ++++++++ src/device/cpu/general_cpu.h | 19 +++++++++++++++++++ src/device/dr4kcpu.c | 4 +++- src/device/drv64cpu.c | 4 +++- src/device/drvcpu.c | 4 +++- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index 2e322a88..d2544a0d 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -233,3 +233,11 @@ extern bool cpu_sc_access(general_cpu_t *cpu, ptr36_t addr, int size) } return cpu->type->sc_access(cpu->data, addr, size); } + +cpu_arch_t cpu_get_arch(general_cpu_t *cpu) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + return cpu->type->arch; +} diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index 4c8be52c..edbcfaed 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -36,6 +36,16 @@ typedef void (*set_pc_func_t)(void *, ptr64_t); /** Function type for notifying the processor about a write to a memory location, used for implementing SC atomic*/ typedef bool (*sc_access_func_t)(void *, ptr36_t, int); +/** The supported CPU architectures */ +typedef enum cpu_arch { + /** MIPS R4000 architecture. */ + CpuArchMips, + /** RISC-V 32-bit architecture. */ + CpuArchRiscV32, + /** RISC-V 64-bit architecture. */ + CpuArchRiscV64, +} cpu_arch_t; + /** Cpu method table * * NULL value means "not implemented" @@ -51,6 +61,7 @@ typedef struct { get_pc_func_t get_pc; set_pc_func_t set_pc; sc_access_func_t sc_access; + cpu_arch_t arch; /** The architecture of the CPU */ } cpu_ops_t; /** Structure describing general CPU */ @@ -194,4 +205,12 @@ extern void cpu_set_pc(general_cpu_t *cpu, ptr64_t pc); */ extern bool cpu_sc_access(general_cpu_t *cpu, ptr36_t addr, int size); +/** + * @brief Get the architecture of the CPU + * + * @param cpu the processor pointer + * @return the architecture of the CPU + */ +extern cpu_arch_t cpu_get_arch(general_cpu_t *cpu); + #endif // GENERAL_CPU_H_ diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index 8849dcc9..580a5115 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -81,7 +81,9 @@ static const cpu_ops_t r4k_cpu = { .set_reg = (set_reg_func_t) r4k_set_reg_wrapper, .get_pc = (get_pc_func_t) r4k_get_pc_wrapper, .set_pc = (set_pc_func_t) r4k_set_pc, - .sc_access = (sc_access_func_t) r4k_sc_access + .sc_access = (sc_access_func_t) r4k_sc_access, + + .arch = CpuArchMips, }; /** Initialization diff --git a/src/device/drv64cpu.c b/src/device/drv64cpu.c index ecb2b9a8..2a312a90 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -81,7 +81,9 @@ static const cpu_ops_t rv_cpu = { .set_reg = (set_reg_func_t) rv64_set_reg_wrapper, .get_pc = (get_pc_func_t) rv64_get_pc_wrapper, .set_pc = (set_pc_func_t) rv64_set_pc_wrapper, - .sc_access = (sc_access_func_t) rv64_sc_access + .sc_access = (sc_access_func_t) rv64_sc_access, + + .arch = CpuArchRiscV64, }; /** diff --git a/src/device/drvcpu.c b/src/device/drvcpu.c index 22f5c817..45281302 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -80,7 +80,9 @@ static const cpu_ops_t rv_cpu = { .set_reg = (set_reg_func_t) rv32_set_reg_wrapper, .get_pc = (get_pc_func_t) rv32_get_pc_wrapper, .set_pc = (set_pc_func_t) rv32_set_pc_wrapper, - .sc_access = (sc_access_func_t) rv32_sc_access + .sc_access = (sc_access_func_t) rv32_sc_access, + + .arch = CpuArchRiscV32, }; /** From 68c6a8a32468308225a3650639d83ef3936eccb8 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sat, 2 May 2026 21:43:20 +0200 Subject: [PATCH 11/16] Add get/set for CSRs to general CPU API --- src/device/cpu/general_cpu.c | 16 ++++++++++++++++ src/device/cpu/general_cpu.h | 26 ++++++++++++++++++++++++++ src/device/dr4kcpu.c | 20 ++++++++++++++++++++ src/device/drv64cpu.c | 29 +++++++++++++++++++++++++++++ src/device/drvcpu.c | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+) diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index d2544a0d..eeb73102 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -203,6 +203,22 @@ bool cpu_set_reg(general_cpu_t *cpu, unsigned int regno, uint64_t value) return cpu->type->set_reg(cpu->data, regno, value); } +bool cpu_get_csr(general_cpu_t *cpu, unsigned int regno, uint64_t *out_value) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + return cpu->type->get_csr(cpu->data, regno, out_value); +} + +bool cpu_set_csr(general_cpu_t *cpu, unsigned int regno, uint64_t value) +{ + if (cpu == NULL) { + cpu = get_fallback_cpu(); + } + return cpu->type->set_csr(cpu->data, regno, value); +} + ptr64_t cpu_get_pc(general_cpu_t *cpu) { if (cpu == NULL) { diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index edbcfaed..e19e384f 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -29,6 +29,10 @@ typedef void (*reg_dump_func_t)(void *); typedef bool (*get_reg_func_t)(void *, unsigned int, uint64_t *); /** Function type for setting the value of a general register of a cpu */ typedef bool (*set_reg_func_t)(void *, unsigned int, uint64_t); +/** Function type for getting the value of a CSR of a cpu */ +typedef bool (*get_csr_func_t)(void *, unsigned int, uint64_t *); +/** Function type for setting the value of a CSR a cpu */ +typedef bool (*set_csr_func_t)(void *, unsigned int, uint64_t); /** Function type for getting the program counter of a cpu */ typedef ptr64_t (*get_pc_func_t)(void *); /** Function type for setting the program counter of a cpu */ @@ -58,6 +62,8 @@ typedef struct { reg_dump_func_t reg_dump; get_reg_func_t get_reg; set_reg_func_t set_reg; + get_csr_func_t get_csr; + set_csr_func_t set_csr; get_pc_func_t get_pc; set_pc_func_t set_pc; sc_access_func_t sc_access; @@ -192,6 +198,26 @@ extern bool cpu_get_reg(general_cpu_t *cpu, unsigned int regno, uint64_t *out_va */ extern bool cpu_set_reg(general_cpu_t *cpu, unsigned int regno, uint64_t value); +/** + * @brief Gets the value of a CSR of the cpu + * + * @param cpu the processor pointer + * @param regno the index of the CSR to get + * @param out_value a pointer to the variable where the CSR value will be stored, only modified if the function returns true + * @return true if the CSR value was successfully retrieved, false otherwise + */ +bool cpu_get_csr(general_cpu_t *cpu, unsigned int regno, uint64_t *out_value); + +/** + * @brief Sets the value of a CSR of the cpu + * + * @param cpu the processor pointer + * @param regno the index of the CSR to set + * @param value the value that will be set to the CSR + * @return true if the CSR value was successfully set, false otherwise + */ +bool cpu_set_csr(general_cpu_t *cpu, unsigned int regno, uint64_t value); + extern ptr64_t cpu_get_pc(general_cpu_t *cpu); extern void cpu_set_pc(general_cpu_t *cpu, ptr64_t pc); diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index 580a5115..c99da585 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -64,6 +64,24 @@ static bool r4k_set_reg_wrapper(void *cpu, unsigned int regno, uint64_t val) return true; } +static bool r4k_get_csr_wrapper(void *cpu, unsigned int regno, uint64_t *out_val) +{ + if (regno >= R4K_REG_COUNT) { + return false; + } + *out_val = ((r4k_cpu_t *) cpu)->cp0[regno].val; + return true; +} + +static bool r4k_set_csr_wrapper(void *cpu, unsigned int regno, uint64_t val) +{ + if (regno >= R4K_REG_COUNT) { + return false; + } + ((r4k_cpu_t *) cpu)->cp0[regno].val = val; + return true; +} + static ptr64_t r4k_get_pc_wrapper(void *cpu) { return ((r4k_cpu_t *) cpu)->pc; @@ -79,6 +97,8 @@ static const cpu_ops_t r4k_cpu = { .get_reg = (get_reg_func_t) r4k_get_reg_wrapper, .set_reg = (set_reg_func_t) r4k_set_reg_wrapper, + .get_csr = (get_csr_func_t) r4k_get_csr_wrapper, + .set_csr = (set_csr_func_t) r4k_set_csr_wrapper, .get_pc = (get_pc_func_t) r4k_get_pc_wrapper, .set_pc = (set_pc_func_t) r4k_set_pc, .sc_access = (sc_access_func_t) r4k_sc_access, diff --git a/src/device/drv64cpu.c b/src/device/drv64cpu.c index 2a312a90..da209611 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -58,6 +58,33 @@ static bool rv64_set_reg_wrapper(void *cpu, unsigned int regno, uint64_t val) return true; } +static bool rv64_get_csr_wrapper(void *cpu, unsigned int csrno, uint64_t *out_val) +{ + rv64_cpu_t *rv_cpu = cpu; + const rv_priv_mode_t prev = rv_cpu->priv_mode; + rv_cpu->priv_mode = rv_mmode; // Temporarily switch to M-mode to be able to read all CSRs + + const bool result = rv64_csr_rs(rv_cpu, csrno, 0, out_val, true) == rv_exc_none; + + rv_cpu->priv_mode = prev; // Restore previous privilege mode + + return result; +} + +static bool rv64_set_csr_wrapper(void *cpu, unsigned int csrno, uint64_t val) +{ + rv64_cpu_t *rv_cpu = cpu; + const rv_priv_mode_t prev = rv_cpu->priv_mode; + rv_cpu->priv_mode = rv_mmode; // Temporarily switch to M-mode to be able to write all CSRs + + uint64_t dummy_target; + const bool result = rv64_csr_rw(rv_cpu, csrno, val, &dummy_target, false) == rv_exc_none; + + rv_cpu->priv_mode = prev; // Restore previous privilege mode + + return result; +} + static ptr64_t rv64_get_pc_wrapper(void *cpu) { return (ptr64_t) { .ptr = ((rv_cpu_t *) cpu)->pc }; @@ -79,6 +106,8 @@ static const cpu_ops_t rv_cpu = { .get_reg = (get_reg_func_t) rv64_get_reg_wrapper, .set_reg = (set_reg_func_t) rv64_set_reg_wrapper, + .get_csr = (get_csr_func_t) rv64_get_csr_wrapper, + .set_csr = (set_csr_func_t) rv64_set_csr_wrapper, .get_pc = (get_pc_func_t) rv64_get_pc_wrapper, .set_pc = (set_pc_func_t) rv64_set_pc_wrapper, .sc_access = (sc_access_func_t) rv64_sc_access, diff --git a/src/device/drvcpu.c b/src/device/drvcpu.c index 45281302..4db8deaa 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -57,6 +57,39 @@ static bool rv32_set_reg_wrapper(void *cpu, unsigned int regno, uint64_t val) return true; } +static bool rv32_get_csr_wrapper(void *cpu, unsigned int csrno, uint64_t *out_val) +{ + rv32_cpu_t *rv_cpu = cpu; + const rv_priv_mode_t prev = rv_cpu->priv_mode; + rv_cpu->priv_mode = rv_mmode; // Temporarily switch to M-mode to be able to read all CSRs + + uint32_t temp_out; + const bool result = rv32_csr_rs(rv_cpu, csrno, 0, &temp_out, false) == rv_exc_none; + + rv_cpu->priv_mode = prev; // Restore previous privilege mode + + if (result) { + *out_val = 0; + *out_val = temp_out; + } + + return result; +} + +static bool rv32_set_csr_wrapper(void *cpu, unsigned int csrno, uint64_t val) +{ + rv32_cpu_t *rv_cpu = cpu; + const rv_priv_mode_t prev = rv_cpu->priv_mode; + rv_cpu->priv_mode = rv_mmode; // Temporarily switch to M-mode to be able to write all CSRs + + uint32_t dummy_target; + const bool result = rv32_csr_rw(rv_cpu, csrno, val, &dummy_target, false) == rv_exc_none; + + rv_cpu->priv_mode = prev; // Restore previous privilege mode + + return result; +} + static ptr64_t rv32_get_pc_wrapper(void *cpu) { return (ptr64_t) { .lo = ((rv_cpu_t *) cpu)->pc, .hi = 0 }; @@ -78,6 +111,8 @@ static const cpu_ops_t rv_cpu = { .get_reg = (get_reg_func_t) rv32_get_reg_wrapper, .set_reg = (set_reg_func_t) rv32_set_reg_wrapper, + .get_csr = (get_csr_func_t) rv32_get_csr_wrapper, + .set_csr = (set_csr_func_t) rv32_set_csr_wrapper, .get_pc = (get_pc_func_t) rv32_get_pc_wrapper, .set_pc = (set_pc_func_t) rv32_set_pc_wrapper, .sc_access = (sc_access_func_t) rv32_sc_access, From 5aec3f0b1e9e87343f7c3865ddace8e274ebec65 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Mon, 4 May 2026 21:06:55 +0200 Subject: [PATCH 12/16] Add steps to general_cpu to make them per CPU This allows tracking steps independently in each CPU. This is only really useful for DAP debugging now, but could be made available for per-device CLI debugging too. --- src/device/cpu/general_cpu.c | 1 + src/device/cpu/general_cpu.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/device/cpu/general_cpu.c b/src/device/cpu/general_cpu.c index eeb73102..6ca86f24 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -22,6 +22,7 @@ general_cpu_t *general_cpu_init(const unsigned int id, void *cpu, const cpu_ops_ gen_cpu->cpuno = id; gen_cpu->data = cpu; gen_cpu->type = type; + gen_cpu->steps_left = 0; list_init(&gen_cpu->bps); return gen_cpu; } diff --git a/src/device/cpu/general_cpu.h b/src/device/cpu/general_cpu.h index e19e384f..9d690de9 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -77,6 +77,7 @@ typedef struct { const cpu_ops_t *type; void *data; list_t bps; // Breakpoints + uint64_t steps_left; // Number of instruction steps to execute before pausing. 0 means infinite. } general_cpu_t; /** List of all CPUs */ From eb2c4368feb3eeca318c5732325d53368bd0c636 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Wed, 20 May 2026 00:33:21 +0200 Subject: [PATCH 13/16] Fix physmem BP bounds check bugs Fixes 2 bound check bugs: 1) introduced in d8aaee03, the half open interval should be checked against access size, not BP size, as was previously done. 2) < instead of <= in half open interval check (allowed the excluded end before) --- src/physmem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/physmem.c b/src/physmem.c index d36347d9..07a9b8a8 100644 --- a/src/physmem.c +++ b/src/physmem.c @@ -409,11 +409,11 @@ static void physmem_breakpoint_find(ptr36_t addr, len36_t size, for_each(physmem_breakpoints, breakpoint, physmem_breakpoint_t) { - if (breakpoint->addr + breakpoint->size < addr) { + if (breakpoint->addr + breakpoint->size <= addr) { continue; } - if (breakpoint->addr > addr + breakpoint->size) { + if (breakpoint->addr >= addr + size) { continue; } From db78227a27708c6b29f98a3feb87464bf0e05bbd Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Wed, 20 May 2026 00:55:16 +0200 Subject: [PATCH 14/16] Fix MIPS CPU physmem BP inconsistency MIPS is inconsistent with RV CPUs, not triggering physmem BPs on instruction fetch --- src/device/cpu/mips_r4000/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/cpu/mips_r4000/cpu.c b/src/device/cpu/mips_r4000/cpu.c index 80b2c77e..7b570342 100644 --- a/src/device/cpu/mips_r4000/cpu.c +++ b/src/device/cpu/mips_r4000/cpu.c @@ -3059,7 +3059,7 @@ static r4k_exc_t execute(r4k_cpu_t *cpu) return r4k_excAdEL; } - r4k_instr_t instr = (r4k_instr_t) physmem_read32(cpu->procno, phys, false); + r4k_instr_t instr = (r4k_instr_t) physmem_read32(cpu->procno, phys, true); /* Execute instruction */ r4k_exc_t exc = fnc(cpu, instr); From fae0f37ad93c1b66c5067171ed8ab9cc291e1578 Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Sun, 24 May 2026 17:23:14 +0200 Subject: [PATCH 15/16] Fix interrupts in RV64 CPU Fixes a bug triggered in rv64_interrupt_up: the RV_INTERRUPT_NO macro expansion applied operator ~ to RV_INTERRUPT_EXC_BITS which expanded to 1UL << 63UL instead of (1UL << 63). This caused it to evaluate to 0 instead of all except MSb set, making the interrupt number default to MEI instead. --- src/device/cpu/riscv_rv_ima/exception.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/cpu/riscv_rv_ima/exception.h b/src/device/cpu/riscv_rv_ima/exception.h index dc23de8f..7de8f449 100644 --- a/src/device/cpu/riscv_rv_ima/exception.h +++ b/src/device/cpu/riscv_rv_ima/exception.h @@ -23,7 +23,7 @@ #define RV_INTERRUPT_NO(interrupt) ((interrupt) & ~RV_INTERRUPT_EXC_BITS) #else // MSb that determines if the exception is an interrupt -#define RV_INTERRUPT_EXC_BITS UINT64_C(1UL << 63) +#define RV_INTERRUPT_EXC_BITS UINT64_C(0x8000000000000000) #define RV_EXCEPTION_EXC_BITS UINT64_C(0) #define RV_EXCEPTION_MASK(exc) (UINT64_C(1) << ((exc) & 0x3F)) #define RV_INTERRUPT_NO(interrupt) ((interrupt) & ~RV_INTERRUPT_EXC_BITS) From b800b08ac6fa1e13d419cf7f3a862584adfa242a Mon Sep 17 00:00:00 2001 From: Viktor Zavodsky Date: Thu, 30 Apr 2026 17:05:43 +0200 Subject: [PATCH 16/16] Update DAP protocol and add DAP test suite - use 3 args - add Step request - add GetConfig request - add register (general+csr) read/write request - add CPU info request - make step/stopped at track the relevant CPU - rename stop request to terminate request - refactor --- doc/reference/dap.rst | 57 +- src/debug/breakpoint.c | 25 +- src/debug/dap.c | 615 +++++++++++++++--- src/debug/dap.h | 3 +- src/main.c | 1 + tests/system/common.bash | 53 ++ tests/system/dap-simple-mips32/base.py | 26 + tests/system/dap-simple-mips32/boot.bin | Bin 0 -> 36 bytes tests/system/dap-simple-mips32/main.S | 11 + tests/system/dap-simple-mips32/msim.conf | 4 + .../dap-simple-mips32/scenario_bad_req.py | 10 + .../dap-simple-mips32/scenario_breakpoint.py | 27 + .../dap-simple-mips32/scenario_cpu_info.py | 9 + .../system/dap-simple-mips32/scenario_csr.py | 32 + .../scenario_data_breakpoint.py | 39 ++ .../dap-simple-mips32/scenario_interrupts.py | 56 ++ .../system/dap-simple-mips32/scenario_mem.py | 51 ++ tests/system/dap-simple-mips32/scenario_pc.py | 37 ++ .../dap-simple-mips32/scenario_register.py | 45 ++ .../dap-simple-mips32/scenario_resume.py | 15 + .../system/dap-simple-mips32/scenario_step.py | 20 + .../dap-simple-mips32/scenario_terminate.py | 6 + tests/system/dap-simple-multiarch/base.py | 25 + tests/system/dap-simple-multiarch/boot.bin | Bin 0 -> 36 bytes tests/system/dap-simple-multiarch/main.S | 11 + tests/system/dap-simple-multiarch/msim.conf | 6 + .../scenario_breakpoint.py | 23 + .../dap-simple-multiarch/scenario_cpu_info.py | 10 + .../dap-simple-multiarch/scenario_csr.py | 26 + .../scenario_data_breakpoint.py | 27 + .../scenario_interrupts.py | 109 ++++ .../dap-simple-multiarch/scenario_pc.py | 29 + .../dap-simple-multiarch/scenario_register.py | 42 ++ .../dap-simple-multiarch/scenario_resume.py | 18 + .../dap-simple-multiarch/scenario_step.py | 24 + .../scenario_terminate.py | 6 + tests/system/dap-simple-riscv32/base.py | 22 + tests/system/dap-simple-riscv32/boot.bin | Bin 0 -> 36 bytes tests/system/dap-simple-riscv32/main.S | 11 + tests/system/dap-simple-riscv32/msim.conf | 4 + .../dap-simple-riscv32/scenario_bad_req.py | 10 + .../dap-simple-riscv32/scenario_breakpoint.py | 35 + .../dap-simple-riscv32/scenario_cpu_info.py | 9 + .../system/dap-simple-riscv32/scenario_csr.py | 29 + .../scenario_data_breakpoint.py | 39 ++ .../dap-simple-riscv32/scenario_interrupts.py | 60 ++ .../system/dap-simple-riscv32/scenario_mem.py | 51 ++ .../system/dap-simple-riscv32/scenario_pc.py | 37 ++ .../dap-simple-riscv32/scenario_register.py | 45 ++ .../dap-simple-riscv32/scenario_resume.py | 15 + .../dap-simple-riscv32/scenario_step.py | 20 + .../dap-simple-riscv32/scenario_terminate.py | 6 + tests/system/dap-simple-riscv64/base.py | 22 + tests/system/dap-simple-riscv64/boot.bin | Bin 0 -> 36 bytes tests/system/dap-simple-riscv64/main.S | 11 + tests/system/dap-simple-riscv64/msim.conf | 4 + .../dap-simple-riscv64/scenario_bad_req.py | 10 + .../dap-simple-riscv64/scenario_breakpoint.py | 27 + .../dap-simple-riscv64/scenario_cpu_info.py | 9 + .../system/dap-simple-riscv64/scenario_csr.py | 29 + .../scenario_data_breakpoint.py | 39 ++ .../dap-simple-riscv64/scenario_interrupts.py | 60 ++ .../system/dap-simple-riscv64/scenario_mem.py | 51 ++ .../system/dap-simple-riscv64/scenario_pc.py | 37 ++ .../dap-simple-riscv64/scenario_register.py | 45 ++ .../dap-simple-riscv64/scenario_resume.py | 15 + .../dap-simple-riscv64/scenario_step.py | 20 + .../dap-simple-riscv64/scenario_terminate.py | 6 + tests/system/dap.bats | 187 ++++++ tests/system/dap_lib.py | 102 +++ 70 files changed, 2452 insertions(+), 113 deletions(-) create mode 100644 tests/system/dap-simple-mips32/base.py create mode 100644 tests/system/dap-simple-mips32/boot.bin create mode 100644 tests/system/dap-simple-mips32/main.S create mode 100644 tests/system/dap-simple-mips32/msim.conf create mode 100644 tests/system/dap-simple-mips32/scenario_bad_req.py create mode 100644 tests/system/dap-simple-mips32/scenario_breakpoint.py create mode 100644 tests/system/dap-simple-mips32/scenario_cpu_info.py create mode 100644 tests/system/dap-simple-mips32/scenario_csr.py create mode 100644 tests/system/dap-simple-mips32/scenario_data_breakpoint.py create mode 100644 tests/system/dap-simple-mips32/scenario_interrupts.py create mode 100644 tests/system/dap-simple-mips32/scenario_mem.py create mode 100644 tests/system/dap-simple-mips32/scenario_pc.py create mode 100644 tests/system/dap-simple-mips32/scenario_register.py create mode 100644 tests/system/dap-simple-mips32/scenario_resume.py create mode 100644 tests/system/dap-simple-mips32/scenario_step.py create mode 100644 tests/system/dap-simple-mips32/scenario_terminate.py create mode 100644 tests/system/dap-simple-multiarch/base.py create mode 100644 tests/system/dap-simple-multiarch/boot.bin create mode 100644 tests/system/dap-simple-multiarch/main.S create mode 100644 tests/system/dap-simple-multiarch/msim.conf create mode 100644 tests/system/dap-simple-multiarch/scenario_breakpoint.py create mode 100644 tests/system/dap-simple-multiarch/scenario_cpu_info.py create mode 100644 tests/system/dap-simple-multiarch/scenario_csr.py create mode 100644 tests/system/dap-simple-multiarch/scenario_data_breakpoint.py create mode 100644 tests/system/dap-simple-multiarch/scenario_interrupts.py create mode 100644 tests/system/dap-simple-multiarch/scenario_pc.py create mode 100644 tests/system/dap-simple-multiarch/scenario_register.py create mode 100644 tests/system/dap-simple-multiarch/scenario_resume.py create mode 100644 tests/system/dap-simple-multiarch/scenario_step.py create mode 100644 tests/system/dap-simple-multiarch/scenario_terminate.py create mode 100644 tests/system/dap-simple-riscv32/base.py create mode 100644 tests/system/dap-simple-riscv32/boot.bin create mode 100644 tests/system/dap-simple-riscv32/main.S create mode 100644 tests/system/dap-simple-riscv32/msim.conf create mode 100644 tests/system/dap-simple-riscv32/scenario_bad_req.py create mode 100644 tests/system/dap-simple-riscv32/scenario_breakpoint.py create mode 100644 tests/system/dap-simple-riscv32/scenario_cpu_info.py create mode 100644 tests/system/dap-simple-riscv32/scenario_csr.py create mode 100644 tests/system/dap-simple-riscv32/scenario_data_breakpoint.py create mode 100644 tests/system/dap-simple-riscv32/scenario_interrupts.py create mode 100644 tests/system/dap-simple-riscv32/scenario_mem.py create mode 100644 tests/system/dap-simple-riscv32/scenario_pc.py create mode 100644 tests/system/dap-simple-riscv32/scenario_register.py create mode 100644 tests/system/dap-simple-riscv32/scenario_resume.py create mode 100644 tests/system/dap-simple-riscv32/scenario_step.py create mode 100644 tests/system/dap-simple-riscv32/scenario_terminate.py create mode 100644 tests/system/dap-simple-riscv64/base.py create mode 100644 tests/system/dap-simple-riscv64/boot.bin create mode 100644 tests/system/dap-simple-riscv64/main.S create mode 100644 tests/system/dap-simple-riscv64/msim.conf create mode 100644 tests/system/dap-simple-riscv64/scenario_bad_req.py create mode 100644 tests/system/dap-simple-riscv64/scenario_breakpoint.py create mode 100644 tests/system/dap-simple-riscv64/scenario_cpu_info.py create mode 100644 tests/system/dap-simple-riscv64/scenario_csr.py create mode 100644 tests/system/dap-simple-riscv64/scenario_data_breakpoint.py create mode 100644 tests/system/dap-simple-riscv64/scenario_interrupts.py create mode 100644 tests/system/dap-simple-riscv64/scenario_mem.py create mode 100644 tests/system/dap-simple-riscv64/scenario_pc.py create mode 100644 tests/system/dap-simple-riscv64/scenario_register.py create mode 100644 tests/system/dap-simple-riscv64/scenario_resume.py create mode 100644 tests/system/dap-simple-riscv64/scenario_step.py create mode 100644 tests/system/dap-simple-riscv64/scenario_terminate.py create mode 100644 tests/system/dap.bats create mode 100644 tests/system/dap_lib.py diff --git a/doc/reference/dap.rst b/doc/reference/dap.rst index 404ed771..6a0985a3 100644 --- a/doc/reference/dap.rst +++ b/doc/reference/dap.rst @@ -4,8 +4,8 @@ DAP support DAP (debugger adapter protocol) is a protocol that enables standardized communication between development tools (IDEs) and debuggers. -An experimental DAP support now arrived to MSIM, allowing DAP-enabled IDEs to connect to -the running MSIM for interactive debugging. +DAP support is integrated to MSIM, allowing DAP-enabled IDEs to connect to +the running MSIM for interactive debugging via a debugging adapter. Currently the only supported and tested IDE is Visual Studio Code with the provided `msim-debugger extension `_. @@ -31,9 +31,9 @@ in future releases. Breakpoints ~~~~~~~~~~~ -The first supported debugging feature are simple breakpoints. +Code breakpoints are fully supported. You can set breakpoints in your IDE, and when MSIM hits them during execution, -it will enter interactive mode to allow you to e.g. inspect CPU registers.extension +it will pause and wait for further commands from the IDE. This can be useful to pause execution at specific points in your program and examine the state of your kernel. @@ -46,25 +46,52 @@ It is conceptually similar to adding a breakpoint via the special `ebreak` instr as described in :ref:`entering-the-debugger`. When stopped at a breakpoint, you can resume the execution just as you would normally, -using the ``continue`` command. +using the resume command in your IDE. -Limitations -^^^^^^^^^^^ +Execution control +~~~~~~~~~~~~~~~~~ + +MSIM can be paused and resumed from the IDE, allowing you to control the execution of your kernel. + +Register Inspection +~~~~~~~~~~~~~~~~~~~ + +When MSIM is paused, you can inspect the values of the registers in your kernel from the IDE. +Both general-purpose and control/status registers are supported. The registers are displayed +in the IDE's variables view, and their values can be modified. + +Memory Inspection +~~~~~~~~~~~~~~~~~ + +When MSIM is paused, you can inspect the memory of your kernel from the IDE. +The memory view in the IDE allows you to view and modify the contents of the memory at specific addresses. +This is done by clicking the memory view next to a register with pointer-like semantics. -You can't remove the breakpoints when MSIM is running yet. -You need to restart MSIM and the debugging session after removing them in your IDE. +CPU View +~~~~~~~~ -The IDE is currently not aware when and where MSIM stops, -this is a planned feature in next releases. +The CPUs in MSIM are displayed in the IDE, presented as threads. You can see the highlighted CPU when MSIM +is paused, denoting the CPU causing the stopping. + +Stepping +~~~~~~~~ + +Stepping is supported, allowing you to step through your kernel's execution one instruction at a time. +The step-in command steps by one instruction, while the step-over command attempts to step to the next statement. +The step-out command is not supported yet, defaulting to step-in behavior. + +Limitations +^^^^^^^^^^^ Some sources files might not be correctly mapped to the memory addresses, and -will not have the breakpoints hit. +will not have the breakpoints hit. This happens especially when entering a userspace application, +as that is usually built as a separate ELF file, which the extension has no access to. + +The step-out command is not supported yet. Bug reports and feedback ------------------------ -This extension is *very* experimental and a work in progress. - -Please, report any bugs or feedback you have to the `issues in the MSIM repository `_. +Please, report any bugs or feedback you have to the `issues in the MSIM repository `_. Your bug reports and feedback are very much appreciated! :) diff --git a/src/debug/breakpoint.c b/src/debug/breakpoint.c index 20c8b293..52f983cc 100644 --- a/src/debug/breakpoint.c +++ b/src/debug/breakpoint.c @@ -185,7 +185,11 @@ void physmem_breakpoint_hit(physmem_breakpoint_t *breakpoint, machine_interactive = true; break; case BREAKPOINT_KIND_DEBUGGER: - gdb_handle_event(GDB_EVENT_BREAKPOINT); + if (dap_enabled) { + dap_event_hit_data_breakpoint(breakpoint->addr); + } else if (remote_gdb) { + gdb_handle_event(GDB_EVENT_BREAKPOINT); + } break; default: die(ERR_INTERN, "Unexpected physical memory breakpoint kind"); @@ -219,9 +223,10 @@ breakpoint_t *breakpoint_init(ptr64_t address, breakpoint_kind_t kind) /** Fires given breakpoint * * @param breakpoint Breakpoint structure to be fired + * @param cpu_no Number of CPU which hit the breakpoint. * */ -static void breakpoint_hit(breakpoint_t *breakpoint) +static void breakpoint_hit(breakpoint_t *breakpoint, unsigned long cpu_no) { breakpoint->hits++; @@ -232,8 +237,7 @@ static void breakpoint_hit(breakpoint_t *breakpoint) break; case BREAKPOINT_KIND_DEBUGGER: if (dap_enabled) { - alert("Debug: Hit debugger breakpoint at %#0" PRIx64, breakpoint->pc.ptr); - dap_event_hit_code_breakpoint(breakpoint->pc.ptr); + dap_event_hit_code_breakpoint(cpu_no); } else if (remote_gdb) { gdb_handle_event(GDB_EVENT_BREAKPOINT); } @@ -246,23 +250,24 @@ static void breakpoint_hit(breakpoint_t *breakpoint) /** Search for a breakpoint * * Search for a breakpoint in given list which should be - * fired on given address and fire it. + * fired based on address of given CPU's PC. * * @param breakpoints List of code breakpoints of some processor. - * @param address Address of executed instruction. + * @param cpu CPU which can hit the breakpoint. * * @return True, if at least one breakpoint has been hit. * */ -static bool breakpoint_hit_by_address(list_t breakpoints, ptr64_t address) +static bool breakpoint_hit_by_cpu(list_t breakpoints, general_cpu_t *cpu) { + ptr64_t address = cpu_get_pc(cpu); bool hit = false; breakpoint_t *breakpoint = NULL; for_each(breakpoints, breakpoint, breakpoint_t) { if (breakpoint->pc.ptr == address.ptr) { - breakpoint_hit(breakpoint); + breakpoint_hit(breakpoint, cpu->cpuno); hit = true; } } @@ -310,10 +315,10 @@ bool breakpoint_check_for_code_breakpoints(void) { bool hit = false; - general_cpu_t* cpu = NULL; + general_cpu_t *cpu = NULL; for_each(cpu_list, cpu, general_cpu_t) { - if (breakpoint_hit_by_address(cpu->bps, cpu_get_pc(cpu))) { + if (breakpoint_hit_by_cpu(cpu->bps, cpu)) { hit = true; } } diff --git a/src/debug/dap.c b/src/debug/dap.c index a755c604..709c79de 100644 --- a/src/debug/dap.c +++ b/src/debug/dap.c @@ -1,36 +1,38 @@ #include +#include #include #include #include #include -#include #include "../assert.h" #include "../device/cpu/general_cpu.h" -#include "../device/cpu/mips_r4000/cpu.h" -#include "../device/cpu/riscv_rv32ima/cpu.h" #include "../fault.h" #include "../main.h" +#include "../physmem.h" #include "dap.h" // be64toh is not on macOS #if defined(__APPLE__) - #include - #define be64toh(x) OSSwapBigToHostInt64(x) - #define htobe64(x) OSSwapHostToBigInt64(x) +#include +#define be64toh(x) OSSwapBigToHostInt64(x) +#define htobe64(x) OSSwapHostToBigInt64(x) #endif +#define DAP_PREFIX "[DAP] " +#define DAP_ARG_COUNT 3 + static int connection_fd = -1; -static unsigned int cpuno_global = 0; // Default CPU device number used +static uint32_t cpuno_default = 0; // Default CPU device number used typedef enum dap_request_type { /** Request to resume execution. Also used for the initial start. */ ResumeRequest = 0x01, /** Request to pause execution. */ PauseRequest = 0x02, - /** Request to stop execution and exit the simulator. */ - StopRequest = 0x03, - /** Request to step `arg0=count` instructions. */ + /** Request to terminate execution and exit the simulator. */ + TerminateRequest = 0x03, + /** Request to step `arg0=cpu` by `arg1=count` instructions. */ StepRequest = 0x04, // Breakpoint requests @@ -39,25 +41,25 @@ typedef enum dap_request_type { /** Request to remove a code breakpoint at `arg0=address`. */ RemoveCodeBreakpointRequest = 0x06, - /** Request to set a data breakpoint at `arg0=address` with `arg1=kind`. */ + /** Request to set a physical memory data breakpoint at `arg0=address` with `arg1=kind` of `arg2=size`. */ SetDataBreakpointRequest = 0x07, - /** Request to remove a data breakpoint at `arg0=address`. */ + /** Request to remove a physical memory data breakpoint at `arg0=address`. */ RemoveDataBreakpointRequest = 0x08, // Register requests - /** Request to read the value of register `arg0=id`. */ + /** Request to read the value of register `arg1=id` in `arg0=cpu`. */ ReadGeneralRegisterRequest = 0x09, - /** Request to write to register `arg0=id` the value `arg1=value`. */ + /** Request to write to register `arg1=id` in `arg0=cpu` the value `arg2=value`. */ WriteGeneralRegisterRequest = 0x0A, - /** Request to read the value of Control and Status Register (CSR) `arg0=id`. */ + /** Request to read the value of Control and Status Register (CSR) `arg1=id` in `arg0=cpu`. */ ReadCsrRequest = 0x0B, - /** Request to write to Control and Status Register (CSR) `arg0=id` the value `arg1=value`. */ + /** Request to write to Control and Status Register (CSR) `arg1=id` the value `arg2=value` in `arg0=cpu`. */ WriteCsrRequest = 0x0C, - /** Request to read the value of the program counter. */ + /** Request to read the value of the program counter of `arg0=cpu`. */ ReadPCRequest = 0x0D, - /** Request to write `arg0=value` to the program counter. */ + /** Request to write `arg1=value` to the program counter of `arg0=cpu`. */ WritePCRequest = 0x0E, // Memory requests @@ -79,6 +81,11 @@ typedef enum dap_request_type { RaiseInterruptRequest = 0x14, /** Request to clear interrupt `arg0=id`. */ ClearInterruptRequest = 0x15, + + /** Request to get the value of configuration */ + GetConfigRequest = 0x16, + /** Request to get CPU-specific information for `arg0=cpu_id` */ + GetCpuInfoRequest = 0x17, } dap_request_type_t; typedef enum dap_outbound_category { @@ -93,14 +100,20 @@ typedef enum dap_response_status { StatusOk = 0x01, /** Response to a failed request with an unspecified error. */ StatusUnspecifiedError = 0x02, - /** Response to an unsupported request. */ - StatusUnsupportedRequestError = 0x03 + /** Response to an unsupported request with type id `arg0`. */ + StatusUnsupportedRequestError = 0x03, + /** Response to a failed request with an unknown CPU id `arg0`. */ + StatusCpuNotFoundError = 0x04, + /** Response to a failed request with an unknown register id `arg0`. */ + StatusRegisterNotFoundError = 0x05, + /** Response to a failed request with bad memory address `arg0`. */ + StatusBadAddressError = 0x06, } dap_response_status_t; typedef enum dap_event_type { - /** Event indicating that the simulator has exited. */ - ExitedEvent = 0x01, - /** Event indicating that the simulator has paused execution. */ + /** Event indicating that the simulator has terminated. */ + TerminatedEvent = 0x01, + /** Event indicating that the simulator has paused execution with `arg0=cpu` at `arg1=address` due to `arg2=reason` */ StoppedAtEvent = 0x02, } dap_event_type_t; @@ -113,7 +126,6 @@ typedef enum dap_stopped_reason { StoppedReasonStep = 0x03, /** Stopped due to an interrupt. */ StoppedReasonInterrupt = 0x04, - } dap_stopped_reason_t; /** Structure for DAP requests */ @@ -121,6 +133,7 @@ typedef struct dap_request { dap_request_type_t type; uint64_t arg0; uint64_t arg1; + uint64_t arg2; } dap_request_t; /** Structure for DAP responses */ @@ -128,6 +141,7 @@ typedef struct dap_response { dap_response_status_t type; uint64_t arg0; uint64_t arg1; + uint64_t arg2; } dap_response_t; /** Structure for DAP events */ @@ -135,18 +149,19 @@ typedef struct dap_event { dap_event_type_t type; uint64_t arg0; uint64_t arg1; + uint64_t arg2; } dap_event_t; enum { - INBOUND_FRAME_SIZE = 17, /** Size of a single DAP request frame */ - OUTBOUND_FRAME_SIZE = 18, /** Size of a single DAP response frame */ + INBOUND_FRAME_SIZE = 25, /** Size of a single DAP request frame */ + OUTBOUND_FRAME_SIZE = 26, /** Size of a single DAP response frame */ }; -/** Length of an inbound frame is always 17 B = 1 B (type) + 8 B (arg0) + 8 B (arg1) */ -static_assert(INBOUND_FRAME_SIZE == sizeof(uint8_t) + 2 * sizeof(uint64_t), "DAP inbound frame must be exactly 17 bytes long"); +/** Length of an inbound frame is always 25 B = 1 B (type) + 8 B (arg0) + 8 B (arg1) + 8 B (arg2) */ +static_assert(INBOUND_FRAME_SIZE == sizeof(uint8_t) + DAP_ARG_COUNT * sizeof(uint64_t), "DAP inbound frame must be exactly 25 bytes long"); -/** Length of an outbound frame is always 17 B = 1 B (category) + 1 B (status/type) + 8 B (arg0) + 8 B (arg1) */ -static_assert(OUTBOUND_FRAME_SIZE == sizeof(uint8_t) + sizeof(uint8_t) + 2 * sizeof(uint64_t), "DAP inbound frame must be exactly 18 bytes long"); +/** Length of an outbound frame is always 26 B = 1 B (category) + 1 B (status/type) + 8 B (arg0) + 8 B (arg1) + 8 B (arg2) */ +static_assert(OUTBOUND_FRAME_SIZE == sizeof(uint8_t) + sizeof(uint8_t) + DAP_ARG_COUNT * sizeof(uint64_t), "DAP inbound frame must be exactly 26 bytes long"); /** Internal buffer for incoming frames */ static uint8_t frame_buffer[INBOUND_FRAME_SIZE]; @@ -181,14 +196,14 @@ bool dap_init(void) return false; } - alert("Listening for DAP connection on port %u.", dap_port); + alert(DAP_PREFIX "Listening for connection on port %u.", dap_port); struct sockaddr_in sa_dap; socklen_t address_len = sizeof(sa_dap); connection_fd = accept(sock, (struct sockaddr *) &sa_dap, &address_len); if (connection_fd < 0) { if (errno == EINTR) { - alert("DAP: Interrupted"); + alert(DAP_PREFIX "Interrupted"); } else { io_error("dap_accept"); } @@ -196,10 +211,12 @@ bool dap_init(void) return false; } - alert("DAP connected."); + alert(DAP_PREFIX "Connected."); return true; } +/* MSIM DAP protocol implementation */ + /** Receive bytes from DAP connection * * This will try to receive `INBOUND_FRAME_SIZE` bytes and store @@ -213,17 +230,17 @@ bool dap_init(void) * @return True if successfully received `INBOUND_FRAME_SIZE` bytes and stored them in `buf`. * False otherwise. */ -static bool dap_receive_bytes(void* buf, const bool block) +static bool dap_receive_bytes(void *buf, const bool block) { const ssize_t need_bytes = INBOUND_FRAME_SIZE - frame_buffered_bytes; - uint8_t* write_buffer = frame_buffer + frame_buffered_bytes; + uint8_t *write_buffer = frame_buffer + frame_buffered_bytes; ASSERT(need_bytes <= INBOUND_FRAME_SIZE); ASSERT(0 < need_bytes); ASSERT(connection_fd != -1); ASSERT(buf != NULL); ASSERT(write_buffer + need_bytes <= frame_buffer + INBOUND_FRAME_SIZE); // No overflow check - const int flags = block ? 0 : MSG_DONTWAIT; + const int flags = block ? MSG_WAITALL : MSG_DONTWAIT; const ssize_t received = recv(connection_fd, write_buffer, need_bytes, flags); // Received @@ -268,15 +285,17 @@ static bool dap_receive_bytes(void* buf, const bool block) * @param buf Pointer to buffer with data to send. * @return True if successful. */ -static bool dap_send_bytes(const void* buf) +static bool dap_send_bytes(const void *buf) { ssize_t to_send = OUTBOUND_FRAME_SIZE; - const uint8_t* ptr = buf; + const uint8_t *ptr = buf; while (to_send > 0) { const ssize_t sent = send(connection_fd, ptr, to_send, 0); // Error if (sent < 0) { - if (errno == EINTR) continue; + if (errno == EINTR) { + continue; + } io_error("dap_send"); dap_state = DAP_DONE; return false; @@ -307,13 +326,16 @@ static bool dap_receive_request(dap_request_t *out_cmd, const bool block) out_cmd->type = buffer[0]; - uint64_t netorder_arg0; - uint64_t netorder_arg1; - memcpy(&netorder_arg0, buffer + sizeof(uint8_t), sizeof(netorder_arg0)); - memcpy(&netorder_arg1, buffer + sizeof(uint8_t) + sizeof(netorder_arg0), sizeof(netorder_arg1)); + uint64_t arg0_no; + uint64_t arg1_no; + uint64_t arg2_no; + memcpy(&arg0_no, buffer + sizeof(uint8_t) + 0 * sizeof(uint64_t), sizeof(arg0_no)); + memcpy(&arg1_no, buffer + sizeof(uint8_t) + 1 * sizeof(uint64_t), sizeof(arg1_no)); + memcpy(&arg2_no, buffer + sizeof(uint8_t) + 2 * sizeof(uint64_t), sizeof(arg2_no)); - out_cmd->arg0 = be64toh(netorder_arg0); - out_cmd->arg1 = be64toh(netorder_arg1); + out_cmd->arg0 = be64toh(arg0_no); + out_cmd->arg1 = be64toh(arg1_no); + out_cmd->arg2 = be64toh(arg2_no); return true; } @@ -327,12 +349,14 @@ static bool dap_send_response(const dap_response_t response) { uint8_t buffer[OUTBOUND_FRAME_SIZE] = { 0 }; buffer[0] = ResponseCategory; - buffer[1] = (uint8_t)response.type; + buffer[1] = (uint8_t) response.type; - const uint64_t netorder_arg0 = htobe64(response.arg0); - const uint64_t netorder_arg1 = htobe64(response.arg1); - memcpy(buffer + 2, &netorder_arg0, sizeof(netorder_arg0)); - memcpy(buffer + 2 + sizeof(netorder_arg0), &netorder_arg1, sizeof(netorder_arg1)); + const uint64_t arg0_no = htobe64(response.arg0); + const uint64_t arg1_no = htobe64(response.arg1); + const uint64_t arg2_no = htobe64(response.arg2); + memcpy(buffer + sizeof(uint8_t) + sizeof(uint8_t) + 0 * sizeof(uint64_t), &arg0_no, sizeof(arg0_no)); + memcpy(buffer + sizeof(uint8_t) + sizeof(uint8_t) + 1 * sizeof(uint64_t), &arg1_no, sizeof(arg1_no)); + memcpy(buffer + sizeof(uint8_t) + sizeof(uint8_t) + 2 * sizeof(uint64_t), &arg2_no, sizeof(arg2_no)); return dap_send_bytes(buffer); } @@ -346,20 +370,25 @@ static bool dap_send_event(const dap_event_t event) { uint8_t buffer[OUTBOUND_FRAME_SIZE] = { 0 }; buffer[0] = EventCategory; - buffer[1] = (uint8_t)event.type; + buffer[1] = (uint8_t) event.type; - const uint64_t netorder_arg0 = htobe64(event.arg0); - const uint64_t netorder_arg1 = htobe64(event.arg1); - memcpy(buffer + 2, &netorder_arg0, sizeof(netorder_arg0)); - memcpy(buffer + 2 + sizeof(netorder_arg0), &netorder_arg1, sizeof(netorder_arg1)); + const uint64_t arg0_no = htobe64(event.arg0); + const uint64_t arg1_no = htobe64(event.arg1); + const uint64_t arg2_no = htobe64(event.arg2); + memcpy(buffer + sizeof(uint8_t) + sizeof(uint8_t) + 0 * sizeof(uint64_t), &arg0_no, sizeof(arg0_no)); + memcpy(buffer + sizeof(uint8_t) + sizeof(uint8_t) + 1 * sizeof(uint64_t), &arg1_no, sizeof(arg1_no)); + memcpy(buffer + sizeof(uint8_t) + sizeof(uint8_t) + 2 * sizeof(uint64_t), &arg2_no, sizeof(arg2_no)); return dap_send_bytes(buffer); } +/* DAP state transitions */ + void dap_close(void) { if (connection_fd != -1) { - dap_send_event((dap_event_t){ExitedEvent, 0, 0}); + alert(DAP_PREFIX "Sending terminated event."); + dap_send_event((dap_event_t) { TerminatedEvent, 0x00, 0x00, 0x00 }); if (close(connection_fd) == -1) { io_error("dap_connection_fd"); @@ -369,15 +398,66 @@ void dap_close(void) dap_state = DAP_DONE; machine_halt = true; - alert("DAP connection closed."); + alert(DAP_PREFIX "Connection closed."); +} + +/* Generic DAP helpers */ + +// Get the CPU with the given ID, or respond with an error if no such CPU exists. Returns NULL in this case. +static general_cpu_t *get_cpu_or_respond_error(const uint64_t cpu_id) +{ + general_cpu_t *cpu = get_cpu(cpu_id); + if (cpu == NULL) { + alert(DAP_PREFIX "No such CPU with ID %" PRIu64 "!", cpu_id); + dap_send_response((dap_response_t) { StatusCpuNotFoundError, cpu_id, 0x00, 0x00 }); + } + return cpu; +} + +static bool dap_validate_phys_addr_or_respond_error(const uint64_t address, ptr36_t *out_phys_addr) +{ + // Physical addresses are at most 36 bits + if (address & ~(uint64_t) 0xfffffffff) { + dap_send_response((dap_response_t) { StatusBadAddressError, address, 0x00, 0x00 }); + return false; + } + *out_phys_addr = (ptr36_t) address; + return true; +} + +static bool dap_translate_virt_or_respond_error(const uint64_t cpu_id, const uint64_t virt_addr, ptr36_t *out_phys_addr) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return false; + } + + const ptr64_t virt = { .ptr = virt_addr }; + if (!cpu_convert_addr(cpu, virt, out_phys_addr, false)) { + alert(DAP_PREFIX "Failed to translate virtual address %#0" PRIx64 "!", virt_addr); + dap_send_response((dap_response_t) { StatusBadAddressError, virt_addr, 0x00, 0x00 }); + return false; + } + + return true; } /* Simulator events */ -void dap_event_hit_code_breakpoint(const uint64_t address) +void dap_event_hit_code_breakpoint(const unsigned int cpu_no) { - dap_state = DAP_PAUSED; - dap_send_event((dap_event_t){StoppedAtEvent, address, StoppedReasonBreakpoint}); + const uint64_t address = cpu_get_pc(get_cpu(cpu_no)).ptr; + alert(DAP_PREFIX "Hit code breakpoint at address %#0" PRIx64 ", stopping", address); + dap_state = DAP_PAUSED; // Can't hit BP while paused, so we must have been running + dap_send_event((dap_event_t) { StoppedAtEvent, cpu_no, address, StoppedReasonBreakpoint }); +} + +void dap_event_hit_data_breakpoint(const uint64_t address) +{ + alert(DAP_PREFIX "Hit data breakpoint at address %#0" PRIx64 ", stopping", address); + dap_state = DAP_PAUSED; // Can't hit BP while paused, so we must have been running + // No way to know which CPU caused the data breakpoint, so we use the default one + dap_send_event((dap_event_t) { StoppedAtEvent, cpuno_default, address, StoppedReasonBreakpoint }); } /* Handlers */ @@ -385,52 +465,359 @@ void dap_event_hit_code_breakpoint(const uint64_t address) static void dap_handle_resume(void) { dap_state = DAP_RUNNING; - alert("DAP: Resuming execution."); - dap_send_response((dap_response_t){ StatusOk, 0, 0 }); + alert(DAP_PREFIX "Resuming execution."); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); } static void dap_handle_pause(void) { + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); + + // Avoid duplicate events + if (dap_state == DAP_PAUSED) { + return; + } + + dap_send_event((dap_event_t) { StoppedAtEvent, + cpuno_default, cpu_get_pc(get_cpu(cpuno_default)).ptr, StoppedReasonPaused }); + + alert(DAP_PREFIX "Pausing execution."); dap_state = DAP_PAUSED; - alert("DAP: Pausing execution."); - dap_send_response((dap_response_t){ StatusOk, 0, 0 }); - const ptr64_t address = cpu_get_pc(get_cpu(cpuno_global)); - dap_send_event((dap_event_t){StoppedAtEvent, address.ptr, StoppedReasonPaused}); } -/** Set a DAP code breakpoint */ +static void dap_handle_terminate(void) +{ + alert(DAP_PREFIX "Got terminate request, exiting."); + // Exited event is handled in dap_close(), which is always called at the end. + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); + dap_state = DAP_DONE; +} + +/** Handle set code breakpoint request */ static void dap_handle_set_code_breakpoint(const uint64_t addr) { - const ptr64_t virt_addr = { addr }; - general_cpu_t* cpu = get_cpu(cpuno_global); + const ptr64_t virt_addr = { .ptr = addr }; - if (!cpu_insert_breakpoint(cpu, virt_addr, BREAKPOINT_KIND_DEBUGGER)) { - dap_send_response((dap_response_t){ StatusUnspecifiedError, 0, 0 }); // TODO: more specific err code - return; + general_cpu_t *cpu = NULL; + uint32_t set_cpus = 0; + for_each(cpu_list, cpu, general_cpu_t) + { + set_cpus += cpu_insert_breakpoint(cpu, virt_addr, BREAKPOINT_KIND_DEBUGGER) ? 1 : 0; } - alert("Added DAP code breakpoint at address %#0" PRIx64, virt_addr.ptr); - dap_send_response((dap_response_t){ StatusOk, 0, 0 }); + if (set_cpus > 0) { + alert(DAP_PREFIX "Added code breakpoint at address %#0" PRIx64, virt_addr.ptr); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); + } else { + alert(DAP_PREFIX "Error setting breakpoint at address %#0" PRIx64 ": no such address in any CPU!", virt_addr.ptr); + dap_send_response((dap_response_t) { StatusBadAddressError, addr, 0x00, 0x00 }); + } } -/** Remove a DAP code breakpoint */ +/** Handle remove code breakpoint request */ static void dap_handle_remove_code_breakpoint(const uint64_t addr) { - const ptr64_t virt_addr = { addr }; - general_cpu_t* cpu = get_cpu(cpuno_global); + const ptr64_t virt_addr = { .ptr = addr }; + + general_cpu_t *cpu = NULL; + uint32_t removed_cpus = 0; + for_each(cpu_list, cpu, general_cpu_t) + { + removed_cpus += cpu_remove_breakpoint(cpu, virt_addr, BREAKPOINT_KIND_DEBUGGER) ? 1 : 0; + } + + if (removed_cpus > 0) { + alert(DAP_PREFIX "Removed code breakpoint at address %#0" PRIx64, virt_addr.ptr); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); + } else { + alert(DAP_PREFIX "Error removing breakpoint at address %#0" PRIx64 ": no such address in any CPU!", virt_addr.ptr); + dap_send_response((dap_response_t) { StatusBadAddressError, addr, 0x00, 0x00 }); + } +} + +static void dap_handle_set_data_breakpoint(const uint64_t addr, const uint64_t kind, const uint64_t size) +{ + ptr36_t phys_addr = 0; + if (!dap_validate_phys_addr_or_respond_error(addr, &phys_addr)) { + return; + } + + const access_filter_t bp_kind = kind; + if (bp_kind != ACCESS_FILTER_READ && bp_kind != ACCESS_FILTER_WRITE && bp_kind != ACCESS_FILTER_ANY) { + alert(DAP_PREFIX "Invalid data breakpoint kind %#0" PRIx64 "!", kind); + dap_send_response((dap_response_t) { StatusUnspecifiedError, kind, 0x00, 0x00 }); + return; + } + + physmem_breakpoint_add(phys_addr, size, BREAKPOINT_KIND_DEBUGGER, bp_kind); + alert(DAP_PREFIX "Added data breakpoint at physical address %#0" PRIx64 "of type %" PRIu64 " and size %" PRIu64, phys_addr, kind, size); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_remove_data_breakpoint(const uint64_t addr) +{ + ptr36_t phys_addr = 0; + if (!dap_validate_phys_addr_or_respond_error(addr, &phys_addr)) { + return; + } + + physmem_breakpoint_remove(phys_addr); + alert(DAP_PREFIX "Removed data breakpoint at physical address %#0" PRIx64, phys_addr); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_step(const uint64_t cpu_id, const uint64_t count) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + cpu->steps_left = count; + alert(DAP_PREFIX "Stepping %" PRIu64 " instructions on CPU %" PRIu64 ".", count, cpu_id); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_read_register(const uint64_t cpu_id, const uint64_t reg_id) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + uint64_t reg_value = 0; + if (!cpu_get_reg(cpu, reg_id, ®_value)) { + alert(DAP_PREFIX "Failed to read general register ID %" PRIu64 "!", reg_id); + dap_send_response((dap_response_t) { StatusRegisterNotFoundError, reg_id, 0x00, 0x00 }); + return; + } + + dap_send_response((dap_response_t) { StatusOk, reg_value, 0x00, 0x00 }); +} + +static void dap_handle_write_register(const uint64_t cpu_id, const uint64_t reg_id, const uint64_t value) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + if (!cpu_set_reg(cpu, reg_id, value)) { + alert(DAP_PREFIX "Failed to write general register ID %" PRIu64 "!", reg_id); + dap_send_response((dap_response_t) { StatusRegisterNotFoundError, reg_id, 0x00, 0x00 }); + return; + } + + alert(DAP_PREFIX "Received WriteGeneralRegisterRequest for register ID %" PRIu64 " with value %#0" PRIx64, reg_id, value); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_read_csr(const uint64_t cpu_id, const uint64_t reg_id) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } - if (!cpu_remove_breakpoint(cpu, virt_addr, BREAKPOINT_KIND_DEBUGGER)) { - alert("No such breakpoint!"); - dap_send_response((dap_response_t){ StatusUnspecifiedError, 0, 0 }); // TODO: more specific err code + uint64_t reg_value = 0; + if (!cpu_get_csr(cpu, reg_id, ®_value)) { + alert(DAP_PREFIX "Failed to read CSR %#0" PRIx64 "!", reg_id); + dap_send_response((dap_response_t) { StatusRegisterNotFoundError, reg_id, 0x00, 0x00 }); return; } - alert("Removed DAP code breakpoint from address %#0" PRIx64, virt_addr.ptr); - dap_send_response((dap_response_t){ StatusOk, 0, 0 }); + dap_send_response((dap_response_t) { StatusOk, reg_value, 0x00, 0x00 }); +} + +static void dap_handle_write_csr(const uint64_t cpu_id, const uint64_t reg_id, const uint64_t value) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + if (!cpu_set_csr(cpu, reg_id, value)) { + alert(DAP_PREFIX "Failed to write CSR %#0" PRIx64 "!", reg_id); + dap_send_response((dap_response_t) { StatusRegisterNotFoundError, reg_id, 0x00, 0x00 }); + return; + } + + alert(DAP_PREFIX "Received WriteGeneralRegisterRequest for register ID %" PRIu64 " with value %#0" PRIx64, reg_id, value); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_read_pc(const uint64_t cpu_id) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + const ptr64_t pc = cpu_get_pc(cpu); + dap_send_response((dap_response_t) { StatusOk, pc.ptr, 0x00, 0x00 }); +} + +static void dap_handle_write_pc(const uint64_t cpu_id, const uint64_t value) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + const ptr64_t pc = { value }; + cpu_set_pc(cpu, pc); + + alert(DAP_PREFIX "Received WritePCRequest with value %#0" PRIx64, value); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_read_phys_memory(const uint64_t address) +{ + ptr36_t phys_addr = 0; + if (!dap_validate_phys_addr_or_respond_error(address, &phys_addr)) { + return; + } + + // Read by uint8 to not have to worry about alignment + uint8_t buffer[DAP_ARG_COUNT * sizeof(uint64_t)] = { 0 }; + for (size_t i = 0; i < sizeof(buffer); ++i) { + buffer[i] = physmem_read8(-1, phys_addr + i, false); + } + + dap_response_t response = { .type = StatusOk }; + memcpy(&response.arg0, buffer + 0 * sizeof(uint64_t), sizeof(response.arg0)); + memcpy(&response.arg1, buffer + 1 * sizeof(uint64_t), sizeof(response.arg1)); + memcpy(&response.arg2, buffer + 2 * sizeof(uint64_t), sizeof(response.arg2)); + + dap_send_response(response); +} + +static void dap_handle_write_phys_memory(const uint64_t address, const uint64_t value) +{ + ptr36_t phys_addr = 0; + if (!dap_validate_phys_addr_or_respond_error(address, &phys_addr)) { + return; + } + + // Write by uint8 to not have to worry about alignment + uint8_t buffer[sizeof(uint64_t)] = { 0 }; + memcpy(buffer, &value, sizeof(value)); + for (size_t i = 0; i < sizeof(buffer); ++i) { + const bool success = physmem_write8(-1, phys_addr + i, buffer[i], false); + if (!success) { + alert(DAP_PREFIX "Failed to write to physical address %#0" PRIx64 "!", phys_addr + i); + dap_send_response((dap_response_t) { StatusBadAddressError, address, 0x00, 0x00 }); + return; + } + } + + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_read_virt_memory(const uint64_t cpu_id, const uint64_t address) +{ + ptr36_t phys_addr = 0; + dap_translate_virt_or_respond_error(cpu_id, address, &phys_addr); + dap_handle_read_phys_memory(phys_addr); +} + +static void dap_handle_write_virt_memory(const uint64_t cpu_id, const uint64_t address, const uint64_t value) +{ + ptr36_t phys_addr = 0; + dap_translate_virt_or_respond_error(cpu_id, address, &phys_addr); + dap_handle_write_phys_memory(phys_addr, value); +} + +static void dap_handle_translate_address(const uint64_t cpu_id, const uint64_t address) +{ + ptr36_t phys_addr = 0; + if (dap_translate_virt_or_respond_error(cpu_id, address, &phys_addr)) { + dap_send_response((dap_response_t) { StatusOk, phys_addr, 0x00, 0x00 }); + } +} + +static void dap_handle_raise_interrupt(const uint64_t cpu_id, const uint64_t interrupt_id) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + cpu_interrupt_up(cpu, interrupt_id); + alert(DAP_PREFIX "Raised interrupt ID %" PRIu64 " on CPU %" PRIu64 ".", interrupt_id, cpu_id); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_clear_interrupt(const uint64_t cpu_id, const uint64_t interrupt_id) +{ + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + cpu_interrupt_down(cpu, interrupt_id); + alert(DAP_PREFIX "Cleared interrupt ID %" PRIu64 " on CPU %" PRIu64 ".", interrupt_id, cpu_id); + dap_send_response((dap_response_t) { StatusOk, 0x00, 0x00, 0x00 }); +} + +static void dap_handle_get_config(void) +{ + uint64_t cpu_count = 0; + general_cpu_t *cpu; + for_each(cpu_list, cpu, general_cpu_t) + { + ++cpu_count; + } + + alert(DAP_PREFIX "Received GetConfigRequest, reporting %" PRIu64 " CPUs.", cpu_count); + dap_send_response((dap_response_t) { StatusOk, cpu_count, 0x00, 0x00 }); +} + +static void dap_handle_get_cpu_info(const uint64_t cpu_id) +{ + alert(DAP_PREFIX "Received GetCpuInfoRequest for CPU ID %" PRIu64 "", cpu_id); + general_cpu_t *cpu = get_cpu_or_respond_error(cpu_id); + if (cpu == NULL) { + return; + } + + uint64_t arch_val = 0x00; + switch (cpu_get_arch(cpu)) { + case CpuArchMips: + arch_val = 0x01; + break; + case CpuArchRiscV32: + arch_val = 0x02; + break; + case CpuArchRiscV64: + arch_val = 0x03; + break; + default: + alert(DAP_PREFIX "CPU with ID %" PRIu64 " has unknown architecture %u!", cpu_id, cpu->type->arch); + arch_val = 0xFF; + } + + dap_send_response((dap_response_t) { StatusOk, arch_val, 0x00, 0x00 }); +} + +static void dap_check_step(void) +{ + general_cpu_t *cpu = NULL; + for_each(cpu_list, cpu, general_cpu_t) + { + if (cpu->steps_left > 0 && --cpu->steps_left == 0) { + alert(DAP_PREFIX "Finished stepping on CPU %u", cpu->cpuno); + dap_send_event((dap_event_t) { StoppedAtEvent, + cpu->cpuno, cpu_get_pc(cpu).ptr, StoppedReasonStep }); + dap_state = DAP_PAUSED; + } + } } void dap_process(void) { + dap_check_step(); + dap_request_t request = { 0 }; while (dap_receive_request(&request, dap_state == DAP_PAUSED)) { @@ -441,19 +828,73 @@ void dap_process(void) case PauseRequest: dap_handle_pause(); continue; - case StopRequest: - // Response is handled in dap_close which is always called at the end. - dap_state = DAP_DONE; + case TerminateRequest: + dap_handle_terminate(); return; + case StepRequest: + dap_handle_step(request.arg0, request.arg1); + continue; case SetCodeBreakpointRequest: dap_handle_set_code_breakpoint(request.arg0); continue; case RemoveCodeBreakpointRequest: dap_handle_remove_code_breakpoint(request.arg0); continue; + case SetDataBreakpointRequest: + dap_handle_set_data_breakpoint(request.arg0, request.arg1, request.arg2); + continue; + case RemoveDataBreakpointRequest: + dap_handle_remove_data_breakpoint(request.arg0); + continue; + case ReadGeneralRegisterRequest: + dap_handle_read_register(request.arg0, request.arg1); + continue; + case WriteGeneralRegisterRequest: + dap_handle_write_register(request.arg0, request.arg1, request.arg2); + continue; + case ReadCsrRequest: + dap_handle_read_csr(request.arg0, request.arg1); + continue; + case WriteCsrRequest: + dap_handle_write_csr(request.arg0, request.arg1, request.arg2); + continue; + case ReadPCRequest: + dap_handle_read_pc(request.arg0); + continue; + case WritePCRequest: + dap_handle_write_pc(request.arg0, request.arg1); + continue; + case ReadPhysMemoryRequest: + dap_handle_read_phys_memory(request.arg0); + continue; + case WritePhysMemoryRequest: + dap_handle_write_phys_memory(request.arg0, request.arg1); + continue; + case ReadVirtMemoryRequest: + dap_handle_read_virt_memory(request.arg0, request.arg1); + continue; + case WriteVirtMemoryRequest: + dap_handle_write_virt_memory(request.arg0, request.arg1, request.arg2); + continue; + case TranslateAddressRequest: + dap_handle_translate_address(request.arg0, request.arg1); + continue; + case RaiseInterruptRequest: + dap_handle_raise_interrupt(request.arg0, request.arg1); + continue; + case ClearInterruptRequest: + dap_handle_clear_interrupt(request.arg0, request.arg1); + continue; + case GetConfigRequest: + dap_handle_get_config(); + continue; + case GetCpuInfoRequest: + dap_handle_get_cpu_info(request.arg0); + continue; + default: - alert("Unknown DAP request type %u.", request.type); - dap_send_response((dap_response_t){ StatusUnsupportedRequestError, 0, 0 }); + alert(DAP_PREFIX "Unknown request type %u.", request.type); + dap_send_response((dap_response_t) { StatusUnsupportedRequestError, request.type, 0x00, 0x00 }); } } } diff --git a/src/debug/dap.h b/src/debug/dap.h index 48766ede..2d1e83d7 100644 --- a/src/debug/dap.h +++ b/src/debug/dap.h @@ -26,6 +26,7 @@ extern void dap_process(void); */ extern void dap_close(void); -extern void dap_event_hit_code_breakpoint(uint64_t address); +extern void dap_event_hit_code_breakpoint(unsigned int cpu_no); +extern void dap_event_hit_data_breakpoint(uint64_t address); #endif // MSIM_DAP_H diff --git a/src/main.c b/src/main.c index 92ce3dcb..cb40db2f 100644 --- a/src/main.c +++ b/src/main.c @@ -306,6 +306,7 @@ static void dap_startup(void) return; } + // Start in PAUSED state dap_state = dap_init() ? DAP_PAUSED : DAP_DONE; } diff --git a/tests/system/common.bash b/tests/system/common.bash index 412cad28..dc21b02e 100644 --- a/tests/system/common.bash +++ b/tests/system/common.bash @@ -132,3 +132,56 @@ msim_run_code() { } | fail fi } + +msim_dap_run() { + local test_dir="$( dirname "$BATS_TEST_FILENAME" )/$1" + local scenario="$2" + + # Find a free port + local port + port="$( python3 -c "import socket; s=socket.socket(); s.bind(('',0)); p=s.getsockname()[1]; s.close(); print(p)" )" + + sed "s#\"boot.bin\"#\"$test_dir/boot.bin\"#" <"$test_dir/msim.conf" >"$MSIM_TEST_TMPDIR/msim.conf" + + { + echo + echo "# MSIM configuration msim.conf" + sed 's:.*:# | &:' "$MSIM_TEST_TMPDIR/msim.conf" + } >&2 + + # Start MSIM in background with DAP + "$MSIM" -c "$MSIM_TEST_TMPDIR/msim.conf" "--dap=$port" >"$MSIM_TEST_TMPDIR/msim.stdout" 2>&1 & + local msim_pid=$! + + # Wait until MSIM is listening + sleep 0.1 + + local scenario_status=0 + python3 "$test_dir/$scenario" "$port" 2>&1 | tee "$MSIM_TEST_TMPDIR/scenario.stdout" >&2 + scenario_status=${PIPESTATUS[0]} + + # If the scenario failed, kill MSIM explicitly in case there's a bug and it doesn't exit on its own + if (( scenario_status != 0 )); then + kill "$msim_pid" 2>/dev/null + fi + + wait "$msim_pid" + local msim_status=$? + + { + echo + echo "# Scenario output" + sed 's:.*:# | &:' "$MSIM_TEST_TMPDIR/scenario.stdout" + echo + echo "# MSIM output" + sed 's:.*:# | &:' "$MSIM_TEST_TMPDIR/msim.stdout" + } >&2 + + if [ "$scenario_status" -ne 0 ]; then + fail "Adapter scenario failed with exit $scenario_status" + fi + + if [ "$msim_status" -ne 0 ]; then + fail "MSIM exited with status $msim_status" + fi +} diff --git a/tests/system/dap-simple-mips32/base.py b/tests/system/dap-simple-mips32/base.py new file mode 100644 index 00000000..56783719 --- /dev/null +++ b/tests/system/dap-simple-mips32/base.py @@ -0,0 +1,26 @@ +import sys + +# region | Hack to import from parent directory, otherwise we would have to install the library as package +try: + from ..dap_lib import * # noqa # This fails at runtime but makes static analysis pass +except ImportError: + import os + + sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) + from dap_lib import * # type: ignore # IDEs don't like this, but it works +# endregion + +# MSIM Configuration +RST_VEC_VIRT = 0xffffffffbfc00000 # Reset vector virtual address +RST_VEC_PHYS = 0x1fc00000 # Reset vector physical address +PROGRAM_LEN = 8 # Length of the test program in instructions, not including the halt instruction +INSTR_LEN = 4 # Length of a single instruction in bytes +REG_COUNT = 32 # Number of general-purpose registers in RISC-V +NOP_INSTR = 0x00000000 # NOP instruction in MIPS32 (sll $zero, $zero, 0) + +DEFAULT_CPU = 0 # Default CPU used in DAP + +at = make_at(RST_VEC_VIRT, INSTR_LEN) +"""get virtual address by instruction number""" + +at_phys = make_at(RST_VEC_PHYS, INSTR_LEN) diff --git a/tests/system/dap-simple-mips32/boot.bin b/tests/system/dap-simple-mips32/boot.bin new file mode 100644 index 0000000000000000000000000000000000000000..a518fc5d50e3e86b9fbf2cb749d013b368a5d578 GIT binary patch literal 36 NcmZQzzz;Nl(f|O&04V?f literal 0 HcmV?d00001 diff --git a/tests/system/dap-simple-mips32/main.S b/tests/system/dap-simple-mips32/main.S new file mode 100644 index 00000000..7c04dfc1 --- /dev/null +++ b/tests/system/dap-simple-mips32/main.S @@ -0,0 +1,11 @@ +.text +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 +nop # sll x0, x0, 0 + +.word 0x00000028 diff --git a/tests/system/dap-simple-mips32/msim.conf b/tests/system/dap-simple-mips32/msim.conf new file mode 100644 index 00000000..90e685eb --- /dev/null +++ b/tests/system/dap-simple-mips32/msim.conf @@ -0,0 +1,4 @@ +add dr4kcpu cpu0 +add rom boot 0x1FC00000 +boot generic 4K +boot load "boot.bin" diff --git a/tests/system/dap-simple-mips32/scenario_bad_req.py b/tests/system/dap-simple-mips32/scenario_bad_req.py new file mode 100644 index 00000000..46f44513 --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_bad_req.py @@ -0,0 +1,10 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +adp.send(0xff).expect_response(StatusUnsupportedRequestError, arg0=0xff) +adp.send(0xf0).expect_response(StatusUnsupportedRequestError, arg0=0xf0) +adp.send(0xff).expect_response(StatusUnsupportedRequestError, arg0=0xff) + +adp.send(TerminateRequest).expect_response().expect_event(TerminatedEvent) +adp.close() \ No newline at end of file diff --git a/tests/system/dap-simple-mips32/scenario_breakpoint.py b/tests/system/dap-simple-mips32/scenario_breakpoint.py new file mode 100644 index 00000000..e7258f82 --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_breakpoint.py @@ -0,0 +1,27 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +# Set breakpoints at the end, we will remove later +adp.send(SetCodeBreakpointRequest, arg0=at(7)).expect_response() +adp.send(SetCodeBreakpointRequest, arg0=at(8)).expect_response() + +# Remove a non-existent breakpoint +adp.send(RemoveCodeBreakpointRequest, arg0=at(6)).expect_response() + +adp.send(SetCodeBreakpointRequest, arg0=at(3)).expect_response() +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at(3), arg2=StoppedReasonBreakpoint) + +adp.send(SetCodeBreakpointRequest, arg0=at(0)).expect_response() # BP should not be hit + +adp.send(SetCodeBreakpointRequest, arg0=at(5)).expect_response() +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at(5), arg2=StoppedReasonBreakpoint) + +# Remove the breakpoints we set at the start, should not get hit +adp.send(RemoveCodeBreakpointRequest, arg0=at(7)).expect_response() +adp.send(RemoveCodeBreakpointRequest, arg0=at(8)).expect_response() + +adp.send(ResumeRequest).expect_response().expect_event(TerminatedEvent) +adp.close() diff --git a/tests/system/dap-simple-mips32/scenario_cpu_info.py b/tests/system/dap-simple-mips32/scenario_cpu_info.py new file mode 100644 index 00000000..1c2528ed --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_cpu_info.py @@ -0,0 +1,9 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +adp.send(GetCpuInfoRequest, arg0=DEFAULT_CPU).expect_response(StatusOk, arg0=0x01) # 0x01 is MIPS R4000 identifier +adp.send(GetConfigRequest).expect_response(StatusOk, arg0=1) # Expect 1 CPU in the system + +adp.send(TerminateRequest).expect_response().expect_event(TerminatedEvent) +adp.close() \ No newline at end of file diff --git a/tests/system/dap-simple-mips32/scenario_csr.py b/tests/system/dap-simple-mips32/scenario_csr.py new file mode 100644 index 00000000..5d864310 --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_csr.py @@ -0,0 +1,32 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +BADVADDR = 0x08 +ENTRYHI = 0x0a +CAUSE = 0x0d +EPC = 0x0e +CONFIG = 0x10 +LLADDR = 0x11 + +for csr in (BADVADDR, ENTRYHI, CAUSE, EPC, CONFIG, LLADDR): + adp.send(WriteCsrRequest, arg0=DEFAULT_CPU, arg1=csr, arg2=0x00).expect_response(StatusOk) + adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=csr).expect_response(StatusOk, arg0=0x00) + adp.send(WriteCsrRequest, arg0=DEFAULT_CPU, arg1=csr, arg2=0x12345678).expect_response(StatusOk) + adp.send(WriteCsrRequest, arg0=DEFAULT_CPU, arg1=csr, arg2=0x12345678).expect_response(StatusOk) # Write twice + adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=csr).expect_response(StatusOk, arg0=0x12345678) + +# Step a bit and check that the PC is updated, but registers are unchanged +adp.send(StepRequest, arg0=DEFAULT_CPU, arg1=2).expect_response(StatusOk) +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at(2), arg2=StoppedReasonStep) +adp.send(StepRequest, arg0=DEFAULT_CPU, arg1=2).expect_response(StatusOk) +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at(4), arg2=StoppedReasonStep) + +for csr in (BADVADDR, ENTRYHI, CAUSE, EPC, CONFIG, LLADDR): + adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=csr).expect_response(StatusOk, + arg0=0x12345678) # CSRs should be unchanged + +adp.send(TerminateRequest).expect_response().expect_event(TerminatedEvent) +adp.close() \ No newline at end of file diff --git a/tests/system/dap-simple-mips32/scenario_data_breakpoint.py b/tests/system/dap-simple-mips32/scenario_data_breakpoint.py new file mode 100644 index 00000000..8e36734b --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_data_breakpoint.py @@ -0,0 +1,39 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +READ = 0x01 +SIZE = INSTR_LEN + +# Set breakpoints at the end of the program, we will remove later +adp.send(SetDataBreakpointRequest, arg0=at_phys(7), arg1=READ, arg2=SIZE).expect_response() +adp.send(SetDataBreakpointRequest, arg0=at_phys(8), arg1=READ, arg2=SIZE).expect_response() + +# Remove a non-existent breakpoint +adp.send(RemoveDataBreakpointRequest, arg0=at_phys(6), arg1=READ, arg2=SIZE).expect_response() + +# Set a breakpoint, run the program, and check that we stop at the right place +adp.send(SetDataBreakpointRequest, arg0=at_phys(3), arg1=READ, arg2=SIZE).expect_response() +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at_phys(3), arg2=StoppedReasonBreakpoint) +# the instruction fetched is still executed before we stop +adp.send(ReadPCRequest, arg0=DEFAULT_CPU).expect_response(StatusOk, arg0=at(4)) # we get back virtual address + +# Set a breakpoint behind current PC, should never get hit +adp.send(SetDataBreakpointRequest, arg0=at_phys(0), arg1=READ, arg2=SIZE).expect_response() + +# Set two breakpoints in front of PC +adp.send(SetDataBreakpointRequest, arg0=at_phys(5), arg1=READ, arg2=SIZE).expect_response() +adp.send(SetDataBreakpointRequest, arg0=at_phys(6), arg1=READ, arg2=SIZE).expect_response() + +# Remove the first breakpoint, should not get hit +adp.send(RemoveDataBreakpointRequest, arg0=at_phys(5)).expect_response() +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at_phys(6), arg2=StoppedReasonBreakpoint) + +# Remove the breakpoints we set at the start, should not get hit +adp.send(RemoveDataBreakpointRequest, arg0=at_phys(7)).expect_response() +adp.send(RemoveDataBreakpointRequest, arg0=at_phys(8)).expect_response() + +adp.send(ResumeRequest).expect_response().expect_event(TerminatedEvent) +adp.close() diff --git a/tests/system/dap-simple-mips32/scenario_interrupts.py b/tests/system/dap-simple-mips32/scenario_interrupts.py new file mode 100644 index 00000000..cfe860c7 --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_interrupts.py @@ -0,0 +1,56 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +STATUS = 12 +CAUSE = 13 +EPC = 14 + +INT_LINE = 0 # IP0 / IM0 (software interrupt line 0) +IP_MASK = 1 << (8 + INT_LINE) # bit 8 in Cause +IM_MASK = IP_MASK # bit 8 in Status +IE_MASK = 1 # bit 0 in Status +EXL_MASK = 0x2 # bit 1 in Status +ERL_MASK = 0x4 # bit 2 in Status + +# BEV=1 (reset default): EXCEPTION_BOOT_BASE_ADDRESS(0xBFC00200) + EXCEPTION_OFFSET(0x180) +HANDLER = 0xFFFFFFFFBFC00380 + +# Enable interrupts: set IE + IM0, clear EXL + ERL (BEV stays 1) +adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=STATUS) +_, _, status_val, _, _ = adp.receive() +status_new = (status_val | IE_MASK | IM_MASK) & ~(EXL_MASK | ERL_MASK) +adp.send(WriteCsrRequest, arg0=DEFAULT_CPU, arg1=STATUS, arg2=status_new).expect_response() + +# Move a bit further +adp.send(StepRequest, arg0=DEFAULT_CPU, arg1=4).expect_response() +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=at(4), arg2=StoppedReasonStep) + +# Set breakpoint at handler to stop when we jump there +adp.send(SetCodeBreakpointRequest, arg0=HANDLER).expect_response() + +# Raise software interrupt line 0 +adp.send(RaiseInterruptRequest, arg0=DEFAULT_CPU, arg1=INT_LINE).expect_response() +adp.send(ResumeRequest).expect_response() +adp.expect_event(StoppedAtEvent, arg0=DEFAULT_CPU, arg1=HANDLER, arg2=StoppedReasonBreakpoint) + +# Check that we jumped to the handler +adp.send(ReadPCRequest, arg0=DEFAULT_CPU).expect_response(StatusOk, arg0=HANDLER) + +# IP0 should be pending in Cause +adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=CAUSE) +_, _, cause_val, _, _ = adp.receive() +assert cause_val & IP_MASK != 0, f"IP0 should be pending in Cause: {hex(cause_val)}" + +# EPC points to the instruction that was about to execute when interrupt was taken +adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=EPC).expect_response(StatusOk, arg0=at(5)) + +# Clear the interrupt, check not pending +adp.send(ClearInterruptRequest, arg0=DEFAULT_CPU, arg1=INT_LINE).expect_response() +adp.send(ReadCsrRequest, arg0=DEFAULT_CPU, arg1=CAUSE) +_, _, cause_val, _, _ = adp.receive() +assert cause_val & IP_MASK == 0, f"IP0 should not be pending: {hex(cause_val)}" + +adp.send(TerminateRequest).expect_response().expect_event(TerminatedEvent) +adp.close() diff --git a/tests/system/dap-simple-mips32/scenario_mem.py b/tests/system/dap-simple-mips32/scenario_mem.py new file mode 100644 index 00000000..ae26a4b3 --- /dev/null +++ b/tests/system/dap-simple-mips32/scenario_mem.py @@ -0,0 +1,51 @@ +from base import * + +adp = Adapter(int(sys.argv[1])) + +nop = struct.pack("B3Q', req_type, arg0, arg1, arg2) + self.sock.sendall(packed) + return self + + def receive(self): + data = self._recv_exact(INBOUND_FRAME_SIZE) + return struct.unpack('>BB3Q', data) + + def _recv_exact(self, num_bytes): + data = b'' + while len(data) < num_bytes: + chunk = self.sock.recv(num_bytes - len(data)) + if not chunk: + raise EOFError("Connection to MSIM closed") + data += chunk + return data + + def close(self): + self.sock.close() + + def expect_response(self, status=StatusOk, arg0=0x00, arg1=0x00, arg2=0x00): + category, kind, a0, a1, a2 = self.receive() + assert category == ResponseCategory, f"Got category {hex(category)}, but expected {hex(ResponseCategory)}" + assert kind == status, f"Got response status {hex(kind)}, but expected {hex(status)}" + assert a0 == arg0, f"Got response arg0 {hex(a0)}, but expected {hex(arg0)}" + assert a1 == arg1, f"Got response arg1 {hex(a1)}, but expected {hex(arg1)}" + assert a2 == arg2, f"Got response arg2 {hex(a2)}, but expected {hex(arg2)}" + return self + + def expect_event(self, event_type, arg0=0x00, arg1=0x00, arg2=0x00): + category, kind, a0, a1, a2 = self.receive() + assert category == EventCategory, f"Got category {hex(category)}, but expected {hex(EventCategory)}" + assert kind == event_type, f"Got event type {hex(kind)}, but expected {hex(event_type)}" + assert a0 == arg0, f"Got event arg0 {hex(a0)}, but expected {hex(arg0)}" + assert a1 == arg1, f"Got event arg1 {hex(a1)}, but expected {hex(arg1)}" + assert a2 == arg2, f"Got event arg2 {hex(a2)}, but expected {hex(arg2)}" + return self + +def make_at(reset_vector, instr_len): + """ Compute the address of the nth instruction in the test program """ + def at(instruction_number): + return reset_vector + instruction_number * instr_len + return at