commit a10cb887cda1fdf0d5474d6452d1107aa19d47a8
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 66ec16106..b2df2ba72 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -377,4 +377,5 @@ ELFUTILS_0.179 {
     dwfl_frame_module;
     dwfl_frame_dwarf_frame;
     dwfl_frame_register;
+    dwfl_frame_eval_expr;
 } ELFUTILS_0.178;
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index 6bf1a3907..13bff8fc6 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -802,3 +802,14 @@ out:
   *bias = state->bias;
   return state->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 fb261698e..1dc080c01 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 a056c850cec0bd55fedbdf916669a141c23ec43f
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 e5def9a54..66ec16106 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -376,4 +376,5 @@ ELFUTILS_0.179 {
     dwfl_detach_thread;
     dwfl_frame_module;
     dwfl_frame_dwarf_frame;
+    dwfl_frame_register;
 } ELFUTILS_0.178;
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index 480636718..6bf1a3907 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))
@@ -81,7 +80,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;
@@ -625,9 +624,9 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Frame *frame, 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
@@ -684,7 +683,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 b494d4582..fb261698e 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -817,6 +817,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 659e671a6..eab003e66 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -284,12 +284,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,
diff --git a/libdwfl/linux-core-attach.c b/libdwfl/linux-core-attach.c
index c0f1b0d00..d55312b6c 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 4fdbad07063bb25333dcf138c8178fd5f6cd3d40
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 4f3ad91d4..e5def9a54 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -374,4 +374,6 @@ ELFUTILS_0.179 {
   global:
     dwfl_attach_thread;
     dwfl_detach_thread;
+    dwfl_frame_module;
+    dwfl_frame_dwarf_frame;
 } ELFUTILS_0.178;
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 61fad8b9c..80f2bc146 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 d7dfa5a94..480636718 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
@@ -724,28 +714,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 = INTUSE(dwfl_frame_dwarf_frame) (state, &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;
@@ -770,3 +748,58 @@ __libdwfl_frame_unwind (Dwfl_Frame *state)
   assert (state->unwound->pc_state == DWFL_FRAME_STATE_PC_SET);
   state->unwound->signal_frame = signal_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;
+      INTUSE(dwfl_frame_pc) (state, &pc, NULL);
+      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;
+}
+
+Dwarf_Frame *
+dwfl_frame_dwarf_frame (Dwfl_Frame *state, Dwarf_Addr *bias)
+{
+  if (state->frame == NULL)
+    {
+      if (state->frameerr == DWFL_E_NOERROR)
+	{
+	  Dwfl_Module *mod = INTUSE(dwfl_frame_module) (state);
+	  if (mod == NULL)
+	    {
+	      state->frameerr = state->moderr;
+	      return NULL;
+	    }
+	  Dwarf_Addr pc;
+	  INTUSE(dwfl_frame_pc) (state, &pc, NULL);
+	  Dwarf_CFI *cfi = INTUSE(dwfl_module_eh_cfi) (state->mod,
+						       &state->bias);
+	  if (cfi
+	      && INTUSE(dwarf_cfi_addrframe) (cfi, pc - state->bias,
+					      &state->frame) == 0)
+	    goto out;
+	  cfi = INTUSE(dwfl_module_dwarf_cfi) (state->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;
+}
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 4167ef7ad..b494d4582 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 bc088861f..659e671a6 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 3835a2d3b2b8b671b9adc398544ffe54083a1642
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 decac05c7..4f3ad91d4 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.179 {
+  global:
+    dwfl_attach_thread;
+    dwfl_detach_thread;
+} ELFUTILS_0.178;
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 5bbf850e8..61fad8b9c 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 d5fa06d47..4167ef7ad 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 25753de2f..bc088861f 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 2b2a97fbfef9be0c68e5db70e1a577b8ac8d1631
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 f4052125b..1beb661eb 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 12ee2f97c..d68d1a1eb 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 e62e8d14b..a811e2dd1 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 b2bff9292..8a5a9d0f0 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 59def7d1b..57dc698ed 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 33b5838dc..0297520bb 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 a35a2873c..29cc51f59 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 47bd62a5e..952be8ba5 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 d0d475b83..b8e0eeb26 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 d5d63f733..94e6e38f6 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 fbfb09a290f529bac4ceb8e367c774ae0e040412
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 bd8926b52..0b13c717f 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 a39e800f7..12ee2f97c 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 51965f65d..e62e8d14b 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
 
