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 osandov#92, and implements the corresponding methods for the case where a
userspace core dump is being debugged. Future work will build on top of
this commit to implement the existing methods for the cases of live
kernel debugging, and kernel core dumps.

Signed-off-by: Kevin Svetlitski <[email protected]>
  • Loading branch information
Svetlitski committed Dec 6, 2021
1 parent 10c66d4 commit fffc103
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 32 deletions.
35 changes: 35 additions & 0 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,41 @@ class Program:
:param lang: :attr:`Type.language`
"""
...
def threads(self) -> ThreadIterator:
"""
Return an iterable over all of the threads resident within the process.
"""
...
def thread(self, tid: int) -> Thread:
"""
Retrieve the thread with the given `tid`, raising a `LookupError` in the
event 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.
"""
...

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

class Thread:
tid: int
""" This thread's ID (as defined by `gettid(2) <http://man7.org/linux/man-pages/man2/gettid.2.html>`_). """
prstatus: bytes
""" The contents of the PRSTATUS note associated with this thread in the core dump. """
object: Object
""" Presently undefined. """
def stack_trace(self) -> StackTrace:
"""
A convenience function for retrieving the stack trace associated with a given thread.
See the documentation for `Program.stack_trace` for more details.
"""
...

class ProgramFlags(enum.Flag):
"""
Expand Down
4 changes: 4 additions & 0 deletions drgn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
Symbol,
SymbolBinding,
SymbolKind,
Thread,
ThreadIterator,
Type,
TypeEnumerator,
TypeKind,
Expand Down Expand Up @@ -114,6 +116,8 @@
"Symbol",
"SymbolBinding",
"SymbolKind",
"Thread",
"ThreadIterator",
"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
4 changes: 2 additions & 2 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_threads_kdump(struct drgn_program *prog)
{
struct drgn_error *err;
kdump_num_t ncpus, i;
Expand Down Expand Up @@ -208,7 +208,7 @@ 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,
err = drgn_program_cache_thread(prog,
prstatus_data,
prstatus_size);
if (err)
Expand Down
122 changes: 97 additions & 25 deletions libdrgn/program.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
#include "util.h"

DEFINE_VECTOR_FUNCTIONS(drgn_prstatus_vector)
DEFINE_HASH_MAP_FUNCTIONS(drgn_prstatus_map, int_key_hash_pair, scalar_key_eq)

static Elf_Type note_header_type(GElf_Phdr *phdr)
{
Expand Down Expand Up @@ -85,11 +84,11 @@ void drgn_program_init(struct drgn_program *prog,

void drgn_program_deinit(struct drgn_program *prog)
{
if (prog->prstatus_cached) {
if (prog->threads_cached) {
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
drgn_prstatus_vector_deinit(&prog->prstatus_vector);
else
drgn_prstatus_map_deinit(&prog->prstatus_map);
drgn_thread_map_deinit(&prog->thread_map);
}
free(prog->pgtable_it);

Expand Down Expand Up @@ -652,7 +651,7 @@ static struct drgn_error *get_prstatus_pid(struct drgn_program *prog, const char
return NULL;
}

struct drgn_error *drgn_program_cache_prstatus_entry(struct drgn_program *prog,
struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog,
const char *data,
size_t size)
{
Expand All @@ -664,36 +663,42 @@ struct drgn_error *drgn_program_cache_prstatus_entry(struct drgn_program *prog,
entry->str = data;
entry->len = size;
} else {
struct drgn_prstatus_map_entry entry = {
.value = { data, size },
};
struct drgn_error *err = get_prstatus_pid(prog, data, size,
&entry.key);
if (err)
return err;
if (drgn_prstatus_map_insert(&prog->prstatus_map, &entry,
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)
return &drgn_enomem;
}
return NULL;
}

static struct drgn_error *drgn_program_cache_prstatus(struct drgn_program *prog)
static struct drgn_error *drgn_program_cache_threads(struct drgn_program *prog)
{
struct drgn_error *err;
size_t phnum, i;

if (prog->prstatus_cached)
if (prog->threads_cached)
return NULL;

if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
drgn_prstatus_vector_init(&prog->prstatus_vector);
else
drgn_prstatus_map_init(&prog->prstatus_map);
drgn_thread_map_init(&prog->thread_map);

#ifdef WITH_LIBKDUMPFILE
if (prog->kdump_ctx) {
err = drgn_program_cache_prstatus_kdump(prog);
err = drgn_program_cache_threads_kdump(prog);
goto out;
}
#endif
Expand Down Expand Up @@ -739,7 +744,7 @@ static struct drgn_error *drgn_program_cache_prstatus(struct drgn_program *prog)
nhdr.n_type != NT_PRSTATUS)
continue;

err = drgn_program_cache_prstatus_entry(prog,
err = drgn_program_cache_thread(prog,
(char *)data->d_buf + desc_offset,
nhdr.n_descsz);
if (err)
Expand All @@ -753,9 +758,9 @@ static struct drgn_error *drgn_program_cache_prstatus(struct drgn_program *prog)
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
drgn_prstatus_vector_deinit(&prog->prstatus_vector);
else
drgn_prstatus_map_deinit(&prog->prstatus_map);
drgn_thread_map_deinit(&prog->thread_map);
} else {
prog->prstatus_cached = true;
prog->threads_cached = true;
}
return err;
}
Expand All @@ -766,7 +771,7 @@ struct drgn_error *drgn_program_find_prstatus_by_cpu(struct drgn_program *prog,
uint32_t *tid_ret)
{
assert(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL);
struct drgn_error *err = drgn_program_cache_prstatus(prog);
struct drgn_error *err = drgn_program_cache_threads(prog);
if (err)
return err;

Expand All @@ -785,23 +790,90 @@ struct drgn_error *drgn_program_find_prstatus_by_tid(struct drgn_program *prog,
struct nstring *ret)
{
struct drgn_error *err;
struct drgn_prstatus_map_iterator it;
struct drgn_thread *thread;
err = drgn_program_find_thread(prog, tid, &thread);
if (err)
return err;

if (!thread) {
ret->str = NULL;
ret->len = 0;
return NULL;
}
*ret = thread->prstatus;
return NULL;
}

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;

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

it = drgn_prstatus_map_search(&prog->prstatus_map, &tid);
it = drgn_thread_map_search(&prog->thread_map, &tid);
if (!it.entry) {
ret->str = NULL;
ret->len = 0;
*ret = NULL;
return NULL;
}
*ret = it.entry->value;
return NULL;
}

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));
err = drgn_program_cache_threads(prog);
if (err)
return err;
*ret = prog->crashed_thread;
return NULL;
}

struct drgn_error *
drgn_thread_iterator_create(struct drgn_program *prog,
struct drgn_thread_iterator **ret)
{
struct drgn_error *err;
err = drgn_program_cache_threads(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;
return NULL;
}

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)
{
if (!it->iterator.entry) {
*ret = NULL;
return NULL;
}

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

struct drgn_error *drgn_program_init_core_dump(struct drgn_program *prog,
const char *path)
{
Expand Down
35 changes: 30 additions & 5 deletions libdrgn/program.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,18 @@ struct vmcoreinfo {
bool pgtable_l5_enabled;
};

struct drgn_thread {
int tid;
struct nstring prstatus;
struct drgn_object thread;
};

DEFINE_VECTOR_TYPE(drgn_typep_vector, struct drgn_type *)
DEFINE_VECTOR_TYPE(drgn_prstatus_vector, struct nstring)
DEFINE_HASH_MAP_TYPE(drgn_prstatus_map, uint32_t, struct nstring)
DEFINE_HASH_MAP_TYPE(drgn_thread_map, uint32_t, struct drgn_thread*)
DEFINE_HASH_MAP_FUNCTIONS(drgn_thread_map, int_key_hash_pair, scalar_key_eq)

struct drgn_thread_iterator { struct drgn_thread_map_iterator iterator; };

struct drgn_program {
/** @privatesection */
Expand Down Expand Up @@ -143,9 +152,10 @@ struct drgn_program {
*/
struct drgn_prstatus_vector prstatus_vector;
/* For userspace programs, PRSTATUS notes indexed by PID. */
struct drgn_prstatus_map prstatus_map;
struct drgn_thread_map thread_map;
};
bool prstatus_cached;
struct drgn_thread *crashed_thread;
bool threads_cached;
bool prefer_orc_unwinder;

/*
Expand Down Expand Up @@ -282,14 +292,29 @@ struct drgn_error *drgn_program_find_prstatus_by_tid(struct drgn_program *prog,
uint32_t tid,
struct nstring *ret);

struct drgn_error *drgn_program_find_thread(struct drgn_program *prog,
uint32_t tid,
struct drgn_thread **ret);

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

struct drgn_error *
drgn_thread_iterator_create(struct drgn_program *prog,
struct drgn_thread_iterator **ret);

void drgn_thread_iterator_destroy(struct drgn_thread_iterator *it);

struct drgn_error *drgn_thread_iterator_next(struct drgn_thread_iterator *it,
struct drgn_thread **ret);
/**
* Cache the @c NT_PRSTATUS note provided by @p data in @p prog.
*
* @param[in] data The pointer to the note data.
* @param[in] size Size of data in note.
*/
struct drgn_error *drgn_program_cache_prstatus_entry(struct drgn_program *prog,
const char *data,
struct drgn_error *drgn_program_cache_thread(struct drgn_program *prog,
const char *data,
size_t size);

/*
Expand Down
Loading

0 comments on commit fffc103

Please sign in to comment.