diff --git a/lib/cfg-source.c b/lib/cfg-source.c index 4695c4022..50fec1296 100644 --- a/lib/cfg-source.c +++ b/lib/cfg-source.c @@ -55,25 +55,24 @@ _print_underline(const gchar *line, gint whitespace_before, gint number_of_caret } static void -_print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gint error_index) +_print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gsize num_lines, gint start_line) { gint line_ndx; gchar line_prefix[12]; - gint error_length = yylloc->last_line - yylloc->first_line + 1; - for (line_ndx = 0; lines[line_ndx]; line_ndx++) + for (line_ndx = 0; line_ndx < num_lines; line_ndx++) { - gint lineno = yylloc->first_line + line_ndx - error_index; + gint lineno = start_line + line_ndx; const gchar *line = lines[line_ndx]; gint line_len = strlen(line); gboolean line_ends_with_newline = line_len > 0 && line[line_len - 1] == '\n'; _format_source_prefix(line_prefix, sizeof(line_prefix), lineno, - line_ndx >= error_index && line_ndx < error_index + error_length); + lineno >= yylloc->first_line && lineno <= yylloc->last_line); fprintf(stderr, "%-8s%s%s", line_prefix, line, line_ends_with_newline ? "" : "\n"); - if (line_ndx == error_index) + if (lineno == yylloc->first_line) { /* print the underline right below the source line we just printed */ fprintf(stderr, "%-8s", line_prefix); @@ -84,20 +83,24 @@ _print_underlined_source_block(const CFG_LTYPE *yylloc, gchar **lines, gint erro multi_line ? strlen(&line[yylloc->first_column]) + 1 : yylloc->last_column - yylloc->first_column); } - else if (line_ndx >= error_index + CONTEXT) - break; } } static void -_report_file_location(const gchar *filename, const CFG_LTYPE *yylloc) +_report_file_location(const gchar *filename, const CFG_LTYPE *yylloc, gint start_line) { FILE *f; gint lineno = 0; gsize buflen = 65520; gchar *buf = g_malloc(buflen); GPtrArray *context = g_ptr_array_new(); - gint error_index = 0; + gint end_line = start_line + 2*CONTEXT; + + if (start_line <= 0) + { + start_line = yylloc->first_line > CONTEXT ? yylloc->first_line - CONTEXT : 1; + end_line = yylloc->first_line + CONTEXT; + } f = fopen(filename, "r"); if (f) @@ -105,49 +108,64 @@ _report_file_location(const gchar *filename, const CFG_LTYPE *yylloc) while (fgets(buf, buflen, f)) { lineno++; - if (lineno > (gint) yylloc->first_line + CONTEXT) + if (lineno > end_line) break; - else if (lineno < (gint) yylloc->first_line - CONTEXT) + else if (lineno < start_line) continue; - else if (lineno == yylloc->first_line) - error_index = context->len; g_ptr_array_add(context, g_strdup(buf)); } - /* NOTE: do we have the appropriate number of lines? */ - if (lineno <= yylloc->first_line) - goto exit; - g_ptr_array_add(context, NULL); fclose(f); } if (context->len > 0) - _print_underlined_source_block(yylloc, (gchar **) context->pdata, error_index); + _print_underlined_source_block(yylloc, (gchar **) context->pdata, context->len, start_line); -exit: g_free(buf); g_ptr_array_foreach(context, (GFunc) g_free, NULL); g_ptr_array_free(context, TRUE); } +/* this will report source content from the buffer, but use the line numbers + * of the file where the block was defined. + * + * buffer_* => tracks buffer related information + * file_* => tracks file related information + */ static void _report_buffer_location(const gchar *buffer_content, const CFG_LTYPE *file_lloc, const CFG_LTYPE *buf_lloc) { - gchar **lines = g_strsplit(buffer_content, "\n", buf_lloc->first_line + CONTEXT + 1); - gint num_lines = g_strv_length(lines); + gchar **buffer_lines = g_strsplit(buffer_content, "\n", buf_lloc->first_line + CONTEXT + 1); + gint buffer_num_lines = g_strv_length(buffer_lines); - if (num_lines <= buf_lloc->first_line) + if (buffer_num_lines <= buf_lloc->first_line) goto exit; - gint start = buf_lloc->first_line - 1 - CONTEXT; - gint error_index = CONTEXT; - if (start < 0) - { - error_index += start; - start = 0; - } - _print_underlined_source_block(file_lloc, &lines[start], error_index); + /* the line number in the file, which we report in the source dump, 1 based */ + gint range_backwards = CONTEXT; + if (file_lloc->first_line <= range_backwards) + range_backwards = file_lloc->first_line - 1; + + /* the index of the line in the buffer where we start printing 0-based */ + gint buffer_start_index = buf_lloc->first_line - 1 - range_backwards; + if (buffer_start_index < 0) + buffer_start_index = 0; + + _print_underlined_source_block(file_lloc, &buffer_lines[buffer_start_index], buffer_num_lines - buffer_start_index, + file_lloc->first_line - range_backwards); exit: - g_strfreev(lines); + g_strfreev(buffer_lines); +} + +gboolean +cfg_source_print_source_text(const gchar *filename, gint line, gint column, gint start_line) +{ + CFG_LTYPE yylloc = {0}; + + yylloc.name = filename; + yylloc.first_line = yylloc.last_line = line; + yylloc.first_column = yylloc.last_column = column; + _report_file_location(yylloc.name, &yylloc, start_line); + return TRUE; } gboolean @@ -155,7 +173,7 @@ cfg_source_print_source_context(CfgLexer *lexer, CfgIncludeLevel *level, const C { if (level->include_type == CFGI_FILE) { - _report_file_location(yylloc->name, yylloc); + _report_file_location(yylloc->name, yylloc, -1); } else if (level->include_type == CFGI_BUFFER) { diff --git a/lib/cfg-source.h b/lib/cfg-source.h index 51e81fbd7..55313b393 100644 --- a/lib/cfg-source.h +++ b/lib/cfg-source.h @@ -26,6 +26,8 @@ #include "cfg-lexer.h" +gboolean cfg_source_print_source_text(const gchar *filename, gint line, gint column, gint offset); + /* These functions are only available during parsing */ gboolean cfg_source_print_source_context(CfgLexer *lexer, CfgIncludeLevel *level, const CFG_LTYPE *yylloc); gboolean cfg_source_extract_source_text(CfgLexer *lexer, const CFG_LTYPE *yylloc, GString *result); diff --git a/lib/debugger/debugger-main.c b/lib/debugger/debugger-main.c index 7f2dc932d..275043f65 100644 --- a/lib/debugger/debugger-main.c +++ b/lib/debugger/debugger-main.c @@ -32,10 +32,11 @@ static Debugger *current_debugger; static gboolean _pipe_hook(LogPipe *s, LogMessage *msg, const LogPathOptions *path_options) { - if (msg->flags & LF_STATE_TRACING) - return debugger_perform_tracing(current_debugger, s, msg); - else + if (debugger_is_to_stop(current_debugger, s, msg)) return debugger_stop_at_breakpoint(current_debugger, s, msg); + else if (debugger_is_to_trace(current_debugger, s, msg)) + return debugger_perform_tracing(current_debugger, s, msg); + return TRUE; } gboolean diff --git a/lib/debugger/debugger.c b/lib/debugger/debugger.c index 9efac8938..36891dea2 100644 --- a/lib/debugger/debugger.c +++ b/lib/debugger/debugger.c @@ -30,24 +30,46 @@ #include "timeutils/misc.h" #include "compat/time.h" #include "scratch-buffers.h" +#include "cfg-source.h" #include #include #include +#include struct _Debugger { + /* debugger_get_mode() assumes this comes as the first field */ + DebuggerMode mode; Tracer *tracer; struct iv_signal sigint; MainLoop *main_loop; GlobalConfig *cfg; - gchar *command_buffer; - LogTemplate *display_template; + GThread *debugger_thread; BreakpointSite *breakpoint_site; struct timespec last_trace_event; - GThread *debugger_thread; + gboolean starting_up; + + /* user interface related state */ + gchar *command_buffer; + struct + { + gchar *filename; + gint line; + gint column; + gint list_start; + } current_location; + LogTemplate *display_template; }; +static void +_set_command(Debugger *self, gchar *new_command) +{ + if (self->command_buffer) + g_free(self->command_buffer); + self->command_buffer = g_strdup(new_command); +} + static gboolean _format_nvpair(NVHandle handle, const gchar *name, @@ -108,34 +130,31 @@ _display_msg_with_template_string(Debugger *self, LogMessage *msg, const gchar * } static void -_display_source_line(LogExprNode *expr_node) +_set_current_location(Debugger *self, LogExprNode *expr_node) { - FILE *f; - gint lineno = 1; - gchar buf[1024]; - - if (!expr_node || !expr_node->filename) - return; - - f = fopen(expr_node->filename, "r"); - if (f) + g_free(self->current_location.filename); + if (expr_node) { - while (fgets(buf, sizeof(buf), f) && lineno < expr_node->line) - lineno++; - if (lineno != expr_node->line) - buf[0] = 0; - fclose(f); + self->current_location.filename = g_strdup(expr_node->filename); + self->current_location.line = expr_node->line; + self->current_location.column = expr_node->column; + self->current_location.list_start = expr_node->line - 5; } else { - buf[0] = 0; + memset(&self->current_location, 0, sizeof(self->current_location)); } - printf("%-8d %s", expr_node->line, buf); - if (buf[0] == 0 || buf[strlen(buf) - 1] != '\n') - putc('\n', stdout); - fflush(stdout); } +static void +_display_source_line(Debugger *self) +{ + if (self->current_location.filename) + cfg_source_print_source_text(self->current_location.filename, self->current_location.line, + self->current_location.column, self->current_location.list_start); + else + puts("Unable to list source, no current location set"); +} static gboolean _cmd_help(Debugger *self, gint argc, gchar *argv[]) @@ -147,9 +166,12 @@ _cmd_help(Debugger *self, gint argc, gchar *argv[]) "The following commands are available:\n\n" " help, h, ? Display this help\n" " info, i Display information about the current execution state\n" + " list, l Display source code at the current location\n" " continue, c Continue until the next breakpoint\n" + " step, s Single step\n" + " follow, f Follow this message, ignoring any other breakpoints\n" " display Set the displayed message template\n" - " trace, t Display timing information as the message traverses the config\n" + " trace, t Trace this message along the configuration\n" " print, p Print the current log message\n" " drop, d Drop the current message\n" " quit, q Tell syslog-ng to exit\n" @@ -161,6 +183,7 @@ _cmd_help(Debugger *self, gint argc, gchar *argv[]) "Stopped on an interrupt.\n" "The following commands are available:\n\n" " help, h, ? Display this help\n" + " list, l Display source code at the current location\n" " continue, c Continue until the next breakpoint\n" " quit, q Tell syslog-ng to exit\n" ); @@ -168,11 +191,6 @@ _cmd_help(Debugger *self, gint argc, gchar *argv[]) return TRUE; } -static gboolean -_cmd_continue(Debugger *self, gint argc, gchar *argv[]) -{ - return FALSE; -} static gboolean _cmd_print(Debugger *self, gint argc, gchar *argv[]) @@ -217,21 +235,6 @@ _cmd_drop(Debugger *self, gint argc, gchar *argv[]) return FALSE; } -static gboolean -_cmd_trace(Debugger *self, gint argc, gchar *argv[]) -{ - self->breakpoint_site->msg->flags |= LF_STATE_TRACING; - return FALSE; -} - -static gboolean -_cmd_quit(Debugger *self, gint argc, gchar *argv[]) -{ - main_loop_exit(self->main_loop); - if (self->breakpoint_site) - self->breakpoint_site->drop = TRUE; - return FALSE; -} static gboolean _cmd_info_pipe(Debugger *self, LogPipe *pipe) @@ -239,7 +242,7 @@ _cmd_info_pipe(Debugger *self, LogPipe *pipe) gchar buf[1024]; printf("LogPipe %p at %s\n", pipe, log_expr_node_format_location(pipe->expr_node, buf, sizeof(buf))); - _display_source_line(pipe->expr_node); + _display_source_line(self); return TRUE; } @@ -258,6 +261,90 @@ _cmd_info(Debugger *self, gint argc, gchar *argv[]) return TRUE; } +static gboolean +_cmd_list(Debugger *self, gint argc, gchar *argv[]) +{ + gint shift = 11; + if (argc >= 2) + { + if (strcmp(argv[1], "+") == 0) + shift = 11; + else if (strcmp(argv[1], "-") == 0) + shift = -11; + else if (strcmp(argv[1], ".") == 0) + { + shift = 0; + if (self->breakpoint_site) + _set_current_location(self, self->breakpoint_site->pipe->expr_node); + } + else if (isdigit(argv[1][0])) + { + gint target_lineno = atoi(argv[1]); + if (target_lineno <= 0) + target_lineno = 1; + self->current_location.list_start = target_lineno; + } + /* drop any arguments for repeated execution */ + _set_command(self, "l"); + } + _display_source_line(self); + if (shift) + self->current_location.list_start += shift; + return TRUE; +} + +static inline void +_set_mode(Debugger *self, DebuggerMode new_mode, gboolean trace_message) +{ + self->mode = new_mode; + if (self->breakpoint_site) + { + if (trace_message) + self->breakpoint_site->msg->flags |= LF_STATE_TRACING; + else + self->breakpoint_site->msg->flags &= ~LF_STATE_TRACING; + } +} + +static gboolean +_cmd_continue(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_WAITING_FOR_BREAKPOINT, FALSE); + return FALSE; +} + +static gboolean +_cmd_step(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_WAITING_FOR_STEP, FALSE); + return FALSE; +} + +static gboolean +_cmd_trace(Debugger *self, gint argc, gchar *argv[]) +{ + clock_gettime(CLOCK_MONOTONIC, &self->last_trace_event); + _set_mode(self, DBG_FOLLOW_AND_TRACE, TRUE); + return FALSE; +} + +static gboolean +_cmd_follow(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_FOLLOW_AND_BREAK, TRUE); + return FALSE; +} + +static gboolean +_cmd_quit(Debugger *self, gint argc, gchar *argv[]) +{ + _set_mode(self, DBG_QUIT, FALSE); + if (self->breakpoint_site) + self->breakpoint_site->drop = TRUE; + main_loop_exit(self->main_loop); + return FALSE; +} + typedef gboolean (*DebuggerCommandFunc)(Debugger *self, gint argc, gchar *argv[]); struct @@ -272,8 +359,14 @@ struct { "?", _cmd_help }, { "continue", _cmd_continue }, { "c", _cmd_continue }, + { "step", _cmd_step }, + { "s", _cmd_step }, + { "follow", _cmd_follow, .requires_breakpoint_site = TRUE }, + { "f", _cmd_follow, .requires_breakpoint_site = TRUE }, { "print", _cmd_print, .requires_breakpoint_site = TRUE }, { "p", _cmd_print, .requires_breakpoint_site = TRUE }, + { "list", _cmd_list, }, + { "l", _cmd_list, }, { "display", _cmd_display }, { "drop", _cmd_drop, .requires_breakpoint_site = TRUE }, { "d", _cmd_drop, .requires_breakpoint_site = TRUE }, @@ -316,6 +409,7 @@ debugger_register_command_fetcher(FetchCommandFunc fetcher) fetch_command_func = fetcher; } + static void _fetch_command(Debugger *self) { @@ -323,16 +417,8 @@ _fetch_command(Debugger *self) command = fetch_command_func(); if (command && strlen(command) > 0) - { - if (self->command_buffer) - g_free(self->command_buffer); - self->command_buffer = command; - } - else - { - if (command) - g_free(command); - } + _set_command(self, command); + g_free(command); } static gboolean @@ -379,19 +465,20 @@ static void _handle_interactive_prompt(Debugger *self) { gchar buf[1024]; - LogPipe *current_pipe; if (self->breakpoint_site) { - current_pipe = self->breakpoint_site->pipe; + LogPipe *current_pipe = self->breakpoint_site->pipe; + _set_current_location(self, current_pipe->expr_node); printf("Breakpoint hit %s\n", log_expr_node_format_location(current_pipe->expr_node, buf, sizeof(buf))); - _display_source_line(current_pipe->expr_node); + _display_source_line(self); _display_msg_with_template(self, self->breakpoint_site->msg, self->display_template); } - else + else if (!self->starting_up) { - printf("Stopping on interrupt, message related commands are unavailable...\n"); + _set_current_location(self, NULL); + printf(" Stopping on Interrupt...\n"); } while (1) { @@ -404,20 +491,64 @@ _handle_interactive_prompt(Debugger *self) printf("(continuing)\n"); } +static gboolean +_debugger_wait_for_event(Debugger *self) +{ + while (1) + { + if (!tracer_wait_for_event(self->tracer, &self->breakpoint_site)) + return FALSE; + + /* this is an interrupt, let's handle it now */ + if (!self->breakpoint_site) + return TRUE; + + /* is this an event we are still interested in? */ + if (debugger_is_to_stop(self, self->breakpoint_site->pipe, self->breakpoint_site->msg)) + return TRUE; + + /* not interesting now, let's resume and wait for another */ + tracer_resume_after_event(self->tracer, self->breakpoint_site); + } + return TRUE; +} + +static void +_debugger_ack_event(Debugger *self) +{ + tracer_resume_after_event(self->tracer, self->breakpoint_site); +} + static gpointer _debugger_thread_func(Debugger *self) { app_thread_start(); - printf("Waiting for breakpoint...\n"); + self->breakpoint_site = NULL; + + printf("axosyslog interactive debugger\n" + "Copyright (c) 2024 Axoflow and contributors\n\n" + + "This program comes with ABSOLUTELY NO WARRANTY;\n" + "This is free software, and you are welcome to redistribute it\n" + "under certain conditions;\n" + "See https://github.com/axoflow/axosyslog/blob/main/COPYING\n" + "License LGPLV2.1+ and GPLv2+\n\n" + + "For help, type \"help\".\n"); + + self->starting_up = TRUE; + _handle_interactive_prompt(self); + self->starting_up = FALSE; while (1) { - self->breakpoint_site = NULL; - if (!tracer_wait_for_event(self->tracer, &self->breakpoint_site)) + if (!_debugger_wait_for_event(self)) break; _handle_interactive_prompt(self); - tracer_resume_after_event(self->tracer, self->breakpoint_site); + + _debugger_ack_event(self); } + scratch_buffers_explicit_gc(); app_thread_stop(); return NULL; @@ -496,7 +627,7 @@ debugger_new(MainLoop *main_loop, GlobalConfig *cfg) self->tracer = tracer_new(cfg); self->cfg = cfg; self->display_template = log_template_new(cfg, NULL); - self->command_buffer = g_strdup("help"); + _set_command(self, "help"); log_template_compile(self->display_template, "$DATE $HOST $MSGHDR$MSG", NULL); return self; } @@ -504,6 +635,7 @@ debugger_new(MainLoop *main_loop, GlobalConfig *cfg) void debugger_free(Debugger *self) { + g_free(self->current_location.filename); log_template_unref(self->display_template); tracer_free(self->tracer); g_free(self->command_buffer); diff --git a/lib/debugger/debugger.h b/lib/debugger/debugger.h index a1c2b38ac..12506fc8a 100644 --- a/lib/debugger/debugger.h +++ b/lib/debugger/debugger.h @@ -27,11 +27,27 @@ #include "syslog-ng.h" #include "cfg.h" #include "mainloop.h" +#include "logpipe.h" + +typedef enum +{ + DBG_WAITING_FOR_STEP, + DBG_WAITING_FOR_BREAKPOINT, + DBG_FOLLOW_AND_BREAK, + DBG_FOLLOW_AND_TRACE, + DBG_QUIT, +} DebuggerMode; typedef struct _Debugger Debugger; -typedef gchar *(*FetchCommandFunc)(void); +static inline DebuggerMode +debugger_get_mode(Debugger *self) +{ + return *(DebuggerMode *) self; +} + +typedef gchar *(*FetchCommandFunc)(void); gchar *debugger_builtin_fetch_command(void); void debugger_register_command_fetcher(FetchCommandFunc fetcher); @@ -43,5 +59,41 @@ gboolean debugger_stop_at_breakpoint(Debugger *self, LogPipe *pipe, LogMessage * Debugger *debugger_new(MainLoop *main_loop, GlobalConfig *cfg); void debugger_free(Debugger *self); +static inline gboolean +debugger_is_to_stop(Debugger *self, LogPipe *pipe, LogMessage *msg) +{ + DebuggerMode mode = debugger_get_mode(self); + + switch (mode) + { + case DBG_WAITING_FOR_BREAKPOINT: + return (pipe->flags & PIF_BREAKPOINT); + + case DBG_WAITING_FOR_STEP: + return TRUE; + + case DBG_FOLLOW_AND_BREAK: + return (msg->flags & LF_STATE_TRACING); + + case DBG_FOLLOW_AND_TRACE: + return FALSE; + + case DBG_QUIT: + return FALSE; + + default: + g_assert_not_reached(); + } + return FALSE; +} + +static inline gboolean +debugger_is_to_trace(Debugger *self, LogPipe *pipe, LogMessage *msg) +{ + DebuggerMode mode = debugger_get_mode(self); + + return (mode == DBG_FOLLOW_AND_TRACE) && (msg->flags & LF_STATE_TRACING); + +} #endif diff --git a/lib/logpipe.h b/lib/logpipe.h index 8d5ae07f7..3b3b40c13 100644 --- a/lib/logpipe.h +++ b/lib/logpipe.h @@ -75,6 +75,8 @@ /* sync filterx state to message in right before calling queue() */ #define PIF_SYNC_FILTERX_TO_MSG 0x0200 +#define PIF_BREAKPOINT 0x0400 + /* private flags range, to be used by other LogPipe instances for their own purposes */ #define PIF_PRIVATE(x) ((x) << 16)