diff --git a/debug-test/Makefile b/debug-test/Makefile new file mode 100644 index 000000000..029f36736 --- /dev/null +++ b/debug-test/Makefile @@ -0,0 +1,12 @@ + +# Run as ./make -f debug-test/Makefile -X --debugger-stop=preaction, +# then do `watch Hell.`. + +UNEXPANDED=expanded + +all: test + echo "$(UNEXPANDED) all done!" + +test: + echo "$(UNEXPANDED) Hello" + echo "$(UNEXPANDED) Goodbye" diff --git a/doc/readthedocs/debugger/commands/breakpoints/break.rst b/doc/readthedocs/debugger/commands/breakpoints/break.rst index 182bdd3b3..5d7110b3b 100644 --- a/doc/readthedocs/debugger/commands/breakpoints/break.rst +++ b/doc/readthedocs/debugger/commands/breakpoints/break.rst @@ -37,4 +37,4 @@ Examples: .. seealso:: - :ref:`delete `. + :ref:`delete `, :ref:`watch `. \ No newline at end of file diff --git a/doc/readthedocs/debugger/commands/breakpoints/watch.rst b/doc/readthedocs/debugger/commands/breakpoints/watch.rst new file mode 100644 index 000000000..38ea3d6a2 --- /dev/null +++ b/doc/readthedocs/debugger/commands/breakpoints/watch.rst @@ -0,0 +1,24 @@ +.. index:: watch +.. _watch: + +Add a command watchpoint (`watch`) +------------------------------------- + +**watch** *regex* + +Add a "command watch" breakpoint that triggers before a command that is about to be executed matches the given regex. +An argument is the regex. + +Example: +++++++++ + +:: + + # 'watch' if something tries to delete "precious" directory + watch rm -[rf] precious + # Alternative regex + watch \brm\b.+precious + +.. seealso:: + + :ref:`delete `, :ref:`break `. diff --git a/libdebugger/Makefile.am b/libdebugger/Makefile.am index 4d37703b5..b8b02e980 100644 --- a/libdebugger/Makefile.am +++ b/libdebugger/Makefile.am @@ -33,6 +33,7 @@ noinst_HEADERS = \ command/help/step.h \ command/help/target.h \ command/help/up.h \ + command/help/watch.h \ command/help/where.h \ command/help/write.h \ file2line.h \ @@ -77,6 +78,7 @@ libdebugger_a_SOURCES = \ command/step.c \ command/target.c \ command/up.c \ + command/watch.c \ command/where.c \ command/write.c \ break.c \ diff --git a/libdebugger/break.c b/libdebugger/break.c index f38431df8..dcd81bb71 100644 --- a/libdebugger/break.c +++ b/libdebugger/break.c @@ -20,18 +20,29 @@ Boston, MA 02111-1307, USA. */ /** debugger command stack routines. */ #include +#include #include "break.h" +#include "cmd.h" #include "msg.h" #include "filedef.h" #include "print.h" +enum breakpoint_type +{ + LINE, + COMMAND_WATCH +}; + /*! Node for an item in the target call stack */ struct breakpoint_node { - file_t *p_target; - unsigned int i_num; - brkpt_mask_t brkpt_mask; - breakpoint_node_t *p_next; + enum breakpoint_type type; + file_t *p_target; + unsigned int i_num; + brkpt_mask_t brkpt_mask; + regex_t regex; + char *psz_text; + breakpoint_node_t *p_next; }; /** Pointers to top/bottom of current breakpoint target stack */ @@ -58,6 +69,7 @@ add_breakpoint (file_t *p_target, const brkpt_mask_t brkpt_mask) p_breakpoint_bottom->p_next = p_new; } p_breakpoint_bottom = p_new; + p_new->type = LINE; p_new->p_target = p_target; p_new->i_num = ++i_breakpoints; p_new->brkpt_mask = brkpt_mask; @@ -123,7 +135,13 @@ remove_breakpoint (unsigned int i, bool silent) if (p_prev) p_prev->p_next = p->p_next; - if (p->p_target->tracing) { + if (p->type == COMMAND_WATCH) { + dbg_msg(_("Command watchpoint %u cleared."), i); + regfree(&p->regex); + free(p->psz_text); + free(p); + return true; + } else if (p->p_target->tracing) { p->p_target->tracing = BRK_NONE; dbg_msg(_("Breakpoint %u on target `%s' cleared."), i, p->p_target->name); @@ -156,17 +174,169 @@ list_breakpoints (void) dbg_msg( "Num Type Disp Enb Mask Target Location"); for (p = p_breakpoint_top; p; p = p->p_next) { - printf("%3u breakpoint keep y 0x%02x %s", - p->i_num, - p->brkpt_mask, - p->p_target->name); - if (p->p_target->floc.filenm) { - printf(" at "); - print_floc_prefix(&(p->p_target->floc)); + if (p->type == LINE) { + printf("%3u breakpoint keep y 0x%02x %s", + p->i_num, + p->brkpt_mask, + p->p_target->name); + if (p->p_target->floc.filenm) { + printf(" at "); + print_floc_prefix(&(p->p_target->floc)); + } + } else { + printf("%3u command watchpoint %s", p->i_num, p->psz_text); } printf("\n"); } } + +bool +add_command_watchpoint(const char *psz_regex) +{ + int re_compile_status; + breakpoint_node_t *p_new = CALLOC (breakpoint_node_t, 1); + + if (!p_new) return false; + + re_compile_status = regcomp(&p_new->regex, psz_regex, REG_EXTENDED); + + // Not sure if regex_t can be memmoved, so initializing it in the allocated breakpoint_node_t. + if (re_compile_status != 0) { + char reg_error_message[100]; + regerror(re_compile_status, &p_new->regex, reg_error_message, sizeof(reg_error_message)-1); + dbg_msg("Could not compile regex: %s: %s", psz_regex, reg_error_message); + regfree(&p_new->regex); + free(p_new); + return false; + } + + p_new->type = COMMAND_WATCH; + p_new->psz_text = strdup(psz_regex); + p_new->i_num = ++i_breakpoints; + p_new->p_target = NULL; + p_new->brkpt_mask = 0; + + /* Add breakpoint to list of breakpoints. */ + if (!p_breakpoint_top) { + assert(!p_breakpoint_bottom); + p_breakpoint_top = p_breakpoint_bottom = p_new; + } else { + p_breakpoint_bottom->p_next = p_new; + } + p_breakpoint_bottom = p_new; + + printf(_("Watchpoint %u for regex `%s' added\n"), + p_new->i_num, p_new->psz_text); + return true; +} + +static size_t +print_till_eol(const char *psz_line) +{ + int count = 0; + while (psz_line[count] != 0 && psz_line[count] != '\n') { + putchar(psz_line[count]); + count++; + } + return count; +} + +static void +print_underline(size_t offset, size_t length) +{ + printf ("%*s", (int) offset, ""); + for (int i = 0; i < length; i++) { + putchar('^'); + } + putchar('\n'); +} + + +static void +print_command_underlined(const char *psz_command, size_t sub_offset, size_t sub_length) +{ + int tail_length; + // TODO: Doesn't account for \t, \r. + + do { + long line_length = print_till_eol(psz_command); + putchar('\n'); + psz_command += line_length; // and a newline/null + + if (*psz_command == '\n') { + psz_command++; + } + + if (sub_offset < line_length) { + size_t underline_length = MIN(sub_length, line_length - sub_offset); + print_underline(sub_offset, underline_length); + break; + } + + sub_offset -= line_length; + + // Skip the newline + if (sub_offset == 0) { + // The offset can, in theory, start on newline. + + sub_length--; + while (*psz_command == '\n') { + putchar('\n'); + // If only \n-s need to be underlined, then underline the last of them. + if (sub_length == 1) { + puts ("^"); + psz_command++; + break; + } + psz_command++; + sub_length--; + } + + } else { + sub_offset--; + } + + } while (*psz_command != '\0'); + + tail_length = printf("%s", psz_command) - 1; + if (tail_length > 0 && psz_command[tail_length] != '\n') { + putchar('\n'); + } +} + + +static bool +any_command_breakpoint_matches(const char *psz_expanded_command) +{ + breakpoint_node_t *p; + int match_status; + + for (p = p_breakpoint_top; p; p = p->p_next) { + if (p->type == COMMAND_WATCH) { + regmatch_t pmatch; + match_status = regexec(&p->regex, psz_expanded_command, (size_t) 1, &pmatch, 0); + if (match_status == 0) { + // TODO: Determine which line of command it was, if it was multi-line. + printf (_("Command matched a watchpoint %u at (%d:%d) '%s':\n%s\n"), + p->i_num, (int) pmatch.rm_so, (int) pmatch.rm_eo, p->psz_text, psz_expanded_command); + print_command_underlined(psz_expanded_command, pmatch.rm_so, pmatch.rm_eo - pmatch.rm_so); + return true; + } + } + } + + return false; +} + +void +check_command_watchpoint(target_stack_node_t *p_call_stack, file_t *p_target, const char *psz_expanded_command) +{ + if (any_command_breakpoint_matches (psz_expanded_command)) { + enter_debugger(p_call_stack, p_target, 0, DEBUG_WATCHPOINT); + } +} + + /* * Local variables: * eval: (c-set-style "gnu") diff --git a/libdebugger/break.h b/libdebugger/break.h index e6f30ae0b..63d9820c6 100644 --- a/libdebugger/break.h +++ b/libdebugger/break.h @@ -26,6 +26,7 @@ Boston, MA 02111-1307, USA. */ #define DBG_BREAK_H #include "types.h" +#include "trace.h" /*! Opaque type definition for an item in the breakpoint list. */ typedef struct breakpoint_node breakpoint_node_t; @@ -45,6 +46,11 @@ extern unsigned int i_breakpoints; */ extern bool add_breakpoint (file_t *p_target, unsigned int brkp_mask); +/*! Add "psz_regex" command watchpoint to the list of breakpoints. + Return true if there were no errors. +*/ +extern bool add_command_watchpoint (const char *psz_regex); + /*! Remove breakpoint i from the list of breakpoints. Return true if there were no errors. If silent is true, then don't warn about not finding breakpoint at "i". @@ -54,4 +60,6 @@ extern bool remove_breakpoint (unsigned int i, bool silent); /*! List breakpoints.*/ extern void list_breakpoints (void); +extern void check_command_watchpoint (target_stack_node_t *p_call_stack, file_t *p_target, const char *psz_expanded_command); + #endif /* DBG_BREAK_H */ diff --git a/libdebugger/cmd.c b/libdebugger/cmd.c index d9fe43741..8a597a823 100644 --- a/libdebugger/cmd.c +++ b/libdebugger/cmd.c @@ -264,8 +264,10 @@ debug_return_t enter_debugger (target_stack_node_t *p, if (!p_target->tracing) return continue_execution; } else if ( !debugger_on_error && !(i_debugger_stepping || i_debugger_nexting) - && p_target && !p_target->tracing && -2 != errcode ) + && p_target && !p_target->tracing && -2 != errcode + && reason != DEBUG_WATCHPOINT ) { return continue_execution; + } /* Clear temporary breakpoints. */ if (p_target && p_target->tracing & BRK_TEMP) diff --git a/libdebugger/cmd.h b/libdebugger/cmd.h index b23b50eb9..a119002d3 100644 --- a/libdebugger/cmd.h +++ b/libdebugger/cmd.h @@ -69,6 +69,7 @@ extern debug_return_t dbg_cmd_help(char *psz_args); extern debug_return_t dbg_cmd_info(char *psz_args); extern debug_return_t dbg_cmd_target(char *psz_args); extern debug_return_t dbg_cmd_show(char *psz_args); +extern debug_return_t dbg_cmd_watch(char *psz_regex); extern debug_return_t dbg_cmd_where(char *psz_args); extern debug_return_t dbg_cmd_set(char *psz_args); extern debug_return_t dbg_cmd_set_var (char *psz_arg, int expand); diff --git a/libdebugger/cmd_initialize.h b/libdebugger/cmd_initialize.h index dfb8e6149..d7b678f4d 100644 --- a/libdebugger/cmd_initialize.h +++ b/libdebugger/cmd_initialize.h @@ -54,6 +54,7 @@ Boston, MA 02111-1307, USA. */ #include "command/help/target.h" #include "command/help/up.h" #include "command/help/where.h" +#include "command/help/watch.h" #include "command/help/write.h" /* A structure which contains information on the commands this program @@ -90,6 +91,7 @@ long_cmd_t dbg_commands[] = { { "step" , 's' }, { "target" , 't' }, { "up" , 'u' }, + { "watch" , 'W' }, { "where" , 'T' }, { "write" , 'w' }, { (char *)NULL, ' '} @@ -322,6 +324,13 @@ dbg_cmd_up_init(unsigned int c) short_command[c].use = _("up [*amount*]"); } +static void +dbg_cmd_watch_init(unsigned int c) +{ + short_command[c].func = &dbg_cmd_watch; + short_command[c].use = _("watch"); +} + static void dbg_cmd_where_init(unsigned int c) { @@ -375,6 +384,7 @@ cmd_initialize(void) DBG_CMD_INIT(step, 's', true); DBG_CMD_INIT(target, 't', false); DBG_CMD_INIT(up, 'u', false); + DBG_CMD_INIT(watch, 'W', false); DBG_CMD_INIT(where, 'T', false); DBG_CMD_INIT(write, 'w', false); } diff --git a/libdebugger/command/help/watch.h b/libdebugger/command/help/watch.h new file mode 100644 index 000000000..f6bbc726a --- /dev/null +++ b/libdebugger/command/help/watch.h @@ -0,0 +1,8 @@ +/** \file libdebugger/command/help/watch.h + * + * \brief Help text for debugger command `watch`. + * + */ +#define watch_HELP_TEXT \ + "Add a \"command watch\" breakpoint that triggers when a command is about to be executed matches the given regex.\n" \ + "An argument is the regex." diff --git a/libdebugger/command/info.c b/libdebugger/command/info.c index 395c01461..e76c98e01 100644 --- a/libdebugger/command/info.c +++ b/libdebugger/command/info.c @@ -279,6 +279,10 @@ dbg_cmd_info_program() printf(_("Program stopped from explicit debugger function call.\n")); printf("\n"); break; + case DEBUG_WATCHPOINT: + printf(_("Program stopped at a command watchpoint.\n")); + printf("\n"); + break; case DEBUG_NOT_GIVEN: printf(_("Reason not given.\n")); break; diff --git a/libdebugger/command/watch.c b/libdebugger/command/watch.c new file mode 100644 index 000000000..35f108582 --- /dev/null +++ b/libdebugger/command/watch.c @@ -0,0 +1,28 @@ +/** + * \brief Debugger `watch` command. + * + * Add a "command watch" breakpoint that triggers when a command is about to be executed matches the given regex. + **/ + +#include +#include "../break.h" + +extern debug_return_t +dbg_cmd_watch (char *psz_regex) +{ + if (!psz_regex || !*psz_regex) { + return debug_readloop; + } + + add_command_watchpoint(psz_regex); + + return debug_readloop; +} + + +/* + * Local variables: + * eval: (c-set-style "gnu") + * indent-tabs-mode: nil + * End: + */ diff --git a/libdebugger/commands.h b/libdebugger/commands.h index 5add4ea22..f632d9cde 100644 --- a/libdebugger/commands.h +++ b/libdebugger/commands.h @@ -53,5 +53,6 @@ extern debug_return_t dbg_cmd_source(char *psz_args); extern debug_return_t dbg_cmd_step(char *psz_args); extern debug_return_t dbg_cmd_target(char *psz_args); extern debug_return_t dbg_cmd_up(char *psz_args); +extern debug_return_t dbg_cmd_watch(char *psz_args); extern debug_return_t dbg_cmd_where(char *psz_args); extern debug_return_t dbg_cmd_write(char *psz_args); diff --git a/libdebugger/fns.c b/libdebugger/fns.c index f1b757535..f9d5a6f99 100644 --- a/libdebugger/fns.c +++ b/libdebugger/fns.c @@ -292,7 +292,8 @@ static const char *reason2str[] = { "!!", "--", "++", - ":o" + ":o", + "wp" }; diff --git a/src/job.c b/src/job.c index a4a40df41..4f5387b8b 100644 --- a/src/job.c +++ b/src/job.c @@ -23,6 +23,7 @@ this program. If not, see . */ #include #include +#include "break.h" #include "job.h" #include "print.h" #include "debug.h" @@ -1214,6 +1215,8 @@ start_job_command (child_t *child, /* We're sure we're going to invoke a command: set up the output. */ output_start (); + check_command_watchpoint(p_call_stack, child->file, child->command_lines[child->command_line - 1]); + p_stack_top = p_call_stack; if (i_debugger_stepping) enter_debugger(p_call_stack, child->file, 0, DEBUG_STEP_COMMAND); diff --git a/src/trace.h b/src/trace.h index fb5e12da1..9b571220a 100644 --- a/src/trace.h +++ b/src/trace.h @@ -54,6 +54,7 @@ typedef enum DEBUG_STEP_HIT = 6, DEBUG_STEP_COMMAND = 7, DEBUG_EXPLICIT_CALL = 8, + DEBUG_WATCHPOINT = 9, DEBUG_STACK_CHANGING = 99, DEBUG_NOT_GIVEN = 100 } debug_enter_reason_t;