Skip to content

Commit

Permalink
Implement a new API for representing threads
Browse files Browse the repository at this point in the history
Previously, drgn had no way to represent a thread – retrieving a stack
trace (the only extant thread-specific operation) was achieved by
requiring the user to directly provide a tid.

This commit introduces the scaffolding for the design outlined in
issue #92, and implements the corresponding methods for userspace core
dumps, the live Linux kernel, and Linux kernel core dumps. Future work
will build on top of this commit to support live userspace processes.

Signed-off-by: Kevin Svetlitski <[email protected]>
  • Loading branch information
Svetlitski authored and osandov committed Jan 11, 2022
1 parent 186d9b2 commit 9710b6e
Show file tree
Hide file tree
Showing 23 changed files with 977 additions and 88 deletions.
47 changes: 47 additions & 0 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,31 @@ class Program:
the given file
"""
...
def threads(self) -> Iterator[Thread]:
"""Get an iterator over all of the threads in the program."""
...
def thread(self, tid: IntegerLike) -> Thread:
"""
Get the thread with the given thread ID.
:param tid: Thread ID (as defined by `gettid(2)
<http://man7.org/linux/man-pages/man2/gettid.2.html>`_).
:raises LookupError: if no thread has the given thread ID
"""
...
def crashed_thread(self) -> Thread:
"""
Get the thread that caused the program to crash.
For userspace programs, this is the thread that received the fatal
signal (e.g., ``SIGSEGV`` or ``SIGQUIT``).
For the kernel, this is the thread that panicked (either directly or as
a result of an oops, ``BUG_ON()``, etc.).
:raises ValueError: if the program is live (i.e., not a core dump)
"""
...
def read(
self, address: IntegerLike, size: IntegerLike, physical: bool = False
) -> bytes:
Expand Down Expand Up @@ -764,6 +789,28 @@ class FindObjectFlags(enum.Flag):
ANY = ...
""

class Thread:
"""A thread in a program."""

tid: int
"""
Thread ID (as defined by `gettid(2)
<http://man7.org/linux/man-pages/man2/gettid.2.html>`_).
"""
object: Object
"""
If the program is the Linux kernel, the ``struct task_struct *`` object for
this thread. Otherwise, not defined.
"""
def stack_trace(self) -> StackTrace:
"""
Get the stack trace for this thread.
This is equivalent to ``prog.stack_trace(thread.tid)``. See
:meth:`Program.stack_trace()`.
"""
...

def filename_matches(haystack: Optional[str], needle: Optional[str]) -> bool:
"""
Return whether a filename containing a definition (*haystack*) matches a
Expand Down
2 changes: 2 additions & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Programs
.. drgndoc:: ProgramFlags
.. drgndoc:: FindObjectFlags

.. drgndoc:: Thread

.. _api-filenames:

Filenames
Expand Down
22 changes: 19 additions & 3 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,29 @@ Other Concepts
In addition to the core concepts above, drgn provides a few additional
abstractions.

Threads
^^^^^^^

The :class:`drgn.Thread` class represents a thread.
:meth:`drgn.Program.threads()`, :meth:`drgn.Program.thread()`, and
:meth:`drgn.Program.crashed_thread()` can be used to find threads::

>>> for thread in prog.threads():
... print(thread.tid)
...
39143
39144
>>> print(prog.crashed_thread().tid)
39144

Stack Traces
^^^^^^^^^^^^

drgn represents stack traces with the :class:`drgn.StackTrace` and
:class:`drgn.StackFrame` classes. :meth:`drgn.Program.stack_trace()` returns
the call stack for a thread. The :meth:`[] <drgn.StackFrame.__getitem__>`
operator looks up an object in the scope of a ``StackFrame``::
:class:`drgn.StackFrame` classes. :meth:`drgn.Thread.stack_trace()` and
:meth:`drgn.Program.stack_trace()` return the call stack for a thread. The
:meth:`[] <drgn.StackFrame.__getitem__>` operator looks up an object in the
scope of a ``StackFrame``::

>>> trace = prog.stack_trace(115)
>>> trace
Expand Down
2 changes: 2 additions & 0 deletions drgn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
Symbol,
SymbolBinding,
SymbolKind,
Thread,
Type,
TypeEnumerator,
TypeKind,
Expand Down Expand Up @@ -114,6 +115,7 @@
"Symbol",
"SymbolBinding",
"SymbolKind",
"Thread",
"Type",
"TypeEnumerator",
"TypeKind",
Expand Down
1 change: 1 addition & 0 deletions libdrgn/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ _drgn_la_SOURCES = python/constants.c \
python/stack_trace.c \
python/symbol.c \
python/test.c \
python/thread.c \
python/type.c \
python/util.c

Expand Down
109 changes: 109 additions & 0 deletions libdrgn/drgn.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ const char * const *drgn_register_names(const struct drgn_register *reg,
/** @} */

struct drgn_object;
struct drgn_thread;

/**
* @defgroup Programs Programs
Expand Down Expand Up @@ -2754,4 +2755,112 @@ struct drgn_error *drgn_object_stack_trace(const struct drgn_object *obj,

/** @} */

/**
* @defgroup Threads Threads
*
* Threads in a program.
*
* @{
*/

/**
* @struct drgn_thread
*
* A thread in a program.
*/
struct drgn_thread;

/**
* Create a copy of a @ref drgn_thread.
*
* @param[in] thread Thread to copy.
* @param[out] ret Returned copy. On success, must be destroyed with @ref
* drgn_thread_destroy().
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_thread_dup(const struct drgn_thread *thread,
struct drgn_thread **ret);

/** Free a @ref drgn_thread. */
void drgn_thread_destroy(struct drgn_thread *thread);

/**
* @struct drgn_thread_iterator
*
* An iterator over all the threads in a program.
*/
struct drgn_thread_iterator;

/**
* Get an iterator over all of the threads in the program.
*
* @param[out] ret Returned iterator, which can be advanced with @ref
* drgn_thread_iterator_next, and must be destroyed with @ref
* drgn_thread_iterator_destroy.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *
drgn_thread_iterator_create(struct drgn_program *prog,
struct drgn_thread_iterator **ret);

/** Free a @ref drgn_thread_iterator. */
void drgn_thread_iterator_destroy(struct drgn_thread_iterator *it);

/**
* Get the next thread from a @ref drgn_thread_iterator.
*
* @param[out] ret Borrowed thread handle, or @c NULL if there are no more
* threads. This is valid until until the next call to @ref
* drgn_thread_iterator_next() with the same @p it, or until @p it is destroyed.
* It may be copied with @ref drgn_thread_dup() if it is needed for longer. This
* must NOT be destroyed with @ref drgn_thread_destroy().
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_thread_iterator_next(struct drgn_thread_iterator *it,
struct drgn_thread **ret);

/**
* Get the thread with the given thread ID.
*
* @param[in] tid Thread ID.
* @param[out] ret New thread handle. On success, must be destroyed with @ref
* drgn_thread_destroy().
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_program_find_thread(struct drgn_program *prog,
uint32_t tid,
struct drgn_thread **ret);

/**
* Get the thread that caused the program to crash.
*
* @param[out] ret Borrowed thread handle. This is valid for the lifetime of @p
* prog. This must NOT be destroyed with @ref drgn_thread_destroy().
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_program_crashed_thread(struct drgn_program *prog,
struct drgn_thread **ret);

/**
* Get the object for the given thread. This is currently only defined for the
* Linux kernel.
*
* @param[out] ret Returned object. This must not be modified and is valid for
* the lifetime of @p thread. It can be copied with @ref drgn_object_copy() if
* it is needed for longer.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_thread_object(struct drgn_thread *thread,
const struct drgn_object **ret);

/**
* Get a stack trace for the thread represented by @p thread.
*
* @sa drgn_program_stack_trace().
*/
struct drgn_error *drgn_thread_stack_trace(struct drgn_thread *thread,
struct drgn_stack_trace **ret);

/** @} */

#endif /* DRGN_H */
7 changes: 3 additions & 4 deletions libdrgn/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,11 @@ void linux_helper_task_iterator_deinit(struct linux_helper_task_iterator *it);
/**
* Get the next task from a @ref linux_helper_task_iterator.
*
* @param[out] ret Returned `struct task_struct *` object. This points to an
* object stored in @p it. The caller may modify this, but it will be
* overwritten the next time this function is called on the same @p it.
* @param[out] ret Returned `struct task_struct *` object. This is valid until
* the next call to this function on the same @p it or until @p it is destroyed.
*/
struct drgn_error *
linux_helper_task_iterator_next(struct linux_helper_task_iterator *it,
struct drgn_object **ret);
const struct drgn_object **ret);

#endif /* DRGN_HELPERS_H */
8 changes: 4 additions & 4 deletions libdrgn/kdump.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ struct drgn_error *drgn_program_set_kdump(struct drgn_program *prog)
return err;
}

struct drgn_error *drgn_program_cache_prstatus_kdump(struct drgn_program *prog)
struct drgn_error *drgn_program_cache_kdump_notes(struct drgn_program *prog)
{
struct drgn_error *err;
kdump_num_t ncpus, i;
Expand Down Expand Up @@ -208,9 +208,9 @@ struct drgn_error *drgn_program_cache_prstatus_kdump(struct drgn_program *prog)

prstatus_data = kdump_blob_pin(prstatus_attr.val.blob);
prstatus_size = kdump_blob_size(prstatus_attr.val.blob);
err = drgn_program_cache_prstatus_entry(prog,
prstatus_data,
prstatus_size);
uint32_t _;
err = drgn_program_cache_prstatus_entry(prog, prstatus_data,
prstatus_size, &_);
if (err)
return err;
}
Expand Down
2 changes: 1 addition & 1 deletion libdrgn/linux_kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ linux_kernel_report_debug_info(struct drgn_debug_info_load_state *load);
#define KDUMP_SIG_LEN (sizeof(KDUMP_SIGNATURE) - 1)

#ifdef WITH_LIBKDUMPFILE
struct drgn_error *drgn_program_cache_prstatus_kdump(struct drgn_program *prog);
struct drgn_error *drgn_program_cache_kdump_notes(struct drgn_program *prog);
struct drgn_error *drgn_program_set_kdump(struct drgn_program *prog);
#else
static inline struct drgn_error *
Expand Down
2 changes: 1 addition & 1 deletion libdrgn/linux_kernel_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ void linux_helper_task_iterator_deinit(struct linux_helper_task_iterator *it)

struct drgn_error *
linux_helper_task_iterator_next(struct linux_helper_task_iterator *it,
struct drgn_object **ret)
const struct drgn_object **ret)
{
if (it->done) {
*ret = NULL;
Expand Down
Loading

0 comments on commit 9710b6e

Please sign in to comment.