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' 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/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" diff --git a/src/debug/breakpoint.c b/src/debug/breakpoint.c index fd6b5390..52f983cc 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" @@ -190,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"); @@ -224,19 +223,24 @@ 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++; 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) { + dap_event_hit_code_breakpoint(cpu_no); + } else if (remote_gdb) { + gdb_handle_event(GDB_EVENT_BREAKPOINT); + } break; default: die(ERR_INTERN, "Unexpected breakpoint kind"); @@ -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; } } @@ -298,38 +303,22 @@ 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; - } - } - - if (hit) { - return hit; - } - - 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_cpu(cpu->bps, cpu)) { hit = true; } } diff --git a/src/debug/dap.c b/src/debug/dap.c index 3fb69679..709c79de 100644 --- a/src/debug/dap.c +++ b/src/debug/dap.c @@ -1,36 +1,171 @@ #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" -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; +// be64toh is not on macOS +#if defined(__APPLE__) +#include +#define be64toh(x) OSSwapBigToHostInt64(x) +#define htobe64(x) OSSwapHostToBigInt64(x) +#endif -typedef enum dap_command_type { - NO_OP = 0, - BREAKPOINT = 1, - CONTINUE = 2, -} dap_command_type_t; +#define DAP_PREFIX "[DAP] " +#define DAP_ARG_COUNT 3 -typedef struct __attribute__((__packed__)) dap_command { - uint8_t type; - uint32_t addr; -} dap_command_t; +static int connection_fd = -1; +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 terminate execution and exit the simulator. */ + TerminateRequest = 0x03, + /** Request to step `arg0=cpu` by `arg1=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 physical memory data breakpoint at `arg0=address` with `arg1=kind` of `arg2=size`. */ + SetDataBreakpointRequest = 0x07, + /** Request to remove a physical memory data breakpoint at `arg0=address`. */ + RemoveDataBreakpointRequest = 0x08, + + // Register requests + /** Request to read the value of register `arg1=id` in `arg0=cpu`. */ + ReadGeneralRegisterRequest = 0x09, + /** 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) `arg1=id` in `arg0=cpu`. */ + ReadCsrRequest = 0x0B, + /** 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 of `arg0=cpu`. */ + ReadPCRequest = 0x0D, + /** Request to write `arg1=value` to the program counter of `arg0=cpu`. */ + 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, + + /** 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 { + /** 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 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 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; + +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; + uint64_t arg2; +} dap_request_t; + +/** Structure for DAP responses */ +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 */ +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 = 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 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 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]; +static ssize_t frame_buffered_bytes = 0; // Number of bytes currently buffered in frame buffer bool dap_init(void) { @@ -61,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"); } @@ -76,158 +211,690 @@ bool dap_init(void) return false; } - alert("DAP connected."); + alert(DAP_PREFIX "Connected."); 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."); -} +/* MSIM DAP protocol implementation */ -/** 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 ? MSG_WAITALL : 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) { - uint8_t buffer[sizeof(dap_command_t)] = { 0 }; - if (!dap_receive_bytes(buffer, sizeof(dap_command_t))) { + 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[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 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(arg0_no); + out_cmd->arg1 = be64toh(arg1_no); + out_cmd->arg2 = be64toh(arg2_no); 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) +{ + uint8_t buffer[OUTBOUND_FRAME_SIZE] = { 0 }; + buffer[0] = ResponseCategory; + buffer[1] = (uint8_t) response.type; + + 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); +} + +/** 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 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) { - ptr64_t virt_address = { 0 }; - virt_address.lo = addr; - rv_cpu_t *cpu = get_cpu(cpuno_global)->data; + if (connection_fd != -1) { + alert(DAP_PREFIX "Sending terminated event."); + dap_send_event((dap_event_t) { TerminatedEvent, 0x00, 0x00, 0x00 }); - // TODO: Register as DAP debugger breakpoint - const breakpoint_t *breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_SIMULATOR); - if (breakpoint != NULL) { + if (close(connection_fd) == -1) { + io_error("dap_connection_fd"); + } + connection_fd = -1; + } + + dap_state = DAP_DONE; + machine_halt = true; + 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 unsigned int cpu_no) +{ + 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 */ + +static void dap_handle_resume(void) +{ + dap_state = DAP_RUNNING; + 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; } - breakpoint_t *inserted_breakpoint = breakpoint_init(virt_address, BREAKPOINT_KIND_SIMULATOR); - list_append(&cpu->bps, &inserted_breakpoint->item); - alert("Added DAP breakpoint at address 0x%x.", addr); + 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; } -/** Remove a DAP breakpoint */ -static void dap_breakpoint_remove(const uint32_t addr) +static void dap_handle_terminate(void) { - alert("Removing DAP breakpoint from address 0x%x.", addr); + 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 = { .ptr = addr }; - ptr64_t virt_address; - virt_address.ptr = UINT64_C(0xffffffff00000000) | addr; - r4k_cpu_t *cpu = get_cpu(cpuno_global)->data; + 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; + } + + 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 }); + } +} - breakpoint_t *breakpoint = breakpoint_find_by_address(cpu->bps, virt_address, BREAKPOINT_FILTER_SIMULATOR); - if (breakpoint != NULL) { +/** Handle remove code breakpoint request */ +static void dap_handle_remove_code_breakpoint(const uint64_t addr) +{ + 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; } - list_remove(&cpu->bps, &breakpoint->item); - safe_free(breakpoint) + 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; + } + + 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; + } + + 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_command_t command = { 0 }; + dap_check_step(); - while (dap_receive_command(&command)) { - switch (command.type) { - case NO_OP: + dap_request_t request = { 0 }; + + 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 TerminateRequest: + dap_handle_terminate(); + return; + case StepRequest: + dap_handle_step(request.arg0, request.arg1); continue; - default: - alert("Unknown DAP command type %u.", command.type); + 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(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 37c0d0ca..2d1e83d7 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,18 @@ 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(unsigned int cpu_no); +extern void dap_event_hit_data_breakpoint(uint64_t address); + #endif // MSIM_DAP_H 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 9ca5650d..6ca86f24 100644 --- a/src/device/cpu/general_cpu.c +++ b/src/device/cpu/general_cpu.c @@ -11,11 +11,22 @@ #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 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; + gen_cpu->steps_left = 0; + list_init(&gen_cpu->bps); + return gen_cpu; +} + general_cpu_t *get_cpu(unsigned int no) { general_cpu_t *cpu; @@ -91,19 +102,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; } /** @@ -132,6 +188,46 @@ 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); +} + +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) { + 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) { @@ -154,3 +250,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 13861807..9d690de9 100644 --- a/src/device/cpu/general_cpu.h +++ b/src/device/cpu/general_cpu.h @@ -19,42 +19,80 @@ /** 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 */ 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 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 */ 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" */ 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_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; + cpu_arch_t arch; /** The architecture of the CPU */ } 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 + uint64_t steps_left; // Number of instruction steps to execute before pausing. 0 means infinite. } general_cpu_t; +/** List of all CPUs */ +extern list_t cpu_list; + +/** + * @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(unsigned int id, void *cpu, const cpu_ops_t *type); + /** * @brief Retrieves the general_cpu_t structure based on the given cpu id */ @@ -89,8 +127,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 @@ -109,6 +179,47 @@ 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); + +/** + * @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); /** @@ -121,4 +232,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/cpu/mips_r4000/cpu.c b/src/device/cpu/mips_r4000/cpu.c index e147877e..7b570342 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 @@ -3062,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); 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/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) 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"); } diff --git a/src/device/dr4kcpu.c b/src/device/dr4kcpu.c index c3809666..c99da585 100644 --- a/src/device/dr4kcpu.c +++ b/src/device/dr4kcpu.c @@ -25,19 +25,85 @@ #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; } +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 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; +} + 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_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 + .sc_access = (sc_access_func_t) r4k_sc_access, + + .arch = CpuArchMips, }; /** Initialization @@ -54,10 +120,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); @@ -266,27 +329,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 @@ -294,7 +341,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"); @@ -317,32 +364,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 1b65d500..da209611 100644 --- a/src/device/drv64cpu.c +++ b/src/device/drv64cpu.c @@ -27,12 +27,69 @@ #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; } +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 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 }; +} + static void rv64_set_pc_wrapper(void *cpu, ptr64_t addr) { // use all 64 bits from addr @@ -43,11 +100,19 @@ 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_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 + .sc_access = (sc_access_func_t) rv64_sc_access, + + .arch = CpuArchRiscV64, }; /** @@ -64,10 +129,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 350adeca..4db8deaa 100644 --- a/src/device/drvcpu.c +++ b/src/device/drvcpu.c @@ -26,12 +26,75 @@ #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; } +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 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 }; +} + static void rv32_set_pc_wrapper(void *cpu, ptr64_t addr) { // use only low 32-bits from addr @@ -42,11 +105,19 @@ 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_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 + .sc_access = (sc_access_func_t) rv32_sc_access, + + .arch = CpuArchRiscV32, }; /** @@ -64,10 +135,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); diff --git a/src/input.c b/src/input.c index c6e5fdfd..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); @@ -186,5 +189,7 @@ int input_is_terminal(void) void input_end(void) { - clear_history(); + if (input_term) { + clear_history(); + } } diff --git a/src/main.c b/src/main.c index bd290f00..cb40db2f 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,18 @@ 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; - } + // Start in PAUSED state + dap_state = dap_init() ? DAP_PAUSED : DAP_DONE; } /** Run 4096 machine cycles @@ -371,19 +369,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 +454,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/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; } 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; 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 00000000..a518fc5d Binary files /dev/null and b/tests/system/dap-simple-mips32/boot.bin differ 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