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);