Hi Ben,

I hope you haven't given up on these ideas. I would really like to see
some of them integrated.

On Tue, Sep 08, 2015 at 02:58:39PM +0200, Mark Wielaard wrote:
> > Unfortunately, this is idea is dead on arrival (which I didn't realize
> > until far too much head-scratching): the stack frames between
> > x86_64_set_initial_registers_local and dwfl_thread_getframes are gone by
> > the time we try to unwind. We return three times back to
> > dwfl_thread_getframes, before calling into the unwinder.
> 
> O fun. I also didn't realize this till now... You want to unwind a bit
> before starting to unwind so you start unwinding at the correct frame...
> 
> > This didn't affect me in GHC since there were no stack frames sitting
> > between the initial register collection and the unwinder.
> > 
> > I see two ways to work around this,
> > 
> >   1. Implement a small amount of unwinding in the initial register 
> > collection
> >      code to get back to a stack frame which won't disappear from
> >      beneath our feet
> > 
> >   2. Ensure that there are no stack frames sitting between initial
> >      register collection and the unwinder
> > 
> > Option 1 strikes me as quite complex and fragile.
> 
> Yeah. I will think a bit about whether we can have a callback interface
> for ebl_set_initial_registers_local so it can reuse the libdwfl
> unwinder. I hope it doesn't have to be complex and fragile, but I am not
> 100% sure yet. The current code isn't really setup for it. But I think
> it should be possible. But...

I tried to implement option 1 anyway. Does the attached help you?

Cheers,

Mark
diff --git a/backends/x86_64_initreg.c b/backends/x86_64_initreg.c
index 1131d1d..6762f3e 100644
--- a/backends/x86_64_initreg.c
+++ b/backends/x86_64_initreg.c
@@ -103,6 +103,10 @@ x86_64_set_initial_registers_local (
     :"r" (&dwarf_regs[0])        /* input */
     :"%rax"                      /* clobbered */
     );
-  return setfunc (0, 17, dwarf_regs, arg);
+  if (! setfunc (0, 17, dwarf_regs, arg))
+    return false;
+
+  /* Explicitly set pc, to signal we are done setting registers.  */
+  return setfunc (-1, 1, &dwarf_regs[16], arg);
 #endif /* __x86_64__ */
 }
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index f6f86c0..524da05 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -472,3 +472,30 @@ dwfl_thread_getframes (Dwfl_Thread *thread,
   return 0;
 }
 INTDEF(dwfl_thread_getframes)
+
+bool
+internal_function
+initial_thread_unwind (Dwfl_Thread *thread,
+                       int (*callback) (Dwfl_Frame *state, void *arg),
+                      void *arg)
+{
+  Dwfl_Frame *state;
+  do
+    {
+      state = thread->unwound;
+      int cb = callback (state, arg);
+      if (cb < 0)
+       return false; /* callback thinks something went wrong. */
+      else if (cb == DWARF_CB_ABORT)
+       return true; /* we are where we want.  */
+
+      __libdwfl_frame_unwind (state);
+      /* The old frame is no longer needed.  */
+      state_free (thread->unwound);
+      state = thread->unwound;
+    }
+  while (state && state->pc_state == DWFL_FRAME_STATE_PC_SET);
+
+  /* Callback wants to unwind even further, but we cannot.  */
+  return false;
+}
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 6e11546..d199f81 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -584,6 +584,12 @@ extern void __libdwfl_process_free (Dwfl_Process *process)
 extern void __libdwfl_frame_unwind (Dwfl_Frame *state)
   internal_function;
 
+extern bool initial_thread_unwind (Dwfl_Thread *thread,
+                                  int (*callback) (Dwfl_Frame *state,
+                                                   void *arg),
+                                  void *arg)
+  internal_function;
+
 /* Align segment START downwards or END upwards addresses according to DWFL.  
*/
 extern GElf_Addr __libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start)
   internal_function;
diff --git a/libdwfl/linux-local-attach.c b/libdwfl/linux-local-attach.c
index ae461d8..abeb7e8 100644
--- a/libdwfl/linux-local-attach.c
+++ b/libdwfl/linux-local-attach.c
@@ -71,6 +71,35 @@ local_getthread (Dwfl *dwfl __attribute__ ((unused)),
   return true;
 }
 
+/* initial unwind callback.  */
+
+static int
+initial_unwind_callback (Dwfl_Frame *state, void *arg)
+{
+  bool *mod_seen = (bool *) arg;
+
+  Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state));
+  Dwfl_Module *mod_here;
+pc_here:
+  mod_here = dwfl_addrmodule (dwfl, (Dwarf_Addr) &&pc_here);
+
+  Dwarf_Addr pc;
+  if (! dwfl_frame_pc (state, &pc, NULL))
+    return -1;
+
+  Dwfl_Module *mod_frame = dwfl_addrmodule (dwfl, pc);
+  if (mod_here == mod_frame)
+    {
+      *mod_seen = true;
+      return DWARF_CB_OK;
+    }
+  else if (*mod_seen)
+    return DWARF_CB_ABORT;
+
+  return DWARF_CB_OK;
+}
+
+
 /* Implement the ebl_set_initial_registers_tid setfunc callback.  */
 
 static bool
@@ -83,7 +112,10 @@ local_thread_state_registers_cb (int firstreg, unsigned 
nregs,
       assert (firstreg == -1);
       assert (nregs == 1);
       INTUSE(dwfl_thread_state_register_pc) (thread, *regs);
-      return true;
+
+      // We assume pc is set last. So we are done. Now unwind a bit.
+      bool seen = false;
+      return initial_thread_unwind (thread, initial_unwind_callback, &seen);
     }
   assert (nregs > 0);
   return INTUSE(dwfl_thread_state_registers) (thread, firstreg, nregs, regs);

Reply via email to