Skip to content

Commit

Permalink
Address @osandov's comments on osandov#129
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Svetlitski <[email protected]>
  • Loading branch information
Svetlitski committed Dec 6, 2021
1 parent fffc103 commit 6b94ea1
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 130 deletions.
20 changes: 13 additions & 7 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -733,28 +733,33 @@ class Program:
:param lang: :attr:`Type.language`
"""
...
def threads(self) -> ThreadIterator:
def threads(self) -> Iterable[Thread]:
"""
Return an iterable over all of the threads resident within the process.
"""
...
def thread(self, tid: int) -> Thread:
def thread(self, tid: IntegerLike) -> Thread:
"""
Retrieve the thread with the given `tid`, raising a `LookupError` in the
event no such thread exists.
Retrieve the thread with the given thread ID.
:param tid: The desired thread's ID (as defined by `gettid(2) <http://man7.org/linux/man-pages/man2/gettid.2.html>`_)
:raises LookupError: if no such thread exists
"""
...
def crashed_thread(self) -> Thread:
"""
Return the thread responsible for causing the process to crash (e.g. the thread that segfaulted,
received a `SIGQUIT`, etc.). This method should only be called when debugging a core dump.
:raises ValueError: if the current target is not a core dump
"""
...

class ThreadIterator:
def __iter__(self) -> Iterable[Thread]: ...

class Thread:
"""
A single thread resident within a process.
"""

tid: int
""" This thread's ID (as defined by `gettid(2) <http://man7.org/linux/man-pages/man2/gettid.2.html>`_). """
prstatus: bytes
Expand All @@ -764,6 +769,7 @@ class Thread:
def stack_trace(self) -> StackTrace:
"""
A convenience function for retrieving the stack trace associated with a given thread.
This is equivalent to `prog.stack_trace(thread.tid)`.
See the documentation for `Program.stack_trace` for more details.
"""
Expand Down
3 changes: 1 addition & 2 deletions drgn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
SymbolBinding,
SymbolKind,
Thread,
ThreadIterator,
Type,
TypeEnumerator,
TypeKind,
Expand Down Expand Up @@ -117,7 +116,7 @@
"SymbolBinding",
"SymbolKind",
"Thread",
"ThreadIterator",
"_ThreadIterator",
"Type",
"TypeEnumerator",
"TypeKind",
Expand Down
71 changes: 71 additions & 0 deletions libdrgn/drgn.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -2754,4 +2754,75 @@ struct drgn_error *drgn_object_stack_trace(const struct drgn_object *obj,

/** @} */

/**
* @defgroup Threads Threads
*
* Individual threads resident within a process.
*
* @{
*/

/**
* @struct drgn_thread
*
* An individual thread resident within a process
*/
struct drgn_thread;

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

/**
* Find the thread with the given thread ID.
*
* @param[out] ret The corresponding thread.
* @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);

/**
* Find the thread which caused the process to crash (e.g. the thread that
* segfaulted, received a @c SIGQUIT, etc.). This function should only be
* called when the target is a core dump.
*
* @param[out] ret The thread which caused the process to crash
* @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 all of the threads within the given program target.
*
* @param[out] ret An iterator over the threads, which can be advanced with
* @ref drgn_thread_iterator_next, and should 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);

/**
* Retrieve the next thread from a @ref drgn_thread_iterator.
*
* @param[out] ret The next thread from the iterator. If its value is @c NULL,
* then this iterator has been exhausted.
* @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);

/** @} */

#endif /* DRGN_H */
6 changes: 5 additions & 1 deletion libdrgn/kdump.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,13 @@ struct drgn_error *drgn_program_cache_threads_kdump(struct drgn_program *prog)

prstatus_data = kdump_blob_pin(prstatus_attr.val.blob);
prstatus_size = kdump_blob_size(prstatus_attr.val.blob);
// TODO: Once threads are properly implemented for kernel
// core dumps, ensure that prog->crashed_thread is set correctly
uint32_t _;
err = drgn_program_cache_thread(prog,
prstatus_data,
prstatus_size);
prstatus_size,
&_);
if (err)
return err;
}
Expand Down
108 changes: 65 additions & 43 deletions libdrgn/program.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@
#include "vector.h"
#include "util.h"

static inline uint32_t drgn_thread_to_key(const struct drgn_thread *entry)
{
return entry->tid;
}

DEFINE_VECTOR_FUNCTIONS(drgn_prstatus_vector)
DEFINE_HASH_TABLE_FUNCTIONS(drgn_thread_set, drgn_thread_to_key, int_key_hash_pair, scalar_key_eq)

struct drgn_thread_iterator {
struct drgn_thread_set_iterator iterator;
};

static Elf_Type note_header_type(GElf_Phdr *phdr)
{
Expand Down Expand Up @@ -88,7 +98,7 @@ void drgn_program_deinit(struct drgn_program *prog)
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
drgn_prstatus_vector_deinit(&prog->prstatus_vector);
else
drgn_thread_map_deinit(&prog->thread_map);
drgn_thread_set_deinit(&prog->thread_set);
}
free(prog->pgtable_it);

Expand Down Expand Up @@ -653,7 +663,8 @@ static struct drgn_error *get_prstatus_pid(struct drgn_program *prog, const char

struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog,
const char *data,
size_t size)
size_t size,
uint32_t *ret)
{
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) {
struct nstring *entry =
Expand All @@ -663,21 +674,12 @@ struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog,
entry->str = data;
entry->len = size;
} else {
struct drgn_thread_map_entry entry;
struct drgn_error *err = get_prstatus_pid(prog, data, size, &entry.key);
if (err)
return err;
struct drgn_thread *thread = malloc(sizeof(struct drgn_thread));
*thread = (struct drgn_thread){ .tid = entry.key, .prstatus = { data, size } };
if (drgn_thread_map_size(&prog->thread_map) == 0) {
// Having perused the GDB, BFD, and Kernel source code, it appears that
// the first thread listed in a core dump is always the thread that caused
// the process to crash.
prog->crashed_thread = thread;
}
entry.value = thread;
if (drgn_thread_map_insert(&prog->thread_map, &entry,
NULL) == -1)
struct drgn_thread thread = { .prstatus = { data, size } };
struct drgn_error *err = get_prstatus_pid(prog, data, size, &thread.tid);
if (err)
return err;
*ret = thread.tid;
if (drgn_thread_set_insert(&prog->thread_set, &thread, NULL) == -1)
return &drgn_enomem;
}
return NULL;
Expand All @@ -687,14 +689,16 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog)
{
struct drgn_error *err;
size_t phnum, i;
bool found_crashed_tid = false;
uint32_t crashed_thread_tid;

if (prog->threads_cached)
return NULL;

if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
drgn_prstatus_vector_init(&prog->prstatus_vector);
else
drgn_thread_map_init(&prog->thread_map);
drgn_thread_set_init(&prog->thread_set);

#ifdef WITH_LIBKDUMPFILE
if (prog->kdump_ctx) {
Expand Down Expand Up @@ -744,11 +748,19 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog)
nhdr.n_type != NT_PRSTATUS)
continue;

uint32_t tid;
err = drgn_program_cache_thread(prog,
(char *)data->d_buf + desc_offset,
nhdr.n_descsz);
nhdr.n_descsz, &tid);
if (err)
goto out;
// Having perused the GDB, BFD, and Kernel source code, it appears that
// the first thread listed in a core dump is always the thread that caused
// the process to crash.
if (!found_crashed_tid) {
found_crashed_tid = true;
crashed_thread_tid = tid;
}
}
}

Expand All @@ -758,9 +770,17 @@ static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog)
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
drgn_prstatus_vector_deinit(&prog->prstatus_vector);
else
drgn_thread_map_deinit(&prog->thread_map);
} else {
drgn_thread_set_deinit(&prog->thread_set);
} else if (found_crashed_tid) {
prog->threads_cached = true;
// The reason we record the tid and do the lookup at the end is because
// we want to ensure the pointer we use is stable, which wouldn't be the case
// if we set prog->crashed_thread and then continued to insert entries into
// prog->thread_set.
struct drgn_thread_set_iterator it =
drgn_thread_set_search(&prog->thread_set, &crashed_thread_tid);
assert(it.entry);
prog->crashed_thread = it.entry;
}
return err;
}
Expand Down Expand Up @@ -804,41 +824,47 @@ struct drgn_error *drgn_program_find_prstatus_by_tid(struct drgn_program *prog,
return NULL;
}

struct drgn_error *drgn_program_find_thread(struct drgn_program *prog,
uint32_t tid,
struct drgn_thread **ret)
LIBDRGN_PUBLIC struct drgn_error *
drgn_program_find_thread(struct drgn_program *prog, uint32_t tid,
struct drgn_thread **ret)
{
struct drgn_error *err;
struct drgn_thread_map_iterator it;
struct drgn_thread_set_iterator it;

assert(!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL));
err = drgn_program_cache_threads(prog);
if (err)
return err;

it = drgn_thread_map_search(&prog->thread_map, &tid);
it = drgn_thread_set_search(&prog->thread_set, &tid);
if (!it.entry) {
*ret = NULL;
return NULL;
}
*ret = it.entry->value;
*ret = it.entry;
return NULL;
}

struct drgn_error *drgn_program_crashed_thread(struct drgn_program *prog,
struct drgn_thread **ret)
LIBDRGN_PUBLIC struct drgn_error *
drgn_program_crashed_thread(struct drgn_program *prog, struct drgn_thread **ret)
{
struct drgn_error *err;

assert(!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL));
if (prog->flags & DRGN_PROGRAM_IS_LIVE)
return drgn_error_create(
DRGN_ERROR_INVALID_ARGUMENT,
"the crashed thread can only be found when debugging a core dump");
err = drgn_program_cache_threads(prog);
if (err)
return err;
if (!prog->crashed_thread)
return drgn_error_create(DRGN_ERROR_OTHER, "crashed thread not found");
*ret = prog->crashed_thread;
return NULL;
}

struct drgn_error *
LIBDRGN_PUBLIC struct drgn_error *
drgn_thread_iterator_create(struct drgn_program *prog,
struct drgn_thread_iterator **ret)
{
Expand All @@ -847,30 +873,26 @@ drgn_thread_iterator_create(struct drgn_program *prog,
if (err)
return err;

struct drgn_thread_map_iterator it =
drgn_thread_map_first(&prog->thread_map);
*ret = malloc(sizeof(struct drgn_thread_iterator));
if (!*ret)
return &drgn_enomem;
(*ret)->iterator = it;
(*ret)->iterator = drgn_thread_set_first(&prog->thread_set);
return NULL;
}

void drgn_thread_iterator_destroy(struct drgn_thread_iterator *it)
LIBDRGN_PUBLIC void
drgn_thread_iterator_destroy(struct drgn_thread_iterator *it)
{
free(it);
}

struct drgn_error *drgn_thread_iterator_next(struct drgn_thread_iterator *it,
struct drgn_thread **ret)
LIBDRGN_PUBLIC struct drgn_error *
drgn_thread_iterator_next(struct drgn_thread_iterator *it,
struct drgn_thread **ret)
{
if (!it->iterator.entry) {
*ret = NULL;
return NULL;
}

*ret = (it->iterator.entry->value);
it->iterator = drgn_thread_map_next(it->iterator);
*ret = it->iterator.entry;
if (it->iterator.entry)
it->iterator = drgn_thread_set_next(it->iterator);
return NULL;
}

Expand Down
Loading

0 comments on commit 6b94ea1

Please sign in to comment.