/* Branch trace support for GDB, the GNU debugger.
- Copyright (C) 2013-2014 Free Software Foundation, Inc.
+ Copyright (C) 2013-2015 Free Software Foundation, Inc.
Contributed by Intel Corp. <markus.t.metzger@intel.com>
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
+#include "defs.h"
#include "btrace.h"
#include "gdbthread.h"
-#include "exceptions.h"
#include "inferior.h"
#include "target.h"
#include "record.h"
sym = bfun->sym;
if (sym != NULL)
- filename = symtab_to_filename_for_display (sym->symtab);
+ filename = symtab_to_filename_for_display (symbol_symtab (sym));
else
filename = "<unknown>";
return 1;
/* Check the location of those functions, as well. */
- bfname = symtab_to_fullname (sym->symtab);
- fname = symtab_to_fullname (fun->symtab);
+ bfname = symtab_to_fullname (symbol_symtab (sym));
+ fname = symtab_to_fullname (symbol_symtab (fun));
if (filename_cmp (fname, bfname) != 0)
return 1;
}
if (sym == NULL)
return 1;
- bfile = symtab_to_fullname (sym->symtab);
+ bfile = symtab_to_fullname (symbol_symtab (sym));
return (filename_cmp (bfile, fullname) != 0);
}
tail calls ending with a jump). */
static struct btrace_function *
-ftrace_find_call (struct gdbarch *gdbarch, struct btrace_function *bfun)
+ftrace_find_call (struct btrace_function *bfun)
{
for (; bfun != NULL; bfun = bfun->up)
{
struct btrace_insn *last;
- CORE_ADDR pc;
/* We do not allow empty function segments. */
gdb_assert (!VEC_empty (btrace_insn_s, bfun->insn));
last = VEC_last (btrace_insn_s, bfun->insn);
- pc = last->pc;
- if (gdbarch_insn_is_call (gdbarch, pc))
+ if (last->iclass == BTRACE_INSN_CALL)
break;
}
MFUN and FUN are the symbol information we have for this function. */
static struct btrace_function *
-ftrace_new_return (struct gdbarch *gdbarch,
- struct btrace_function *prev,
+ftrace_new_return (struct btrace_function *prev,
struct minimal_symbol *mfun,
struct symbol *fun)
{
wrong or that the call is simply not included in the trace. */
/* Let's search for some actual call. */
- caller = ftrace_find_call (gdbarch, prev->up);
+ caller = ftrace_find_call (prev->up);
if (caller == NULL)
{
/* There is no call in PREV's back trace. We assume that the
Return the chronologically latest function segment, never NULL. */
static struct btrace_function *
-ftrace_update_function (struct gdbarch *gdbarch,
- struct btrace_function *bfun, CORE_ADDR pc)
+ftrace_update_function (struct btrace_function *bfun, CORE_ADDR pc)
{
struct bound_minimal_symbol bmfun;
struct minimal_symbol *mfun;
if (last != NULL)
{
- CORE_ADDR lpc;
+ switch (last->iclass)
+ {
+ case BTRACE_INSN_RETURN:
+ return ftrace_new_return (bfun, mfun, fun);
- lpc = last->pc;
+ case BTRACE_INSN_CALL:
+ /* Ignore calls to the next instruction. They are used for PIC. */
+ if (last->pc + last->size == pc)
+ break;
- /* Check for returns. */
- if (gdbarch_insn_is_ret (gdbarch, lpc))
- return ftrace_new_return (gdbarch, bfun, mfun, fun);
+ return ftrace_new_call (bfun, mfun, fun);
- /* Check for calls. */
- if (gdbarch_insn_is_call (gdbarch, lpc))
- {
- int size;
+ case BTRACE_INSN_JUMP:
+ {
+ CORE_ADDR start;
- size = gdb_insn_length (gdbarch, lpc);
+ start = get_pc_function_start (pc);
- /* Ignore calls to the next instruction. They are used for PIC. */
- if (lpc + size != pc)
- return ftrace_new_call (bfun, mfun, fun);
+ /* If we can't determine the function for PC, we treat a jump at
+ the end of the block as tail call. */
+ if (start == 0 || start == pc)
+ return ftrace_new_tailcall (bfun, mfun, fun);
+ }
}
}
ftrace_print_function_name (bfun),
ftrace_print_filename (bfun));
- if (last != NULL)
- {
- CORE_ADDR start, lpc;
-
- start = get_pc_function_start (pc);
-
- /* If we can't determine the function for PC, we treat a jump at
- the end of the block as tail call. */
- if (start == 0)
- start = pc;
-
- lpc = last->pc;
-
- /* Jumps indicate optimized tail calls. */
- if (start == pc && gdbarch_insn_is_jump (gdbarch, lpc))
- return ftrace_new_tailcall (bfun, mfun, fun);
- }
-
return ftrace_new_switch (bfun, mfun, fun);
}
/* Add the instruction at PC to BFUN's instructions. */
static void
-ftrace_update_insns (struct btrace_function *bfun, CORE_ADDR pc)
+ftrace_update_insns (struct btrace_function *bfun,
+ const struct btrace_insn *insn)
{
- struct btrace_insn *insn;
-
- insn = VEC_safe_push (btrace_insn_s, bfun->insn, NULL);
- insn->pc = pc;
+ VEC_safe_push (btrace_insn_s, bfun->insn, insn);
if (record_debug > 1)
ftrace_debug (bfun, "update insn");
}
-/* Compute the function branch trace from a block branch trace BTRACE for
- a thread given by BTINFO. */
+/* Classify the instruction at PC. */
+
+static enum btrace_insn_class
+ftrace_classify_insn (struct gdbarch *gdbarch, CORE_ADDR pc)
+{
+ volatile struct gdb_exception error;
+ enum btrace_insn_class iclass;
+
+ iclass = BTRACE_INSN_OTHER;
+ TRY_CATCH (error, RETURN_MASK_ERROR)
+ {
+ if (gdbarch_insn_is_call (gdbarch, pc))
+ iclass = BTRACE_INSN_CALL;
+ else if (gdbarch_insn_is_ret (gdbarch, pc))
+ iclass = BTRACE_INSN_RETURN;
+ else if (gdbarch_insn_is_jump (gdbarch, pc))
+ iclass = BTRACE_INSN_JUMP;
+ }
+
+ return iclass;
+}
+
+/* Compute the function branch trace from BTS trace. */
static void
-btrace_compute_ftrace (struct btrace_thread_info *btinfo,
- VEC (btrace_block_s) *btrace)
+btrace_compute_ftrace_bts (struct thread_info *tp,
+ const struct btrace_data_bts *btrace)
{
+ struct btrace_thread_info *btinfo;
struct btrace_function *begin, *end;
struct gdbarch *gdbarch;
unsigned int blk;
int level;
- DEBUG ("compute ftrace");
-
gdbarch = target_gdbarch ();
+ btinfo = &tp->btrace;
begin = btinfo->begin;
end = btinfo->end;
level = begin != NULL ? -btinfo->level : INT_MAX;
- blk = VEC_length (btrace_block_s, btrace);
+ blk = VEC_length (btrace_block_s, btrace->blocks);
while (blk != 0)
{
blk -= 1;
- block = VEC_index (btrace_block_s, btrace, blk);
+ block = VEC_index (btrace_block_s, btrace->blocks, blk);
pc = block->begin;
for (;;)
{
+ volatile struct gdb_exception error;
+ struct btrace_insn insn;
int size;
/* We should hit the end of the block. Warn if we went too far. */
break;
}
- end = ftrace_update_function (gdbarch, end, pc);
+ end = ftrace_update_function (end, pc);
if (begin == NULL)
begin = end;
if (blk != 0)
level = min (level, end->level);
- ftrace_update_insns (end, pc);
+ size = 0;
+ TRY_CATCH (error, RETURN_MASK_ERROR)
+ size = gdb_insn_length (gdbarch, pc);
+
+ insn.pc = pc;
+ insn.size = size;
+ insn.iclass = ftrace_classify_insn (gdbarch, pc);
+
+ ftrace_update_insns (end, &insn);
ftrace_update_lines (end, pc);
/* We're done once we pushed the instruction at the end. */
if (block->end == pc)
break;
- size = gdb_insn_length (gdbarch, pc);
-
- /* Make sure we terminate if we fail to compute the size. */
+ /* We can't continue if we fail to compute the size. */
if (size <= 0)
{
warning (_("Recorded trace may be incomplete around %s."),
btinfo->level = -level;
}
+/* Compute the function branch trace from a block branch trace BTRACE for
+ a thread given by BTINFO. */
+
+static void
+btrace_compute_ftrace (struct thread_info *tp, struct btrace_data *btrace)
+{
+ DEBUG ("compute ftrace");
+
+ switch (btrace->format)
+ {
+ case BTRACE_FORMAT_NONE:
+ return;
+
+ case BTRACE_FORMAT_BTS:
+ btrace_compute_ftrace_bts (tp, &btrace->variant.bts);
+ return;
+ }
+
+ internal_error (__FILE__, __LINE__, _("Unkown branch trace format."));
+}
+
/* Add an entry for the current PC. */
static void
btrace_add_pc (struct thread_info *tp)
{
- VEC (btrace_block_s) *btrace;
+ struct btrace_data btrace;
struct btrace_block *block;
struct regcache *regcache;
struct cleanup *cleanup;
regcache = get_thread_regcache (tp->ptid);
pc = regcache_read_pc (regcache);
- btrace = NULL;
- cleanup = make_cleanup (VEC_cleanup (btrace_block_s), &btrace);
+ btrace_data_init (&btrace);
+ btrace.format = BTRACE_FORMAT_BTS;
+ btrace.variant.bts.blocks = NULL;
+
+ cleanup = make_cleanup_btrace_data (&btrace);
- block = VEC_safe_push (btrace_block_s, btrace, NULL);
+ block = VEC_safe_push (btrace_block_s, btrace.variant.bts.blocks, NULL);
block->begin = pc;
block->end = pc;
- btrace_compute_ftrace (&tp->btrace, btrace);
+ btrace_compute_ftrace (tp, &btrace);
do_cleanups (cleanup);
}
/* See btrace.h. */
void
-btrace_enable (struct thread_info *tp)
+btrace_enable (struct thread_info *tp, const struct btrace_config *conf)
{
if (tp->btrace.target != NULL)
return;
- if (!target_supports_btrace ())
+ if (!target_supports_btrace (conf->format))
error (_("Target does not support branch tracing."));
DEBUG ("enable thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
- tp->btrace.target = target_enable_btrace (tp->ptid);
+ tp->btrace.target = target_enable_btrace (tp->ptid, conf);
/* Add an entry for the current PC so we start tracing from where we
enabled it. */
/* See btrace.h. */
+const struct btrace_config *
+btrace_conf (const struct btrace_thread_info *btinfo)
+{
+ if (btinfo->target == NULL)
+ return NULL;
+
+ return target_btrace_conf (btinfo->target);
+}
+
+/* See btrace.h. */
+
void
btrace_disable (struct thread_info *tp)
{
btrace_clear (tp);
}
-/* Adjust the block trace in order to stitch old and new trace together.
- BTRACE is the new delta trace between the last and the current stop.
- BTINFO is the old branch trace until the last stop.
- May modify BTRACE as well as the existing trace in BTINFO.
- Return 0 on success, -1 otherwise. */
+/* Stitch branch trace in BTS format. */
static int
-btrace_stitch_trace (VEC (btrace_block_s) **btrace,
- const struct btrace_thread_info *btinfo)
+btrace_stitch_bts (struct btrace_data_bts *btrace,
+ const struct btrace_thread_info *btinfo)
{
struct btrace_function *last_bfun;
struct btrace_insn *last_insn;
btrace_block_s *first_new_block;
- /* If we don't have trace, there's nothing to do. */
- if (VEC_empty (btrace_block_s, *btrace))
- return 0;
-
last_bfun = btinfo->end;
gdb_assert (last_bfun != NULL);
/* Beware that block trace starts with the most recent block, so the
chronologically first block in the new trace is the last block in
the new trace's block vector. */
- first_new_block = VEC_last (btrace_block_s, *btrace);
+ gdb_assert (!VEC_empty (btrace_block_s, btrace->blocks));
+ first_new_block = VEC_last (btrace_block_s, btrace->blocks);
last_insn = VEC_last (btrace_insn_s, last_bfun->insn);
/* If the current PC at the end of the block is the same as in our current
In the second case, the delta trace vector should contain exactly one
entry for the partial block containing the current PC. Remove it. */
if (first_new_block->end == last_insn->pc
- && VEC_length (btrace_block_s, *btrace) == 1)
+ && VEC_length (btrace_block_s, btrace->blocks) == 1)
{
- VEC_pop (btrace_block_s, *btrace);
+ VEC_pop (btrace_block_s, btrace->blocks);
return 0;
}
return 0;
}
+/* Adjust the block trace in order to stitch old and new trace together.
+ BTRACE is the new delta trace between the last and the current stop.
+ BTINFO is the old branch trace until the last stop.
+ May modifx BTRACE as well as the existing trace in BTINFO.
+ Return 0 on success, -1 otherwise. */
+
+static int
+btrace_stitch_trace (struct btrace_data *btrace,
+ const struct btrace_thread_info *btinfo)
+{
+ /* If we don't have trace, there's nothing to do. */
+ if (btrace_data_empty (btrace))
+ return 0;
+
+ switch (btrace->format)
+ {
+ case BTRACE_FORMAT_NONE:
+ return 0;
+
+ case BTRACE_FORMAT_BTS:
+ return btrace_stitch_bts (&btrace->variant.bts, btinfo);
+ }
+
+ internal_error (__FILE__, __LINE__, _("Unkown branch trace format."));
+}
+
/* Clear the branch trace histories in BTINFO. */
static void
{
struct btrace_thread_info *btinfo;
struct btrace_target_info *tinfo;
- VEC (btrace_block_s) *btrace;
+ struct btrace_data btrace;
struct cleanup *cleanup;
int errcode;
DEBUG ("fetch thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
- btrace = NULL;
btinfo = &tp->btrace;
tinfo = btinfo->target;
if (tinfo == NULL)
if (btinfo->replay != NULL)
return;
- cleanup = make_cleanup (VEC_cleanup (btrace_block_s), &btrace);
+ btrace_data_init (&btrace);
+ cleanup = make_cleanup_btrace_data (&btrace);
/* Let's first try to extend the trace we already have. */
if (btinfo->end != NULL)
errcode = target_read_btrace (&btrace, tinfo, BTRACE_READ_NEW);
/* If we got any new trace, discard what we have. */
- if (errcode == 0 && !VEC_empty (btrace_block_s, btrace))
+ if (errcode == 0 && !btrace_data_empty (&btrace))
btrace_clear (tp);
}
error (_("Failed to read branch trace."));
/* Compute the trace, provided we have any. */
- if (!VEC_empty (btrace_block_s, btrace))
+ if (!btrace_data_empty (&btrace))
{
btrace_clear_history (btinfo);
- btrace_compute_ftrace (btinfo, btrace);
+ btrace_compute_ftrace (tp, &btrace);
}
do_cleanups (cleanup);
DEBUG ("free objfile");
- ALL_THREADS (tp)
+ ALL_NON_EXITED_THREADS (tp)
btrace_clear (tp);
}
const struct gdb_xml_element *element,
void *user_data, VEC (gdb_xml_value_s) *attributes)
{
- VEC (btrace_block_s) **btrace;
+ struct btrace_data *btrace;
struct btrace_block *block;
ULONGEST *begin, *end;
btrace = user_data;
- block = VEC_safe_push (btrace_block_s, *btrace, NULL);
+
+ switch (btrace->format)
+ {
+ case BTRACE_FORMAT_BTS:
+ break;
+
+ case BTRACE_FORMAT_NONE:
+ btrace->format = BTRACE_FORMAT_BTS;
+ btrace->variant.bts.blocks = NULL;
+ break;
+
+ default:
+ gdb_xml_error (parser, _("Btrace format error."));
+ }
begin = xml_find_attribute (attributes, "begin")->value;
end = xml_find_attribute (attributes, "end")->value;
+ block = VEC_safe_push (btrace_block_s, btrace->variant.bts.blocks, NULL);
block->begin = *begin;
block->end = *end;
}
/* See btrace.h. */
-VEC (btrace_block_s) *
-parse_xml_btrace (const char *buffer)
+void
+parse_xml_btrace (struct btrace_data *btrace, const char *buffer)
{
- VEC (btrace_block_s) *btrace = NULL;
struct cleanup *cleanup;
int errcode;
#if defined (HAVE_LIBEXPAT)
- cleanup = make_cleanup (VEC_cleanup (btrace_block_s), &btrace);
+ btrace->format = BTRACE_FORMAT_NONE;
+
+ cleanup = make_cleanup_btrace_data (btrace);
errcode = gdb_xml_parse_quick (_("btrace"), "btrace.dtd", btrace_elements,
- buffer, &btrace);
+ buffer, btrace);
if (errcode != 0)
error (_("Error parsing branch trace."));
error (_("Cannot process branch trace. XML parsing is not supported."));
#endif /* !defined (HAVE_LIBEXPAT) */
+}
+
+#if defined (HAVE_LIBEXPAT)
+
+/* Parse a btrace-conf "bts" xml record. */
+
+static void
+parse_xml_btrace_conf_bts (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC (gdb_xml_value_s) *attributes)
+{
+ struct btrace_config *conf;
+ struct gdb_xml_value *size;
+
+ conf = user_data;
+ conf->format = BTRACE_FORMAT_BTS;
+ conf->bts.size = 0;
- return btrace;
+ size = xml_find_attribute (attributes, "size");
+ if (size != NULL)
+ conf->bts.size = (unsigned int) * (ULONGEST *) size->value;
+}
+
+static const struct gdb_xml_attribute btrace_conf_bts_attributes[] = {
+ { "size", GDB_XML_AF_OPTIONAL, gdb_xml_parse_attr_ulongest, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+static const struct gdb_xml_element btrace_conf_children[] = {
+ { "bts", btrace_conf_bts_attributes, NULL, GDB_XML_EF_OPTIONAL,
+ parse_xml_btrace_conf_bts, NULL },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+static const struct gdb_xml_attribute btrace_conf_attributes[] = {
+ { "version", GDB_XML_AF_NONE, NULL, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+static const struct gdb_xml_element btrace_conf_elements[] = {
+ { "btrace-conf", btrace_conf_attributes, btrace_conf_children,
+ GDB_XML_EF_NONE, NULL, NULL },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+#endif /* defined (HAVE_LIBEXPAT) */
+
+/* See btrace.h. */
+
+void
+parse_xml_btrace_conf (struct btrace_config *conf, const char *xml)
+{
+ int errcode;
+
+#if defined (HAVE_LIBEXPAT)
+
+ errcode = gdb_xml_parse_quick (_("btrace-conf"), "btrace-conf.dtd",
+ btrace_conf_elements, xml, conf);
+ if (errcode != 0)
+ error (_("Error parsing branch trace configuration."));
+
+#else /* !defined (HAVE_LIBEXPAT) */
+
+ error (_("XML parsing is not supported."));
+
+#endif /* !defined (HAVE_LIBEXPAT) */
}
/* See btrace.h. */
return btrace_insn_cmp (&begin, &end) == 0;
}
+
+/* Forward the cleanup request. */
+
+static void
+do_btrace_data_cleanup (void *arg)
+{
+ btrace_data_fini (arg);
+}
+
+/* See btrace.h. */
+
+struct cleanup *
+make_cleanup_btrace_data (struct btrace_data *data)
+{
+ return make_cleanup (do_btrace_data_cleanup, data);
+}