From a6bdcc6c498a54fb4a4637804f762541887b0b9c Mon Sep 17 00:00:00 2001 From: Max Wash Date: Sat, 6 Jun 2026 17:54:20 +0100 Subject: [PATCH] toolchain: add new debugging tool using magenta_debug --- toolchain/CMakeLists.txt | 3 +- toolchain/mxdbg/CMakeLists.txt | 3 + toolchain/mxdbg/client/CMakeLists.txt | 11 + toolchain/mxdbg/client/attach.c | 15 + toolchain/mxdbg/client/breakpoint.c | 16 ++ toolchain/mxdbg/client/command.c | 74 +++++ toolchain/mxdbg/client/command.h | 33 +++ toolchain/mxdbg/client/context.c | 228 +++++++++++++++ toolchain/mxdbg/client/context.h | 54 ++++ toolchain/mxdbg/client/continue.c | 27 ++ toolchain/mxdbg/client/event.c | 42 +++ toolchain/mxdbg/client/event.h | 9 + toolchain/mxdbg/client/info.c | 37 +++ toolchain/mxdbg/client/info/kernel.c | 38 +++ toolchain/mxdbg/client/line-ed/buffer.c | 44 +++ toolchain/mxdbg/client/line-ed/buffer.h | 17 ++ toolchain/mxdbg/client/line-ed/cursor.c | 26 ++ toolchain/mxdbg/client/line-ed/cursor.h | 9 + toolchain/mxdbg/client/line-ed/history.c | 87 ++++++ toolchain/mxdbg/client/line-ed/history.h | 12 + toolchain/mxdbg/client/line-ed/hook.c | 41 +++ toolchain/mxdbg/client/line-ed/hook.h | 16 ++ toolchain/mxdbg/client/line-ed/input.c | 232 +++++++++++++++ toolchain/mxdbg/client/line-ed/input.h | 15 + toolchain/mxdbg/client/line-ed/line-ed.c | 348 +++++++++++++++++++++++ toolchain/mxdbg/client/line-ed/line-ed.h | 111 ++++++++ toolchain/mxdbg/client/line-ed/prompt.c | 27 ++ toolchain/mxdbg/client/line-ed/prompt.h | 12 + toolchain/mxdbg/client/line-ed/refresh.c | 267 +++++++++++++++++ toolchain/mxdbg/client/line-ed/refresh.h | 33 +++ toolchain/mxdbg/client/main.c | 105 +++++++ toolchain/mxdbg/client/memory.c | 19 ++ toolchain/mxdbg/client/packet.c | 15 + toolchain/mxdbg/client/packet.h | 16 ++ toolchain/mxdbg/client/quit.c | 18 ++ toolchain/mxdbg/client/repl.c | 130 +++++++++ toolchain/mxdbg/client/repl.h | 10 + toolchain/mxdbg/lib/CMakeLists.txt | 4 + toolchain/mxdbg/server/CMakeLists.txt | 4 + toolchain/mxdbg/server/main.c | 4 + 40 files changed, 2211 insertions(+), 1 deletion(-) create mode 100644 toolchain/mxdbg/CMakeLists.txt create mode 100644 toolchain/mxdbg/client/CMakeLists.txt create mode 100644 toolchain/mxdbg/client/attach.c create mode 100644 toolchain/mxdbg/client/breakpoint.c create mode 100644 toolchain/mxdbg/client/command.c create mode 100644 toolchain/mxdbg/client/command.h create mode 100644 toolchain/mxdbg/client/context.c create mode 100644 toolchain/mxdbg/client/context.h create mode 100644 toolchain/mxdbg/client/continue.c create mode 100644 toolchain/mxdbg/client/event.c create mode 100644 toolchain/mxdbg/client/event.h create mode 100644 toolchain/mxdbg/client/info.c create mode 100644 toolchain/mxdbg/client/info/kernel.c create mode 100644 toolchain/mxdbg/client/line-ed/buffer.c create mode 100644 toolchain/mxdbg/client/line-ed/buffer.h create mode 100644 toolchain/mxdbg/client/line-ed/cursor.c create mode 100644 toolchain/mxdbg/client/line-ed/cursor.h create mode 100644 toolchain/mxdbg/client/line-ed/history.c create mode 100644 toolchain/mxdbg/client/line-ed/history.h create mode 100644 toolchain/mxdbg/client/line-ed/hook.c create mode 100644 toolchain/mxdbg/client/line-ed/hook.h create mode 100644 toolchain/mxdbg/client/line-ed/input.c create mode 100644 toolchain/mxdbg/client/line-ed/input.h create mode 100644 toolchain/mxdbg/client/line-ed/line-ed.c create mode 100644 toolchain/mxdbg/client/line-ed/line-ed.h create mode 100644 toolchain/mxdbg/client/line-ed/prompt.c create mode 100644 toolchain/mxdbg/client/line-ed/prompt.h create mode 100644 toolchain/mxdbg/client/line-ed/refresh.c create mode 100644 toolchain/mxdbg/client/line-ed/refresh.h create mode 100644 toolchain/mxdbg/client/main.c create mode 100644 toolchain/mxdbg/client/memory.c create mode 100644 toolchain/mxdbg/client/packet.c create mode 100644 toolchain/mxdbg/client/packet.h create mode 100644 toolchain/mxdbg/client/quit.c create mode 100644 toolchain/mxdbg/client/repl.c create mode 100644 toolchain/mxdbg/client/repl.h create mode 100644 toolchain/mxdbg/lib/CMakeLists.txt create mode 100644 toolchain/mxdbg/server/CMakeLists.txt create mode 100644 toolchain/mxdbg/server/main.c diff --git a/toolchain/CMakeLists.txt b/toolchain/CMakeLists.txt index 46cdb49..56784c3 100644 --- a/toolchain/CMakeLists.txt +++ b/toolchain/CMakeLists.txt @@ -10,10 +10,11 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) add_subdirectory(../lib/libfx ${CMAKE_CURRENT_BINARY_DIR}/libfx) find_package(FX REQUIRED COMPONENTS - fx.runtime fx.collections fx.cmdline fx.serial fx.io) + fx.runtime fx.collections fx.cmdline fx.serial fx.io fx.term) add_subdirectory( ../kernel/tools ${CMAKE_CURRENT_BINARY_DIR}/kernel-tools) add_subdirectory(xpcg) +add_subdirectory(mxdbg) diff --git a/toolchain/mxdbg/CMakeLists.txt b/toolchain/mxdbg/CMakeLists.txt new file mode 100644 index 0000000..db568a4 --- /dev/null +++ b/toolchain/mxdbg/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(../../kernel/debug ${CMAKE_CURRENT_BINARY_DIR}/lib) +add_subdirectory(client) +add_subdirectory(server) diff --git a/toolchain/mxdbg/client/CMakeLists.txt b/toolchain/mxdbg/client/CMakeLists.txt new file mode 100644 index 0000000..c5c9e32 --- /dev/null +++ b/toolchain/mxdbg/client/CMakeLists.txt @@ -0,0 +1,11 @@ +file(GLOB sources + *.c *.h + info/*.c + line-ed/*.c + line-ed/*.h) + +add_executable(mxdbg ${sources}) + +target_link_libraries(mxdbg + magenta_debug + FX::Runtime FX::Collections FX::Term FX::Cmdline) diff --git a/toolchain/mxdbg/client/attach.c b/toolchain/mxdbg/client/attach.c new file mode 100644 index 0000000..a27e5b7 --- /dev/null +++ b/toolchain/mxdbg/client/attach.c @@ -0,0 +1,15 @@ +#include "command.h" + +static int attach( + const struct command *cmd, + int argc, + const char **argv, + void *argp) +{ + return 0; +} + +const struct command cmd_attach = { + .cmd_name = "attach", + .cmd_callback = NULL, +}; diff --git a/toolchain/mxdbg/client/breakpoint.c b/toolchain/mxdbg/client/breakpoint.c new file mode 100644 index 0000000..167ff05 --- /dev/null +++ b/toolchain/mxdbg/client/breakpoint.c @@ -0,0 +1,16 @@ +#include "command.h" + +static int breakpoint( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + return 0; +} + +const struct command cmd_breakpoint = { + .cmd_name = "breakpoint", + .cmd_callback = breakpoint, +}; diff --git a/toolchain/mxdbg/client/command.c b/toolchain/mxdbg/client/command.c new file mode 100644 index 0000000..12a6bbf --- /dev/null +++ b/toolchain/mxdbg/client/command.c @@ -0,0 +1,74 @@ +#include "command.h" + +#include +#include + +static int command_name_compare(const char *cmd_name, const char *target) +{ + int score = 0; + + for (size_t i = 0;; i++) { + if (cmd_name[i] != target[i]) { + break; + } + + if (!cmd_name[i]) { + break; + } + + score++; + } + + return score; +} + +const struct command *command_find( + const struct command **commands, + size_t nr_commands, + const char *name) +{ + int best_score = 0; + const struct command *best_match = NULL; + bool ambiguous = false; + + for (size_t i = 0; i < nr_commands; i++) { + int score = command_name_compare(commands[i]->cmd_name, name); + if (score == best_score && score > 0) { + ambiguous = true; + } + + if (score > best_score) { + ambiguous = false; + best_score = score; + best_match = commands[i]; + } + } + + if (!best_match) { + printf("'%s' is not a valid command.\n", name); + return NULL; + } + + if (!ambiguous) { + return best_match; + } + + printf("command name '%s' is ambiguous, and could refer to multiple " + "commands.\n", + name); + return NULL; +} + +int command_execute( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + if (!cmd->cmd_callback) { + return -1; + } + + return cmd->cmd_callback(cmd, ctx, argc, argv, argp); +} diff --git a/toolchain/mxdbg/client/command.h b/toolchain/mxdbg/client/command.h new file mode 100644 index 0000000..52b3503 --- /dev/null +++ b/toolchain/mxdbg/client/command.h @@ -0,0 +1,33 @@ +#ifndef COMMAND_H_ +#define COMMAND_H_ + +#include "context.h" + +#include + +struct command; + +typedef int (*command_callback_t)( + const struct command *, + struct debug_context *, + int, + const char **, + void *); + +struct command { + const char *cmd_name; + command_callback_t cmd_callback; +}; + +extern const struct command *command_find( + const struct command **commands, + size_t nr_commands, + const char *name); +extern int command_execute( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp); + +#endif diff --git a/toolchain/mxdbg/client/context.c b/toolchain/mxdbg/client/context.c new file mode 100644 index 0000000..02252cd --- /dev/null +++ b/toolchain/mxdbg/client/context.c @@ -0,0 +1,228 @@ +#include "context.h" + +#include "packet.h" + +#include +#include +#include +#include +#include +#include +#include + +void debug_context_init(struct debug_context *ctx) +{ + memset(ctx, 0x0, sizeof *ctx); + ctx->ctx_sock = -1; + pthread_mutex_init(&ctx->ctx_packet_lock, NULL); + pthread_cond_init(&ctx->ctx_packet_cond, NULL); +} + +void debug_context_cleanup(struct debug_context *ctx) +{ + ctx->ctx_quit = true; + + if (ctx->ctx_sock != -1) { + close(ctx->ctx_sock); + } + + pthread_join(ctx->ctx_packet_thread, NULL); +} + +static void *packet_thread(void *arg) +{ + struct debug_context *ctx = arg; + while (!ctx->ctx_quit) { + struct mxdbg_packet hdr; + int err = 0; + size_t r; + + err = debug_context_recv(ctx, &hdr, sizeof hdr, &r); + if (err != 0) { + printf("packet recv failed\n"); + continue; + } + + if (r != sizeof hdr) { + printf("packet size is wrong\n"); + continue; + } + + if (!mxdbg_packet_validate(&hdr)) { + printf("packet is malformed\n"); + continue; + } + + struct packet *packet = packet_create(hdr.pkt_length); + if (!packet) { + printf("packet alloc failed\n"); + continue; + } + + memcpy(&packet->p_packet, &hdr, sizeof hdr); + err = debug_context_recv_packet_data( + ctx, + &packet->p_packet, + &r); + if (err != 0) { + printf("packet data read failed\n"); + free(packet); + continue; + } + + pthread_mutex_lock(&ctx->ctx_packet_lock); + fx_queue_push_back(&ctx->ctx_packets, &packet->p_entry); + pthread_cond_broadcast(&ctx->ctx_packet_cond); + pthread_mutex_unlock(&ctx->ctx_packet_lock); + } + + return NULL; +} + +static void *event_thread(void *arg) +{ + struct debug_context *ctx = arg; + while (!ctx->ctx_quit) { + struct packet *packet + = debug_context_await_packet(ctx, MXDBG_PACKET_EVENT); + + ctx->ctx_on_event(ctx, packet); + packet_destroy(packet); + } + + return NULL; +} + +void debug_context_start_threads(struct debug_context *ctx) +{ + pthread_create(&ctx->ctx_packet_thread, NULL, packet_thread, ctx); + pthread_create(&ctx->ctx_event_thread, NULL, event_thread, ctx); +} + +int debug_context_connect_net( + struct debug_context *ctx, + const char *address, + int port) +{ + ctx->ctx_sock = socket(AF_INET, SOCK_STREAM, 0); + if (ctx->ctx_sock < 0) { + return -1; + } + + struct sockaddr_in dest = {0}; + dest.sin_family = AF_INET; + dest.sin_port = htons(port); + inet_pton(AF_INET, address, &dest.sin_addr); + + return connect(ctx->ctx_sock, (struct sockaddr *)&dest, sizeof dest); +} + +int debug_context_recv( + struct debug_context *ctx, + void *p, + size_t len, + size_t *nr_read) +{ + size_t total_read = 0; + unsigned char *buf = p; + + while (total_read < len) { + long r = read(ctx->ctx_sock, p + total_read, len - total_read); + if (r < 0) { + return -1; + } + + total_read += r; + } + + *nr_read = total_read; + return 0; +} + +int debug_context_send_packet( + struct debug_context *ctx, + const struct mxdbg_packet *packet) +{ + pthread_mutex_lock(&ctx->ctx_sock_lock); + int err = 0; + size_t len = packet->pkt_length; + size_t total_written = 0; + const unsigned char *buf = (const unsigned char *)packet; + + while (total_written < len) { + long w + = write(ctx->ctx_sock, + buf + total_written, + len - total_written); + if (w < 0) { + err = -1; + break; + } + + total_written += w; + } + + pthread_mutex_unlock(&ctx->ctx_sock_lock); + return err; +} + +int debug_context_recv_packet_data( + struct debug_context *ctx, + struct mxdbg_packet *packet, + size_t *nr_read) +{ + if (packet->pkt_magic != MXDBG_PACKET_MAGIC) { + return -1; + } + + if (!packet->pkt_length) { + *nr_read = 0; + return 0; + } + + void *dest = packet + 1; + return debug_context_recv( + ctx, + dest, + packet->pkt_length - sizeof *packet, + nr_read); +} + +static struct packet *dequeue_packet( + struct debug_context *ctx, + enum mxdbg_packet_type packet_type) +{ + fx_queue_entry *cur = fx_queue_first(&ctx->ctx_packets); + while (cur) { + struct packet *packet = fx_unbox(struct packet, cur, p_entry); + + if (packet->p_packet.pkt_type == packet_type) { + fx_queue_delete(&ctx->ctx_packets, cur); + return packet; + } + + cur = fx_queue_next(cur); + } + + return NULL; +} + +struct packet *debug_context_await_packet( + struct debug_context *ctx, + enum mxdbg_packet_type packet_type) +{ + pthread_mutex_lock(&ctx->ctx_packet_lock); + struct packet *result = NULL; + + while (1) { + result = dequeue_packet(ctx, packet_type); + if (result) { + break; + } + + pthread_cond_wait(&ctx->ctx_packet_cond, &ctx->ctx_packet_lock); + } + + pthread_mutex_unlock(&ctx->ctx_packet_lock); + return result; +} diff --git a/toolchain/mxdbg/client/context.h b/toolchain/mxdbg/client/context.h new file mode 100644 index 0000000..f7f2b98 --- /dev/null +++ b/toolchain/mxdbg/client/context.h @@ -0,0 +1,54 @@ +#ifndef CONTEXT_H_ +#define CONTEXT_H_ + +#include +#include +#include + +enum mxdbg_packet_type; +struct packet; +struct mxdbg_packet; +struct debug_context; + +typedef void (*event_callback_t)(struct debug_context *, struct packet *); + +struct debug_context { + bool ctx_quit; + int ctx_sock; + pthread_mutex_t ctx_sock_lock; + fx_queue ctx_packets; + pthread_mutex_t ctx_packet_lock; + pthread_cond_t ctx_packet_cond; + pthread_t ctx_packet_thread; + pthread_t ctx_event_thread; + + event_callback_t ctx_on_event; +}; + +extern void debug_context_init(struct debug_context *ctx); +extern void debug_context_cleanup(struct debug_context *ctx); +extern void debug_context_start_threads(struct debug_context *ctx); + +extern int debug_context_connect_net( + struct debug_context *ctx, + const char *address, + int port); + +extern int debug_context_recv( + struct debug_context *ctx, + void *p, + size_t len, + size_t *nr_read); +extern int debug_context_send_packet( + struct debug_context *ctx, + const struct mxdbg_packet *packet); + +extern int debug_context_recv_packet_data( + struct debug_context *ctx, + struct mxdbg_packet *packet, + size_t *nr_read); +extern struct packet *debug_context_await_packet( + struct debug_context *ctx, + enum mxdbg_packet_type packet_type); + +#endif diff --git a/toolchain/mxdbg/client/continue.c b/toolchain/mxdbg/client/continue.c new file mode 100644 index 0000000..e228415 --- /dev/null +++ b/toolchain/mxdbg/client/continue.c @@ -0,0 +1,27 @@ +#include "command.h" + +#include +#include +#include + +static int continue_impl( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + printf("continuing...\n"); + struct mxdbg_packet packet; + mxdbg_packet_init(&packet, sizeof packet, MXDBG_PACKET_RESUME, 0); + + size_t w; + debug_context_send_packet(ctx, &packet); + + return 0; +} + +const struct command cmd_continue = { + .cmd_name = "continue", + .cmd_callback = continue_impl, +}; diff --git a/toolchain/mxdbg/client/event.c b/toolchain/mxdbg/client/event.c new file mode 100644 index 0000000..5b46bf1 --- /dev/null +++ b/toolchain/mxdbg/client/event.c @@ -0,0 +1,42 @@ +#include "context.h" +#include "packet.h" +#include "repl.h" + +#include + +void event_handle(struct debug_context *ctx, struct packet *event_packet) +{ + const struct mxdbg_packet_event *event + = (const struct mxdbg_packet_event *)&event_packet->p_packet; + repl_suspend(); + printf("EVENT: %s", mxdbg_event_type_get_description(event->ev_type)); + + size_t w; + struct packet *p = NULL; + + switch (event->ev_type) { + case MXDBG_EVENT_TASK_CREATED: { + struct mxdbg_packet_identify_task id = {0}; + mxdbg_packet_init( + &id.id_base, + sizeof id, + MXDBG_PACKET_IDENTIFY_TASK, + 0); + id.id_task = event->ev_task; + printf(" %d", id.id_task); + debug_context_send_packet(ctx, &id.id_base); + p = debug_context_await_packet(ctx, MXDBG_PACKET_IDENTIFY_TASK); + + struct mxdbg_packet_identify_task *resp + = (struct mxdbg_packet_identify_task *)&p->p_packet; + printf(" (%s)", resp->id_task_name); + packet_destroy(p); + break; + } + default: + break; + } + + printf("\n"); + repl_resume(); +} diff --git a/toolchain/mxdbg/client/event.h b/toolchain/mxdbg/client/event.h new file mode 100644 index 0000000..ccf5df2 --- /dev/null +++ b/toolchain/mxdbg/client/event.h @@ -0,0 +1,9 @@ +#ifndef EVENT_H_ +#define EVENT_H_ + +struct packet; +struct debug_context; + +extern void event_handle(struct debug_context *ctx, struct packet *event); + +#endif diff --git a/toolchain/mxdbg/client/info.c b/toolchain/mxdbg/client/info.c new file mode 100644 index 0000000..0b8a69a --- /dev/null +++ b/toolchain/mxdbg/client/info.c @@ -0,0 +1,37 @@ +#include "command.h" + +#include + +extern const struct command cmd_info_kernel; + +static const struct command *commands[] = { + &cmd_info_kernel, +}; +static const size_t nr_commands = sizeof commands / sizeof commands[0]; + +static int info( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + if (argc < 1) { + printf("query information about the system\n"); + return 0; + } + + const struct command *subcmd + = command_find(commands, nr_commands, argv[0]); + if (!subcmd) { + return -1; + } + + command_execute(subcmd, ctx, argc - 1, argv + 1, argp); + return 0; +} + +const struct command cmd_info = { + .cmd_name = "info", + .cmd_callback = info, +}; diff --git a/toolchain/mxdbg/client/info/kernel.c b/toolchain/mxdbg/client/info/kernel.c new file mode 100644 index 0000000..76e4c0e --- /dev/null +++ b/toolchain/mxdbg/client/info/kernel.c @@ -0,0 +1,38 @@ +#include "../command.h" +#include "../packet.h" + +#include +#include + +static int info_kernel( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + struct mxdbg_packet req; + mxdbg_packet_init(&req, sizeof req, MXDBG_PACKET_IDENTIFY_KERNEL, 0); + size_t r, w; + debug_context_send_packet(ctx, &req); + + struct packet *response + = debug_context_await_packet(ctx, MXDBG_PACKET_IDENTIFY_KERNEL); + + struct mxdbg_packet_identify_kernel *id + = (struct mxdbg_packet_identify_kernel *)&response->p_packet; + + printf("kernel identity: %s %s (%s)\n", + id->id_kernel_name, + id->id_kernel_version, + id->id_kernel_arch); + + packet_destroy(response); + + return 0; +} + +const struct command cmd_info_kernel = { + .cmd_name = "kernel", + .cmd_callback = info_kernel, +}; diff --git a/toolchain/mxdbg/client/line-ed/buffer.c b/toolchain/mxdbg/client/line-ed/buffer.c new file mode 100644 index 0000000..507a455 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/buffer.c @@ -0,0 +1,44 @@ +#include "buffer.h" + +#include "line-ed.h" + +#include +#include + +const fx_wchar *line_start(struct line_ed *ed, size_t y) +{ + const fx_wchar *line = ed->l_buf; + + for (size_t i = 0; i < y; i++) { + line += fx_wstrcspn(line, L"\n"); + + if (*line == '\n') { + line++; + } + } + + return line; +} + +size_t line_length(struct line_ed *ed, size_t y) +{ + const fx_wchar *line = ed->l_buf; + + for (size_t i = 0; i < y; i++) { + line += fx_wstrcspn(line, L"\n"); + if (*line == '\n') { + line++; + } + } + + if (*line == '\0') { + return 0; + } + + size_t len = fx_wstrcspn(line, L"\n"); + if (line[len] == '\n') { + len++; + } + + return len; +} diff --git a/toolchain/mxdbg/client/line-ed/buffer.h b/toolchain/mxdbg/client/line-ed/buffer.h new file mode 100644 index 0000000..a77af4c --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/buffer.h @@ -0,0 +1,17 @@ +#ifndef LINE_ED_BUFFER_H_ +#define LINE_ED_BUFFER_H_ + +#include +#include + +struct line_ed; + +/* returns a pointer to the start of the line based on the given `y` + * coordinate */ +extern const fx_wchar *line_start(struct line_ed *ed, size_t y); +/* returns the length of the line based on the given `y` coordinate. + * for any line other than the last line in the buffer, this length + * INCLUDES the trailing linefeed. */ +extern size_t line_length(struct line_ed *ed, size_t y); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/cursor.c b/toolchain/mxdbg/client/line-ed/cursor.c new file mode 100644 index 0000000..70a644d --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/cursor.c @@ -0,0 +1,26 @@ +#include "line-ed.h" +#include "cursor.h" +#include "prompt.h" + +void line_ed_coords_to_physical_coords( + struct line_ed *ed, size_t x, size_t y, size_t *out_x, size_t *out_y) +{ + size_t prompt_len = 0; + if (ed->l_cursor_y == 0) { + prompt_len = prompt_length(ed, PROMPT_MAIN); + } else if (ed->l_cursor_y <= ed->l_continuations) { + prompt_len = prompt_length(ed, PROMPT_CONT); + } + + if (y == 0) { + x += prompt_len; + } + + if (out_x) { + *out_x = x; + } + + if (out_y) { + *out_y = y; + } +} diff --git a/toolchain/mxdbg/client/line-ed/cursor.h b/toolchain/mxdbg/client/line-ed/cursor.h new file mode 100644 index 0000000..3d8a4b4 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/cursor.h @@ -0,0 +1,9 @@ +#ifndef LINE_ED_CURSOR_H_ +#define LINE_ED_CURSOR_H_ + +struct line_ed; + +extern void line_ed_coords_to_physical_coords( + struct line_ed *ed, size_t x, size_t y, size_t *out_x, size_t *out_y); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/history.c b/toolchain/mxdbg/client/line-ed/history.c new file mode 100644 index 0000000..0665662 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/history.c @@ -0,0 +1,87 @@ +#include "line-ed.h" + +#include +#include + +void alloc_empty_history_entry(struct line_ed *ed) +{ + fx_value *str_v = fx_array_get_ref( + ed->l_history, + fx_array_get_size(ed->l_history) - 1); + fx_string *str = NULL; + fx_value_get_object(str_v, &str); + if (!str || fx_string_get_size(str, FX_STRLEN_NORMAL) > 0) { + str = fx_string_create(); + fx_array_push_back(ed->l_history, FX_VALUE_OBJECT(str)); + fx_string_unref(str); + } + + ed->l_history_pos = fx_array_get_size(ed->l_history) - 1; +} + +void save_buf_to_history(struct line_ed *ed) +{ + fx_value *cur_v = fx_array_get_ref(ed->l_history, ed->l_history_pos); + fx_string *cur = NULL; + fx_value_get_object(cur_v, &cur); + fx_string_clear(cur); + fx_string_append_wstr(cur, ed->l_buf); +} + +void append_buf_to_history(struct line_ed *ed) +{ + fx_value *cur_v = fx_array_get_ref(ed->l_history, ed->l_history_pos); + fx_string *cur = NULL; + fx_value_get_object(cur_v, &cur); + char s[] = {'\n', 0}; + fx_string_append_cstr(cur, s); + fx_string_append_wstr(cur, ed->l_buf); +} + +void load_buf_from_history(struct line_ed *ed) +{ + fx_value *cur_v = fx_array_get_ref(ed->l_history, ed->l_history_pos); + fx_string *cur = NULL; + fx_value_get_object(cur_v, &cur); + size_t len = fx_min( + size_t, + (size_t)(ed->l_buf_end - ed->l_buf - 1), + fx_string_get_size(cur, FX_STRLEN_CODEPOINTS)); + + const char *src = fx_string_get_cstr(cur); + for (size_t i = 0; i < len; i++) { + fx_wchar ch_src = fx_wchar_utf8_codepoint_decode(src); + ed->l_buf[i] = ch_src; + src += fx_wchar_utf8_codepoint_stride(src); + } + + ed->l_buf[len] = '\0'; + + unsigned int x = 0, y = 0; + for (size_t i = 0; ed->l_buf[i]; i++) { + if (ed->l_buf[i] == '\n') { + x = 0; + y++; + } else { + x++; + } + } + + ed->l_buf_ptr = ed->l_buf + len; + ed->l_line_end = ed->l_buf_ptr; + ed->l_cursor_x = x; + ed->l_cursor_y = y; +} + +const char *last_history_line(struct line_ed *ed) +{ + size_t nlines = fx_array_get_size(ed->l_history); + if (nlines < 2) { + return NULL; + } + + fx_value *last_v = fx_array_get_ref(ed->l_history, nlines - 2); + fx_string *last = NULL; + fx_value_get_object(last_v, &last); + return fx_string_get_cstr(last); +} diff --git a/toolchain/mxdbg/client/line-ed/history.h b/toolchain/mxdbg/client/line-ed/history.h new file mode 100644 index 0000000..d166aa2 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/history.h @@ -0,0 +1,12 @@ +#ifndef LINE_ED_HISTORY_H_ +#define LINE_ED_HISTORY_H_ + +struct line_ed; + +extern void alloc_empty_history_entry(struct line_ed *ed); +extern void save_buf_to_history(struct line_ed *ed); +extern void append_buf_to_history(struct line_ed *ed); +extern void load_buf_from_history(struct line_ed *ed); +extern const char *last_history_line(struct line_ed *ed); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/hook.c b/toolchain/mxdbg/client/line-ed/hook.c new file mode 100644 index 0000000..cb8e8f4 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/hook.c @@ -0,0 +1,41 @@ +#include "hook.h" + +#include "line-ed.h" + +void line_ed_add_hook(struct line_ed *ed, struct line_ed_hook *hook) +{ + fx_queue_push_back(&ed->l_hooks, &hook->hook_entry); +} + +void line_ed_remove_hook(struct line_ed *ed, struct line_ed_hook *hook) +{ + fx_queue_delete(&ed->l_hooks, &hook->hook_entry); +} + +void hook_keypress(struct line_ed *ed, fx_keycode key) +{ + fx_queue_entry *entry = fx_queue_first(&ed->l_hooks); + while (entry) { + struct line_ed_hook *hook + = fx_unbox(struct line_ed_hook, entry, hook_entry); + if (hook->hook_keypress) { + hook->hook_keypress(ed, hook, key); + } + + entry = fx_queue_next(entry); + } +} + +void hook_buffer_modified(struct line_ed *ed) +{ + fx_queue_entry *entry = fx_queue_first(&ed->l_hooks); + while (entry) { + struct line_ed_hook *hook + = fx_unbox(struct line_ed_hook, entry, hook_entry); + if (hook->hook_buffer_modified) { + hook->hook_buffer_modified(ed, hook); + } + + entry = fx_queue_next(entry); + } +} diff --git a/toolchain/mxdbg/client/line-ed/hook.h b/toolchain/mxdbg/client/line-ed/hook.h new file mode 100644 index 0000000..613344a --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/hook.h @@ -0,0 +1,16 @@ +#ifndef LINE_ED_HOOK_H_ +#define LINE_ED_HOOK_H_ + +#include + +enum hook_id { + HOOK_KEYPRESS, + HOOK_BEFORE_PAINT, +}; + +struct line_ed; + +extern void hook_keypress(struct line_ed *ed, fx_keycode key); +extern void hook_buffer_modified(struct line_ed *ed); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/input.c b/toolchain/mxdbg/client/line-ed/input.c new file mode 100644 index 0000000..3ea9624 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/input.c @@ -0,0 +1,232 @@ +#include "buffer.h" +#include "cursor.h" +#include "history.h" +#include "hook.h" +#include "line-ed.h" +#include "prompt.h" +#include "refresh.h" + +#include + +void put_char(struct line_ed *ed, fx_wchar c) +{ + if (ed->l_buf_ptr > ed->l_line_end + 1) { + return; + } + + struct refresh_state state = { + .r_prev_cursor_x = ed->l_cursor_x, + .r_prev_cursor_y = ed->l_cursor_y, + }; + + size_t prev_cursor = ed->l_buf_ptr - ed->l_buf; + + fx_wchar *dest = ed->l_buf_ptr; + size_t len = (ed->l_line_end - ed->l_buf_ptr + 1) * sizeof *dest; + + if (dest < ed->l_line_end) { + memmove(dest + 1, dest, len); + } + + ed->l_cursor_x++; + ed->l_line_end++; + ed->l_buf_ptr++; + *dest = c; + + if (ed->l_buf_ptr == ed->l_line_end) { + *ed->l_buf_ptr = '\0'; + } + + hook_buffer_modified(ed); + + put_refresh(ed, &state); +} + +static void backspace_simple(struct line_ed *ed) +{ + if (ed->l_buf_ptr == ed->l_buf) { + return; + } + + struct refresh_state state = { + .r_prev_cursor_x = ed->l_cursor_x, + .r_prev_cursor_y = ed->l_cursor_y, + }; + + size_t prev_cursor = ed->l_buf_ptr - ed->l_buf; + + fx_wchar *dest = ed->l_buf_ptr; + size_t len = (ed->l_line_end - ed->l_buf_ptr + 1) * sizeof *dest; + memmove(dest - 1, dest, len); + + ed->l_cursor_x--; + ed->l_line_end--; + ed->l_buf_ptr--; + + hook_buffer_modified(ed); + + backspace_simple_refresh(ed, &state); +} + +static void backspace_nl(struct line_ed *ed) +{ + size_t prev_line_len = line_length(ed, ed->l_cursor_y - 1); + + struct refresh_state state = { + .r_prev_cursor_x = ed->l_cursor_x, + .r_prev_cursor_y = ed->l_cursor_y, + .r_prev_line_len = prev_line_len, + }; + + fx_wchar *dest = ed->l_buf_ptr; + size_t len = (ed->l_line_end - ed->l_buf_ptr + 1) * sizeof *dest; + memmove(dest - 1, dest, len); + + ed->l_cursor_x = prev_line_len - 1; + ed->l_cursor_y--; + ed->l_buf_ptr--; + ed->l_line_end--; + + hook_buffer_modified(ed); + + backspace_nl_refresh(ed, &state); +} + +void backspace(struct line_ed *ed) +{ + if (ed->l_buf_ptr == ed->l_buf) { + return; + } + + if (ed->l_cursor_x == 0 && ed->l_cursor_y <= ed->l_continuations) { + return; + } + + if (ed->l_cursor_x == 0 && ed->l_cursor_y > 0) { + backspace_nl(ed); + } else { + backspace_simple(ed); + } +} + +void cursor_left(struct line_ed *ed) +{ + if (ed->l_cursor_x != 0) { + // fputs("\010", stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fflush(stdout); + ed->l_cursor_x--; + ed->l_buf_ptr--; + return; + } + + if (ed->l_cursor_y <= ed->l_continuations + || ed->l_buf_ptr <= ed->l_buf) { + return; + } + + ed->l_cursor_y--; + ed->l_buf_ptr--; + size_t prompt_len = 0; + if (ed->l_cursor_y == 0) { + prompt_len = prompt_length(ed, PROMPT_MAIN); + } + + size_t len = line_length(ed, ed->l_cursor_y); + ed->l_cursor_x = len - 1; + + // printf("\033[A\033[%dG", len + prompt_len); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_START, + (int)(len + prompt_len)); + + fflush(stdout); +} + +void cursor_right(struct line_ed *ed) +{ + if (ed->l_buf_ptr >= ed->l_line_end) { + return; + } + + if (*ed->l_buf_ptr != '\n') { + ed->l_cursor_x++; + ed->l_buf_ptr++; + // fputs("\033[C", stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, 1); + fflush(stdout); + return; + } + + if (ed->l_buf_ptr >= ed->l_line_end) { + return; + } + + ed->l_cursor_y++; + ed->l_cursor_x = 0; + ed->l_buf_ptr++; + + // printf("\033[B\033[G"); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, 1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, 0); + fflush(stdout); +} + +void arrow_up(struct line_ed *ed) +{ + if (ed->l_history_pos == 0) { + return; + } + + if (ed->l_cursor_y > 0) { + // printf("\033[%uA", ed->l_cursor_y); + fx_tty_move_cursor_y( + ed->l_tty, + FX_TTY_POS_CURSOR, + (long long)ed->l_cursor_y); + } + + // printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_START, + (long long)prompt_length(ed, PROMPT_MAIN)); + fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR); + + save_buf_to_history(ed); + ed->l_history_pos--; + load_buf_from_history(ed); + + print_buffer(ed); + fflush(stdout); +} + +void arrow_down(struct line_ed *ed) +{ + if (ed->l_history_pos == fx_array_get_size(ed->l_history) - 1) { + return; + } + + if (ed->l_cursor_y > 0) { + // printf("\033[%uA", ed->l_cursor_y); + fx_tty_move_cursor_y( + ed->l_tty, + FX_TTY_POS_CURSOR, + (int)ed->l_cursor_y); + } + + // printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_START, + prompt_length(ed, PROMPT_MAIN) + 1); + + save_buf_to_history(ed); + ed->l_history_pos++; + load_buf_from_history(ed); + + print_buffer(ed); + fflush(stdout); +} diff --git a/toolchain/mxdbg/client/line-ed/input.h b/toolchain/mxdbg/client/line-ed/input.h new file mode 100644 index 0000000..26b8255 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/input.h @@ -0,0 +1,15 @@ +#ifndef LINE_ED_INPUT_H_ +#define LINE_ED_INPUT_H_ + +#include + +struct line_ed; + +extern void put_char(struct line_ed *ed, fx_wchar c); +extern void backspace(struct line_ed *ed); +extern void cursor_left(struct line_ed *ed); +extern void cursor_right(struct line_ed *ed); +extern void arrow_up(struct line_ed *ed); +extern void arrow_down(struct line_ed *ed); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/line-ed.c b/toolchain/mxdbg/client/line-ed/line-ed.c new file mode 100644 index 0000000..beef02c --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/line-ed.c @@ -0,0 +1,348 @@ +#include "line-ed.h" + +#include "history.h" +#include "hook.h" +#include "input.h" +#include "prompt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct line_ed *line_ed_create(void) +{ + struct line_ed *out = malloc(sizeof *out); + if (!out) { + return NULL; + } + + memset(out, 0x0, sizeof *out); + + out->l_buf = malloc(LINE_MAX); + if (!out->l_buf) { + free(out); + return NULL; + } + + out->l_history = fx_array_create(); + if (!out->l_history) { + free(out->l_buf); + free(out); + return NULL; + } + + pthread_mutex_init(&out->l_lock, NULL); + out->l_tty = fx_stdtty; + out->l_buf_end = out->l_buf + LINE_MAX; + out->l_buf_ptr = out->l_buf; + out->l_line_end = out->l_buf; + + out->l_prompt[PROMPT_MAIN] = ">>> "; + out->l_prompt[PROMPT_CONT] = "> "; + + return out; +} + +void line_ed_destroy(struct line_ed *ed) +{ + fx_array_unref(ed->l_history); + free(ed->l_buf); + free(ed); +} + +void line_ed_set_flags(struct line_ed *ed, enum line_ed_flags flags) +{ + ed->l_flags |= flags; +} + +void line_ed_set_scope_type(struct line_ed *ed, const char *scope_type) +{ + ed->l_scope_type = scope_type; +} + +static void clear_buffer(struct line_ed *ed) +{ + memset(ed->l_buf, 0x0, ed->l_buf_end - ed->l_buf); + + ed->l_buf_ptr = ed->l_buf; + ed->l_line_end = ed->l_buf; + + ed->l_cursor_x = 0; + ed->l_cursor_y = 0; + + ed->l_continuations = 0; +} + +static void convert_continuation_feeds(fx_wchar *s, size_t max) +{ + fx_wchar *end = s + max; + size_t len = fx_wstrlen(s); + + while (1) { + size_t r = fx_wstrcspn(s, L"\\"); + if (s + r > end) { + break; + } + + s += r; + len -= r; + + if (*s == '\0') { + break; + } + + if (*(s + 1) != '\n') { + s++; + continue; + } + + memmove(s, s + 1, len); + s += 1; + } +} + +static void remove_continuation_feeds(fx_wchar *s, size_t max) +{ + fx_wchar *end = s + max; + size_t len = fx_wstrlen(s); + + while (1) { + size_t r = fx_wstrcspn(s, L"\\"); + if (s + r > end) { + break; + } + + s += r; + len -= r; + + if (*s == '\0') { + break; + } + + if (*(s + 1) != '\n') { + s++; + continue; + } + + memmove(s, s + 2, len); + s += 2; + } +} + +static bool input_is_empty(struct line_ed *ed) +{ + const fx_wchar *p = ed->l_buf; + while (p < ed->l_line_end) { + if (!fx_wchar_is_space(*p)) { + return false; + } + } + + return true; +} + +static void check_suspended(struct line_ed *ed) +{ + pthread_mutex_lock(&ed->l_lock); + while (1) { + if (!ed->l_suspended) { + break; + } + pthread_cond_wait(&ed->l_suspended_cond, &ed->l_lock); + } + + pthread_mutex_unlock(&ed->l_lock); +} + +size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out) +{ + clear_buffer(ed); + + bool append_history = false; + size_t append_to_index = (size_t)-1; + if (!ed->l_scope_type) { + alloc_empty_history_entry(ed); + } else { + append_history = true; + append_to_index = ed->l_history_pos; + } + + fx_tty *tty = ed->l_tty; + fx_tty_set_mode(tty, FX_TTY_RAW); + show_prompt(ed); + + for (int i = 0; ed->l_buf[i]; i++) { + if (ed->l_buf[i] == '\n') { + fputc('\r', stdout); + } + + fx_stream_write_char(fx_stdout, ed->l_buf[i]); + } + fflush(stdout); + + bool end = false; + bool eof = false; + + while (!end) { + check_suspended(ed); + + fx_keycode key = fx_tty_read_key(tty); + hook_keypress(ed, key); + + if (ed->l_suspended) { + continue; + } + + if (key == FX_TTY_CTRL_KEY('d')) { + if (!input_is_empty(ed)) { + continue; + } + + eof = true; + break; + } + + if (key & FX_MOD_CTRL) { + continue; + } + + switch (key) { + case FX_KEY_RETURN: + fx_tty_reset_vmode(tty); + if (ed->l_line_end > ed->l_buf + && *(ed->l_line_end - 1) != '\\') { + end = true; + break; + } + + if (input_is_empty(ed)) { + fputc('\r', stdout); + fputc('\n', stdout); + clear_buffer(ed); + show_prompt(ed); + break; + } + + *ed->l_line_end = '\n'; + ed->l_line_end++; + ed->l_buf_ptr = ed->l_line_end; + + ed->l_cursor_x = 0; + ed->l_cursor_y++; + ed->l_continuations++; + fputc('\r', stdout); + fputc('\n', stdout); + // fputs("\033[G\n", stdout); + show_prompt(ed); + break; + case FX_KEY_BACKSPACE: + backspace(ed); + break; + case FX_KEY_ARROW_LEFT: + cursor_left(ed); + break; + case FX_KEY_ARROW_RIGHT: + cursor_right(ed); + break; + case FX_KEY_ARROW_UP: + arrow_up(ed); + break; + case FX_KEY_ARROW_DOWN: + arrow_down(ed); + break; + default: + if (fx_wchar_is_graph(key) || key == ' ') { + put_char(ed, key); + } + break; + } + } + + fx_tty_set_mode(tty, FX_TTY_CANONICAL); + fputc('\n', stdout); + + if (*ed->l_buf == '\0') { + return eof ? -1 : 0; + } + + if (append_history) { + ed->l_history_pos = append_to_index; + append_buf_to_history(ed); + } else { + ed->l_history_pos = fx_array_get_size(ed->l_history) - 1; + + const char *last = last_history_line(ed); + if (!last || fx_wastrcmp(last, ed->l_buf) != 0) { + save_buf_to_history(ed); + } + } + + size_t sz = ed->l_line_end - ed->l_buf + 1; + + if ((ed->l_flags & LINE_ED_REMOVE_CONTINUATIONS) + == LINE_ED_REMOVE_CONTINUATIONS) { + remove_continuation_feeds(ed->l_buf, sz); + } else if ( + (ed->l_flags & LINE_ED_CONVERT_CONTINUATIONS) + == LINE_ED_CONVERT_CONTINUATIONS) { + convert_continuation_feeds(ed->l_buf, sz); + } + + fx_stream_write_wstr(out, ed->l_buf, NULL); + + if (!(ed->l_flags & LINE_ED_NO_TRAILING_LINEFEED)) { + fx_stream_write_char(out, '\n'); + } + + return sz; +} + +int line_ed_suspend(struct line_ed *ed) +{ + pthread_mutex_lock(&ed->l_lock); + ed->l_suspended = true; + pthread_cond_signal(&ed->l_suspended_cond); + fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_LINE | FX_TTY_CLEAR_ALL); + fx_tty_set_mode(ed->l_tty, FX_TTY_CANONICAL); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, 0); + pthread_mutex_unlock(&ed->l_lock); + + return 0; +} + +int line_ed_resume(struct line_ed *ed) +{ + pthread_mutex_lock(&ed->l_lock); + fx_tty_set_mode(ed->l_tty, FX_TTY_RAW); + fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_LINE | FX_TTY_CLEAR_ALL); + show_prompt(ed); + + size_t prompt_len = 0; + if (ed->l_cursor_y == 0) { + prompt_len = prompt_length(ed, PROMPT_MAIN); + } + + size_t x = 0; + for (int i = 0; ed->l_buf[i]; i++) { + if (ed->l_buf[i] == '\n') { + fputc('\r', stdout); + } + + fx_stream_write_char(fx_stdout, ed->l_buf[i]); + x++; + } + + fflush(stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, prompt_len + x); + ed->l_suspended = false; + pthread_cond_signal(&ed->l_suspended_cond); + pthread_mutex_unlock(&ed->l_lock); + + return 0; +} diff --git a/toolchain/mxdbg/client/line-ed/line-ed.h b/toolchain/mxdbg/client/line-ed/line-ed.h new file mode 100644 index 0000000..6f47928 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/line-ed.h @@ -0,0 +1,111 @@ +#ifndef LINE_ED_H_ +#define LINE_ED_H_ + +#define LINE_MAX 4096 +#define LINE_ED_EOF ((size_t)-1) + +#include +#include +#include +#include +#include +#include + +struct s_tty; +struct fx_tty_vmode; + +struct line_ed; + +struct line_ed_hook { + void (*hook_keypress)( + struct line_ed *, + struct line_ed_hook *, + fx_keycode); + void (*hook_buffer_modified)(struct line_ed *, struct line_ed_hook *); + fx_queue_entry hook_entry; +}; + +enum line_ed_flags { + /* always reprint an entire line when a character is added/deleted. + * without this flag, only the modified character any subsequent + * characters are reprinted. */ + LINE_ED_FULL_REPRINT = 0x01u, + + /* keep line continuation (backslash-newline) tokens in the output + * buffer (default behaviour) */ + LINE_ED_KEEP_CONTINUATIONS = 0x00u, + /* convert line continuation tokens in the output buffer to simple + * linefeeds. */ + LINE_ED_CONVERT_CONTINUATIONS = 0x02u, + /* remove line continuation tokens from the output buffer, so that all + * chars are on a single line */ + LINE_ED_REMOVE_CONTINUATIONS = 0x06u, + /* remove the trailing linefeed from the input buffer */ + LINE_ED_NO_TRAILING_LINEFEED = 0x08u, +}; + +struct line_ed { + enum line_ed_flags l_flags; + + /* array of basic prompt strings */ + const char *l_prompt[2]; + /* input buffer, pointer to the buffer cell that corresponds to + * the current cursor position, and pointer to the byte AFTER the last + * usable byte in the buffer */ + fx_wchar *l_buf, *l_buf_ptr, *l_buf_end; + /* pointer to the byte AFTER the last byte of the user's input */ + fx_wchar *l_line_end; + /* 2-dimensional coordinates of the current cursor position. + * this does NOT include any prompts that are visible on the terminal */ + size_t l_cursor_x, l_cursor_y; + /* the number of line continuations that have been inputted */ + unsigned int l_continuations; + + /* pointer to tty interface */ + fx_tty *l_tty; + + /* the lexical scope that we are currently in. + * this is provided by components further up the input pipeline, + * for example, when we are inside a string or if-statement. */ + const char *l_scope_type; + + /* array of previously entered commands */ + fx_array *l_history; + /* index of the currently selected history entry */ + size_t l_history_pos; + + /* list of defined highlight ranges */ + fx_queue l_hl_ranges; + /* list of installed hooks */ + fx_queue l_hooks; + + bool l_suspended; + + pthread_mutex_t l_lock; + pthread_cond_t l_suspended_cond; +}; + +extern struct line_ed *line_ed_create(void); +extern void line_ed_destroy(struct line_ed *ed); +extern void line_ed_set_flags(struct line_ed *ed, enum line_ed_flags flags); +extern void line_ed_set_scope_type(struct line_ed *ed, const char *scope_type); + +extern void line_ed_put_highlight( + struct line_ed *ed, + unsigned long start_x, + unsigned long start_y, + unsigned long end_x, + unsigned long end_y, + const struct fx_tty_vmode *vmode); +extern void line_ed_clear_highlights(struct line_ed *ed); +extern void line_ed_print_highlights(struct line_ed *ed); + +extern void line_ed_add_hook(struct line_ed *ed, struct line_ed_hook *hook); +extern void line_ed_remove_hook(struct line_ed *ed, struct line_ed_hook *hook); + +extern size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out); + +extern int line_ed_suspend(struct line_ed *ed); +extern int line_ed_resume(struct line_ed *ed); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/prompt.c b/toolchain/mxdbg/client/line-ed/prompt.c new file mode 100644 index 0000000..963109c --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/prompt.c @@ -0,0 +1,27 @@ +#include +#include "line-ed.h" +#include "prompt.h" + +void show_prompt(struct line_ed *ed) +{ + int type = PROMPT_MAIN; + if (ed->l_scope_type) { + type = PROMPT_CONT; + + /* this is a temporary solution to show the current + * scope type, until prompts are implemented properly. */ + fputs(ed->l_scope_type, stdout); + } + + if (ed->l_continuations > 0) { + type = PROMPT_CONT; + } + + fputs(ed->l_prompt[type], stdout); + fflush(stdout); +} + +size_t prompt_length(struct line_ed *ed, int prompt_id) +{ + return strlen(ed->l_prompt[prompt_id]); +} diff --git a/toolchain/mxdbg/client/line-ed/prompt.h b/toolchain/mxdbg/client/line-ed/prompt.h new file mode 100644 index 0000000..eb135e3 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/prompt.h @@ -0,0 +1,12 @@ +#ifndef LINE_ED_PROMPT_H_ +#define LINE_ED_PROMPT_H_ + +#define PROMPT_MAIN 0 +#define PROMPT_CONT 1 + +struct line_ed; + +extern void show_prompt(struct line_ed *ed); +extern size_t prompt_length(struct line_ed *ed, int prompt_id); + +#endif diff --git a/toolchain/mxdbg/client/line-ed/refresh.c b/toolchain/mxdbg/client/line-ed/refresh.c new file mode 100644 index 0000000..c9f86f7 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/refresh.c @@ -0,0 +1,267 @@ +#include "refresh.h" + +#include "buffer.h" +#include "cursor.h" +#include "line-ed.h" + +#include +#include +#include +#include +#include + +/* prints the provided string to the terminal, applying any relevant highlight + * ranges. this function prints all characters in `s` until it encounters a null + * char (\0) or linefeed (\n). + * + * the (x, y) coordinates provided should be the data coordinates of the + * first character in `s`. + */ +void print_text(struct line_ed *ed, size_t x, size_t y, const fx_wchar *s) +{ + fx_tty *tty = ed->l_tty; + + for (size_t i = 0; s[i] != '\n' && s[i] != '\0'; i++) { + fx_stream_write_char(fx_stdout, s[i]); + } +} + +void print_buffer(struct line_ed *ed) +{ + const fx_wchar *line_buf = ed->l_buf; + size_t line_len = fx_wstrcspn(line_buf, L"\n"); + + unsigned int y = 0; + + while (1) { + print_text(ed, 0, y, line_buf); + + line_buf += line_len; + if (*line_buf == '\n') { + line_buf++; + } + + if (*line_buf == '\0') { + break; + } + + y++; + line_len = fx_wstrcspn(line_buf, L"\n"); + + fputc('\r', stdout); + fputc('\n', stdout); + } +} + +/* this function is called after a character is inserted into the line_ed + *buffer. the function performs the following steps: + * 1. get a pointer to the start of the line that was modified. + * 2. determine the first character in the line that needs to be redrawn. + * this may result in an append, a partial reprint, or a full reprint. + * 3. re-print the relevant portion of the buffer: + * for an append (a character added to the end of the line): + * * write the inserted char to the terminal. + * * done. + * for a partial reprint: + * * clear all printed chars from the logical cursor position to + *the end of the line. + * * print the portion of the line corresponding to the cleared + *section. + * * move the physical (terminal) cursor backwards until its + *position matches the logical (line_ed) cursor. for a full reprint: + * * same as a partial reprint except that, rather than reprinting + * from the logical cursor position, the entire line is + *reprinted. + */ +void put_refresh(struct line_ed *ed, struct refresh_state *state) +{ + /* get the data for the line that is being refreshed */ + const fx_wchar *line_buf = line_start(ed, ed->l_cursor_y); + size_t line_len = fx_wstrcspn(line_buf, L"\n"); + + /* the index of the first char in line_buf that needs to be reprinted */ + size_t start_x = state->r_prev_cursor_x; + + /* the distance between the first char to be reprinted and the end + * of the line. + * the physical cursor will be moved back by this amount after the + * line is reprinted. */ + long cursor_rdelta = (long)(line_len - start_x); + + if (ed->l_flags & LINE_ED_FULL_REPRINT) { + if (start_x) { + // fprintf(stdout, "\033[%uD", start_x); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_CURSOR, + -(long long)start_x); + } + + start_x = 0; + } + + print_text(ed, start_x, ed->l_cursor_y, line_buf + start_x); + + /* decrement the rdelta (move the cursor back one fewer cells), + * so that the physical cursor will be placed AFTER the character that + * was just inserted. */ + cursor_rdelta--; + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -cursor_rdelta); + +#if 0 + for (unsigned int i = 0; i < cursor_rdelta; i++) { + fputs("\010", stdout); + } +#endif + + fflush(stdout); +} + +/* this function is called after a character is removed from the line_ed buffer. + * IF the character was a linefeed. + * + * this is separate from backspace_simple_refresh because, in this situation, + * the cursor position depends on the length of the previous line before + * the linefeed was deleted, and we have to reprint every line following the + * two that were combined. + */ +void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state) +{ + /* get the data for the line that is being refreshed. + * relative to where the cursor was before the linefeed was deleted, + * this is the PREVIOUS line. */ + const fx_wchar *line_buf = line_start(ed, ed->l_cursor_y); + size_t line_len = fx_wstrcspn(line_buf, L"\n"); + + /* the index of the first char in line_buf that needs to be reprinted. + * subtract one to account for the linefeed that has since been deleted. + */ + size_t start_x = state->r_prev_line_len - 1; + + /* the column to move the physical cursor to after it has been moved + * to the previous line. + * NOTE that this number includes the length of the prompt! + * we add 1 to start_x to ensure that the cursor is moved to the cell + * AFTER the last char of the line. */ + size_t new_x; + line_ed_coords_to_physical_coords( + ed, + start_x + 1, + ed->l_cursor_y, + &new_x, + NULL); + + /* the physical cursor is currently at the beginning of the line that + * has just been moved up. from here, clear this line and the rest + * from the screen. */ + // fputs("\033[J", stdout); + fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR); + + if (ed->l_flags & LINE_ED_FULL_REPRINT) { + /* next, move the physical cursor up and to the beginning of the + * previous line */ + size_t tmp_x; + line_ed_coords_to_physical_coords( + ed, + 0, + ed->l_cursor_y, + &tmp_x, + NULL); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, tmp_x); + // fprintf(stdout, "\033[A\033[%uG", tmp_x + 1); + start_x = 0; + } else { + /* next, move the physical cursor up and to the end of the + * previous line */ + // fprintf(stdout, "\033[A\033[%uG", new_x); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, new_x); + } + + /* now reprint all of the buffer lines, starting with the first of the + * two lines that were concatenated. */ + size_t ydiff = 0; + while (1) { + print_text( + ed, + start_x, + ed->l_cursor_y + ydiff, + line_buf + start_x); + + line_buf += line_len + 1; + line_len = fx_wstrcspn(line_buf, L"\n"); + start_x = 0; + + if (*line_buf == '\0') { + break; + } + + fputc('\r', stdout); + fputc('\n', stdout); + ydiff++; + } + + /* finally, move the cursor BACK to the point where the two lines + * were concatenated. */ + if (ydiff) { + // fprintf(stdout, "\033[%uA", ydiff); + fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, ydiff); + } + + // fprintf(stdout, "\033[%uG", new_x); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, new_x); + fflush(stdout); +} + +/* this function is called after a character is removed from the line_ed buffer. + * IF the character was not a linefeed. + */ +void backspace_simple_refresh(struct line_ed *ed, struct refresh_state *state) +{ + /* get the data for the line that is being refreshed */ + const fx_wchar *line_buf = line_start(ed, ed->l_cursor_y); + size_t line_len = fx_wstrcspn(line_buf, L"\n"); + + /* the index of the first char in line_buf that needs to be reprinted */ + size_t start_x = ed->l_cursor_x; + // get_data_cursor_position(ed, &start_x, NULL); + + /* the distance between the first char to be reprinted and the end + * of the line. + * the physical cursor will be moved back by this amount after the + * line is reprinted. */ + long long cursor_rdelta = (long long)(line_len - start_x); + + if (ed->l_flags & LINE_ED_FULL_REPRINT) { + if (start_x) { + // fprintf(stdout, "\033[%uD", start_x); + fx_tty_move_cursor_x( + ed->l_tty, + FX_TTY_POS_CURSOR, + -(long long)start_x); + } + + start_x = 0; + } + + // fputc('\010', stdout); + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -1); + print_text(ed, start_x, ed->l_cursor_y, line_buf + start_x); + fputc(' ', stdout); + + /* increment the rdelta (move the cursor back one more cell), so + * that the cursor will appear to move back one cell (to accord with + * the fact that backspace was just pressed) */ + cursor_rdelta++; + + fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -cursor_rdelta); + +#if 0 + for (unsigned int i = 0; i < cursor_rdelta; i++) { + fputs("\010", stdout); + } +#endif + + fflush(stdout); +} diff --git a/toolchain/mxdbg/client/line-ed/refresh.h b/toolchain/mxdbg/client/line-ed/refresh.h new file mode 100644 index 0000000..df380c5 --- /dev/null +++ b/toolchain/mxdbg/client/line-ed/refresh.h @@ -0,0 +1,33 @@ +#ifndef LINE_ED_REFRESH_H_ +#define LINE_ED_REFRESH_H_ + +#include +#include + +struct line_ed; + +struct refresh_state { + /* cursor position before the update was performed (excluding the + * prompt) */ + size_t r_prev_cursor_x, r_prev_cursor_y; + /* when a backspace results in two separate lines being combined, + * this property contains the length of the first of the two combined + * lines BEFORE the concotenation was performed */ + size_t r_prev_line_len; +}; + +extern void print_text( + struct line_ed *ed, + size_t x, + size_t y, + const fx_wchar *s); +extern void print_buffer(struct line_ed *ed); +extern void put_refresh(struct line_ed *ed, struct refresh_state *state); +extern void backspace_nl_refresh( + struct line_ed *ed, + struct refresh_state *state); +extern void backspace_simple_refresh( + struct line_ed *ed, + struct refresh_state *state); + +#endif diff --git a/toolchain/mxdbg/client/main.c b/toolchain/mxdbg/client/main.c new file mode 100644 index 0000000..7e1ea05 --- /dev/null +++ b/toolchain/mxdbg/client/main.c @@ -0,0 +1,105 @@ +#include "context.h" +#include "event.h" +#include "repl.h" + +#include +#include +#include +#include +#include + +static struct debug_context ctx = {0}; + +enum { + CMD_ROOT, + + OPT_REMOTE, + OPT_REMOTE_ADDRESS, + OPT_REMOTE_PORT, +}; + +static void on_event(struct debug_context *ctx, struct packet *packet) +{ + event_handle(ctx, packet); +} + +static int mxdbg( + const fx_command *self, + const fx_arglist *opt, + const fx_array *args) +{ + debug_context_init(&ctx); + ctx.ctx_on_event = on_event; + + char *ep = NULL; + + const char *ip_address = NULL; + const char *port_cstr = NULL; + fx_arglist_get_string( + opt, + OPT_REMOTE, + OPT_REMOTE_ADDRESS, + 0, + &ip_address); + fx_arglist_get_string(opt, OPT_REMOTE, OPT_REMOTE_PORT, 0, &port_cstr); + + int err = 0; + if (ip_address && port_cstr) { + long port = strtol(port_cstr, &ep, 10); + if (*ep) { + fprintf(stderr, + "'%s' is not a valid port number.\n", + port_cstr); + return -1; + } + + printf("connecting to %s:%ld...\n", ip_address, port); + err = debug_context_connect_net(&ctx, ip_address, port); + } else { + printf("no target specified\n"); + return 0; + } + + if (err != 0) { + printf("connection failed\n"); + return err; + } + + debug_context_start_threads(&ctx); + err = repl(&ctx); + + debug_context_cleanup(&ctx); + + return err; +} + +FX_COMMAND(CMD_ROOT, FX_COMMAND_INVALID_ID) +{ + FX_COMMAND_NAME("mxdbg"); + FX_COMMAND_DESC("Magenta kernel debugger."); + FX_COMMAND_FLAGS(FX_COMMAND_SHOW_HELP_BY_DEFAULT); + FX_COMMAND_FUNCTION(mxdbg); + + FX_COMMAND_OPTION(OPT_REMOTE) + { + FX_OPTION_LONG_NAME("remote"); + FX_OPTION_SHORT_NAME('r'); + FX_OPTION_DESC("The TCP/IP address and port to connect to."); + FX_OPTION_ARG(OPT_REMOTE_ADDRESS) + { + FX_ARG_NAME("ip-address"); + FX_ARG_NR_VALUES(1); + } + + FX_OPTION_ARG(OPT_REMOTE_PORT) + { + FX_ARG_NAME("port"); + FX_ARG_NR_VALUES(1); + } + } +} + +int main(int argc, const char **argv) +{ + return fx_command_dispatch(CMD_ROOT, argc, argv); +} diff --git a/toolchain/mxdbg/client/memory.c b/toolchain/mxdbg/client/memory.c new file mode 100644 index 0000000..d23be07 --- /dev/null +++ b/toolchain/mxdbg/client/memory.c @@ -0,0 +1,19 @@ +#include "command.h" + +#include + +static int memory( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + printf("inspect memory\n"); + return 0; +} + +const struct command cmd_memory = { + .cmd_name = "memory", + .cmd_callback = memory, +}; diff --git a/toolchain/mxdbg/client/packet.c b/toolchain/mxdbg/client/packet.c new file mode 100644 index 0000000..ac7d3b8 --- /dev/null +++ b/toolchain/mxdbg/client/packet.c @@ -0,0 +1,15 @@ +#include "packet.h" + +#include + +struct packet *packet_create(size_t packet_size) +{ + size_t real_packet_size = sizeof(struct packet) + packet_size + - sizeof(struct mxdbg_packet); + return calloc(1, packet_size); +} + +void packet_destroy(struct packet *packet) +{ + free(packet); +} diff --git a/toolchain/mxdbg/client/packet.h b/toolchain/mxdbg/client/packet.h new file mode 100644 index 0000000..dcf3231 --- /dev/null +++ b/toolchain/mxdbg/client/packet.h @@ -0,0 +1,16 @@ +#ifndef PACKET_H_ +#define PACKET_H_ + +#include +#include + +struct packet { + fx_queue_entry p_entry; + struct mxdbg_packet p_packet; + /* excess packet data here */ +}; + +extern struct packet *packet_create(size_t packet_size); +extern void packet_destroy(struct packet *packet); + +#endif diff --git a/toolchain/mxdbg/client/quit.c b/toolchain/mxdbg/client/quit.c new file mode 100644 index 0000000..446a909 --- /dev/null +++ b/toolchain/mxdbg/client/quit.c @@ -0,0 +1,18 @@ +#include "command.h" +#include "context.h" + +static int quit( + const struct command *cmd, + struct debug_context *ctx, + int argc, + const char **argv, + void *argp) +{ + ctx->ctx_quit = true; + return 0; +} + +const struct command cmd_quit = { + .cmd_name = "quit", + .cmd_callback = quit, +}; diff --git a/toolchain/mxdbg/client/repl.c b/toolchain/mxdbg/client/repl.c new file mode 100644 index 0000000..a65a1df --- /dev/null +++ b/toolchain/mxdbg/client/repl.c @@ -0,0 +1,130 @@ +#include "command.h" +#include "context.h" +#include "line-ed/line-ed.h" + +#include +#include + +static struct line_ed *ed = NULL; + +extern struct command cmd_quit; +extern struct command cmd_info; +extern struct command cmd_memory; +extern struct command cmd_breakpoint; +extern struct command cmd_attach; +extern struct command cmd_continue; + +static const struct command *commands[] = { + &cmd_quit, + &cmd_info, + &cmd_memory, + &cmd_breakpoint, + &cmd_attach, + &cmd_continue, +}; +static const size_t nr_commands = sizeof commands / sizeof commands[0]; + +static int tokenise_command(const char *s, int *out_argc, char ***out_argv) +{ + fx_array *args = fx_array_create(); + if (!args) { + return -1; + } + + fx_string *str = fx_string_create_from_cstr(s); + if (!str) { + fx_array_unref(args); + return -1; + } + + const char *delim = " "; + const fx_iterator *it + = fx_string_tokenise(str, &delim, 1, FX_STRING_TOK_F_NORMAL); + fx_foreach(tok, it) + { + const char *tok_cstr = NULL; + fx_value_get_cstr(tok, &tok_cstr); + fx_string *tok_str = fx_string_create_from_cstr(tok_cstr); + fx_array_push_back(args, FX_VALUE_OBJECT(tok_str)); + fx_string_unref(tok_str); + } + + fx_string_unref(str); + + int argc = fx_array_get_size(args); + char **argv = calloc(argc, sizeof *argv); + if (!argv) { + fx_array_unref(args); + return -1; + } + + for (int i = 0; i < argc; i++) { + const fx_value *arg_value = fx_array_get_ref(args, i); + const char *arg_cstr = NULL; + fx_value_get_cstr(arg_value, &arg_cstr); + + char *arg_cstr_owned = fx_strdup(arg_cstr); + argv[i] = arg_cstr_owned; + } + fx_array_unref(args); + + *out_argc = argc; + *out_argv = argv; + + return 0; +} + +int repl(struct debug_context *ctx) +{ + ed = line_ed_create(); + if (!ed) { + return -1; + } + + int ret = 0; + line_ed_set_flags(ed, LINE_ED_NO_TRAILING_LINEFEED); + + fx_stringstream *line = fx_stringstream_create(); + while (!ctx->ctx_quit && line_ed_readline(ed, line) != LINE_ED_EOF) { + const char *cmd_cstr = fx_stringstream_ptr(line); + int argc; + char **argv = NULL; + if (tokenise_command(cmd_cstr, &argc, &argv) != 0) { + ret = -1; + break; + } + + if (argc == 0) { + goto skip; + } + + const struct command *cmd + = command_find(commands, nr_commands, argv[0]); + if (cmd) { + command_execute( + cmd, + ctx, + argc - 1, + (const char **)argv + 1, + NULL); + } + + skip: + fx_stringstream_reset(line); + } + + fx_stringstream_unref(line); + line_ed_destroy(ed); + ed = NULL; + return ret; +} + +int repl_suspend(void) +{ + return line_ed_suspend(ed); +} + +int repl_resume(void) +{ + return line_ed_resume(ed); +} diff --git a/toolchain/mxdbg/client/repl.h b/toolchain/mxdbg/client/repl.h new file mode 100644 index 0000000..c3039f0 --- /dev/null +++ b/toolchain/mxdbg/client/repl.h @@ -0,0 +1,10 @@ +#ifndef REPL_H_ +#define REPL_H_ + +#include "context.h" + +extern int repl(struct debug_context *ctx); +extern int repl_suspend(void); +extern int repl_resume(void); + +#endif diff --git a/toolchain/mxdbg/lib/CMakeLists.txt b/toolchain/mxdbg/lib/CMakeLists.txt new file mode 100644 index 0000000..d45ed3f --- /dev/null +++ b/toolchain/mxdbg/lib/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB sources *.c *.h include/mxdbg/*.h) + +add_library(libmxdbg STATIC ${sources}) +set_target_properties(libmxdbg PROPERTIES PREFIX "") diff --git a/toolchain/mxdbg/server/CMakeLists.txt b/toolchain/mxdbg/server/CMakeLists.txt new file mode 100644 index 0000000..c26d77b --- /dev/null +++ b/toolchain/mxdbg/server/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB sources *.c *.h) + +add_executable(mxdbg-server ${sources}) +target_link_libraries(mxdbg-server magenta_debug) diff --git a/toolchain/mxdbg/server/main.c b/toolchain/mxdbg/server/main.c new file mode 100644 index 0000000..1ffdcea --- /dev/null +++ b/toolchain/mxdbg/server/main.c @@ -0,0 +1,4 @@ +int main(int argc, const char **argv) +{ + return 0; +}