Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: #107: Debugger "watchpoints": breakpoints that trigger when a command… #108

Open
wants to merge 1 commit into
base: remake-4-3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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