summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog26
-rw-r--r--gdb/NEWS2
-rw-r--r--gdb/btrace.c4
-rw-r--r--gdb/dwarf2-frame.c6
-rw-r--r--gdb/record-btrace.c288
-rw-r--r--gdb/record.h4
-rw-r--r--gdb/testsuite/ChangeLog5
-rw-r--r--gdb/testsuite/gdb.btrace/record_goto.exp12
-rw-r--r--gdb/testsuite/gdb.btrace/tailcall.exp15
9 files changed, 352 insertions, 10 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 78a95af7776..5cdf5698271 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,31 @@
2014-01-16 Markus Metzger <markus.t.metzger@intel.com>
+ * record.h (record_btrace_frame_unwind)
+ (record_btrace_tailcall_frame_unwind): New declarations.
+ * dwarf2-frame: Include record.h
+ (dwarf2_frame_cfa): Throw an error for btrace frames.
+ * record-btrace.c: Include hashtab.h.
+ (btrace_get_bfun_name): New.
+ (btrace_call_history): Call btrace_get_bfun_name.
+ (struct btrace_frame_cache): New.
+ (bfcache): New.
+ (bfcache_hash, bfcache_eq, bfcache_new): New.
+ (btrace_get_frame_function): New.
+ (record_btrace_frame_unwind_stop_reason): Allow unwinding.
+ (record_btrace_frame_this_id): Compute own id.
+ (record_btrace_frame_prev_register): Provide PC, throw_error
+ for all other registers.
+ (record_btrace_frame_sniffer): Detect btrace frames.
+ (record_btrace_tailcall_frame_sniffer): New.
+ (record_btrace_frame_dealloc_cache): New.
+ (record_btrace_frame_unwind): Add new functions.
+ (record_btrace_tailcall_frame_unwind): New.
+ (_initialize_record_btrace): Allocate cache.
+ * btrace.c (btrace_clear): Call reinit_frame_cache.
+ * NEWS: Announce it.
+
+2014-01-16 Markus Metzger <markus.t.metzger@intel.com>
+
* record-btrace.c (record_btrace_set_replay)
(record_btrace_goto_begin, record_btrace_goto_end)
(record_btrace_goto): New.
diff --git a/gdb/NEWS b/gdb/NEWS
index c6402feb0d0..6b7f4a40e3c 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -19,6 +19,8 @@
'record instruction-history' commands are now inclusive.
* The btrace record target now supports the 'record goto' command.
+ For locations inside the execution trace, the back trace is computed
+ based on the information stored in the execution trace.
*** Changes in GDB 7.7
diff --git a/gdb/btrace.c b/gdb/btrace.c
index 632ebe10cbb..ba87e1633d1 100644
--- a/gdb/btrace.c
+++ b/gdb/btrace.c
@@ -765,6 +765,10 @@ btrace_clear (struct thread_info *tp)
DEBUG ("clear thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
+ /* Make sure btrace frames that may hold a pointer into the branch
+ trace data are destroyed. */
+ reinit_frame_cache ();
+
btinfo = &tp->btrace;
it = btinfo->begin;
diff --git a/gdb/dwarf2-frame.c b/gdb/dwarf2-frame.c
index 772de56b4d1..ce21112ba76 100644
--- a/gdb/dwarf2-frame.c
+++ b/gdb/dwarf2-frame.c
@@ -31,6 +31,7 @@
#include "objfiles.h"
#include "regcache.h"
#include "value.h"
+#include "record.h"
#include "gdb_assert.h"
#include <string.h>
@@ -1510,6 +1511,11 @@ dwarf2_frame_base_sniffer (struct frame_info *this_frame)
CORE_ADDR
dwarf2_frame_cfa (struct frame_info *this_frame)
{
+ if (frame_unwinder_is (this_frame, &record_btrace_tailcall_frame_unwind)
+ || frame_unwinder_is (this_frame, &record_btrace_frame_unwind))
+ throw_error (NOT_AVAILABLE_ERROR,
+ _("cfa not available for record btrace target"));
+
while (get_frame_type (this_frame) == INLINE_FRAME)
this_frame = get_prev_frame (this_frame);
if (get_frame_unwind_stop_reason (this_frame) == UNWIND_UNAVAILABLE)
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 74249fcbe1d..4c392056615 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -34,6 +34,7 @@
#include "filenames.h"
#include "regcache.h"
#include "frame-unwind.h"
+#include "hashtab.h"
/* The target_ops of record-btrace. */
static struct target_ops record_btrace_ops;
@@ -524,6 +525,28 @@ btrace_call_history_src_line (struct ui_out *uiout,
ui_out_field_int (uiout, "max line", end);
}
+/* Get the name of a branch trace function. */
+
+static const char *
+btrace_get_bfun_name (const struct btrace_function *bfun)
+{
+ struct minimal_symbol *msym;
+ struct symbol *sym;
+
+ if (bfun == NULL)
+ return "??";
+
+ msym = bfun->msym;
+ sym = bfun->sym;
+
+ if (sym != NULL)
+ return SYMBOL_PRINT_NAME (sym);
+ else if (msym != NULL)
+ return SYMBOL_PRINT_NAME (msym);
+ else
+ return "??";
+}
+
/* Disassemble a section of the recorded function trace. */
static void
@@ -545,8 +568,8 @@ btrace_call_history (struct ui_out *uiout,
struct symbol *sym;
bfun = btrace_call_get (&it);
- msym = bfun->msym;
sym = bfun->sym;
+ msym = bfun->msym;
/* Print the function index. */
ui_out_field_uint (uiout, "index", bfun->number);
@@ -965,13 +988,100 @@ record_btrace_prepare_to_store (struct target_ops *ops,
}
}
+/* The branch trace frame cache. */
+
+struct btrace_frame_cache
+{
+ /* The thread. */
+ struct thread_info *tp;
+
+ /* The frame info. */
+ struct frame_info *frame;
+
+ /* The branch trace function segment. */
+ const struct btrace_function *bfun;
+};
+
+/* A struct btrace_frame_cache hash table indexed by NEXT. */
+
+static htab_t bfcache;
+
+/* hash_f for htab_create_alloc of bfcache. */
+
+static hashval_t
+bfcache_hash (const void *arg)
+{
+ const struct btrace_frame_cache *cache = arg;
+
+ return htab_hash_pointer (cache->frame);
+}
+
+/* eq_f for htab_create_alloc of bfcache. */
+
+static int
+bfcache_eq (const void *arg1, const void *arg2)
+{
+ const struct btrace_frame_cache *cache1 = arg1;
+ const struct btrace_frame_cache *cache2 = arg2;
+
+ return cache1->frame == cache2->frame;
+}
+
+/* Create a new btrace frame cache. */
+
+static struct btrace_frame_cache *
+bfcache_new (struct frame_info *frame)
+{
+ struct btrace_frame_cache *cache;
+ void **slot;
+
+ cache = FRAME_OBSTACK_ZALLOC (struct btrace_frame_cache);
+ cache->frame = frame;
+
+ slot = htab_find_slot (bfcache, cache, INSERT);
+ gdb_assert (*slot == NULL);
+ *slot = cache;
+
+ return cache;
+}
+
+/* Extract the branch trace function from a branch trace frame. */
+
+static const struct btrace_function *
+btrace_get_frame_function (struct frame_info *frame)
+{
+ const struct btrace_frame_cache *cache;
+ const struct btrace_function *bfun;
+ struct btrace_frame_cache pattern;
+ void **slot;
+
+ pattern.frame = frame;
+
+ slot = htab_find_slot (bfcache, &pattern, NO_INSERT);
+ if (slot == NULL)
+ return NULL;
+
+ cache = *slot;
+ return cache->bfun;
+}
+
/* Implement stop_reason method for record_btrace_frame_unwind. */
static enum unwind_stop_reason
record_btrace_frame_unwind_stop_reason (struct frame_info *this_frame,
void **this_cache)
{
- return UNWIND_UNAVAILABLE;
+ const struct btrace_frame_cache *cache;
+ const struct btrace_function *bfun;
+
+ cache = *this_cache;
+ bfun = cache->bfun;
+ gdb_assert (bfun != NULL);
+
+ if (bfun->up == NULL)
+ return UNWIND_UNAVAILABLE;
+
+ return UNWIND_NO_REASON;
}
/* Implement this_id method for record_btrace_frame_unwind. */
@@ -980,7 +1090,27 @@ static void
record_btrace_frame_this_id (struct frame_info *this_frame, void **this_cache,
struct frame_id *this_id)
{
- /* Leave there the outer_frame_id value. */
+ const struct btrace_frame_cache *cache;
+ const struct btrace_function *bfun;
+ CORE_ADDR code, special;
+
+ cache = *this_cache;
+
+ bfun = cache->bfun;
+ gdb_assert (bfun != NULL);
+
+ while (bfun->segment.prev != NULL)
+ bfun = bfun->segment.prev;
+
+ code = get_frame_func (this_frame);
+ special = bfun->number;
+
+ *this_id = frame_id_build_unavailable_stack_special (code, special);
+
+ DEBUG ("[frame] %s id: (!stack, pc=%s, special=%s)",
+ btrace_get_bfun_name (cache->bfun),
+ core_addr_to_string_nz (this_id->code_addr),
+ core_addr_to_string_nz (this_id->special_addr));
}
/* Implement prev_register method for record_btrace_frame_unwind. */
@@ -990,8 +1120,46 @@ record_btrace_frame_prev_register (struct frame_info *this_frame,
void **this_cache,
int regnum)
{
- throw_error (NOT_AVAILABLE_ERROR,
- _("Registers are not available in btrace record history"));
+ const struct btrace_frame_cache *cache;
+ const struct btrace_function *bfun, *caller;
+ const struct btrace_insn *insn;
+ struct gdbarch *gdbarch;
+ CORE_ADDR pc;
+ int pcreg;
+
+ gdbarch = get_frame_arch (this_frame);
+ pcreg = gdbarch_pc_regnum (gdbarch);
+ if (pcreg < 0 || regnum != pcreg)
+ throw_error (NOT_AVAILABLE_ERROR,
+ _("Registers are not available in btrace record history"));
+
+ cache = *this_cache;
+ bfun = cache->bfun;
+ gdb_assert (bfun != NULL);
+
+ caller = bfun->up;
+ if (caller == NULL)
+ throw_error (NOT_AVAILABLE_ERROR,
+ _("No caller in btrace record history"));
+
+ if ((bfun->flags & BFUN_UP_LINKS_TO_RET) != 0)
+ {
+ insn = VEC_index (btrace_insn_s, caller->insn, 0);
+ pc = insn->pc;
+ }
+ else
+ {
+ insn = VEC_last (btrace_insn_s, caller->insn);
+ pc = insn->pc;
+
+ pc += gdb_insn_length (gdbarch, pc);
+ }
+
+ DEBUG ("[frame] unwound PC in %s on level %d: %s",
+ btrace_get_bfun_name (bfun), bfun->level,
+ core_addr_to_string_nz (pc));
+
+ return frame_unwind_got_address (this_frame, regnum, pc);
}
/* Implement sniffer method for record_btrace_frame_unwind. */
@@ -1001,15 +1169,99 @@ record_btrace_frame_sniffer (const struct frame_unwind *self,
struct frame_info *this_frame,
void **this_cache)
{
+ const struct btrace_function *bfun;
+ struct btrace_frame_cache *cache;
struct thread_info *tp;
- struct btrace_thread_info *btinfo;
- struct btrace_insn_iterator *replay;
+ struct frame_info *next;
/* THIS_FRAME does not contain a reference to its thread. */
tp = find_thread_ptid (inferior_ptid);
gdb_assert (tp != NULL);
- return btrace_is_replaying (tp);
+ bfun = NULL;
+ next = get_next_frame (this_frame);
+ if (next == NULL)
+ {
+ const struct btrace_insn_iterator *replay;
+
+ replay = tp->btrace.replay;
+ if (replay != NULL)
+ bfun = replay->function;
+ }
+ else
+ {
+ const struct btrace_function *callee;
+
+ callee = btrace_get_frame_function (next);
+ if (callee != NULL && (callee->flags & BFUN_UP_LINKS_TO_TAILCALL) == 0)
+ bfun = callee->up;
+ }
+
+ if (bfun == NULL)
+ return 0;
+
+ DEBUG ("[frame] sniffed frame for %s on level %d",
+ btrace_get_bfun_name (bfun), bfun->level);
+
+ /* This is our frame. Initialize the frame cache. */
+ cache = bfcache_new (this_frame);
+ cache->tp = tp;
+ cache->bfun = bfun;
+
+ *this_cache = cache;
+ return 1;
+}
+
+/* Implement sniffer method for record_btrace_tailcall_frame_unwind. */
+
+static int
+record_btrace_tailcall_frame_sniffer (const struct frame_unwind *self,
+ struct frame_info *this_frame,
+ void **this_cache)
+{
+ const struct btrace_function *bfun, *callee;
+ struct btrace_frame_cache *cache;
+ struct frame_info *next;
+
+ next = get_next_frame (this_frame);
+ if (next == NULL)
+ return 0;
+
+ callee = btrace_get_frame_function (next);
+ if (callee == NULL)
+ return 0;
+
+ if ((callee->flags & BFUN_UP_LINKS_TO_TAILCALL) == 0)
+ return 0;
+
+ bfun = callee->up;
+ if (bfun == NULL)
+ return 0;
+
+ DEBUG ("[frame] sniffed tailcall frame for %s on level %d",
+ btrace_get_bfun_name (bfun), bfun->level);
+
+ /* This is our frame. Initialize the frame cache. */
+ cache = bfcache_new (this_frame);
+ cache->tp = find_thread_ptid (inferior_ptid);
+ cache->bfun = bfun;
+
+ *this_cache = cache;
+ return 1;
+}
+
+static void
+record_btrace_frame_dealloc_cache (struct frame_info *self, void *this_cache)
+{
+ struct btrace_frame_cache *cache;
+ void **slot;
+
+ cache = this_cache;
+
+ slot = htab_find_slot (bfcache, cache, NO_INSERT);
+ gdb_assert (slot != NULL);
+
+ htab_remove_elt (bfcache, cache);
}
/* btrace recording does not store previous memory content, neither the stack
@@ -1018,14 +1270,26 @@ record_btrace_frame_sniffer (const struct frame_unwind *self,
Therefore this unwinder reports any possibly unwound registers as
<unavailable>. */
-static const struct frame_unwind record_btrace_frame_unwind =
+const struct frame_unwind record_btrace_frame_unwind =
{
NORMAL_FRAME,
record_btrace_frame_unwind_stop_reason,
record_btrace_frame_this_id,
record_btrace_frame_prev_register,
NULL,
- record_btrace_frame_sniffer
+ record_btrace_frame_sniffer,
+ record_btrace_frame_dealloc_cache
+};
+
+const struct frame_unwind record_btrace_tailcall_frame_unwind =
+{
+ TAILCALL_FRAME,
+ record_btrace_frame_unwind_stop_reason,
+ record_btrace_frame_this_id,
+ record_btrace_frame_prev_register,
+ NULL,
+ record_btrace_tailcall_frame_sniffer,
+ record_btrace_frame_dealloc_cache
};
/* The to_resume method of target record-btrace. */
@@ -1232,6 +1496,7 @@ init_record_btrace_ops (void)
ops->to_store_registers = record_btrace_store_registers;
ops->to_prepare_to_store = record_btrace_prepare_to_store;
ops->to_get_unwinder = &record_btrace_frame_unwind;
+ ops->to_get_tailcall_unwinder = &record_btrace_tailcall_frame_unwind;
ops->to_resume = record_btrace_resume;
ops->to_wait = record_btrace_wait;
ops->to_find_new_threads = record_btrace_find_new_threads;
@@ -1268,4 +1533,7 @@ _initialize_record_btrace (void)
init_record_btrace_ops ();
add_target (&record_btrace_ops);
+
+ bfcache = htab_create_alloc (50, bfcache_hash, bfcache_eq, NULL,
+ xcalloc, xfree);
}
diff --git a/gdb/record.h b/gdb/record.h
index 17d1772dac4..f5987e344a2 100644
--- a/gdb/record.h
+++ b/gdb/record.h
@@ -30,6 +30,10 @@ extern struct cmd_list_element *set_record_cmdlist;
extern struct cmd_list_element *show_record_cmdlist;
extern struct cmd_list_element *info_record_cmdlist;
+/* Unwinders for some record targets. */
+extern const struct frame_unwind record_btrace_frame_unwind;
+extern const struct frame_unwind record_btrace_tailcall_frame_unwind;
+
/* A list of flags specifying what record target methods should print. */
enum record_print_flag
{
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index feebda86237..85e63bb6833 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,5 +1,10 @@
2014-01-16 Markus Metzger <markus.t.metzger@intel.com>
+ * gdb.btrace/record_goto.exp: Add backtrace test.
+ * gdb.btrace/tailcall.exp: Add backtrace test.
+
+2014-01-16 Markus Metzger <markus.t.metzger@intel.com>
+
* gdb.btrace/Makefile.in (EXECUTABLES): Add record_goto.
* gdb.btrace/record_goto.c: New.
* gdb.btrace/record_goto.exp: New.
diff --git a/gdb/testsuite/gdb.btrace/record_goto.exp b/gdb/testsuite/gdb.btrace/record_goto.exp
index eb07b9b797d..ab69bef2283 100644
--- a/gdb/testsuite/gdb.btrace/record_goto.exp
+++ b/gdb/testsuite/gdb.btrace/record_goto.exp
@@ -87,6 +87,18 @@ gdb_test "record instruction-history" [join [list \
# let's go to another place in the history
gdb_test "record goto 26" ".*fun3 \\(\\) at record_goto.c:35.*"
+# check the back trace at that location
+gdb_test "backtrace" [join [list \
+ "#0.*fun3.*at record_goto.c:35.*" \
+ "#1.*fun4.*at record_goto.c:43.*" \
+ "#2.*main.*at record_goto.c:49.*" \
+ "Backtrace stopped: not enough registers or memory available to unwind further" \
+ ] "\r\n"]
+
+# walk the backtrace
+gdb_test "up" ".*fun4.*at record_goto.c:43.*" "up to fun4"
+gdb_test "up" ".*main.*at record_goto.c:49.*" "up to main"
+
# the function call history should start at the new location
gdb_test "record function-call-history /ci -" [join [list \
"8\t fun3\tinst 19,21" \
diff --git a/gdb/testsuite/gdb.btrace/tailcall.exp b/gdb/testsuite/gdb.btrace/tailcall.exp
index c965675fd82..a00178323a9 100644
--- a/gdb/testsuite/gdb.btrace/tailcall.exp
+++ b/gdb/testsuite/gdb.btrace/tailcall.exp
@@ -60,3 +60,18 @@ gdb_test "record function-call-history /c 1" [join [list \
"2\t bar" \
"3\tmain" \
] "\r\n"] "indented"
+
+# go into bar
+gdb_test "record goto 3" ".*bar \\(\\) at .*x86-tailcall.c:24\r\n.*"
+
+# check the backtrace
+gdb_test "backtrace" [join [list \
+ "#0.*bar \\(\\) at x86-tailcall.c:24" \
+ "#1.*foo \\(\\) at x86-tailcall.c:29" \
+ "#2.*main \\(\\) at x86-tailcall.c:37" \
+ "Backtrace stopped: not enough registers or memory available to unwind further" \
+ ] "\r\n"]
+
+# walk the backtrace
+gdb_test "up" "#1\[^\r\n\]*foo \\(\\) at x86-tailcall.c:29\r\n.*" "up to foo"
+gdb_test "up" "#2\[^\r\n\]*main \\(\\) at x86-tailcall.c:37\r\n.*" "up to main"