record-btrace: extend unwinder
authorMarkus Metzger <markus.t.metzger@intel.com>
Wed, 27 Mar 2013 08:49:47 +0000 (09:49 +0100)
committerMarkus Metzger <markus.t.metzger@intel.com>
Thu, 16 Jan 2014 12:09:42 +0000 (13:09 +0100)
Extend the always failing unwinder to provide the PC based on the call
structure detected in the branch trace.

The unwinder supports normal frames and tailcall frames.
Inline frames are not supported.

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.

testsuite/
* gdb.btrace/record_goto.exp: Add backtrace test.
* gdb.btrace/tailcall.exp: Add backtrace test.

gdb/ChangeLog
gdb/NEWS
gdb/btrace.c
gdb/dwarf2-frame.c
gdb/record-btrace.c
gdb/record.h
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.btrace/record_goto.exp
gdb/testsuite/gdb.btrace/tailcall.exp

index 78a95af7776bc7f9f10723e3f4c3f678d3b1fab4..5cdf5698271a93a86d7cd98d9b55d51874e387c2 100644 (file)
@@ -1,3 +1,29 @@
+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)
index c6402feb0d06472291eae25023f33b42b0254060..6b7f4a40e3c76c7395f53fcdfcd5fff1594d32ff 100644 (file)
--- 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
 
index 632ebe10cbb7add2c1f6dc57dfbdc690cc3940c5..ba87e1633d18ed22b765538b474ad61c7a507935 100644 (file)
@@ -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;
index 772de56b4d1059c790e6897a94b5874fd073f24b..ce21112ba7620ee6a025d00b1dad7cadc0856a05 100644 (file)
@@ -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)
index 74249fcbe1d352920c3d9920ddb751d6ec782fd4..4c39205661570c1b6823250be8269fe4b609e6c7 100644 (file)
@@ -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);
 }
index 17d1772dac4466eba203e6827725f6c17b0516ab..f5987e344a2165719a3b0c691968fc53cd0b2c12 100644 (file)
@@ -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
 {
index feebda86237e66c31869993336f401023a0e9fb1..85e63bb68331546d88b77c4be653c29a5fc985f1 100644 (file)
@@ -1,3 +1,8 @@
+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.
index eb07b9b797d24fb3d9f45cf288402f7bf7586e23..ab69bef2283a70934ebfffd7c145a3a114af727b 100644 (file)
@@ -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" \
index c965675fd828de56dae43eae2060c7b836f781be..a00178323a9e918b8293d74d78089846b491248f 100644 (file)
@@ -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"