This is the mail archive of the
elfutils-devel@sourceware.org
mailing list for the elfutils project.
[PATCH 3/5] libdwfl: add interface for attaching to/detaching from threads
- From: Omar Sandoval <osandov at osandov dot com>
- To: elfutils-devel at sourceware dot org
- Date: Mon, 7 Oct 2019 02:05:37 -0700
- Subject: [PATCH 3/5] libdwfl: add interface for attaching to/detaching from threads
- References: <cover.1570438723.git.osandov@fb.com>
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