commit eaa481f284524d7f5d14f88dcf6cb62faeae5713
Author: Omar Sandoval <osandov@fb.com>
Date:   Mon Oct 7 01:30:35 2019 -0700

    libdwfl: add interface for evaluating DWARF expressions in a frame
    
    libdwfl can evaluate DWARF expressions in order to unwind the stack, but
    this functionality isn't exposed to clients of the library. Now that the
    pieces are in place, add dwfl_frame_eval_expr to provide this feature.

diff --git a/libdw/libdw.map b/libdw/libdw.map
index f4134e318a..25ae8b6e6c 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -377,4 +377,5 @@ ELFUTILS_0.180 {
     dwfl_frame_register;
     dwfl_frame_module;
     dwfl_frame_dwarf_frame;
+    dwfl_frame_eval_expr;
 } ELFUTILS_0.177;
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index bbbd73add4..a6df269763 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -837,3 +837,14 @@ __libdwfl_frame_unwind (Dwfl_Frame *state)
   assert (state->unwound->pc_state == DWFL_FRAME_STATE_PC_SET);
   state->unwound->signal_frame = signal_frame;
 }
+
+bool
+dwfl_frame_eval_expr (Dwfl_Frame *state, const Dwarf_Op *ops, size_t nops,
+		      Dwarf_Addr *result)
+{
+  Dwarf_Addr bias;
+  Dwarf_Frame *frame = dwfl_frame_dwarf_frame(state, &bias);
+  if (frame == NULL)
+    return false;
+  return expr_eval (state, frame, ops, nops, result, bias);
+}
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index fb261698ed..1dc080c01e 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -822,6 +822,12 @@ bool dwfl_frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation)
 bool dwfl_frame_register (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *value)
   __nonnull_attribute__ (1);
 
+/* Evaluate a DWARF expression in the context of a frame.  On success, returns
+   true and fills in *RESULT.  On error, returns false.  */
+bool dwfl_frame_eval_expr (Dwfl_Frame *state, const Dwarf_Op *ops, size_t nops,
+			   Dwarf_Addr *result)
+  __nonnull_attribute__ (1, 2, 4);
+
 #ifdef __cplusplus
 }
 #endif
commit 5a6524c0b89a71bc712ccab7fc801d12c429bcd5
Author: Omar Sandoval <osandov@fb.com>
Date:   Thu Feb 20 11:18:28 2020 -0800

    libdwfl: add interface for getting Dwfl_Module and Dwarf_Frame for Dwfl_Frame
    
    The next change will need to have the Dwarf_Frame readily available, so
    let's add dwfl_frame_dwarf_frame which looks up the Dwarf_Frame and
    caches it. The CFI frame can also be useful to clients of libdwfl, so
    let's make it a public interface. We can reorganize
    __libdwfl_frame_unwind and handle_cfi to use it, too.
    
    Similarly, the Dwfl_Module is frequently needed in conjuction with the
    frame, so add dwfl_frame_module.

diff --git a/libdw/libdw.map b/libdw/libdw.map
index 73f592c03e..f4134e318a 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -375,4 +375,6 @@ ELFUTILS_0.180 {
     dwfl_attach_thread;
     dwfl_detach_thread;
     dwfl_frame_register;
+    dwfl_frame_module;
+    dwfl_frame_dwarf_frame;
 } ELFUTILS_0.177;
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 61fad8b9cf..80f2bc1466 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -68,6 +68,13 @@ state_fetch_pc (Dwfl_Frame *state)
   abort ();
 }
 
+static void
+state_free (Dwfl_Frame *state)
+{
+  free (state->frame);
+  free (state);
+}
+
 /* Do not call it on your own, to be used by thread_* functions only.  */
 
 static void
@@ -76,7 +83,7 @@ free_states (Dwfl_Frame *state)
   while (state)
     {
       Dwfl_Frame *next = state->unwound;
-      free(state);
+      state_free (state);
       state = next;
     }
 }
@@ -94,6 +101,10 @@ state_alloc (Dwfl_Thread *thread)
   if (state == NULL)
     return NULL;
   state->thread = thread;
+  state->mod = NULL;
+  state->frame = NULL;
+  state->moderr = DWFL_E_NOERROR;
+  state->frameerr = DWFL_E_NOERROR;
   state->signal_frame = false;
   state->initial_frame = true;
   state->pc_state = DWFL_FRAME_STATE_ERROR;
@@ -486,7 +497,7 @@ dwfl_thread_getframes (Dwfl_Thread *thread,
       if (! cache)
 	{
 	  /* The old frame is no longer needed.  */
-	  free (state);
+	  state_free (state);
 	}
       state = next;
     }
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index f7459b283d..bbbd73add4 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -523,6 +523,10 @@ new_unwound (Dwfl_Frame *state)
   state->unwound = unwound;
   unwound->thread = thread;
   unwound->unwound = NULL;
+  unwound->mod = NULL;
+  unwound->frame = NULL;
+  unwound->moderr = DWFL_E_NOERROR;
+  unwound->frameerr = DWFL_E_NOERROR;
   unwound->signal_frame = false;
   unwound->initial_frame = false;
   unwound->pc_state = DWFL_FRAME_STATE_ERROR;
@@ -536,22 +540,9 @@ new_unwound (Dwfl_Frame *state)
    later.  Therefore we continue unwinding leaving the registers undefined.  */
 
 static void
-handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias)
+handle_cfi (Dwfl_Frame *state, Dwarf_Frame *frame, Dwarf_Addr bias)
 {
-  Dwarf_Frame *frame;
-  if (INTUSE(dwarf_cfi_addrframe) (cfi, pc, &frame) != 0)
-    {
-      __libdwfl_seterrno (DWFL_E_LIBDW);
-      return;
-    }
-
-  Dwfl_Frame *unwound = new_unwound (state);
-  if (unwound == NULL)
-    {
-      __libdwfl_seterrno (DWFL_E_NOMEM);
-      return;
-    }
-
+  Dwfl_Frame *unwound = state->unwound;
   unwound->signal_frame = frame->fde->cie->signal_frame;
   Dwfl_Thread *thread = state->thread;
   Dwfl_Process *process = thread->process;
@@ -665,7 +656,6 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias)
 	    unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
 	}
     }
-  free (frame);
 }
 
 static bool
@@ -709,6 +699,95 @@ readfunc (Dwarf_Addr addr, Dwarf_Word *datap, void *arg)
 					  process->callbacks_arg);
 }
 
+/* Internal version of dwfl_frame_module when we already know the PC.  */
+static Dwfl_Module *
+dwfl_frame_module_pc (Dwfl_Frame *state, Dwarf_Addr pc)
+{
+  if (state->mod != NULL)
+    return state->mod;
+  if (state->moderr == DWFL_E_NOERROR)
+    {
+      state->mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl, pc);
+      if (state->mod != NULL)
+	return state->mod;
+      state->moderr = DWFL_E_NO_DWARF;
+    }
+  __libdwfl_seterrno (state->moderr);
+  return NULL;
+}
+
+/* Internal version of dwfl_frame_dwarf_frame when we already know the PC.  */
+static Dwarf_Frame *
+dwfl_frame_dwarf_frame_pc (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_Addr *bias)
+{
+  if (state->frame == NULL)
+    {
+      if (state->frameerr == DWFL_E_NOERROR)
+	{
+	  Dwfl_Module *mod = dwfl_frame_module_pc (state, pc);
+	  if (mod == NULL)
+	    {
+	      state->frameerr = state->moderr;
+	      /* errno is already set.  */
+	      return NULL;
+	    }
+	  Dwarf_CFI *cfi = INTUSE(dwfl_module_eh_cfi) (mod, &state->bias);
+	  if (cfi
+	      && INTUSE(dwarf_cfi_addrframe) (cfi, pc - state->bias,
+					      &state->frame) == 0)
+	    goto out;
+	  cfi = INTUSE(dwfl_module_dwarf_cfi) (mod, &state->bias);
+	  if (cfi
+	      && INTUSE(dwarf_cfi_addrframe) (cfi, pc - state->bias,
+					      &state->frame) == 0)
+	    goto out;
+	  state->frameerr = DWFL_E_NO_DWARF;
+	}
+      __libdwfl_seterrno (state->frameerr);
+      return NULL;
+    }
+
+out:
+  *bias = state->bias;
+  return state->frame;
+}
+
+Dwfl_Module *
+dwfl_frame_module (Dwfl_Frame *state)
+{
+  if (state->mod != NULL)
+    return state->mod;
+  if (state->moderr == DWFL_E_NOERROR)
+    {
+      Dwarf_Addr pc;
+      bool isactivation;
+      INTUSE(dwfl_frame_pc) (state, &pc, &isactivation);
+      if (! isactivation)
+	pc--;
+      return dwfl_frame_module_pc (state, pc);
+    }
+  __libdwfl_seterrno (state->moderr);
+  return NULL;
+}
+
+Dwarf_Frame *
+dwfl_frame_dwarf_frame (Dwfl_Frame *state, Dwarf_Addr *bias)
+{
+  if (state->frame != NULL)
+    return state->frame;
+  if (state->frameerr == DWFL_E_NOERROR)
+    {
+      Dwarf_Addr pc;
+      bool isactivation;
+      INTUSE(dwfl_frame_pc) (state, &pc, &isactivation);
+      if (! isactivation)
+	pc--;
+      return dwfl_frame_dwarf_frame_pc (state, pc, bias);
+    }
+  __libdwfl_seterrno (state->frameerr);
+  return NULL;
+}
+
 void
 internal_function
 __libdwfl_frame_unwind (Dwfl_Frame *state)
@@ -724,28 +803,16 @@ __libdwfl_frame_unwind (Dwfl_Frame *state)
      Then we need to unwind from the original, unadjusted PC.  */
   if (! state->initial_frame && ! state->signal_frame)
     pc--;
-  Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl, pc);
-  if (mod == NULL)
-    __libdwfl_seterrno (DWFL_E_NO_DWARF);
-  else
+  Dwarf_Addr bias;
+  Dwarf_Frame *frame = dwfl_frame_dwarf_frame_pc (state, pc, &bias);
+  if (frame != NULL)
     {
-      Dwarf_Addr bias;
-      Dwarf_CFI *cfi_eh = INTUSE(dwfl_module_eh_cfi) (mod, &bias);
-      if (cfi_eh)
-	{
-	  handle_cfi (state, pc - bias, cfi_eh, bias);
-	  if (state->unwound)
-	    return;
-	}
-      Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias);
-      if (cfi_dwarf)
-	{
-	  handle_cfi (state, pc - bias, cfi_dwarf, bias);
-	  if (state->unwound)
-	    return;
-	}
+      if (new_unwound (state) == NULL)
+	__libdwfl_seterrno (DWFL_E_NOMEM);
+      else
+	handle_cfi (state, frame, bias);
+      return;
     }
-  assert (state->unwound == NULL);
   Dwfl_Thread *thread = state->thread;
   Dwfl_Process *process = thread->process;
   Ebl *ebl = process->ebl;
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 639ed186b6..fb261698ed 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -738,6 +738,16 @@ pid_t dwfl_thread_tid (Dwfl_Thread *thread)
 Dwfl_Thread *dwfl_frame_thread (Dwfl_Frame *state)
   __nonnull_attribute__ (1);
 
+/* Return module containing the PC for frame STATE.  Returns NULL if no module
+   contains the PC.  */
+Dwfl_Module *dwfl_frame_module (Dwfl_Frame *state)
+  __nonnull_attribute__ (1);
+
+/* Return CFI frame for frame STATE.  Returns NULL if no CFI frame was
+   found.  The returned frame is valid until STATE is freed.  */
+Dwarf_Frame *dwfl_frame_dwarf_frame (Dwfl_Frame *state, Dwarf_Addr *bias)
+  __nonnull_attribute__ (1, 2);
+
 /* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation.
    For every known continuous block of registers <FIRSTREG..FIRSTREG+NREGS)
    (inclusive..exclusive) set their content to REGS (array of NREGS items).
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 4ae7c0b3ba..7baa3223f5 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -252,6 +252,15 @@ struct Dwfl_Frame
   Dwfl_Thread *thread;
   /* Previous (outer) frame.  */
   Dwfl_Frame *unwound;
+  /* Module containing pc.  */
+  Dwfl_Module *mod;
+  /* CFI frame containing pc.  */
+  Dwarf_Frame *frame;
+  Dwarf_Addr bias;
+  /* Error trying to get mod.  */
+  Dwfl_Error moderr;
+  /* Error trying to get frame.  */
+  Dwfl_Error frameerr;
   bool signal_frame : 1;
   bool initial_frame : 1;
   enum
commit 8b2e113fc74aa8b019e826d1526f692497442909
Author: Omar Sandoval <osandov@fb.com>
Date:   Thu Feb 20 11:44:38 2020 -0800

    libdwfl: export __libdwfl_frame_reg_get as dwfl_frame_register
    
    This is useful for debuggers that want to dump register values.

diff --git a/libdw/libdw.map b/libdw/libdw.map
index c8961b3a6a..73f592c03e 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -374,4 +374,5 @@ ELFUTILS_0.180 {
   global:
     dwfl_attach_thread;
     dwfl_detach_thread;
+    dwfl_frame_register;
 } ELFUTILS_0.177;
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index d7dfa5a94b..f7459b283d 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -44,8 +44,7 @@
 #define DWARF_EXPR_STEPS_MAX 0x1000
 
 bool
-internal_function
-__libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
+dwfl_frame_register (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
 {
   Ebl *ebl = state->thread->process->ebl;
   if (! ebl_dwarf_to_regno (ebl, &regno))
@@ -59,6 +58,7 @@ __libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
     *val = state->regs[regno];
   return true;
 }
+INTDEF (dwfl_frame_register)
 
 bool
 internal_function
@@ -81,7 +81,7 @@ __libdwfl_frame_reg_set (Dwfl_Frame *state, unsigned regno, Dwarf_Addr val)
 static bool
 state_get_reg (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
 {
-  if (! __libdwfl_frame_reg_get (state, regno, val))
+  if (! INTUSE(dwfl_frame_register) (state, regno, val))
     {
       __libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
       return false;
@@ -634,9 +634,9 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias)
     }
   if (unwound->pc_state == DWFL_FRAME_STATE_ERROR)
     {
-      if (__libdwfl_frame_reg_get (unwound,
-				   frame->fde->cie->return_address_register,
-				   &unwound->pc))
+      if (INTUSE(dwfl_frame_register) (unwound,
+				       frame->fde->cie->return_address_register,
+				       &unwound->pc))
 	{
 	  /* PPC32 __libc_start_main properly CFI-unwinds PC as zero.
 	     Currently none of the archs supported for unwinding have
@@ -694,7 +694,7 @@ getfunc (int firstreg, unsigned nregs, Dwarf_Word *regs, void *arg)
   Dwfl_Frame *state = arg;
   assert (firstreg >= 0);
   while (nregs--)
-    if (! __libdwfl_frame_reg_get (state, firstreg++, regs++))
+    if (! INTUSE(dwfl_frame_register) (state, firstreg++, regs++))
       return false;
   return true;
 }
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 4167ef7ad8..639ed186b6 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -807,6 +807,11 @@ int dwfl_getthread_frames (Dwfl *dwfl, pid_t tid,
 bool dwfl_frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation)
   __nonnull_attribute__ (1, 2);
 
+/* Return the *VALUE of register REGNO in frame STATE.  VALUE may be NULL.
+   Returns false if the register value is unknown.  */
+bool dwfl_frame_register (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *value)
+  __nonnull_attribute__ (1);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index bc088861ff..4ae7c0b3ba 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -275,12 +275,6 @@ struct Dwfl_Frame
   Dwarf_Addr regs[];
 };
 
-/* Fetch value from Dwfl_Frame->regs indexed by DWARF REGNO.
-   No error code is set if the function returns FALSE.  */
-bool __libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno,
-			      Dwarf_Addr *val)
-  internal_function;
-
 /* Store value to Dwfl_Frame->regs indexed by DWARF REGNO.
    No error code is set if the function returns FALSE.  */
 bool __libdwfl_frame_reg_set (Dwfl_Frame *state, unsigned regno,
@@ -778,6 +772,7 @@ INTDECL (dwfl_getthread_frames)
 INTDECL (dwfl_getthreads)
 INTDECL (dwfl_thread_getframes)
 INTDECL (dwfl_frame_pc)
+INTDECL (dwfl_frame_register)
 
 /* Leading arguments standard to callbacks passed a Dwfl_Module.  */
 #define MODCB_ARGS(mod)	(mod), &(mod)->userdata, (mod)->name, (mod)->low_addr
diff --git a/libdwfl/linux-core-attach.c b/libdwfl/linux-core-attach.c
index c0f1b0d004..d55312b6cd 100644
--- a/libdwfl/linux-core-attach.c
+++ b/libdwfl/linux-core-attach.c
@@ -253,10 +253,9 @@ core_set_initial_registers (Dwfl_Thread *thread, void *thread_arg_voidp)
 	  /* PPC provides DWARF register 65 irrelevant for
 	     CFI which clashes with register 108 (LR) we need.
 	     LR (108) is provided earlier (in NT_PRSTATUS) than the # 65.
-	     FIXME: It depends now on their order in core notes.
-	     FIXME: It uses private function.  */
+	     FIXME: It depends now on their order in core notes.  */
 	  if (regno < nregs
-	      && __libdwfl_frame_reg_get (thread->unwound, regno, NULL))
+	      && INTUSE(dwfl_frame_register) (thread->unwound, regno, NULL))
 	    continue;
 	  Dwarf_Word val;
 	  switch (regloc->bits)
commit 5ea774858ed9d25f308ccb147f3f430e2b0f8599
Author: Omar Sandoval <osandov@fb.com>
Date:   Mon Oct 7 01:22:39 2019 -0700

    libdwfl: add interface for attaching to/detaching from threads
    
    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.

diff --git a/libdw/libdw.map b/libdw/libdw.map
index decac05c7c..c8961b3a6a 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.180 {
+  global:
+    dwfl_attach_thread;
+    dwfl_detach_thread;
+} ELFUTILS_0.177;
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 5bbf850e8e..61fad8b9cf 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 d5fa06d476..4167ef7ad8 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -763,6 +763,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 25753de2f8..bc088861ff 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -239,7 +239,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;
 };
commit 75ceb320352708c76a2f9a1c71e99d12322f75ea
Author: Omar Sandoval <osandov@osandov.com>
Date:   Wed Sep 4 17:13:40 2019 -0700

    configure: Add --disable-shared
    
    If we're building the elfutils libraries to link statically, then
    there's no point in building shared libraries. Add --disable-shared
    which lets us skip over building any .so's and the _pic.a's that we
    build them from.

diff --git a/backends/Makefile.am b/backends/Makefile.am
index f4052125bd..1beb661eb1 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -34,7 +34,10 @@ endif
 AM_CPPFLAGS += -I$(top_srcdir)/libebl -I$(top_srcdir)/libasm \
 	   -I$(top_srcdir)/libelf -I$(top_srcdir)/libdw
 
-noinst_LIBRARIES = libebl_backends.a libebl_backends_pic.a
+noinst_LIBRARIES = libebl_backends.a
+if ENABLE_SHARED
+noinst_LIBRARIES += libebl_backends_pic.a
+endif
 
 modules = i386 sh x86_64 ia64 alpha arm aarch64 sparc ppc ppc64 s390 \
 	  tilegx m68k bpf riscv csky
diff --git a/configure.ac b/configure.ac
index 12ee2f97c1..d68d1a1eb1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,11 @@ AC_ARG_ENABLE([programs],
 	      [], [enable_programs=yes])
 AM_CONDITIONAL(ENABLE_PROGRAMS, test "$enable_programs" = yes)
 
+AC_ARG_ENABLE([shared],
+	      AS_HELP_STRING([--disable-shared], [do not build shared libraries]),
+	      [], [enable_shared=yes])
+AM_CONDITIONAL(ENABLE_SHARED, test "$enable_shared" = yes)
+
 AC_ARG_ENABLE(deterministic-archives,
 [AS_HELP_STRING([--enable-deterministic-archives],
 		[ar and ranlib default to -D behavior])], [
diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am
index e62e8d14b0..a811e2dd1d 100644
--- a/debuginfod/Makefile.am
+++ b/debuginfod/Makefile.am
@@ -86,16 +86,20 @@ libdebuginfod.so$(EXEEXT): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS)
 	@$(textrel_check)
 	$(AM_V_at)ln -fs $@ $@.$(VERSION)
 
+if ENABLE_SHARED
 install: install-am libdebuginfod.so
 	$(mkinstalldirs) $(DESTDIR)$(libdir)
 	$(INSTALL_PROGRAM) libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
 	ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION)
 	ln -fs libdebuginfod.so.$(VERSION) $(DESTDIR)$(libdir)/libdebuginfod.so
+endif
 
 uninstall: uninstall-am
+if ENABLE_SHARED
 	rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
 	rm -f $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION)
 	rm -f $(DESTDIR)$(libdir)/libdebuginfod.so
+endif
 	rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils
 
 EXTRA_DIST = libdebuginfod.map
diff --git a/libasm/Makefile.am b/libasm/Makefile.am
index b2bff92923..8a5a9d0f04 100644
--- a/libasm/Makefile.am
+++ b/libasm/Makefile.am
@@ -34,8 +34,10 @@ GCC_INCLUDE = -I$(shell $(CC) -print-file-name=include)
 VERSION = 1
 
 lib_LIBRARIES = libasm.a
+if ENABLE_SHARED
 noinst_LIBRARIES = libasm_pic.a
 noinst_PROGRAMS = $(noinst_LIBRARIES:_pic.a=.so)
+endif
 pkginclude_HEADERS = libasm.h
 
 libasm_a_SOURCES = asm_begin.c asm_abort.c asm_end.c asm_error.c \
@@ -72,16 +74,20 @@ libasm.so$(EXEEXT): $(srcdir)/libasm.map $(libasm_so_LIBS) $(libasm_so_DEPS)
 	@$(textrel_check)
 	$(AM_V_at)ln -fs $@ $@.$(VERSION)
 
+if ENABLE_SHARED
 install: install-am libasm.so
 	$(mkinstalldirs) $(DESTDIR)$(libdir)
 	$(INSTALL_PROGRAM) libasm.so $(DESTDIR)$(libdir)/libasm-$(PACKAGE_VERSION).so
 	ln -fs libasm-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libasm.so.$(VERSION)
 	ln -fs libasm.so.$(VERSION) $(DESTDIR)$(libdir)/libasm.so
+endif
 
 uninstall: uninstall-am
+if ENABLE_SHARED
 	rm -f $(DESTDIR)$(libdir)/libasm-$(PACKAGE_VERSION).so
 	rm -f $(DESTDIR)$(libdir)/libasm.so.$(VERSION)
 	rm -f $(DESTDIR)$(libdir)/libasm.so
+endif
 	rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils
 
 noinst_HEADERS = libasmP.h symbolhash.h
diff --git a/libcpu/Makefile.am b/libcpu/Makefile.am
index 59def7d1bc..57dc698ed3 100644
--- a/libcpu/Makefile.am
+++ b/libcpu/Makefile.am
@@ -38,7 +38,10 @@ LEXCOMPILE = $(LEX) $(LFLAGS) $(AM_LFLAGS) -P$(<F:lex.l=)
 LEX_OUTPUT_ROOT = lex.$(<F:lex.l=)
 AM_YFLAGS = -p$(<F:parse.y=)
 
-noinst_LIBRARIES = libcpu.a libcpu_pic.a
+noinst_LIBRARIES = libcpu.a
+if ENABLE_SHARED
+noinst_LIBRARIES += libcpu_pic.a
+endif
 
 noinst_HEADERS = i386_dis.h x86_64_dis.h
 
diff --git a/libdw/Makefile.am b/libdw/Makefile.am
index 33b5838dc4..0297520bb8 100644
--- a/libdw/Makefile.am
+++ b/libdw/Makefile.am
@@ -35,8 +35,10 @@ AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdwelf -pthread
 VERSION = 1
 
 lib_LIBRARIES = libdw.a
+if ENABLE_SHARED
 noinst_LIBRARIES = libdw_pic.a
 noinst_PROGRAMS = $(noinst_LIBRARIES:_pic.a=.so)
+endif
 
 include_HEADERS = dwarf.h
 pkginclude_HEADERS = libdw.h known-dwarf.h
@@ -120,16 +122,20 @@ libdw.so$(EXEEXT): $(srcdir)/libdw.map $(libdw_so_LIBS) $(libdw_so_DEPS)
 	@$(textrel_check)
 	$(AM_V_at)ln -fs $@ $@.$(VERSION)
 
+if ENABLE_SHARED
 install: install-am libdw.so
 	$(mkinstalldirs) $(DESTDIR)$(libdir)
 	$(INSTALL_PROGRAM) libdw.so $(DESTDIR)$(libdir)/libdw-$(PACKAGE_VERSION).so
 	ln -fs libdw-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdw.so.$(VERSION)
 	ln -fs libdw.so.$(VERSION) $(DESTDIR)$(libdir)/libdw.so
+endif
 
 uninstall: uninstall-am
+if ENABLE_SHARED
 	rm -f $(DESTDIR)$(libdir)/libdw-$(PACKAGE_VERSION).so
 	rm -f $(DESTDIR)$(libdir)/libdw.so.$(VERSION)
 	rm -f $(DESTDIR)$(libdir)/libdw.so
+endif
 	rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils
 
 libdwfl_objects = $(shell $(AR) t ../libdwfl/libdwfl.a)
diff --git a/libdwelf/Makefile.am b/libdwelf/Makefile.am
index a35a2873cd..29cc51f59a 100644
--- a/libdwelf/Makefile.am
+++ b/libdwelf/Makefile.am
@@ -34,7 +34,10 @@ AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdw \
 	       -I$(srcdir)/../libdwfl -I$(srcdir)/../libebl
 VERSION = 1
 
-noinst_LIBRARIES = libdwelf.a libdwelf_pic.a
+noinst_LIBRARIES = libdwelf.a
+if ENABLE_SHARED
+noinst_LIBRARIES += libdwelf_pic.a
+endif
 
 pkginclude_HEADERS = libdwelf.h
 noinst_HEADERS = libdwelfP.h
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index 47bd62a5e4..952be8ba5c 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -35,7 +35,9 @@ AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \
 VERSION = 1
 
 noinst_LIBRARIES = libdwfl.a
+if ENABLE_SHARED
 noinst_LIBRARIES += libdwfl_pic.a
+endif
 
 pkginclude_HEADERS = libdwfl.h
 
diff --git a/libebl/Makefile.am b/libebl/Makefile.am
index d0d475b838..b8e0eeb264 100644
--- a/libebl/Makefile.am
+++ b/libebl/Makefile.am
@@ -34,7 +34,10 @@ endif
 AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm
 VERSION = 1
 
-noinst_LIBRARIES = libebl.a libebl_pic.a
+noinst_LIBRARIES = libebl.a
+if ENABLE_SHARED
+noinst_LIBRARIES += libebl_pic.a
+endif
 
 libebl_a_SOURCES = eblopenbackend.c eblclosebackend.c eblreloctypename.c \
 		   eblsegmenttypename.c eblsectiontypename.c \
diff --git a/libelf/Makefile.am b/libelf/Makefile.am
index d5d63f7335..94e6e38f68 100644
--- a/libelf/Makefile.am
+++ b/libelf/Makefile.am
@@ -35,8 +35,10 @@ GCC_INCLUDE = -I$(shell $(CC) -print-file-name=include)
 VERSION = 1
 
 lib_LIBRARIES = libelf.a
+if ENABLE_SHARED
 noinst_LIBRARIES = libelf_pic.a
 noinst_PROGRAMS = $(noinst_LIBRARIES:_pic.a=.so)
+endif
 include_HEADERS = libelf.h gelf.h nlist.h
 
 noinst_HEADERS = abstract.h common.h exttypes.h gelf_xlate.h libelfP.h \
@@ -122,6 +124,7 @@ libelf.so$(EXEEXT): $(srcdir)/libelf.map $(libelf_so_LIBS) $(libelf_so_DEPS)
 	@$(textrel_check)
 	$(AM_V_at)ln -fs $@ $@.$(VERSION)
 
+if ENABLE_SHARED
 install: install-am libelf.so
 	$(mkinstalldirs) $(DESTDIR)$(libdir)
 	$(INSTALL_PROGRAM) libelf.so $(DESTDIR)$(libdir)/libelf-$(PACKAGE_VERSION).so
@@ -132,6 +135,7 @@ uninstall: uninstall-am
 	rm -f $(DESTDIR)$(libdir)/libelf-$(PACKAGE_VERSION).so
 	rm -f $(DESTDIR)$(libdir)/libelf.so.$(VERSION)
 	rm -f $(DESTDIR)$(libdir)/libelf.so
+endif
 
 EXTRA_DIST = libelf.map
 
commit 93463f8e4415ce19b89419a2b5e00d42f5cecb1e
Author: Omar Sandoval <osandov@osandov.com>
Date:   Wed Sep 4 17:13:23 2019 -0700

    configure: Add --disable-programs
    
    In some cases, it's useful to build the elfutils libraries without the
    utilities. Add a configure option which lets us do that. The default is
    still to build everything.

diff --git a/Makefile.am b/Makefile.am
index bd8926b523..0b13c717fa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,7 +27,11 @@ AM_MAKEFLAGS = --no-print-directory
 pkginclude_HEADERS = version.h
 
 SUBDIRS = config m4 lib libelf libcpu backends libebl libdwelf libdwfl libdw \
-	  libasm src po doc tests
+	  libasm
+if ENABLE_PROGRAMS
+SUBDIRS += src
+endif
+SUBDIRS += po doc tests
 
 if DEBUGINFOD
 SUBDIRS += debuginfod
diff --git a/configure.ac b/configure.ac
index a39e800f70..12ee2f97c1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -65,6 +65,11 @@ AC_CONFIG_FILES([debuginfod/Makefile])
 
 AC_CANONICAL_HOST
 
+AC_ARG_ENABLE([programs],
+	      AS_HELP_STRING([--disable-programs], [do not build utility programs]),
+	      [], [enable_programs=yes])
+AM_CONDITIONAL(ENABLE_PROGRAMS, test "$enable_programs" = yes)
+
 AC_ARG_ENABLE(deterministic-archives,
 [AS_HELP_STRING([--enable-deterministic-archives],
 		[ar and ranlib default to -D behavior])], [
diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am
index 51965f65db..e62e8d14b0 100644
--- a/debuginfod/Makefile.am
+++ b/debuginfod/Makefile.am
@@ -57,7 +57,9 @@ libeu = ../lib/libeu.a
 
 AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw:.
 
+if ENABLE_PROGRAMS
 bin_PROGRAMS = debuginfod debuginfod-find
+endif
 debuginfod_SOURCES = debuginfod.cxx
 debuginfod_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(libmicrohttpd_LIBS) $(libcurl_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS) -lpthread -ldl
 
