| Index: gdb/btrace.c
|
| diff --git a/gdb/btrace.c b/gdb/btrace.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3230a3ee6e4aff9a4bbc4aebc8cc107b800985f2
|
| --- /dev/null
|
| +++ b/gdb/btrace.c
|
| @@ -0,0 +1,543 @@
|
| +/* Branch trace support for GDB, the GNU debugger.
|
| +
|
| + Copyright (C) 2013 Free Software Foundation, Inc.
|
| +
|
| + Contributed by Intel Corp. <markus.t.metzger@intel.com>
|
| +
|
| + This file is part of GDB.
|
| +
|
| + This program is free software; you can redistribute it and/or modify
|
| + it under the terms of the GNU General Public License as published by
|
| + the Free Software Foundation; either version 3 of the License, or
|
| + (at your option) any later version.
|
| +
|
| + This program is distributed in the hope that it will be useful,
|
| + but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| + GNU General Public License for more details.
|
| +
|
| + 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 "btrace.h"
|
| +#include "gdbthread.h"
|
| +#include "exceptions.h"
|
| +#include "inferior.h"
|
| +#include "target.h"
|
| +#include "record.h"
|
| +#include "symtab.h"
|
| +#include "disasm.h"
|
| +#include "source.h"
|
| +#include "filenames.h"
|
| +#include "xml-support.h"
|
| +
|
| +/* Print a record debug message. Use do ... while (0) to avoid ambiguities
|
| + when used in if statements. */
|
| +
|
| +#define DEBUG(msg, args...) \
|
| + do \
|
| + { \
|
| + if (record_debug != 0) \
|
| + fprintf_unfiltered (gdb_stdlog, \
|
| + "[btrace] " msg "\n", ##args); \
|
| + } \
|
| + while (0)
|
| +
|
| +#define DEBUG_FTRACE(msg, args...) DEBUG ("[ftrace] " msg, ##args)
|
| +
|
| +/* Initialize the instruction iterator. */
|
| +
|
| +static void
|
| +btrace_init_insn_iterator (struct btrace_thread_info *btinfo)
|
| +{
|
| + DEBUG ("init insn iterator");
|
| +
|
| + btinfo->insn_iterator.begin = 1;
|
| + btinfo->insn_iterator.end = 0;
|
| +}
|
| +
|
| +/* Initialize the function iterator. */
|
| +
|
| +static void
|
| +btrace_init_func_iterator (struct btrace_thread_info *btinfo)
|
| +{
|
| + DEBUG ("init func iterator");
|
| +
|
| + btinfo->func_iterator.begin = 1;
|
| + btinfo->func_iterator.end = 0;
|
| +}
|
| +
|
| +/* Compute the instruction trace from the block trace. */
|
| +
|
| +static VEC (btrace_inst_s) *
|
| +compute_itrace (VEC (btrace_block_s) *btrace)
|
| +{
|
| + VEC (btrace_inst_s) *itrace;
|
| + struct gdbarch *gdbarch;
|
| + unsigned int b;
|
| +
|
| + DEBUG ("compute itrace");
|
| +
|
| + itrace = NULL;
|
| + gdbarch = target_gdbarch ();
|
| + b = VEC_length (btrace_block_s, btrace);
|
| +
|
| + while (b-- != 0)
|
| + {
|
| + btrace_block_s *block;
|
| + CORE_ADDR pc;
|
| +
|
| + block = VEC_index (btrace_block_s, btrace, b);
|
| + pc = block->begin;
|
| +
|
| + /* Add instructions for this block. */
|
| + for (;;)
|
| + {
|
| + btrace_inst_s *inst;
|
| + int size;
|
| +
|
| + /* We should hit the end of the block. Warn if we went too far. */
|
| + if (block->end < pc)
|
| + {
|
| + warning (_("Recorded trace may be corrupted."));
|
| + break;
|
| + }
|
| +
|
| + inst = VEC_safe_push (btrace_inst_s, itrace, NULL);
|
| + inst->pc = 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. */
|
| + if (size <= 0)
|
| + {
|
| + warning (_("Recorded trace may be incomplete."));
|
| + break;
|
| + }
|
| +
|
| + pc += size;
|
| + }
|
| + }
|
| +
|
| + return itrace;
|
| +}
|
| +
|
| +/* Return the function name of a recorded function segment for printing.
|
| + This function never returns NULL. */
|
| +
|
| +static const char *
|
| +ftrace_print_function_name (struct btrace_func *bfun)
|
| +{
|
| + struct minimal_symbol *msym;
|
| + struct symbol *sym;
|
| +
|
| + msym = bfun->msym;
|
| + sym = bfun->sym;
|
| +
|
| + if (sym != NULL)
|
| + return SYMBOL_PRINT_NAME (sym);
|
| +
|
| + if (msym != NULL)
|
| + return SYMBOL_PRINT_NAME (msym);
|
| +
|
| + return "<unknown>";
|
| +}
|
| +
|
| +/* Return the file name of a recorded function segment for printing.
|
| + This function never returns NULL. */
|
| +
|
| +static const char *
|
| +ftrace_print_filename (struct btrace_func *bfun)
|
| +{
|
| + struct symbol *sym;
|
| + const char *filename;
|
| +
|
| + sym = bfun->sym;
|
| +
|
| + if (sym != NULL)
|
| + filename = symtab_to_filename_for_display (sym->symtab);
|
| + else
|
| + filename = "<unknown>";
|
| +
|
| + return filename;
|
| +}
|
| +
|
| +/* Print an ftrace debug status message. */
|
| +
|
| +static void
|
| +ftrace_debug (struct btrace_func *bfun, const char *prefix)
|
| +{
|
| + DEBUG_FTRACE ("%s: fun = %s, file = %s, lines = [%d; %d], insn = [%u; %u]",
|
| + prefix, ftrace_print_function_name (bfun),
|
| + ftrace_print_filename (bfun), bfun->lbegin, bfun->lend,
|
| + bfun->ibegin, bfun->iend);
|
| +}
|
| +
|
| +/* Initialize a recorded function segment. */
|
| +
|
| +static void
|
| +ftrace_init_func (struct btrace_func *bfun, struct minimal_symbol *mfun,
|
| + struct symbol *fun, unsigned int idx)
|
| +{
|
| + bfun->msym = mfun;
|
| + bfun->sym = fun;
|
| + bfun->lbegin = INT_MAX;
|
| + bfun->lend = 0;
|
| + bfun->ibegin = idx;
|
| + bfun->iend = idx;
|
| +}
|
| +
|
| +/* Check whether the function has changed. */
|
| +
|
| +static int
|
| +ftrace_function_switched (struct btrace_func *bfun,
|
| + struct minimal_symbol *mfun, struct symbol *fun)
|
| +{
|
| + struct minimal_symbol *msym;
|
| + struct symbol *sym;
|
| +
|
| + /* The function changed if we did not have one before. */
|
| + if (bfun == NULL)
|
| + return 1;
|
| +
|
| + msym = bfun->msym;
|
| + sym = bfun->sym;
|
| +
|
| + /* If the minimal symbol changed, we certainly switched functions. */
|
| + if (mfun != NULL && msym != NULL
|
| + && strcmp (SYMBOL_LINKAGE_NAME (mfun), SYMBOL_LINKAGE_NAME (msym)) != 0)
|
| + return 1;
|
| +
|
| + /* If the symbol changed, we certainly switched functions. */
|
| + if (fun != NULL && sym != NULL)
|
| + {
|
| + const char *bfname, *fname;
|
| +
|
| + /* Check the function name. */
|
| + if (strcmp (SYMBOL_LINKAGE_NAME (fun), SYMBOL_LINKAGE_NAME (sym)) != 0)
|
| + return 1;
|
| +
|
| + /* Check the location of those functions, as well. */
|
| + bfname = symtab_to_fullname (sym->symtab);
|
| + fname = symtab_to_fullname (fun->symtab);
|
| + if (filename_cmp (fname, bfname) != 0)
|
| + return 1;
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* Check if we should skip this file when generating the function call
|
| + history. We would want to do that if, say, a macro that is defined
|
| + in another file is expanded in this function. */
|
| +
|
| +static int
|
| +ftrace_skip_file (struct btrace_func *bfun, const char *filename)
|
| +{
|
| + struct symbol *sym;
|
| + const char *bfile;
|
| +
|
| + sym = bfun->sym;
|
| +
|
| + if (sym != NULL)
|
| + bfile = symtab_to_fullname (sym->symtab);
|
| + else
|
| + bfile = "";
|
| +
|
| + if (filename == NULL)
|
| + filename = "";
|
| +
|
| + return (filename_cmp (bfile, filename) != 0);
|
| +}
|
| +
|
| +/* Compute the function trace from the instruction trace. */
|
| +
|
| +static VEC (btrace_func_s) *
|
| +compute_ftrace (VEC (btrace_inst_s) *itrace)
|
| +{
|
| + VEC (btrace_func_s) *ftrace;
|
| + struct btrace_inst *binst;
|
| + struct btrace_func *bfun;
|
| + unsigned int idx;
|
| +
|
| + DEBUG ("compute ftrace");
|
| +
|
| + ftrace = NULL;
|
| + bfun = NULL;
|
| +
|
| + for (idx = 0; VEC_iterate (btrace_inst_s, itrace, idx, binst); ++idx)
|
| + {
|
| + struct symtab_and_line sal;
|
| + struct bound_minimal_symbol mfun;
|
| + struct symbol *fun;
|
| + const char *filename;
|
| + CORE_ADDR pc;
|
| +
|
| + pc = binst->pc;
|
| +
|
| + /* Try to determine the function we're in. We use both types of symbols
|
| + to avoid surprises when we sometimes get a full symbol and sometimes
|
| + only a minimal symbol. */
|
| + fun = find_pc_function (pc);
|
| + mfun = lookup_minimal_symbol_by_pc (pc);
|
| +
|
| + if (fun == NULL && mfun.minsym == NULL)
|
| + {
|
| + DEBUG_FTRACE ("no symbol at %u, pc=%s", idx,
|
| + core_addr_to_string_nz (pc));
|
| + continue;
|
| + }
|
| +
|
| + /* If we're switching functions, we start over. */
|
| + if (ftrace_function_switched (bfun, mfun.minsym, fun))
|
| + {
|
| + bfun = VEC_safe_push (btrace_func_s, ftrace, NULL);
|
| +
|
| + ftrace_init_func (bfun, mfun.minsym, fun, idx);
|
| + ftrace_debug (bfun, "init");
|
| + }
|
| +
|
| + /* Update the instruction range. */
|
| + bfun->iend = idx;
|
| + ftrace_debug (bfun, "update insns");
|
| +
|
| + /* Let's see if we have source correlation, as well. */
|
| + sal = find_pc_line (pc, 0);
|
| + if (sal.symtab == NULL || sal.line == 0)
|
| + {
|
| + DEBUG_FTRACE ("no lines at %u, pc=%s", idx,
|
| + core_addr_to_string_nz (pc));
|
| + continue;
|
| + }
|
| +
|
| + /* Check if we switched files. This could happen if, say, a macro that
|
| + is defined in another file is expanded here. */
|
| + filename = symtab_to_fullname (sal.symtab);
|
| + if (ftrace_skip_file (bfun, filename))
|
| + {
|
| + DEBUG_FTRACE ("ignoring file at %u, pc=%s, file=%s", idx,
|
| + core_addr_to_string_nz (pc), filename);
|
| + continue;
|
| + }
|
| +
|
| + /* Update the line range. */
|
| + bfun->lbegin = min (bfun->lbegin, sal.line);
|
| + bfun->lend = max (bfun->lend, sal.line);
|
| + ftrace_debug (bfun, "update lines");
|
| + }
|
| +
|
| + return ftrace;
|
| +}
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +void
|
| +btrace_enable (struct thread_info *tp)
|
| +{
|
| + if (tp->btrace.target != NULL)
|
| + return;
|
| +
|
| + if (!target_supports_btrace ())
|
| + 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);
|
| +}
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +void
|
| +btrace_disable (struct thread_info *tp)
|
| +{
|
| + struct btrace_thread_info *btp = &tp->btrace;
|
| + int errcode = 0;
|
| +
|
| + if (btp->target == NULL)
|
| + return;
|
| +
|
| + DEBUG ("disable thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
|
| +
|
| + target_disable_btrace (btp->target);
|
| + btp->target = NULL;
|
| +
|
| + btrace_clear (tp);
|
| +}
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +void
|
| +btrace_teardown (struct thread_info *tp)
|
| +{
|
| + struct btrace_thread_info *btp = &tp->btrace;
|
| + int errcode = 0;
|
| +
|
| + if (btp->target == NULL)
|
| + return;
|
| +
|
| + DEBUG ("teardown thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
|
| +
|
| + target_teardown_btrace (btp->target);
|
| + btp->target = NULL;
|
| +
|
| + btrace_clear (tp);
|
| +}
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +void
|
| +btrace_fetch (struct thread_info *tp)
|
| +{
|
| + struct btrace_thread_info *btinfo;
|
| + VEC (btrace_block_s) *btrace;
|
| +
|
| + DEBUG ("fetch thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
|
| +
|
| + btinfo = &tp->btrace;
|
| + if (btinfo->target == NULL)
|
| + return;
|
| +
|
| + btrace = target_read_btrace (btinfo->target, btrace_read_new);
|
| + if (VEC_empty (btrace_block_s, btrace))
|
| + return;
|
| +
|
| + btrace_clear (tp);
|
| +
|
| + btinfo->btrace = btrace;
|
| + btinfo->itrace = compute_itrace (btinfo->btrace);
|
| + btinfo->ftrace = compute_ftrace (btinfo->itrace);
|
| +
|
| + /* Initialize branch trace iterators. */
|
| + btrace_init_insn_iterator (btinfo);
|
| + btrace_init_func_iterator (btinfo);
|
| +}
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +void
|
| +btrace_clear (struct thread_info *tp)
|
| +{
|
| + struct btrace_thread_info *btinfo;
|
| +
|
| + DEBUG ("clear thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
|
| +
|
| + btinfo = &tp->btrace;
|
| +
|
| + VEC_free (btrace_block_s, btinfo->btrace);
|
| + VEC_free (btrace_inst_s, btinfo->itrace);
|
| + VEC_free (btrace_func_s, btinfo->ftrace);
|
| +
|
| + btinfo->btrace = NULL;
|
| + btinfo->itrace = NULL;
|
| + btinfo->ftrace = NULL;
|
| +}
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +void
|
| +btrace_free_objfile (struct objfile *objfile)
|
| +{
|
| + struct thread_info *tp;
|
| +
|
| + DEBUG ("free objfile");
|
| +
|
| + ALL_THREADS (tp)
|
| + btrace_clear (tp);
|
| +}
|
| +
|
| +#if defined (HAVE_LIBEXPAT)
|
| +
|
| +/* Check the btrace document version. */
|
| +
|
| +static void
|
| +check_xml_btrace_version (struct gdb_xml_parser *parser,
|
| + const struct gdb_xml_element *element,
|
| + void *user_data, VEC (gdb_xml_value_s) *attributes)
|
| +{
|
| + const char *version = xml_find_attribute (attributes, "version")->value;
|
| +
|
| + if (strcmp (version, "1.0") != 0)
|
| + gdb_xml_error (parser, _("Unsupported btrace version: \"%s\""), version);
|
| +}
|
| +
|
| +/* Parse a btrace "block" xml record. */
|
| +
|
| +static void
|
| +parse_xml_btrace_block (struct gdb_xml_parser *parser,
|
| + const struct gdb_xml_element *element,
|
| + void *user_data, VEC (gdb_xml_value_s) *attributes)
|
| +{
|
| + VEC (btrace_block_s) **btrace;
|
| + struct btrace_block *block;
|
| + ULONGEST *begin, *end;
|
| +
|
| + btrace = user_data;
|
| + block = VEC_safe_push (btrace_block_s, *btrace, NULL);
|
| +
|
| + begin = xml_find_attribute (attributes, "begin")->value;
|
| + end = xml_find_attribute (attributes, "end")->value;
|
| +
|
| + block->begin = *begin;
|
| + block->end = *end;
|
| +}
|
| +
|
| +static const struct gdb_xml_attribute block_attributes[] = {
|
| + { "begin", GDB_XML_AF_NONE, gdb_xml_parse_attr_ulongest, NULL },
|
| + { "end", GDB_XML_AF_NONE, gdb_xml_parse_attr_ulongest, NULL },
|
| + { NULL, GDB_XML_AF_NONE, NULL, NULL }
|
| +};
|
| +
|
| +static const struct gdb_xml_attribute btrace_attributes[] = {
|
| + { "version", GDB_XML_AF_NONE, NULL, NULL },
|
| + { NULL, GDB_XML_AF_NONE, NULL, NULL }
|
| +};
|
| +
|
| +static const struct gdb_xml_element btrace_children[] = {
|
| + { "block", block_attributes, NULL,
|
| + GDB_XML_EF_REPEATABLE | GDB_XML_EF_OPTIONAL, parse_xml_btrace_block, NULL },
|
| + { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
|
| +};
|
| +
|
| +static const struct gdb_xml_element btrace_elements[] = {
|
| + { "btrace", btrace_attributes, btrace_children, GDB_XML_EF_NONE,
|
| + check_xml_btrace_version, NULL },
|
| + { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
|
| +};
|
| +
|
| +#endif /* defined (HAVE_LIBEXPAT) */
|
| +
|
| +/* See btrace.h. */
|
| +
|
| +VEC (btrace_block_s) *
|
| +parse_xml_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);
|
| + errcode = gdb_xml_parse_quick (_("btrace"), "btrace.dtd", btrace_elements,
|
| + buffer, &btrace);
|
| + if (errcode != 0)
|
| + {
|
| + do_cleanups (cleanup);
|
| + return NULL;
|
| + }
|
| +
|
| + /* Keep parse results. */
|
| + discard_cleanups (cleanup);
|
| +
|
| +#else /* !defined (HAVE_LIBEXPAT) */
|
| +
|
| + error (_("Cannot process branch trace. XML parsing is not supported."));
|
| +
|
| +#endif /* !defined (HAVE_LIBEXPAT) */
|
| +
|
| + return btrace;
|
| +}
|
|
|