[PATCH] dynamic printf

Stan Shebs
2012-02-29T07:04:26Z

[-- Attachment #1: Type: text/plain, Size: 5023 bytes --]

This patch implements a "dynamic printf", which is basically a
breakpoint with a printf;continue as its command list - but with
additional features that make it more interesting. First, there is an
option to send the result to the inferior's output stream rather than to
the GDB console; second, there is an option to do the printing in a
remote agent/stub; and third, the agent version can continue printf'ing
even after GDB detaches(!).

(For those who pay attention to the competitive landscape, this is an
Ericsson-requested GDBification of a Wind River feature.)

The syntax of the command is

dprintf <location> <format>,<arg>,<arg>...

where the location is as for breakpoints, while the format and args are
as for the printf command. So you could have something like

dprintf myfun "myfun args are %d,%d\n",arg1,arg2

A setshow variable dprintf-style controls whether the printing is done
by GDB, the inferior, or by a remote agent, and another variable
disconnected-dprintf controls persistence upon disconnection, similarly
to disconnected tracing.

The patch itself is somewhat of a hack-n-slash through the middle of
GDB, and there is much to critique. :-) Partly this is because I wanted
to push through to a genuinely useful example of target-side breakpoint
commands, rather than doing limited-functionality "commands" in the
abstract. It also points up our continuing need for being able to
define types of breakpoints by properties rather than adding enums... I
borrowed some of Luis' target-side conditional code, and got some
inspiration from Hui Zhu's previous try at a printf bytecode.

Anyway, without further ado, here's the patch itself. I've skipped the
testsuite for now, as I expect everybody will want nontrivial
modifications. :-)

Stan

2012-02-28 Stan Shebs <stan@codesourcery.com>

Add dynamic printf.
* breakpoint.h (enum bptype): New type bp_dprintf.
(struct bp_target_info): New field commands and persist.
(struct bp_location): New field cmd_bytecode.
(struct breakpoint): New field extra_string.
* breakpoint.c (dprintf_breakpoint_ops): New.
(disconnected_dprintf): New global.
(is_breakpoint): Add bp_dprintf.
(parse_format_string): New function.
(parse_cmd_to_aexpr): New function.
(build_target_command_list): New function.
(insert_bp_location): Call it.
(remove_breakpoints_pid): Skip dprintf breakpoints.
(bpstat_what): Add dprintf case.
(bptype_string): Ditto.
(print_one_breakpoint_location): Ditto.
(init_bp_location): Ditto.
(bkpt_print_mention): Ditto.
(dprintf_style_enums): New array.
(dprintf_style): New global.
(glob_extra_string): New global.
(init_breakpoint_sal): Handle extra string.
(addr_string_to_sals): Ditto.
(find_condition_and_thread): Add extra argument.
(create_breakpoint): Save away additional text at end of command.
(dprintf_command): New function.
(_initialize_breakpoint): Add new commands.
* common/ax.def (printf): New bytecode.
* ax-gdb.c (gen_printf): New function.
(agent_print_command): New function.
(_initialize_ax_gdb): Add maint agent-printf command.
* ax-gdb.h (gen_printf): Declare.
* ax-general.c (ax_print): Add printf disassembly.
* remote.c (remote_state) <cond_breakpoints>: New field.
(PACKET_BreakpointCommandss): New enum.
(remote_breakpoint_commands_feature): New function.
(remote_protocol_features): Add new BreakpointCommands entry.
(remote_can_run_breakpoint_commands): New function.
(remote_add_target_side_commands): New function.
(remote_insert_breakpoint): Call it.
(remote_insert_hw_breakpoint): Ditto.
(_initialize_remote): Add new packet configuration for
target-side breakpoint commands.
* target.h (struct target_ops): New field
to_can_run_breakpoint_commands.
(target_can_run_breakpoint_commands): New macro.
* target.c (update_current_target): Handle
to_can_run_breakpoint_commands.

[gdbserver]
* server.c (handle_query): Claim support for breakpoint commands.
(process_point_options): Add command case.
(process_serial_event): Leave running if there are printfs in
effect.
* mem-break.c (struct point_command_list): New struct.
(struct breakpoint): New field command_list.
(any_persistent_commands): New function.
(add_commands_to_breakpoint): New function.
(add_breakpoint_commands): New function.
(gdb_no_commands_at_breakpoint): New function.
(run_breakpoint_commands): New function.
* mem-break.h (any_persistent_commands): Declare.
(add_breakpoint_commands): Declare.
(gdb_no_commands_at_breakpoint): Declare.
(run_breakpoint_commands): Declare.
* linux-low.c (linux_wait_1): Test for and run breakpoint commands
locally.
* ax.c (gdb_eval_agent_expr): Add printf opcode.

[doc]
* gdb.texinfo (Dynamic Printf): New subsection.

[-- Attachment #2: dprintf-patch-1 --]
[-- Type: text/plain, Size: 46628 bytes --]

Index: ax-gdb.c
===================================================================
RCS file: /cvs/src/src/gdb/ax-gdb.c,v
retrieving revision 1.92
diff -u -p -r1.92 ax-gdb.c
--- ax-gdb.c 7 Feb 2012 04:48:18 -0000 1.92
+++ ax-gdb.c 29 Feb 2012 05:52:13 -0000
@@ -2511,6 +2511,55 @@ gen_trace_for_return_address (CORE_ADDR
return ax;
}

+/* Given a collection of printf-style arguments, generate code to
+ evaluate the arguments and pass everything to a special
+ bytecode. */
+
+struct agent_expr *
+gen_printf (CORE_ADDR scope, int channel, char *format,
+ int nargs, struct expression **exprs)
+{
+ struct expression *expr;
+ struct cleanup *old_chain = 0;
+ struct agent_expr *ax = new_agent_expr (exprs[0]->gdbarch, scope);
+ union exp_element *pc;
+ struct axs_value value;
+ int tem, bot;
+
+ old_chain = make_cleanup_free_agent_expr (ax);
+
+ /* Evaluate and push the args on the stack in reverse order,
+ for simplicity of collecting them on the target side. */
+ for (tem = nargs - 1; tem >= 0; --tem)
+ {
+ pc = exprs[tem]->elts;
+ /* We're computing values, not doing side effects. */
+ trace_kludge = 0;
+ value.optimized_out = 0;
+ gen_expr (exprs[tem], &pc, ax, &value);
+ require_rvalue (ax, &value);
+ }
+
+ ax_simple (ax, aop_printf);
+ ax_simple (ax, channel);
+ ax_simple (ax, nargs);
+ /* FIXME define a string-adder in ax-general.c */
+ ax_simple (ax, strlen (format) + 1);
+ while (*format != '\0')
+ ax_simple (ax, *format++);
+ ax_simple (ax, '\0');
+
+ /* Oh, and terminate. */
+ ax_simple (ax, aop_end);
+
+ /* We have successfully built the agent expr, so cancel the cleanup
+ request. If we add more cleanups that we always want done, this
+ will have to get more complicated. */
+ discard_cleanups (old_chain);
+
+ return ax;
+}
+
static void
agent_command (char *exp, int from_tty)
{
@@ -2594,6 +2643,66 @@ agent_eval_command (char *exp, int from_
do_cleanups (old_chain);
dont_repeat ();
}
+/* Parse the given expression, compile it into an agent expression
+ that does a printf, and display the resulting expression. */
+
+extern char *parse_format_string (char **arg);
+
+static void
+agent_printf_command (char *exp, int from_tty)
+{
+ struct cleanup *old_chain = 0;
+ struct expression *expr;
+ struct expression *argvec[100];
+ struct agent_expr *agent;
+ struct frame_info *fi = get_current_frame (); /* need current scope */
+ char *cmdrest, *string;
+ int nargs;
+
+ /* We don't deal with overlay debugging at the moment. We need to
+ think more carefully about this. If you copy this code into
+ another command, change the error message; the user shouldn't
+ have to know anything about agent expressions. */
+ if (overlay_debugging)
+ error (_("GDB can't do agent expression translation with overlays."));
+
+ if (exp == 0)
+ error_no_arg (_("expression to translate"));
+
+ cmdrest = exp;
+
+ string = parse_format_string (&cmdrest);
+
+ nargs = 0;
+ while (*cmdrest != '\0')
+ {
+ char *cmd1;
+
+ cmd1 = cmdrest;
+ expr = parse_exp_1 (&cmd1, (struct block *) 0, 1);
+ argvec[nargs] = expr;
+ ++nargs;
+ cmdrest = cmd1;
+ if (*cmdrest == ',')
+ ++cmdrest;
+ }
+
+#if 0
+ old_chain = make_cleanup (free_current_contents, &expr);
+#endif
+ agent = gen_printf (get_frame_pc (fi), 0, string, nargs, argvec);
+ make_cleanup_free_agent_expr (agent);
+ ax_reqs (agent);
+ ax_print (gdb_stdout, agent);
+
+ /* It would be nice to call ax_reqs here to gather some general info
+ about the expression, and then print out the result. */
+
+#if 0
+ do_cleanups (old_chain);
+#endif
+ dont_repeat ();
+}
\f

/* Initialization code. */
@@ -2611,4 +2720,9 @@ _initialize_ax_gdb (void)
_("Translate an expression into remote "
"agent bytecode for evaluation."),
&maintenancelist);
+
+ add_cmd ("agent-printf", class_maintenance, agent_printf_command,
+ _("Translate an expression into remote "
+ "agent bytecode for evaluation."),
+ &maintenancelist);
}
Index: ax-gdb.h
===================================================================
RCS file: /cvs/src/src/gdb/ax-gdb.h,v
retrieving revision 1.23
diff -u -p -r1.23 ax-gdb.h
--- ax-gdb.h 4 Jan 2012 08:16:59 -0000 1.23
+++ ax-gdb.h 29 Feb 2012 05:52:13 -0000
@@ -110,6 +110,9 @@ extern struct agent_expr *gen_trace_for_

extern struct agent_expr *gen_eval_for_expr (CORE_ADDR, struct expression *);

+extern struct agent_expr *gen_printf (CORE_ADDR, int, char *,
+ int, struct expression **);
+
extern int trace_kludge;
extern int trace_string_kludge;

Index: ax-general.c
===================================================================
RCS file: /cvs/src/src/gdb/ax-general.c,v
retrieving revision 1.29
diff -u -p -r1.29 ax-general.c
--- ax-general.c 4 Jan 2012 08:16:59 -0000 1.29
+++ ax-general.c 29 Feb 2012 05:52:13 -0000
@@ -391,6 +391,19 @@ ax_print (struct ui_file *f, struct agen
print_longest (f, 'd', 0,
read_const (x, i + 1, aop_map[op].op_size));
}
+ /* Handle the complicated printf arguments specially. */
+ else if (op == aop_printf)
+ {
+ int chan, slen, nargs;
+
+ ++i;
+ chan = x->buf[i++];
+ nargs = x->buf[i++];
+ slen = x->buf[i++];
+ fprintf_filtered (f, " %d \"%s\", %d args",
+ chan, &(x->buf[i]), nargs);
+ i += slen - 1;
+ }
fprintf_filtered (f, "\n");
i += 1 + aop_map[op].op_size;

Index: breakpoint.c
===================================================================
RCS file: /cvs/src/src/gdb/breakpoint.c,v
retrieving revision 1.657
diff -u -p -r1.657 breakpoint.c
--- breakpoint.c 24 Feb 2012 16:26:36 -0000 1.657
+++ breakpoint.c 29 Feb 2012 05:52:13 -0000
@@ -290,6 +290,12 @@ static struct breakpoint_ops momentary_b
breakpoints. */
struct breakpoint_ops bkpt_breakpoint_ops;

+static struct breakpoint_ops dprintf_breakpoint_ops;
+
+/* True if dprintf commands should continue to operate even if GDB
+ has disconnected. */
+static int disconnected_dprintf = 1;
+
/* A reference-counted struct command_line. This lets multiple
breakpoints share a single command list. */
struct counted_command_line
@@ -1480,7 +1486,8 @@ int
is_breakpoint (const struct breakpoint *bpt)
{
return (bpt->type == bp_breakpoint
- || bpt->type == bp_hardware_breakpoint);
+ || bpt->type == bp_hardware_breakpoint
+ || bpt->type == bp_dprintf);
}

/* Return true if BPT is of any hardware watchpoint kind. */
@@ -2062,6 +2069,247 @@ build_target_condition_list (struct bp_l
return;
}

+char *
+parse_format_string (char **arg)
+{
+ char *s, *f, *string;
+
+ s = *arg;
+
+ s = skip_spaces (s);
+
+ /* A format string should follow, enveloped in double quotes. */
+ if (*s++ != '"')
+ error (_("Bad format string, missing '\"'."));
+
+ /* Parse the format-control string and copy it into the string STRING,
+ processing some kinds of escape sequence. */
+
+ f = string = (char *) alloca (strlen (s) + 1);
+
+ while (*s != '"')
+ {
+ int c = *s++;
+ switch (c)
+ {
+ case '\0':
+ error (_("Bad format string, non-terminated '\"'."));
+
+ case '\\':
+ switch (c = *s++)
+ {
+ case '\\':
+ *f++ = '\\';
+ break;
+ case 'a':
+ *f++ = '\a';
+ break;
+ case 'b':
+ *f++ = '\b';
+ break;
+ case 'f':
+ *f++ = '\f';
+ break;
+ case 'n':
+ *f++ = '\n';
+ break;
+ case 'r':
+ *f++ = '\r';
+ break;
+ case 't':
+ *f++ = '\t';
+ break;
+ case 'v':
+ *f++ = '\v';
+ break;
+ case '"':
+ *f++ = '"';
+ break;
+ default:
+ /* ??? TODO: handle other escape sequences. */
+ error (_("Unrecognized escape character \\%c in format string."),
+ c);
+ }
+ break;
+
+ default:
+ *f++ = c;
+ }
+ }
+
+ /* Skip over " and following space and comma. */
+ s++;
+ *f++ = '\0';
+ s = skip_spaces (s);
+
+ if (*s != ',' && *s != 0)
+ error (_("Invalid argument syntax"));
+
+ if (*s == ',')
+ s++;
+ s = skip_spaces (s);
+
+ *arg = s;
+
+ return xstrdup (string);
+}
+
+/* Parses a command described by string CMD into an agent expression
+ bytecode suitable for evaluation by the bytecode interpreter.
+ Return NULL if there was any error during parsing. */
+
+struct agent_expr *
+parse_cmd_to_aexpr (CORE_ADDR scope, char *cmd)
+{
+ struct expression *expr;
+ struct expression *argvec[100];
+ struct agent_expr *aexpr = NULL;
+ struct cleanup *old_chain = NULL;
+ volatile struct gdb_exception ex;
+ char *cmdrest, *string;
+ int nargs;
+
+ if (!cmd)
+ return NULL;
+
+ /* For each argument, make an expression. */
+
+ cmdrest = cmd;
+
+ string = parse_format_string (&cmdrest);
+
+ /* (should get comma separator here) */
+
+ nargs = 0;
+ while (*cmdrest != '\0')
+ {
+ char *cmd1;
+
+ cmd1 = cmdrest;
+ expr = parse_exp_1 (&cmd1, (struct block *) 0, 1);
+ argvec[nargs++] = expr;
+ cmdrest = cmd1;
+ if (*cmdrest == ',')
+ ++cmdrest;
+ }
+
+ /* We don't want to stop processing, so catch any errors
+ that may show up. */
+ TRY_CATCH (ex, RETURN_MASK_ERROR)
+ {
+ aexpr = gen_printf (scope, 0, string, nargs, argvec);
+ }
+
+ if (ex.reason < 0)
+ {
+ /* If we got here, it means the condition could not be parsed to a valid
+ bytecode expression and thus can't be evaluated on the target's side.
+ It's no use iterating through the conditions. */
+ return NULL;
+ }
+
+ /* We have a valid agent expression. */
+ return aexpr;
+}
+
+/* Based on location BL, create a list of breakpoint commands to be
+ passed on to the target. If we have duplicated locations with
+ different commands, we will add any such to the list. */
+
+static void
+build_target_command_list (struct bp_location *bl)
+{
+ struct bp_location **locp = NULL, **loc2p;
+ int null_command_or_parse_error = 0;
+ int modified = bl->needs_update;
+ struct bp_location *loc;
+
+ /* For now, limit to dprintf breakpoints. */
+ if (bl->owner->type != bp_dprintf)
+ return;
+
+ if (!target_can_run_breakpoint_commands ())
+ return;
+
+ /* Do a first pass to check for locations with no assigned
+ conditions or conditions that fail to parse to a valid agent expression
+ bytecode. If any of these happen, then it's no use to send conditions
+ to the target since this location will always trigger and generate a
+ response back to GDB. */
+ ALL_BP_LOCATIONS_AT_ADDR (loc2p, locp, bl->address)
+ {
+ loc = (*loc2p);
+ if (is_breakpoint (loc->owner) && loc->pspace->num == bl->pspace->num)
+ {
+ if (modified)
+ {
+ struct agent_expr *aexpr;
+
+ /* Re-parse the conditions since something changed. In that
+ case we already freed the condition bytecodes (see
+ force_breakpoint_reinsertion). We just
+ need to parse the condition to bytecodes again. */
+ aexpr = parse_cmd_to_aexpr (bl->address,
+ loc->owner->extra_string);
+ loc->cmd_bytecode = aexpr;
+
+ if (!aexpr)
+ continue;
+ }
+
+ /* If we have a NULL bytecode expression, it means something
+ went wrong or we have a null condition expression. */
+ if (!loc->cmd_bytecode)
+ {
+ null_command_or_parse_error = 1;
+ break;
+ }
+ }
+ }
+
+ /* If anything failed, then we're not doing target-side commands,
+ and so clean up. */
+ if (null_command_or_parse_error)
+ {
+ ALL_BP_LOCATIONS_AT_ADDR (loc2p, locp, bl->address)
+ {
+ loc = (*loc2p);
+ if (is_breakpoint (loc->owner)
+ && loc->pspace->num == bl->pspace->num)
+ {
+ /* Only go as far as the first NULL bytecode is
+ located. */
+ if (!loc->cond_bytecode)
+ return;
+
+ free_agent_expr (loc->cond_bytecode);
+ loc->cond_bytecode = NULL;
+ }
+ }
+ }
+
+ /* No NULL commands or failed bytecode generation. Build a condition list
+ for this location's address. */
+ ALL_BP_LOCATIONS_AT_ADDR (loc2p, locp, bl->address)
+ {
+ loc = (*loc2p);
+ if (loc->owner->extra_string
+ && is_breakpoint (loc->owner)
+ && loc->pspace->num == bl->pspace->num
+ && loc->owner->enable_state == bp_enabled
+ && loc->enabled)
+ /* Add the command to the vector. This will be used later
+ to send the conditions to the target. */
+ VEC_safe_push (agent_expr_p, bl->target_info.commands,
+ loc->cmd_bytecode);
+ }
+
+ bl->target_info.persist = 0;
+ /* Maybe flag this location as persistent. */
+ if (bl->owner->type == bp_dprintf && disconnected_dprintf)
+ bl->target_info.persist = 1;
+}
+
/* Insert a low-level "breakpoint" of some type. BL is the breakpoint
location. Any error messages are printed to TMP_ERROR_STREAM; and
DISABLED_BREAKS, and HW_BREAKPOINT_ERROR are used to report problems.
@@ -2095,7 +2343,8 @@ insert_bp_location (struct bp_location *
if (is_breakpoint (bl->owner))
{
build_target_condition_list (bl);
- /* Reset the condition modification marker. */
+ build_target_command_list (bl);
+ /* Reset the modification marker. */
bl->needs_update = 0;
}

@@ -2601,6 +2850,9 @@ remove_breakpoints_pid (int pid)
if (bl->pspace != inf->pspace)
continue;

+ if (bl->owner->type == bp_dprintf)
+ continue;
+
if (bl->inserted)
{
val = remove_breakpoint (bl, mark_uninserted);
@@ -5054,6 +5306,11 @@ bpstat_what (bpstat bs_head)
PC of the former breakpoint. */
this_action = BPSTAT_WHAT_KEEP_CHECKING;
break;
+
+ case bp_dprintf:
+ this_action = BPSTAT_WHAT_STOP_SILENT;
+ break;
+
default:
internal_error (__FILE__, __LINE__,
_("bpstat_what: unhandled bptype %d"), (int) bptype);
@@ -5318,6 +5575,7 @@ bptype_string (enum bptype type)
{bp_tracepoint, "tracepoint"},
{bp_fast_tracepoint, "fast tracepoint"},
{bp_static_tracepoint, "static tracepoint"},
+ {bp_dprintf, "dprintf"},
{bp_jit_event, "jit events"},
{bp_gnu_ifunc_resolver, "STT_GNU_IFUNC resolver"},
{bp_gnu_ifunc_resolver_return, "STT_GNU_IFUNC resolver return"},
@@ -5458,6 +5716,7 @@ print_one_breakpoint_location (struct br
case bp_tracepoint:
case bp_fast_tracepoint:
case bp_static_tracepoint:
+ case bp_dprintf:
case bp_jit_event:
case bp_gnu_ifunc_resolver:
case bp_gnu_ifunc_resolver_return:
@@ -5635,6 +5894,15 @@ print_one_breakpoint_location (struct br
}
}

+ if (!part_of_multiple && b->extra_string
+ && b->type == bp_dprintf && !b->commands)
+ {
+ annotate_field (7);
+ ui_out_text (uiout, "\t(agent printf) ");
+ ui_out_field_string (uiout, "printf", b->extra_string);
+ ui_out_text (uiout, "\n");
+ }
+
l = b->commands ? b->commands->commands : NULL;
if (!part_of_multiple && l)
{
@@ -6320,6 +6588,7 @@ init_bp_location (struct bp_location *lo
case bp_exception_master:
case bp_gnu_ifunc_resolver:
case bp_gnu_ifunc_resolver_return:
+ case bp_dprintf:
loc->loc_type = bp_loc_software_breakpoint;
mark_breakpoint_location_modified (loc);
break;
@@ -8192,7 +8461,19 @@ bp_loc_is_permanent (struct bp_location
return retval;
}

+static const char dprintf_style_gdb[] = "gdb";
+static const char dprintf_style_call[] = "call";
+static const char dprintf_style_agent[] = "agent";
+static const char *const dprintf_style_enums[] = {
+ dprintf_style_gdb,
+ dprintf_style_call,
+ dprintf_style_agent,
+ NULL
+};
+static const char *dprintf_style = dprintf_style_gdb;

+/* Temporary hack to smuggle remainder of command line through. */
+char *glob_extra_string = NULL;

/* Create a breakpoint with SAL as location. Use ADDR_STRING
as textual description of the location, and COND_STRING
@@ -8247,6 +8528,7 @@ init_breakpoint_sal (struct breakpoint *
b->task = task;

b->cond_string = cond_string;
+ b->extra_string = glob_extra_string;
b->ignore_count = ignore_count;
b->enable_state = enabled ? bp_enabled : bp_disabled;
b->disposition = disposition;
@@ -8306,6 +8588,40 @@ init_breakpoint_sal (struct breakpoint *
if (*arg)
error (_("Garbage %s follows condition"), arg);
}
+
+ if (b->extra_string)
+ {
+ char *printf_line = NULL;
+
+ if (strcmp (dprintf_style, "gdb") == 0)
+ printf_line = xstrprintf ("printf %s", b->extra_string);
+ else if (strcmp (dprintf_style, "call") == 0)
+ printf_line = xstrprintf ("call printf(%s)", b->extra_string);
+ else if (strcmp (dprintf_style, "agent") == 0)
+ /* Nothing to do here. */ ;
+ else
+ internal_error (__FILE__, __LINE__,
+ _("Invalid dprintf style."));
+
+ if (printf_line)
+ {
+ struct command_line *printf_cmd_line, *cont_cmd_line;
+
+ cont_cmd_line = xmalloc (sizeof (struct command_line));
+ cont_cmd_line->body_count = 0;
+ cont_cmd_line->control_type = simple_control;
+ cont_cmd_line->next = NULL;
+ cont_cmd_line->line = "continue";
+
+ printf_cmd_line = xmalloc (sizeof (struct command_line));
+ printf_cmd_line->body_count = 0;
+ printf_cmd_line->control_type = simple_control;
+ printf_cmd_line->next = cont_cmd_line;
+ printf_cmd_line->line = printf_line;
+
+ breakpoint_set_commands (b, printf_cmd_line);
+ }
+ }
}

b->display_canonical = display_canonical;
@@ -8536,7 +8852,8 @@ check_fast_tracepoint_sals (struct gdbar
If no thread is found, *THREAD is set to -1. */
static void
find_condition_and_thread (char *tok, CORE_ADDR pc,
- char **cond_string, int *thread, int *task)
+ char **cond_string, int *thread, int *task,
+ char **rest)
{
*cond_string = NULL;
*thread = -1;
@@ -8549,6 +8866,12 @@ find_condition_and_thread (char *tok, CO

tok = skip_spaces (tok);

+ if (*tok == '"' && rest)
+ {
+ *rest = savestring (tok, strlen (tok));
+ return;
+ }
+
end_tok = skip_to_space (tok);

toklen = end_tok - tok;
@@ -8588,6 +8911,11 @@ find_condition_and_thread (char *tok, CO
if (!valid_task_id (*task))
error (_("Unknown task %d."), *task);
}
+ else if (rest)
+ {
+ *rest = savestring (tok, toklen);
+ tok += toklen;
+ }
else
error (_("Junk at end of arguments."));
}
@@ -8673,6 +9001,7 @@ create_breakpoint (struct gdbarch *gdbar
int pending = 0;
int task = 0;
int prev_bkpt_count = breakpoint_count;
+ char *extra_string = NULL;

gdb_assert (ops != NULL);

@@ -8776,16 +9105,22 @@ create_breakpoint (struct gdbarch *gdbar

if (parse_condition_and_thread)
{
+ char *rest;
/* Here we only parse 'arg' to separate condition
from thread number, so parsing in context of first
sal is OK. When setting the breakpoint we'll
re-parse it in context of each sal. */
cond_string = NULL;
thread = -1;
+ rest = NULL;
find_condition_and_thread (arg, lsal->sals.sals[0].pc, &cond_string,
- &thread, &task);
+ &thread, &task, &rest);
if (cond_string)
make_cleanup (xfree, cond_string);
+ if (rest)
+ make_cleanup (xfree, rest);
+ if (rest)
+ extra_string = rest;
}
else
{
@@ -8797,6 +9132,7 @@ create_breakpoint (struct gdbarch *gdbar
}
}

+ glob_extra_string = extra_string;
ops->create_breakpoints_sal (gdbarch, &canonical, lsal,
cond_string, type_wanted,
tempflag ? disp_del : disp_donttouch,
@@ -8944,6 +9280,21 @@ break_command (char *arg, int from_tty)
}

void
+dprintf_command (char *arg, int from_tty)
+{
+ create_breakpoint (get_current_arch (),
+ arg,
+ NULL, 0, 1 /* parse arg */,
+ 0, bp_dprintf,
+ 0 /* Ignore count */,
+ pending_break_support,
+ &dprintf_breakpoint_ops,
+ from_tty,
+ 1 /* enabled */,
+ 0 /* internal */);
+}
+
+void
tbreak_command (char *arg, int from_tty)
{
break_command_1 (arg, BP_TEMPFLAG, from_tty);
@@ -12132,6 +12483,9 @@ bkpt_print_mention (struct breakpoint *b
case bp_hardware_breakpoint:
printf_filtered (_("Hardware assisted breakpoint %d"), b->number);
break;
+ case bp_dprintf:
+ printf_filtered (_("Dprintf %d"), b->number);
+ break;
}

say_where (b);
@@ -13151,13 +13505,17 @@ addr_string_to_sals (struct breakpoint *
char *cond_string = 0;
int thread = -1;
int task = 0;
+ char *extra_string = NULL;

find_condition_and_thread (s, sals.sals[0].pc,
- &cond_string, &thread, &task);
+ &cond_string, &thread, &task,
+ &extra_string);
if (cond_string)
b->cond_string = cond_string;
b->thread = thread;
b->task = task;
+ if (extra_string)
+ b->extra_string = extra_string;
b->condition_not_parsed = 0;
}

@@ -14946,6 +15304,14 @@ initialize_breakpoint_ops (void)
ops->print_one = print_one_catch_solib;
ops->print_mention = print_mention_catch_solib;
ops->print_recreate = print_recreate_catch_solib;
+
+ ops = &dprintf_breakpoint_ops;
+ *ops = bkpt_base_breakpoint_ops;
+ ops->re_set = bkpt_re_set;
+ ops->resources_needed = bkpt_resources_needed;
+ ops->print_it = bkpt_print_it;
+ ops->print_mention = bkpt_print_mention;
+ ops->print_recreate = bkpt_print_recreate;
}

void
@@ -15524,6 +15890,31 @@ The breakpoint will stop execution of th
an instruction at any address within the [START-LOCATION, END-LOCATION]\n\
range (including START-LOCATION and END-LOCATION)."));

+ c = add_com ("dprintf", class_breakpoint, dprintf_command, _("\
+Set a dynamic printf at specified line or function.\n"
+BREAK_ARGS_HELP ("dprintf")));
+ set_cmd_completer (c, location_completer);
+
+ add_setshow_enum_cmd ("dprintf-style", class_support,
+ dprintf_style_enums, &dprintf_style, _("\
+Set the style of usage for dynamic printf."), _("\
+Show the style of usage for dynamic printf."), _("\
+Choose the style of usage for dynamic printf.\n\
+foo"),
+ NULL,
+ NULL,
+ &setlist, &showlist);
+
+ add_setshow_boolean_cmd ("disconnected-dprintf", no_class,
+ &disconnected_dprintf, _("\
+Set whether dprintf continues after GDB disconnects."), _("\
+Show whether dprintf continues after GDB disconnects."), _("\
+Use this to let dprintf commands continue to hit and produce output\n\
+even if GDB disconnects or detaches from the target."),
+ NULL,
+ NULL,
+ &setlist, &showlist);
+
automatic_hardware_breakpoints = 1;

observer_attach_about_to_proceed (breakpoint_about_to_proceed);
Index: breakpoint.h
===================================================================
RCS file: /cvs/src/src/gdb/breakpoint.h,v
retrieving revision 1.175
diff -u -p -r1.175 breakpoint.h
--- breakpoint.h 24 Feb 2012 15:10:59 -0000 1.175
+++ breakpoint.h 29 Feb 2012 05:52:13 -0000
@@ -154,6 +154,8 @@ enum bptype
bp_fast_tracepoint,
bp_static_tracepoint,

+ bp_dprintf,
+
/* Event for JIT compiled code generation or deletion. */
bp_jit_event,

@@ -264,6 +266,14 @@ struct bp_target_info
/* Vector of conditions the target should evaluate if it supports target-side
breakpoint conditions. */
VEC(agent_expr_p) *conditions;
+
+ /* Vector of commands the target should evaluate if it supports
+ target-side breakpoint commands. */
+ VEC(agent_expr_p) *commands;
+
+ /* Flag that is true if the breakpoint should be left in place even
+ when GDB is not connected. */
+ int persist;
};

/* GDB maintains two types of information about each breakpoint (or
@@ -350,8 +360,11 @@ struct bp_location

enum condition_status condition_changed;

- /* Signals that breakpoint conditions need to be re-synched with the
- target. This has no use other than target-side breakpoints. */
+ struct agent_expr *cmd_bytecode;
+
+ /* Signals that breakpoint conditions and/or commands need to be
+ re-synched with the target. This has no use other than
+ target-side breakpoints. */
char needs_update;

/* This location's address is in an unloaded solib, and so this
@@ -673,8 +686,9 @@ struct breakpoint
/* String form of the breakpoint condition (malloc'd), or NULL if
there is no condition. */
char *cond_string;
- /* String form of exp to use for displaying to the user
- (malloc'd), or NULL if none. */
+
+ /* String form of extra parameters, or NULL if there are none. */
+ char *extra_string;

/* Holds the address of the related watchpoint_scope breakpoint
when using watchpoints on local variables (might the concept of
Index: remote.c
===================================================================
RCS file: /cvs/src/src/gdb/remote.c,v
retrieving revision 1.486
diff -u -p -r1.486 remote.c
--- remote.c 24 Feb 2012 23:48:37 -0000 1.486
+++ remote.c 29 Feb 2012 05:52:14 -0000
@@ -244,6 +244,8 @@ static void remote_console_output (char

static int remote_supports_cond_breakpoints (void);

+static int remote_can_run_breakpoint_commands (void);
+
/* The non-stop remote protocol provisions for one pending stop reply.
This is where we keep it until it is acknowledged. */

@@ -321,6 +323,10 @@ struct remote_state
conditions. */
int cond_breakpoints;

+ /* True if the stub reports support for target-side breakpoint
+ commands. */
+ int breakpoint_commands;
+
/* True if the stub reports support for fast tracepoints. */
int fast_tracepoints;

@@ -1270,6 +1276,7 @@ enum {
PACKET_qAttached,
PACKET_ConditionalTracepoints,
PACKET_ConditionalBreakpoints,
+ PACKET_BreakpointCommands,
PACKET_FastTracepoints,
PACKET_StaticTracepoints,
PACKET_InstallInTrace,
@@ -3734,6 +3741,16 @@ remote_cond_breakpoint_feature (const st
}

static void
+remote_breakpoint_commands_feature (const struct protocol_feature *feature,
+ enum packet_support support,
+ const char *value)
+{
+ struct remote_state *rs = get_remote_state ();
+
+ rs->breakpoint_commands = (support == PACKET_ENABLE);
+}
+
+static void
remote_fast_tracepoint_feature (const struct protocol_feature *feature,
enum packet_support support,
const char *value)
@@ -3829,6 +3846,8 @@ static struct protocol_feature remote_pr
PACKET_ConditionalTracepoints },
{ "ConditionalBreakpoints", PACKET_DISABLE, remote_cond_breakpoint_feature,
PACKET_ConditionalBreakpoints },
+ { "BreakpointCommands", PACKET_DISABLE, remote_breakpoint_commands_feature,
+ PACKET_BreakpointCommands },
{ "FastTracepoints", PACKET_DISABLE, remote_fast_tracepoint_feature,
PACKET_FastTracepoints },
{ "StaticTracepoints", PACKET_DISABLE, remote_static_tracepoint_feature,
@@ -7768,6 +7787,37 @@ remote_add_target_side_condition (struct
return 0;
}

+static void
+remote_add_target_side_commands (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt, char *buf)
+{
+ struct agent_expr *aexpr = NULL;
+ int i, ix;
+
+ if (VEC_empty (agent_expr_p, bp_tgt->commands))
+ return;
+
+ buf += strlen (buf);
+
+ sprintf (buf, ";cmds:%x,", bp_tgt->persist);
+ buf += strlen (buf);
+
+ /* Concatenate all the agent expressions that are commands into the
+ cmds parameter. */
+ for (ix = 0;
+ VEC_iterate (agent_expr_p, bp_tgt->commands, ix, aexpr);
+ ix++)
+ {
+ sprintf (buf, "X%x,", aexpr->len);
+ buf += strlen (buf);
+ for (i = 0; i < aexpr->len; ++i)
+ buf = pack_hex_byte (buf, aexpr->buf[i]);
+ *buf = '\0';
+ }
+
+ VEC_free (agent_expr_p, bp_tgt->commands);
+}
+
/* Insert a breakpoint. On targets that have software breakpoint
support, we ask the remote target to do the work; on targets
which don't, we insert a traditional memory breakpoint. */
@@ -7804,6 +7854,9 @@ remote_insert_breakpoint (struct gdbarch
if (remote_supports_cond_breakpoints ())
remote_add_target_side_condition (gdbarch, bp_tgt, p);

+ if (remote_can_run_breakpoint_commands ())
+ remote_add_target_side_commands (gdbarch, bp_tgt, p);
+
putpkt (rs->buf);
getpkt (&rs->buf, &rs->buf_size, 0);

@@ -8041,6 +8094,9 @@ remote_insert_hw_breakpoint (struct gdba
if (remote_supports_cond_breakpoints ())
remote_add_target_side_condition (gdbarch, bp_tgt, p);

+ if (remote_can_run_breakpoint_commands ())
+ remote_add_target_side_commands (gdbarch, bp_tgt, p);
+
putpkt (rs->buf);
getpkt (&rs->buf, &rs->buf_size, 0);

@@ -9973,6 +10029,14 @@ remote_supports_string_tracing (void)
return rs->string_tracing;
}

+static int
+remote_can_run_breakpoint_commands (void)
+{
+ struct remote_state *rs = get_remote_state ();
+
+ return rs->breakpoint_commands;
+}
+
static void
remote_trace_init (void)
{
@@ -10839,6 +10903,7 @@ Specify the serial device it is connecte
remote_ops.to_supports_enable_disable_tracepoint = remote_supports_enable_disable_tracepoint;
remote_ops.to_supports_string_tracing = remote_supports_string_tracing;
remote_ops.to_supports_evaluation_of_breakpoint_conditions = remote_supports_cond_breakpoints;
+ remote_ops.to_can_run_breakpoint_commands = remote_can_run_breakpoint_commands;
remote_ops.to_trace_init = remote_trace_init;
remote_ops.to_download_tracepoint = remote_download_tracepoint;
remote_ops.to_can_download_tracepoint = remote_can_download_tracepoint;
@@ -11361,6 +11426,10 @@ Show the maximum size of the address (in
"ConditionalBreakpoints",
"conditional-breakpoints", 0);

+ add_packet_config_cmd (&remote_protocol_packets[PACKET_BreakpointCommands],
+ "BreakpointCommands",
+ "breakpoint-commands", 0);
+
add_packet_config_cmd (&remote_protocol_packets[PACKET_FastTracepoints],
"FastTracepoints", "fast-tracepoints", 0);

Index: target.c
===================================================================
RCS file: /cvs/src/src/gdb/target.c,v
retrieving revision 1.300
diff -u -p -r1.300 target.c
--- target.c 24 Feb 2012 15:10:59 -0000 1.300
+++ target.c 29 Feb 2012 05:52:14 -0000
@@ -698,6 +698,7 @@ update_current_target (void)
INHERIT (to_static_tracepoint_marker_at, t);
INHERIT (to_static_tracepoint_markers_by_strid, t);
INHERIT (to_traceframe_info, t);
+ INHERIT (to_can_run_breakpoint_commands, t);
INHERIT (to_magic, t);
INHERIT (to_supports_evaluation_of_breakpoint_conditions, t);
/* Do not inherit to_memory_map. */
@@ -929,6 +930,9 @@ update_current_target (void)
de_fault (to_supports_evaluation_of_breakpoint_conditions,
(int (*) (void))
return_zero);
+ de_fault (to_can_run_breakpoint_commands,
+ (int (*) (void))
+ return_zero);
de_fault (to_execution_direction, default_execution_direction);

#undef de_fault
Index: target.h
===================================================================
RCS file: /cvs/src/src/gdb/target.h,v
retrieving revision 1.228
diff -u -p -r1.228 target.h
--- target.h 24 Feb 2012 23:46:48 -0000 1.228
+++ target.h 29 Feb 2012 05:52:14 -0000
@@ -666,6 +666,10 @@ struct target_ops
end? */
int (*to_supports_evaluation_of_breakpoint_conditions) (void);

+ /* Does this target support evaluation of breakpoint commands on its
+ end? */
+ int (*to_can_run_breakpoint_commands) (void);
+
/* Determine current architecture of thread PTID.

The target is supposed to determine the architecture of the code where
@@ -978,6 +982,12 @@ int target_supports_disable_randomizatio
#define target_supports_evaluation_of_breakpoint_conditions() \
(*current_target.to_supports_evaluation_of_breakpoint_conditions) ()

+/* Returns true if this target can handle breakpoint commands
+ on its end. */
+
+#define target_can_run_breakpoint_commands() \
+ (*current_target.to_can_run_breakpoint_commands) ()
+
/* Invalidate all target dcaches. */
extern void target_dcache_invalidate (void);

Index: common/ax.def
===================================================================
RCS file: /cvs/src/src/gdb/common/ax.def,v
retrieving revision 1.6
diff -u -p -r1.6 ax.def
--- common/ax.def 4 Jan 2012 08:17:17 -0000 1.6
+++ common/ax.def 29 Feb 2012 05:52:14 -0000
@@ -93,3 +93,5 @@ DEFOP (invalid2, 0, 0, 0, 0, 0x31)
express the right thing. */
DEFOP (pick, 1, 0, 0, 1, 0x32)
DEFOP (rot, 0, 0, 3, 3, 0x33)
+/* Both the argument and consumed numbers are dynamic for this one. */
+DEFOP (printf, 0, 0, 0, 0, 0x34)
Index: doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.926
diff -u -p -r1.926 gdb.texinfo
--- doc/gdb.texinfo 25 Feb 2012 13:54:25 -0000 1.926
+++ doc/gdb.texinfo 29 Feb 2012 05:52:15 -0000
@@ -3301,6 +3301,7 @@ all breakpoints in that range are operat
* Disabling:: Disabling breakpoints
* Conditions:: Break conditions
* Break Commands:: Breakpoint command lists
+* Dynamic Printf:: Dynamic printf
* Save Breakpoints:: How to save breakpoints in a file
* Error in Breakpoints:: ``Cannot insert breakpoints''
* Breakpoint-related Warnings:: ``Breakpoint address adjusted...''
@@ -4587,6 +4588,68 @@ cont
end
@end smallexample

+@node Dynamic Printf
+@subsection Dynamic Printf
+
+@cindex dynamic printf
+The dynamic printf command @code{dprintf} is a convenient way to
+combine breakpoints with formatted printing of your program's data.
+
+In its most basic form, the output comes back to the GDB console and
+the @code{dprintf} only lasts as long as the debugging session.
+However, you can set the variable @code{dprintf-style} for additional
+approaches. For instance, you can ask to format the output by calling
+your program's @code{printf} function. This has the advantage that
+the characters go to the program's output device, so they can recorded
+in redirects to files and so forth.
+
+If you are doing remote debugging with a stub or agent, you can also
+ask to have the printf handled by the remote agent. In addition to
+ensuring that the output goes to the remote program's device along
+with any other output the program might produce, you can also ask that
+the dprintf remain active even after disconnecting from the remote
+target. Using the stub/agent is also more efficient, as it can do
+everything without needing to communicate with @value{GDBN}.
+
+@table @code
+@kindex dprintf
+@item dprintf @var{location} @var{template},@var{expression}[,@var{expression}@dots{}]
+Whenever execution reaches @var{location}, print the values of one or
+more @var{expressions} under the control of the string @var{template}.
+To print several values, separate them with commas.
+
+@item set dprintf-style @var{style}
+Set the dprintf output to be handled in one of several different
+styles enumerated below:
+
+@item gdb
+@kindex dprintf-style gdb
+Handle the output using the @value{GDBN} @code{printf} command.
+
+@item call
+@kindex dprintf-style call
+Handle the output by calling your program's @code{printf} function.
+(This expects @code{printf} to have been linked into the program.)
+
+@item agent
+@kindex dprintf-style agent
+Have the remote debugging agent (such as @code{gdbserver}) handle
+the output itself. This style is only available for agents that
+support it.
+
+@item set disconnected-dprintf on
+@itemx set disconnected-dprintf off
+@kindex set disconnected-dprintf
+Choose whether @code{dprintf} commands should continue to run if
+@value{GDBN} has disconnected from the target.
+
+@item show disconnected-dprintf off
+@kindex show disconnected-dprintf
+Show the current choice for disconnected @code{dprintf}.
+
+@end table
+
+@cindex breakpoint commands
@node Save Breakpoints
@subsection How to save breakpoints to a file

Index: gdbserver/ax.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/ax.c,v
retrieving revision 1.1
diff -u -p -r1.1 ax.c
--- gdbserver/ax.c 24 Feb 2012 15:14:47 -0000 1.1
+++ gdbserver/ax.c 29 Feb 2012 05:52:15 -0000
@@ -1152,6 +1152,30 @@ gdb_eval_agent_expr (struct regcache *re
top = stack[sp];
break;

+ case gdb_agent_op_printf:
+ {
+ int chan, nargs, slen, i;
+ int args[100];
+ char *format;
+
+ chan = aexpr->bytes[pc++];
+ nargs = aexpr->bytes[pc++];
+ slen = aexpr->bytes[pc++];
+ format = (char *) &(aexpr->bytes[pc]);
+ pc += slen;
+ for (i = 0; i < nargs; ++i)
+ {
+ args[i] = top;
+ if (--sp >= 0)
+ top = stack[sp];
+ }
+ /* (should re-check format before calling?) */
+ printf (format,
+ args[0], args[1], args[2], args[3], args[4],
+ args[5], args[6], args[7], args[8], args[9]);
+ }
+ break;
+
/* GDB never (currently) generates any of these ops. */
case gdb_agent_op_float:
case gdb_agent_op_ref_float:
Index: gdbserver/linux-low.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/linux-low.c,v
retrieving revision 1.195
diff -u -p -r1.195 linux-low.c
--- gdbserver/linux-low.c 27 Feb 2012 16:22:16 -0000 1.195
+++ gdbserver/linux-low.c 29 Feb 2012 05:52:15 -0000
@@ -2426,7 +2426,10 @@ Check if we're already there.\n",
|| (!step_over_finished
&& !bp_explains_trap && !trace_event)
|| (gdb_breakpoint_here (event_child->stop_pc)
- && gdb_condition_true_at_breakpoint (event_child->stop_pc)));
+ && gdb_condition_true_at_breakpoint (event_child->stop_pc)
+ && gdb_no_commands_at_breakpoint (event_child->stop_pc)));
+
+ run_breakpoint_commands (event_child->stop_pc);

/* We found no reason GDB would want us to stop. We either hit one
of our own breakpoints, or finished an internal step GDB
@@ -3292,7 +3295,8 @@ need_step_over_p (struct inferior_list_e
though. If the condition is being evaluated on the target's side
and it evaluate to false, step over this breakpoint as well. */
if (gdb_breakpoint_here (pc)
- && gdb_condition_true_at_breakpoint (pc))
+ && gdb_condition_true_at_breakpoint (pc)
+ && gdb_no_commands_at_breakpoint (pc))
{
if (debug_threads)
fprintf (stderr,
Index: gdbserver/mem-break.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/mem-break.c,v
retrieving revision 1.33
diff -u -p -r1.33 mem-break.c
--- gdbserver/mem-break.c 25 Feb 2012 19:54:50 -0000 1.33
+++ gdbserver/mem-break.c 29 Feb 2012 05:52:15 -0000
@@ -97,6 +97,20 @@ struct point_cond_list
struct point_cond_list *next;
};

+struct point_command_list
+{
+ /* Pointer to the agent expression that is the breakpoint's
+ commands. */
+ struct agent_expr *cmd;
+
+ /* Flag that is true if this command should run even while GDB is
+ disconnected. */
+ int persistence;
+
+ /* Pointer to the next command. */
+ struct point_command_list *next;
+};
+
/* A high level (in gdbserver's perspective) breakpoint. */
struct breakpoint
{
@@ -111,6 +125,9 @@ struct breakpoint
target's side. */
struct point_cond_list *cond_list;

+ /* Point to the list of commands to run when this is hit. */
+ struct point_command_list *command_list;
+
/* Link to this breakpoint's raw breakpoint. This is always
non-NULL. */
struct raw_breakpoint *raw;
@@ -121,6 +138,23 @@ struct breakpoint
int (*handler) (CORE_ADDR);
};

+int
+any_persistent_commands ()
+{
+ struct process_info *proc = current_process ();
+ struct breakpoint *bp;
+ struct point_command_list *cl;
+
+ for (bp = proc->breakpoints; bp != NULL; bp = bp->next)
+ {
+ for (cl = bp->command_list; cl != NULL; cl = cl->next)
+ if (cl->persistence)
+ return 1;
+ }
+
+ return 0;
+}
+
static struct raw_breakpoint *
find_raw_breakpoint_at (CORE_ADDR where)
{
@@ -835,6 +869,97 @@ gdb_condition_true_at_breakpoint (CORE_A
return (value != 0);
}

+/* Add commands COMMANDS to GDBserver's breakpoint BP. */
+
+void
+add_commands_to_breakpoint (struct breakpoint *bp,
+ struct agent_expr *commands, int persist)
+{
+ struct point_command_list *new_cmd;
+
+ /* Create new command. */
+ new_cmd = xcalloc (1, sizeof (*new_cmd));
+ new_cmd->cmd = commands;
+ new_cmd->persistence = persist;
+
+ /* Add commands to the list. */
+ new_cmd->next = bp->command_list;
+ bp->command_list = new_cmd;
+}
+
+/* Add a target-side command COMMAND to the breakpoint at ADDR. */
+
+int
+add_breakpoint_commands (CORE_ADDR addr, char **command, int persist)
+{
+ struct breakpoint *bp = find_gdb_breakpoint_at (addr);
+ char *actparm = *command;
+ struct agent_expr *cmd;
+
+ if (bp == NULL)
+ return 1;
+
+ if (command == NULL)
+ return 1;
+
+ cmd = gdb_parse_agent_expr (&actparm);
+
+ if (cmd == NULL)
+ {
+ fprintf (stderr, "Command evaluation failed. "
+ "Disabling.\n");
+ return 0;
+ }
+
+ add_commands_to_breakpoint (bp, cmd, persist);
+
+ *command = actparm;
+
+ return 0;
+}
+
+/* Return true if there are no commands to run at this location,
+ which likely means we want to report back to GDB. */
+int
+gdb_no_commands_at_breakpoint (CORE_ADDR where)
+{
+ struct breakpoint *bp = find_gdb_breakpoint_at (where);
+
+ if (bp == NULL)
+ return 0;
+
+ if (debug_threads)
+ fprintf (stderr, "at 0x%s, bp command_list is 0x%x\n",
+ paddress (where), (int) bp->command_list);
+ return (bp->command_list == NULL);
+}
+
+void
+run_breakpoint_commands (CORE_ADDR where)
+{
+ /* Fetch registers for the current inferior. */
+ struct breakpoint *bp = find_gdb_breakpoint_at (where);
+ ULONGEST value = 0;
+ struct point_command_list *cl;
+ int err = 0;
+
+ struct regcache *regcache = get_thread_regcache (current_inferior, 1);
+
+ if (bp == NULL)
+ return;
+
+ for (cl = bp->command_list;
+ cl && !value && !err; cl = cl->next)
+ {
+ /* Run the command. */
+ err = gdb_eval_agent_expr (regcache, NULL, cl->cmd, &value);
+
+ /* If one command has a problem, stop digging the hole deeper. */
+ if (err)
+ break;
+ }
+}
+
/* Return 1 if there is a breakpoint inserted in address WHERE
and if its condition, if it exists, is true. */

Index: gdbserver/mem-break.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/mem-break.h,v
retrieving revision 1.25
diff -u -p -r1.25 mem-break.h
--- gdbserver/mem-break.h 24 Feb 2012 15:15:56 -0000 1.25
+++ gdbserver/mem-break.h 29 Feb 2012 05:52:15 -0000
@@ -52,11 +52,19 @@ void clear_gdb_breakpoint_conditions (CO

int add_breakpoint_condition (CORE_ADDR addr, char **condition);

+int add_breakpoint_commands (CORE_ADDR addr, char **commands, int persist);
+
+int any_persistent_commands (void);
+
/* Evaluation condition (if any) at breakpoint BP. Return 1 if
true and 0 otherwise. */

int gdb_condition_true_at_breakpoint (CORE_ADDR where);

+int gdb_no_commands_at_breakpoint (CORE_ADDR where);
+
+void run_breakpoint_commands (CORE_ADDR where);
+
/* Returns TRUE if there's a GDB breakpoint set at ADDR. */

int gdb_breakpoint_here (CORE_ADDR where);
Index: gdbserver/server.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbserver/server.c,v
retrieving revision 1.162
diff -u -p -r1.162 server.c
--- gdbserver/server.c 24 Feb 2012 15:15:56 -0000 1.162
+++ gdbserver/server.c 29 Feb 2012 05:52:15 -0000
@@ -1621,8 +1621,9 @@ handle_query (char *own_buf, int packet_
strcat (own_buf, ";tracenz+");
}

- /* Support target-side breakpoint conditions. */
+ /* Support target-side breakpoint conditions and commands. */
strcat (own_buf, ";ConditionalBreakpoints+");
+ strcat (own_buf, ";BreakpointCommands+");

return;
}
@@ -2836,6 +2837,7 @@ static void
process_point_options (CORE_ADDR point_addr, char **packet)
{
char *dataptr = *packet;
+ int persist;

/* Check if data has the correct format. */
if (*dataptr != ';')
@@ -2845,21 +2847,33 @@ process_point_options (CORE_ADDR point_a

while (*dataptr)
{
- switch (*dataptr)
+ if (*dataptr == ';')
+ ++dataptr;
+
+ if (*dataptr == 'X')
{
- case 'X':
- /* Conditional expression. */
- fprintf (stderr, "Found breakpoint condition.\n");
- add_breakpoint_condition (point_addr, &dataptr);
- break;
- default:
- /* Unrecognized token, just skip it. */
- fprintf (stderr, "Unknown token %c, ignoring.\n",
- *dataptr);
+ /* Conditional expression. */
+ fprintf (stderr, "Found breakpoint condition.\n");
+ add_breakpoint_condition (point_addr, &dataptr);
+ }
+ else if (strncmp (dataptr, "cmds:", strlen ("cmds:")) == 0)
+ {
+ dataptr += strlen ("cmds:");
+ if (debug_threads)
+ fprintf (stderr, "Found breakpoint commands %s.\n", dataptr);
+ persist = (*dataptr == '1');
+ dataptr += 2;
+ add_breakpoint_commands (point_addr, &dataptr, persist);
+ }
+ else
+ {
+ /* Unrecognized token, just skip it. */
+ fprintf (stderr, "Unknown token %c, ignoring.\n",
+ *dataptr);
}

/* Skip tokens until we find one that we recognize. */
- while (*dataptr && *dataptr != 'X' && *dataptr != ';')
+ while (*dataptr && *dataptr != ';')
dataptr++;
}
*packet = dataptr;
@@ -2925,7 +2939,7 @@ process_serial_event (void)
pid =
ptid_get_pid (((struct inferior_list_entry *) current_inferior)->id);

- if (tracing && disconnected_tracing)
+ if ((tracing && disconnected_tracing) || any_persistent_commands ())
{
struct thread_resume resume_info;
struct process_info *process = find_process_pid (pid);
@@ -2936,9 +2950,15 @@ process_serial_event (void)
break;
}

- fprintf (stderr,
- "Disconnected tracing in effect, "
- "leaving gdbserver attached to the process\n");
+ if (tracing && disconnected_tracing)
+ fprintf (stderr,
+ "Disconnected tracing in effect, "
+ "leaving gdbserver attached to the process\n");
+
+ if (any_persistent_commands ())
+ fprintf (stderr,
+ "Persistent commands are present, "
+ "leaving gdbserver attached to the process\n");

/* Make sure we're in non-stop/async mode, so we we can both
wait for an async socket accept, and handle async target