[patch v5 02/12] cli, btrace: add btrace cli

markus.t.metzger
2012-12-07T10:38:11Z

Add branch trace commands:

- "btrace enable/disable" perform the obvious operation

"" on the current thread.
"all" on each existing thread.
"auto" on each newly created thread.

Actually, "btrace enable auto" turns on automatic enabling for new threads,
and "btrace disable auto" turns it off, again.

- "btrace list" prints the blocks that have been traced.

The output may be configured using modifiers. It prints:
- the block number
/a the begin and end code address of that block
/f the function containing the block
/l the source lines contained in the block

With the /t modifier, it prints the total number of blocks and exits.

It accepts an optional range argument specifying the range of blocks to be
listed. If no argument is given, all blocks are listed.

The block number can be used to print the trace for one particular block or
for a range of blocks.

- "btrace" prints the branch trace disassembly for the current thread.

Branch trace is printed block-by-block. Typically, one block at a time is
printed.

By default, the disassembly for the next block is printed, thus iterating
over the full branch trace.

The command supports the /m and /r modifiers accepted by the disassemble
command.

In addition, the command supports the following arguments:
- "<n>" set the iterator to the <n>-th block
- "+[<n>]" advance the iterator by <n> (default: 1)
- "-[<n>]" advance the iterator by -<n> (default: 1)
- "<l>-<h>" set the iterator to the <h>'th block and
print the blocks in the range in reverse (i.e. original
control flow) order.

Mixed source and disassembly does not work very well for inlined functions,
a problem that it shares with the disassemble command.

2012-12-07 Markus Metzger <markus.t.metzger@intel.com>

* command.h (enum command_class): Add class_btrace.
* btrace.c (warn_enable_btrace): New function.
(warn_disable_btrace): New function.
(cmd_btrace_enable): New function.
(cmd_btrace_enable_all): New function.
(cmd_btrace_enable_auto): New function.
(cmd_btrace_disable): New function.
(cmd_btrace_disable_all): New function.
(cmd_btrace_disable_auto): New function.
(do_btrace_list_address): New function.
(do_btrace_list_function): New function.
(do_btrace_list_line): New function.
(do_btrace_list_item): New function.
(do_btrace_list): New function.
(cmd_btrace_list): New function.
(do_btrace): New function.
(do_btrace_range): New function.
(do_prev_btrace): New function.
(do_next_btrace): New function.
(cmd_btrace): New function.
(_initialize_btrace): New function.

---
gdb/btrace.c | 600 +++++++++++++++++++++++++++++++++++++++
gdb/command.h | 2 +-
gdb/testsuite/gdb.base/page.exp | 3 +-
3 files changed, 603 insertions(+), 2 deletions(-)

diff --git a/gdb/btrace.c b/gdb/btrace.c
index 418f360..68b109a 100644
--- a/gdb/btrace.c
+++ b/gdb/btrace.c
@@ -25,6 +25,28 @@
#include "exceptions.h"
#include "inferior.h"
#include "target.h"
+#include "observer.h"
+#include "command.h"
+#include "cli/cli-cmds.h"
+#include "cli/cli-utils.h"
+#include "arch-utils.h"
+#include "disasm.h"
+#include "gdbarch.h"
+
+#include <ctype.h>
+#include <string.h>
+
+/* A new thread observer enabling branch tracing for the new thread. */
+static struct observer *btrace_thread_observer;
+
+/* Branch tracing command list. */
+static struct cmd_list_element *btcmdlist;
+
+/* Branch tracing enable command list. */
+static struct cmd_list_element *btencmdlist;
+
+/* Branch tracing disable command list. */
+static struct cmd_list_element *btdiscmdlist;

/* See btrace.h. */

@@ -166,3 +188,581 @@ next_btrace (struct thread_info *tp)

return VEC_index (btrace_block_s, btp->btrace, btp->iterator);
}
+
+/* Enable branch tracing. Turn errors into warnings. */
+
+static void
+warn_enable_btrace (struct thread_info *tp)
+{
+ volatile struct gdb_exception error;
+
+ TRY_CATCH (error, RETURN_MASK_ERROR)
+ enable_btrace (tp);
+
+ if (error.message)
+ warning (_("%s"), error.message);
+}
+
+/* Disable branch tracing. Turn errors into warnings. */
+
+static void
+warn_disable_btrace (struct thread_info *tp)
+{
+ volatile struct gdb_exception error;
+
+ TRY_CATCH (error, RETURN_MASK_ERROR)
+ disable_btrace (tp);
+
+ if (error.message)
+ warning (_("%s"), error.message);
+}
+
+/* The "btrace enable" command. */
+
+static void
+cmd_btrace_enable (char *args, int from_tty)
+{
+ struct thread_info *tp;
+
+ if (args && *args)
+ {
+ ALL_THREADS (tp)
+ if (number_is_in_list (args, tp->num))
+ warn_enable_btrace (tp);
+ }
+ else
+ {
+ tp = find_thread_ptid (inferior_ptid);
+ if (!tp)
+ error (_("Couldn't enable branch tracing: no inferior thread."));
+
+ enable_btrace (tp);
+ }
+}
+
+/* The "btrace enable all" command. */
+
+static void
+cmd_btrace_enable_all (char *args, int from_tty)
+{
+ struct thread_info *tp;
+
+ if (args && *args)
+ error (_("Invalid argument."));
+
+ ALL_THREADS (tp)
+ warn_enable_btrace (tp);
+}
+
+/* The "btrace enable auto" command. */
+
+static void
+cmd_btrace_enable_auto (char *args, int from_tty)
+{
+ if (args && *args)
+ error (_("Invalid argument."));
+
+ if (btrace_thread_observer)
+ return;
+
+ btrace_thread_observer
+ = observer_attach_new_thread (warn_enable_btrace);
+}
+
+/* The "btrace disable" command. */
+
+static void
+cmd_btrace_disable (char *args, int from_tty)
+{
+ struct thread_info *tp;
+
+ if (args && *args)
+ {
+ ALL_THREADS (tp)
+ if (number_is_in_list (args, tp->num))
+ warn_enable_btrace (tp);
+ }
+ else
+ {
+ tp = find_thread_ptid (inferior_ptid);
+ if (!tp)
+ error (_("Couldn't disable branch tracing: no inferior thread."));
+
+ disable_btrace (tp);
+ }
+}
+
+/* The "btrace disable all" command. */
+
+static void
+cmd_btrace_disable_all (char *args, int from_tty)
+{
+ struct thread_info *tp;
+
+ if (args && *args)
+ error (_("Invalid argument."));
+
+ ALL_THREADS (tp)
+ warn_disable_btrace (tp);
+}
+
+/* The "btrace disable auto" command. */
+
+static void
+cmd_btrace_disable_auto (char *args, int from_tty)
+{
+ if (args && *args)
+ error (_("Invalid argument."));
+
+ if (!btrace_thread_observer)
+ return;
+
+ observer_detach_new_thread (btrace_thread_observer);
+ btrace_thread_observer = NULL;
+}
+
+/* Flags controlling the output of a btrace list command. */
+enum btrace_list_flags
+{
+ /* Print code addresses. */
+ BTR_LIST_ADDRESS = (1 << 0),
+
+ /* Print function names. */
+ BTR_LIST_FUNCTION = (1 << 1),
+
+ /* Print line information. */
+ BTR_LIST_LINE = (1 << 2),
+
+ /* Print the total number of btrace blocks. */
+ BTR_LIST_TOTAL = (1 << 3)
+};
+
+/* Print the addresses for a btrace block. */
+
+static void
+do_btrace_list_address (struct btrace_block *trace)
+{
+ struct gdbarch *gdbarch = target_gdbarch ();
+
+ ui_out_field_core_addr (current_uiout, "begin", gdbarch, trace->begin);
+ ui_out_text (current_uiout, " - ");
+ ui_out_field_core_addr (current_uiout, "end", gdbarch, trace->end);
+}
+
+/* Print the function containing a btrace block. */
+
+static void
+do_btrace_list_function (struct btrace_block *trace)
+{
+ struct minimal_symbol *msymbol;
+ struct symbol *symbol;
+ const char* func;
+
+ func = "??";
+ symbol = find_pc_function (trace->begin);
+ if (symbol)
+ func = SYMBOL_PRINT_NAME (symbol);
+ else
+ {
+ msymbol = lookup_minimal_symbol_by_pc (trace->begin);
+ if (msymbol)
+ func = SYMBOL_PRINT_NAME (msymbol);
+ }
+
+ ui_out_text (current_uiout, " in ");
+ ui_out_field_string (current_uiout, "function", func);
+}
+
+/* Print the line information for a btrace block. */
+
+static void
+do_btrace_list_line (struct btrace_block *trace)
+{
+ struct symbol *symbol = find_pc_function (trace->begin);
+ struct symtab *symtab = NULL;
+ struct linetable *linetable = NULL;
+ const char *filename = NULL;
+
+ if (symbol)
+ symtab = symbol->symtab;
+ if (!symtab)
+ symtab = find_pc_symtab (trace->begin);
+ if (symtab)
+ {
+ linetable = symtab->linetable;
+ filename = symtab->filename;
+ }
+
+ if (filename)
+ {
+ ui_out_text (current_uiout, " at ");
+ ui_out_field_string (current_uiout, "file", filename);
+
+ if (linetable)
+ {
+ struct linetable_entry *line = linetable->item;
+ int nitems = linetable->nitems, i = 0;
+ int begin = INT_MAX, end = 0;
+
+ /* Skip all preceding entries. */
+ for (; i < (nitems - 1) && line[i + 1].pc <= trace->begin; ++i);
+
+ for (; i < nitems && line[i].pc <= trace->end; ++i)
+ {
+ begin = min (begin, line[i].line);
+ end = max (end, line[i].line);
+ }
+
+ if (begin <= end)
+ {
+ ui_out_text (current_uiout, ":");
+ ui_out_field_int (current_uiout, "lbegin", begin);
+
+ if (begin < end)
+ {
+ ui_out_text (current_uiout, "-");
+ ui_out_field_int (current_uiout, "lend", end);
+ }
+ }
+ }
+ }
+}
+
+/* Print a btrace block. */
+
+static void
+do_btrace_list_item (struct btrace_block *trace, enum btrace_list_flags flags)
+{
+ if (flags & BTR_LIST_ADDRESS)
+ do_btrace_list_address (trace);
+
+ if (flags & BTR_LIST_FUNCTION)
+ do_btrace_list_function (trace);
+
+ if (flags & BTR_LIST_LINE)
+ do_btrace_list_line (trace);
+}
+
+/* Print the btrace list for a range of blocks. */
+
+static void
+do_btrace_list (VEC (btrace_block_s) *btrace, char *range,
+ enum btrace_list_flags flags)
+{
+ struct cleanup *ui_out_chain;
+ unsigned int block;
+ struct btrace_block *trace;
+
+ ui_out_chain
+ = make_cleanup_ui_out_tuple_begin_end (current_uiout, "btrace blocks");
+ block = 0;
+
+ while (VEC_iterate (btrace_block_s, btrace, block, trace))
+ {
+ ++block;
+
+ if (number_is_in_list (range, block))
+ {
+ ui_out_field_fmt_int (current_uiout, 5, ui_left, "block", block);
+ do_btrace_list_item (trace, flags);
+ ui_out_text (current_uiout, "\n");
+ }
+ }
+
+ do_cleanups (ui_out_chain);
+}
+
+/* The "btrace list" command. */
+
+static void
+cmd_btrace_list (char *args, int from_tty)
+{
+ struct thread_info *tp = find_thread_ptid (inferior_ptid);
+ enum btrace_list_flags flags = BTR_LIST_FUNCTION | BTR_LIST_LINE;
+ VEC (btrace_block_s) *btrace;
+
+ if (!tp)
+ error (_("No thread."));
+
+ btrace = get_btrace (tp);
+
+ /* Parse modifier. */
+ if (args && *args)
+ {
+ if (*args == '/')
+ flags = 0;
+
+ while (*args == '/')
+ {
+ ++args;
+
+ if (*args == '\0')
+ error (_("Missing modifier."));
+
+ for (; *args; ++args)
+ {
+ if (isspace (*args))
+ break;
+
+ if (*args == '/')
+ continue;
+
+ switch (*args)
+ {
+ case 'a':
+ flags |= BTR_LIST_ADDRESS;
+ break;
+ case 'f':
+ flags |= BTR_LIST_FUNCTION;
+ break;
+ case 'l':
+ flags |= BTR_LIST_LINE;
+ break;
+ case 't':
+ flags |= BTR_LIST_TOTAL;
+ break;
+ default:
+ error (_("Invalid modifier: %c."), *args);
+ }
+ }
+
+ args = skip_spaces (args);
+ }
+ }
+
+ if (flags & BTR_LIST_TOTAL)
+ {
+ struct cleanup *ui_out_chain;
+ unsigned int total;
+
+ total = VEC_length (btrace_block_s, btrace);
+ ui_out_chain
+ = make_cleanup_ui_out_tuple_begin_end (current_uiout, "btrace blocks");
+
+ ui_out_text (current_uiout, "total: ");
+ ui_out_field_int (current_uiout, "total", total);
+ ui_out_text (current_uiout, "\n");
+
+ do_cleanups (ui_out_chain);
+ }
+ else if (btrace)
+ do_btrace_list (btrace, args, flags);
+ else
+ error (_("No trace."));
+}
+
+/* Print the disassembly of a btrace block. */
+
+static void
+do_btrace (struct btrace_block *trace, int flags)
+{
+ struct gdbarch *gdbarch = target_gdbarch ();
+
+ if (!trace)
+ error (_("No trace."));
+
+ if (trace->end < trace->begin)
+ warning (_("Bad trace: %s - %s"), paddress (gdbarch, trace->begin),
+ paddress (gdbarch, trace->end));
+
+ gdb_disassembly (gdbarch, current_uiout, 0, flags, -1,
+ trace->begin, trace->end + 1);
+}
+
+/* Print the branch trace disassembly for a range of btrace blocks. */
+
+static void
+do_btrace_range (struct thread_info *tp, int flags, char *args)
+{
+ struct get_number_or_range_state state;
+ unsigned int i;
+ VEC (btrace_block_s) *btrace;
+ struct cleanup *cleanup;
+
+ btrace = NULL;
+ cleanup = make_cleanup (VEC_cleanup (btrace_block_s), &btrace);
+
+ /* We store the relevant blocks into a separate vector, so we can display
+ them in reverse order. */
+ init_number_or_range (&state, args);
+ while (!state.finished)
+ {
+ struct btrace_block *trace;
+ int index;
+
+ index = get_number_or_range (&state);
+ if (!index)
+ error (_("Args must be numbers or '$' variables."));
+
+ trace = read_btrace (tp, index - 1);
+ if (!trace)
+ continue;
+
+ VEC_safe_push (btrace_block_s, btrace, trace);
+ }
+
+ i = VEC_length (btrace_block_s, btrace);
+ while (i--)
+ do_btrace (VEC_index (btrace_block_s, btrace, i), flags);
+
+ do_cleanups (cleanup);
+}
+
+/* Print the branch trace disassembly for a preceding block. */
+
+static void
+do_prev_btrace (struct thread_info *tp, int flags, char *args)
+{
+ struct btrace_block *trace = NULL;
+ size_t skip = 1;
+
+ if (args[0])
+ skip = get_number (&args);
+
+ while (skip--)
+ trace = prev_btrace (tp);
+
+ if (args[0])
+ error (_("Junk after argument: %s"), args);
+
+ do_btrace (trace, flags);
+}
+
+/* Print the branch trace disassembly for a succeding block. */
+
+static void
+do_next_btrace (struct thread_info *tp, int flags, char *args)
+{
+ struct btrace_block *trace = NULL;
+ size_t skip = 1;
+
+ if (args[0])
+ skip = get_number (&args);
+
+ while (skip--)
+ trace = next_btrace (tp);
+
+ if (args[0])
+ error (_("Junk after argument: %s"), args);
+
+ do_btrace (trace, flags);
+}
+
+/* The "btrace" command. */
+
+static void
+cmd_btrace (char *args, int from_tty)
+{
+ struct thread_info *tp = find_thread_ptid (inferior_ptid);
+ struct btrace_block *trace = NULL;
+ int flags = 0;
+
+ if (!tp)
+ error (_("No thread."));
+
+ /* Parse modifier. We accept the same modifiers as the disas command. */
+ if (args && *args)
+ {
+ while (*args == '/')
+ {
+ ++args;
+
+ if (*args == '\0')
+ error (_("Missing modifier."));
+
+ for (; *args; ++args)
+ {
+ if (isspace (*args))
+ break;
+
+ if (*args == '/')
+ continue;
+
+ switch (*args)
+ {
+ case 'm':
+ flags |= DISASSEMBLY_SOURCE;
+ flags |= DISASSEMBLY_FILENAME;
+ break;
+ case 'r':
+ flags |= DISASSEMBLY_RAW_INSN;
+ break;
+ default:
+ error (_("Invalid modifier: %c."), *args);
+ }
+ }
+
+ args = skip_spaces (args);
+ }
+ }
+
+ if (!args || !*args)
+ do_btrace (prev_btrace (tp), flags);
+ else if (args[0] == '+')
+ do_prev_btrace (tp, flags, args+1);
+ else if (args[0] == '-')
+ do_next_btrace (tp, flags, args+1);
+ else
+ do_btrace_range (tp, flags, args);
+}
+
+void _initialize_btrace (void);
+
+/* Initialize btrace commands. */
+
+void
+_initialize_btrace (void)
+{
+ struct cmd_list_element *c;
+
+ add_cmd ("branchtrace", class_btrace, NULL, _("Recording a branch trace."),
+ &cmdlist);
+
+ add_prefix_cmd ("btrace", class_btrace, cmd_btrace, _("\
+Disassemble the selected branch trace block.\n\n\
+With a /m modifier, source lines are included (if available).\n\
+With a /r modifier, raw instructions in hex are included.\n\n\
+Without arguments, selects the chronologically preceding block.\n\
+With \"+[<n>]\" argument, selects the n-th chronologically preceding block.\n\
+With \"-[<n>]\" argument, selects the n-th chronologically succeeding block.\n\
+With one positive integer argument, selects the respective block.\n\
+With a range argument \"<l>-<h>\", selects the h-th block and disassembles \
+blocks in the range in reverse (i.e. original control flow) order.\n"),
+ &btcmdlist, "btrace ", 1, &cmdlist);
+
+ add_prefix_cmd ("enable", class_btrace, cmd_btrace_enable, _("\
+Enable branch trace recording for the current thread.\n"),
+ &btencmdlist, "btrace enable ", 1, &btcmdlist);
+
+ add_cmd ("all", class_btrace, cmd_btrace_enable_all, _("\
+Enable branch trace recording for all existing threads.\n"),
+ &btencmdlist);
+
+ add_cmd ("auto", class_btrace, cmd_btrace_enable_auto, _("\
+Enable branch trace recording for each new thread.\n"),
+ &btencmdlist);
+
+ add_prefix_cmd ("disable", class_btrace, cmd_btrace_disable, _("\
+Disable branch trace recording for the current thread.\n"),
+ &btdiscmdlist, "btrace disable ", 1, &btcmdlist);
+
+ add_cmd ("all", class_btrace, cmd_btrace_disable_all, _("\
+Disable branch trace recording for all existing threads.\n"),
+ &btdiscmdlist);
+
+ add_cmd ("auto", class_btrace, cmd_btrace_disable_auto, _("\
+Stop enabling branch trace recording for each new thread.\n"),
+ &btdiscmdlist);
+
+ add_cmd ("list", class_btrace, cmd_btrace_list, _("\
+List branch trace blocks.\n\n\
+Prints a list of all blocks for which branch trace is available.\n\
+With a /a modifier, addresses are included.\n\
+With a /f modifier, the function name is included (if available).\n\
+With a /l modifier, source lines are include (if available).\n\
+With a /t modifier, prints the total number of trace blocks and stops.\n\
+Without any modifier, behaves as if /fl were specified.\n\n\
+Without arguments, the full list of blocks is listed.\n\
+With a range (<start>[-<end>]) argument, lists all blocks in that range.\n"),
+ &btcmdlist);
+}
diff --git a/gdb/command.h b/gdb/command.h
index 8eb86ba..b576b70 100644
--- a/gdb/command.h
+++ b/gdb/command.h
@@ -39,7 +39,7 @@ enum command_class
no_class = -1, class_run = 0, class_vars, class_stack, class_files,
class_support, class_info, class_breakpoint, class_trace,
class_alias, class_bookmark, class_obscure, class_maintenance,
- class_pseudo, class_tui, class_user, class_xdb,
+ class_pseudo, class_tui, class_user, class_xdb, class_btrace,
no_set_class /* Used for "show" commands that have no corresponding
"set" command. */
};
diff --git a/gdb/testsuite/gdb.base/page.exp b/gdb/testsuite/gdb.base/page.exp
index 0629807..d9b3899 100644
--- a/gdb/testsuite/gdb.base/page.exp
+++ b/gdb/testsuite/gdb.base/page.exp
@@ -25,6 +25,7 @@ gdb_test_sequence "help" "unpaged help" {
"List of classes of commands:"
""
"aliases -- Aliases of other commands"
+ "branchtrace -- Recording a branch trace"
"breakpoints -- Making program stop at certain points"
"data -- Examining data"
"files -- Specifying and examining files"
@@ -52,12 +53,12 @@ gdb_expect_list "paged help" \
"List of classes of commands:"
""
"aliases -- Aliases of other commands"
+ "branchtrace -- Recording a branch trace"
"breakpoints -- Making program stop at certain points"
"data -- Examining data"
"files -- Specifying and examining files"
"internals -- Maintenance commands"
"obscure -- Obscure features"
- "running -- Running the program"
}
gdb_test "q"

--
1.7.1