Skip to content

Commit

Permalink
#107: Debugger "watchpoints": breakpoints that trigger when a command…
Browse files Browse the repository at this point in the history
… executed matches a particular regex.
  • Loading branch information
singalen committed Aug 10, 2020
1 parent 4971ac8 commit a874697
Show file tree
Hide file tree
Showing 17 changed files with 291 additions and 15 deletions.
12 changes: 12 additions & 0 deletions debug-test/Makefile
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion doc/readthedocs/debugger/commands/breakpoints/break.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ Examples:

.. seealso::

:ref:`delete <delete>`.
:ref:`delete <delete>`, :ref:`watch <watch>`.
24 changes: 24 additions & 0 deletions doc/readthedocs/debugger/commands/breakpoints/watch.rst
Original file line number Diff line number Diff line change
@@ -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 <delete>`, :ref:`break <break>`.
2 changes: 2 additions & 0 deletions libdebugger/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
194 changes: 182 additions & 12 deletions libdebugger/break.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,29 @@ Boston, MA 02111-1307, USA. */
/** debugger command stack routines. */

#include <assert.h>
#include <regex.h>
#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 */
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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")
Expand Down
8 changes: 8 additions & 0 deletions libdebugger/break.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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".
Expand All @@ -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 */
4 changes: 3 additions & 1 deletion libdebugger/cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions libdebugger/cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions libdebugger/cmd_initialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,6 +91,7 @@ long_cmd_t dbg_commands[] = {
{ "step" , 's' },
{ "target" , 't' },
{ "up" , 'u' },
{ "watch" , 'W' },
{ "where" , 'T' },
{ "write" , 'w' },
{ (char *)NULL, ' '}
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
}
8 changes: 8 additions & 0 deletions libdebugger/command/help/watch.h
Original file line number Diff line number Diff line change
@@ -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."
4 changes: 4 additions & 0 deletions libdebugger/command/info.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit a874697

Please sign in to comment.