[PATCH 3/5] libdwfl: add interface for attaching to/detaching from threads

Omar Sandoval osandov@osandov.com
Mon Oct 7 09:05:00 GMT 2019


From: Omar Sandoval <osandov@fb.com>

libdwfl has implementations of attaching to/detaching from threads and
unwinding stack traces. However, that functionality is only available
through the dwfl_thread_getframes interface, which isn't very flexible.
This adds two new functions, dwfl_attach_thread and dwfl_detach_thread,
which separate the thread stopping functionality out of
dwfl_thread_getframes. Additionally, it makes dwfl_thread_getframes
cache the stack trace for threads stopped this way. This makes it
possible to use the frames after dwfl_thread_getframes returns.

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 libdw/ChangeLog      |   6 ++
 libdw/libdw.map      |   5 ++
 libdwfl/ChangeLog    |   8 +++
 libdwfl/dwfl_frame.c | 136 ++++++++++++++++++++++++++++++-------------
 libdwfl/libdwfl.h    |  12 ++++
 libdwfl/libdwflP.h   |   3 +-
 6 files changed, 128 insertions(+), 42 deletions(-)

diff --git a/libdw/ChangeLog b/libdw/ChangeLog
index 498cf0b7..1de8e2fc 100644
--- a/libdw/ChangeLog
+++ b/libdw/ChangeLog
@@ -1,3 +1,9 @@
+2019-10-07  Omar Sandoval  <osandov@fb.com>
+
+	* libdw.map (ELFUTILS_0.178): New section.
+	Add dwfl_attach_thread.
+	Add dwfl_detach_thread.
+
 2019-07-05  Omar Sandoval  <osandov@fb.com>
 
 	* Makefile.am (libdw_so_LIBS): Replace libebl.a with libebl_pic.a.
diff --git a/libdw/libdw.map b/libdw/libdw.map
index decac05c..f20ffc2f 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -370,3 +370,8 @@ ELFUTILS_0.177 {
     # presume that NULL is only returned on error (otherwise ELF_K_NONE).
     dwelf_elf_begin;
 } ELFUTILS_0.175;
+ELFUTILS_0.178 {
+  global:
+    dwfl_attach_thread;
+    dwfl_detach_thread;
+} ELFUTILS_0.177;
diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog
index 07a1e8df..b7eaedca 100644
--- a/libdwfl/ChangeLog
+++ b/libdwfl/ChangeLog
@@ -7,6 +7,14 @@
 	(thread_free_all_states): Remove function.
 	(free_states): Add function.
 	(dwfl_thread_getframes): Don't update thread->unwound while unwinding.
+	Use start_unwind.
+	Cache frames when attached with dwfl_attach_thread.
+	(start_unwind): Add function.
+	(attach_thread_cb): Add function.
+	(dwfl_attach_thread): Add function.
+	(dwfl_detach_thread): Add function.
+	* libdwfl.h (dwfl_attach_thread): Add definition.
+	(dwfl_detach_thread): Add definition.
 	* libdwflP.h (struct Dwfl_Thread): Update comment for unwound member.
 
 2019-08-12  Mark Wielaard  <mark@klomp.org>
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 5bbf850e..61fad8b9 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -103,6 +103,29 @@ state_alloc (Dwfl_Thread *thread)
   return state;
 }
 
+static Dwfl_Frame *
+start_unwind(Dwfl_Thread *thread)
+{
+  if (ebl_frame_nregs (thread->process->ebl) == 0)
+    {
+      __libdwfl_seterrno (DWFL_E_NO_UNWIND);
+      return NULL;
+    }
+  if (state_alloc (thread) == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return NULL;
+    }
+  if (! thread->process->callbacks->set_initial_registers (thread,
+							   thread->callbacks_arg))
+    {
+      free_states (thread->unwound);
+      thread->unwound = NULL;
+      return NULL;
+    }
+  return thread->unwound;
+}
+
 void
 internal_function
 __libdwfl_process_free (Dwfl_Process *process)
@@ -366,6 +389,45 @@ getthread (Dwfl *dwfl, pid_t tid,
    return err;
 }
 
+static int
+attach_thread_cb(Dwfl_Thread *thread, void *arg)
+{
+  Dwfl_Thread *copied = malloc (sizeof (*copied));
+  if (copied == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return DWARF_CB_ABORT;
+    }
+  *copied = *thread;
+  if (start_unwind (copied) == NULL)
+    {
+      free (copied);
+      return DWARF_CB_ABORT;
+    }
+  *(Dwfl_Thread **)arg = copied;
+  return DWARF_CB_OK;
+}
+
+Dwfl_Thread *
+dwfl_attach_thread(Dwfl *dwfl, pid_t tid)
+{
+  Dwfl_Thread *thread;
+  if (getthread (dwfl, tid, attach_thread_cb, &thread))
+    return NULL;
+  return thread;
+}
+
+void
+dwfl_detach_thread(Dwfl_Thread *thread)
+{
+  if (thread == NULL)
+    return;
+  if (thread->process->callbacks->thread_detach)
+    thread->process->callbacks->thread_detach (thread, thread->callbacks_arg);
+  free_states (thread->unwound);
+  free (thread);
+}
+
 struct one_thread
 {
   int (*callback) (Dwfl_Frame *frame, void *arg);
@@ -394,63 +456,55 @@ dwfl_thread_getframes (Dwfl_Thread *thread,
 		       int (*callback) (Dwfl_Frame *state, void *arg),
 		       void *arg)
 {
-  Ebl *ebl = thread->process->ebl;
-  if (ebl_frame_nregs (ebl) == 0)
-    {
-      __libdwfl_seterrno (DWFL_E_NO_UNWIND);
-      return -1;
-    }
-  if (state_alloc (thread) == NULL)
-    {
-      __libdwfl_seterrno (DWFL_E_NOMEM);
-      return -1;
-    }
   Dwfl_Process *process = thread->process;
-  if (! process->callbacks->set_initial_registers (thread,
-						   thread->callbacks_arg))
-    {
-      free_states (thread->unwound);
-      thread->unwound = NULL;
-      return -1;
-    }
+  int ret = -1;
+  bool cache = thread->unwound != NULL;
+  if (! cache && start_unwind (thread) == NULL)
+    return -1;
   Dwfl_Frame *state = thread->unwound;
-  thread->unwound = NULL;
+  if (! cache)
+    thread->unwound = NULL;
   if (! state_fetch_pc (state))
-    {
-      if (process->callbacks->thread_detach)
-	process->callbacks->thread_detach (thread, thread->callbacks_arg);
-      free_states (state);
-      return -1;
-    }
+    goto out;
   do
     {
       int err = callback (state, arg);
       if (err != DWARF_CB_OK)
 	{
-	  if (process->callbacks->thread_detach)
-	    process->callbacks->thread_detach (thread, thread->callbacks_arg);
-	  free_states (state);
-	  return err;
+	  ret = err;
+	  goto out;
+	}
+      if (state->unwound == NULL)
+	__libdwfl_frame_unwind (state);
+      else if (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR)
+	{
+	  /* This frame was previously cached as an error.  We still return -1,
+	     but we don't know what the original error was.  */
+	  __libdwfl_seterrno (DWFL_E_NOERROR);
 	}
-      __libdwfl_frame_unwind (state);
       Dwfl_Frame *next = state->unwound;
-      /* The old frame is no longer needed.  */
-      free (state);
+      if (! cache)
+	{
+	  /* The old frame is no longer needed.  */
+	  free (state);
+	}
       state = next;
     }
   while (state && state->pc_state == DWFL_FRAME_STATE_PC_SET);
 
-  Dwfl_Error err = dwfl_errno ();
-  if (process->callbacks->thread_detach)
-    process->callbacks->thread_detach (thread, thread->callbacks_arg);
-  if (state == NULL || state->pc_state == DWFL_FRAME_STATE_ERROR)
+  if (state && state->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED)
+    ret = 0;
+out:
+  if (! cache)
     {
+      if (process->callbacks->thread_detach)
+	{
+	  Dwfl_Error err = dwfl_errno ();
+	  process->callbacks->thread_detach (thread, thread->callbacks_arg);
+	  __libdwfl_seterrno (err);
+	}
       free_states (state);
-      __libdwfl_seterrno (err);
-      return -1;
     }
-  assert (state->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED);
-  free_states (state);
-  return 0;
+  return ret;
 }
 INTDEF(dwfl_thread_getframes)
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index a0c1d357..a22afc78 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -775,6 +775,18 @@ int dwfl_getthreads (Dwfl *dwfl,
 		     void *arg)
   __nonnull_attribute__ (1, 2);
 
+/* Attach to a thread.  The returned thread must be detached and freed with
+   dwfl_detach_thread.  Returns NULL on error.  This calls the
+   set_initial_registers callback.  While a thread is attached,
+   dwfl_thread_getframes will cache the unwound frames for the thread.  They
+   remain valid until dwfl_detach_thread is called.  */
+Dwfl_Thread *dwfl_attach_thread(Dwfl *dwfl, pid_t tid)
+  __nonnull_attribute__ (1);
+
+/* Detach from a thread that was attached with dwfl_attach_thread and free it.
+   This calls the detach_thread callback.  */
+void dwfl_detach_thread(Dwfl_Thread *thread);
+
 /* Iterate through the frames for a thread.  Returns zero if all frames
    have been processed by the callback, returns -1 on error, or the value of
    the callback when not DWARF_CB_OK.  -1 returned on error will
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 6b2d4867..c80d2051 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -242,7 +242,8 @@ struct Dwfl_Thread
 {
   Dwfl_Process *process;
   pid_t tid;
-  /* Bottom (innermost) frame while we're initializing, NULL afterwards.  */
+  /* Bottom (innermost) frame.  If the stack trace is not cached, then this is
+     NULL except during initialization.  */
   Dwfl_Frame *unwound;
   void *callbacks_arg;
 };
-- 
2.23.0



More information about the Elfutils-devel mailing list