diff --git a/_drgn.pyi b/_drgn.pyi index 487e11b21..14782ac86 100644 --- a/_drgn.pyi +++ b/_drgn.pyi @@ -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) `_). """ + 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): """ diff --git a/drgn/__init__.py b/drgn/__init__.py index 4fb73012f..70958673a 100644 --- a/drgn/__init__.py +++ b/drgn/__init__.py @@ -69,6 +69,8 @@ Symbol, SymbolBinding, SymbolKind, + Thread, + ThreadIterator, Type, TypeEnumerator, TypeKind, @@ -114,6 +116,8 @@ "Symbol", "SymbolBinding", "SymbolKind", + "Thread", + "ThreadIterator", "Type", "TypeEnumerator", "TypeKind", diff --git a/libdrgn/Makefile.am b/libdrgn/Makefile.am index d966917c6..993e36cec 100644 --- a/libdrgn/Makefile.am +++ b/libdrgn/Makefile.am @@ -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 diff --git a/libdrgn/kdump.c b/libdrgn/kdump.c index dd651938a..6e2b6e650 100644 --- a/libdrgn/kdump.c +++ b/libdrgn/kdump.c @@ -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; @@ -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) diff --git a/libdrgn/program.c b/libdrgn/program.c index 3e5ecf019..b00d09495 100644 --- a/libdrgn/program.c +++ b/libdrgn/program.c @@ -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) { @@ -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); @@ -650,7 +649,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) { @@ -662,36 +661,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 @@ -737,7 +742,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) @@ -751,9 +756,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; } @@ -764,7 +769,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; @@ -783,23 +788,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) { diff --git a/libdrgn/program.h b/libdrgn/program.h index 0924cb64d..8c24f62e6 100644 --- a/libdrgn/program.h +++ b/libdrgn/program.h @@ -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 */ @@ -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; /* @@ -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); /* diff --git a/libdrgn/python/drgnpy.h b/libdrgn/python/drgnpy.h index e5aa04bd5..d5377827b 100644 --- a/libdrgn/python/drgnpy.h +++ b/libdrgn/python/drgnpy.h @@ -97,6 +97,18 @@ typedef struct { struct pyobjectp_set objects; } Program; +typedef struct { + PyObject_HEAD + Program *prog; + struct drgn_thread *thread; +} Thread; + +typedef struct { + PyObject_HEAD + Program *prog; + struct drgn_thread_iterator *iterator; +} ThreadIterator; + typedef struct { PyObject_HEAD const struct drgn_register *reg; @@ -175,6 +187,8 @@ extern PyTypeObject Register_type; extern PyTypeObject StackFrame_type; extern PyTypeObject StackTrace_type; extern PyTypeObject Symbol_type; +extern PyTypeObject Thread_type; +extern PyTypeObject ThreadIterator_type; extern PyTypeObject TypeEnumerator_type; extern PyTypeObject TypeMember_type; extern PyTypeObject TypeParameter_type; @@ -229,6 +243,8 @@ Program *program_from_pid(PyObject *self, PyObject *args, PyObject *kwds); PyObject *Symbol_wrap(struct drgn_symbol *sym, Program *prog); +Thread *create_thread(Program *program, struct drgn_thread *drgn_thread); + static inline Program *DrgnType_prog(DrgnType *type) { return container_of(drgn_type_program(type->type), Program, prog); @@ -246,6 +262,9 @@ DrgnType *Program_typedef_type(Program *self, PyObject *args, PyObject *kwds); DrgnType *Program_pointer_type(Program *self, PyObject *args, PyObject *kwds); DrgnType *Program_array_type(Program *self, PyObject *args, PyObject *kwds); DrgnType *Program_function_type(Program *self, PyObject *args, PyObject *kwds); +Thread *Program_thread(Program *self, PyObject *tid); +ThreadIterator *Program_threads(Program *self); +Thread *Program_crashed_thread(Program *self); int append_string(PyObject *parts, const char *s); int append_format(PyObject *parts, const char *format, ...); diff --git a/libdrgn/python/module.c b/libdrgn/python/module.c index 7fd1d2c3a..ecf28ea8d 100644 --- a/libdrgn/python/module.c +++ b/libdrgn/python/module.c @@ -230,6 +230,8 @@ DRGNPY_PUBLIC PyMODINIT_FUNC PyInit__drgn(void) add_type(m, &StackTrace_type) || add_type(m, &Symbol_type) || add_type(m, &DrgnType_type) || + add_type(m, &Thread_type) || + add_type(m, &ThreadIterator_type) || add_type(m, &TypeEnumerator_type) || add_type(m, &TypeMember_type) || add_type(m, &TypeParameter_type) || diff --git a/libdrgn/python/program.c b/libdrgn/python/program.c index 309fa6a21..30268c4a3 100644 --- a/libdrgn/python/program.c +++ b/libdrgn/python/program.c @@ -698,6 +698,7 @@ static DrgnObject *Program_variable(Program *self, PyObject *args, DRGN_FIND_OBJECT_VARIABLE); } + static StackTrace *Program_stack_trace(Program *self, PyObject *args, PyObject *kwds) { @@ -931,6 +932,10 @@ static PyMethodDef Program_methods[] = { METH_VARARGS | METH_KEYWORDS, drgn_Program_array_type_DOC}, {"function_type", (PyCFunction)Program_function_type, METH_VARARGS | METH_KEYWORDS, drgn_Program_function_type_DOC}, + {"thread", (PyCFunction)Program_thread, METH_O, drgn_Program_thread_DOC }, + {"threads", (PyCFunction)Program_threads, METH_NOARGS, drgn_Program_threads_DOC }, + {"crashed_thread", (PyCFunction)Program_crashed_thread, METH_NOARGS, + drgn_Program_crashed_thread_DOC }, {}, }; @@ -1040,3 +1045,47 @@ Program *program_from_pid(PyObject *self, PyObject *args, PyObject *kwds) } return prog; } + +ThreadIterator *Program_threads(Program *self) +{ + Py_INCREF(self); + ThreadIterator *iterator = + (ThreadIterator *)ThreadIterator_type.tp_alloc( + &ThreadIterator_type, 0); + iterator->prog = self; + drgn_thread_iterator_create(&self->prog, &iterator->iterator); + return iterator; +} + +Thread *Program_crashed_thread(Program *self) +{ + struct drgn_thread *drgn_thread; + struct drgn_error *err; + + err = drgn_program_crashed_thread(&self->prog, &drgn_thread); + if (err) + return set_drgn_error(err); + + return create_thread(self, drgn_thread); +} + +Thread *Program_thread(Program *self, PyObject *tid) +{ + struct drgn_error *err; + struct drgn_thread *drgn_thread; + + if (!PyLong_Check(tid)) { + return set_drgn_error(drgn_error_format( + DRGN_ERROR_TYPE, "expected argument of type int, received %s", + tid->ob_type->tp_name)); + } + long tid_value = PyLong_AsLong(tid); + err = drgn_program_find_thread(&self->prog, tid_value, &drgn_thread); + if (err) + return set_drgn_error(err); + if (drgn_thread == NULL) + return set_drgn_error(drgn_error_format( + DRGN_ERROR_LOOKUP, "thread with tid of %ld not found", tid_value)); + + return create_thread(self, drgn_thread); +} diff --git a/libdrgn/python/thread.c b/libdrgn/python/thread.c new file mode 100644 index 000000000..7648a6d8a --- /dev/null +++ b/libdrgn/python/thread.c @@ -0,0 +1,91 @@ +#include "drgnpy.h" + +static void Thread_dealloc(Thread *self) +{ + Py_DECREF(self->prog); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyLongObject *Thread_get_tid(Thread *self) +{ + return (PyLongObject *)PyLong_FromLong(self->thread->tid); +} + +static PyByteArrayObject *Thread_get_prstatus(Thread *self) +{ + return (PyByteArrayObject *)PyByteArray_FromStringAndSize( + self->thread->prstatus.str, self->thread->prstatus.len); +} + +static StackTrace *Thread_stack_trace(Thread *self) +{ + struct drgn_error *err; + struct drgn_stack_trace *trace; + err = drgn_program_stack_trace(&self->prog->prog, self->thread->tid, + &trace); + if (err) + return set_drgn_error(err); + StackTrace *stack_trace = + (StackTrace *)StackTrace_type.tp_alloc(&StackTrace_type, 0); + stack_trace->trace = trace; + return stack_trace; +} + +Thread *create_thread(Program *program, struct drgn_thread *drgn_thread) +{ + Thread *thread = (Thread *)Thread_type.tp_alloc(&Thread_type, 0); + thread->thread = drgn_thread; + Py_INCREF(program); + thread->prog = program; + return thread; +} + +static PyGetSetDef Thread_getset[] = { + {"tid", (getter)Thread_get_tid, NULL, drgn_Thread_tid_DOC}, + {"prstatus", (getter)Thread_get_prstatus, NULL, drgn_Thread_prstatus_DOC}, + {}, +}; + +static PyMethodDef Thread_methods[] = { + {"stack_trace", (PyCFunction)Thread_stack_trace, METH_NOARGS, + drgn_Thread_stack_trace_DOC}, + {}, +}; + +PyTypeObject Thread_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_drgn.Thread", + .tp_basicsize = sizeof(Thread), + .tp_dealloc = (destructor)Thread_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_getset = Thread_getset, + .tp_methods = Thread_methods, +}; + +static void ThreadIterator_dealloc(ThreadIterator *self) +{ + Py_DECREF(self->prog); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static Thread *ThreadIterator_next(ThreadIterator *self) +{ + struct drgn_thread *drgn_thread; + struct drgn_error *err; + err = drgn_thread_iterator_next(self->iterator, &drgn_thread); + if (err) + return set_drgn_error(err); + if (drgn_thread == NULL) + return NULL; + return create_thread(self->prog, drgn_thread); +} + +PyTypeObject ThreadIterator_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_drgn.ThreadIterator", + .tp_basicsize = sizeof(ThreadIterator), + .tp_dealloc = (destructor)ThreadIterator_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_iter = PyObject_SelfIter, + .tp_iternext = (iternextfunc)ThreadIterator_next, +};