Skip to content

Commit

Permalink
Add name to drgn.Thread
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Wilson <[email protected]>
  • Loading branch information
ryantimwilson committed Sep 27, 2024
1 parent 9a52953 commit 739697e
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 0 deletions.
13 changes: 13 additions & 0 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,19 @@ class Thread:

tid: Final[int]
"""Thread ID (as defined by :manpage:`gettid(2)`)."""
name: Optional[str]
"""
Thread name, or ``None`` if unknown.
See `PR_SET_NAME
<https://man7.org/linux/man-pages/man2/PR_SET_NAME.2const.html>`_ and
`/proc/pid/comm
<https://man7.org/linux/man-pages/man5/proc_pid_comm.5.html>`_.
.. note::
Linux userspace core dumps only save the name of the main thread, so
:attr:`name` will be ``None`` for other threads.
"""
object: Final[Object]
"""
If the program is the Linux kernel, the ``struct task_struct *`` object for
Expand Down
9 changes: 9 additions & 0 deletions libdrgn/cleanup.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define _cleanup_(x) __attribute__((__cleanup__(x)))

Expand All @@ -30,6 +31,14 @@ static inline void fclosep(FILE **fp)
fclose(*fp);
}

/** Call @c close() when the variable goes out of scope. */
#define _cleanup_close_ _cleanup_(closep)
static inline void closep(int *fd)
{
if (*fd >= 0)
close(*fd);
}

/**
* Get the value of a pointer variable and reset it to @c NULL.
*
Expand Down
9 changes: 9 additions & 0 deletions libdrgn/drgn.h
Original file line number Diff line number Diff line change
Expand Up @@ -3429,6 +3429,15 @@ struct drgn_error *drgn_thread_object(struct drgn_thread *thread,
struct drgn_error *drgn_thread_stack_trace(struct drgn_thread *thread,
struct drgn_stack_trace **ret);

/**
* Get name for the thread represented by @p thread.
*
* @param[out] ret Returned thread name, or @c NULL if not found. On success, it
* should be freed with free(). On error, it is not modified.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_thread_name(struct drgn_thread *thread, char **ret);

/** @} */

#endif /* DRGN_H */
108 changes: 108 additions & 0 deletions libdrgn/program.c
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,35 @@ static struct drgn_error *get_prpsinfo_pid(struct drgn_program *prog,
return NULL;
}

static struct drgn_error *get_prpsinfo_fname(struct drgn_program *prog,
const char *data, size_t size,
const char **ret)
{
bool is_64_bit;
struct drgn_error *err = drgn_program_is_64_bit(prog, &is_64_bit);
if (err)
return err;
size_t offset = is_64_bit ? 40 : 28;
// pr_fname is defined as 16 byte buffer in elf_prpsinfo
// https://github.com/torvalds/linux/blob/075dbe9f6e3c21596c5245826a4ee1f1c1676eb8/include/linux/elfcore.h#L73
#define PR_FNAME_LEN 16
if (size < offset + PR_FNAME_LEN) {
return drgn_error_create(DRGN_ERROR_OTHER,
"NT_PRPSINFO is truncated");
}
// No need to make a copy: the data returned by elf_getdata_rawchunk()
// is valid for the lifetime of the Elf handle, and prog->core is valid for
// the lifetime of prog.
const char *tmp = data + offset;
size_t len = strnlen(tmp, PR_FNAME_LEN);
if (len == PR_FNAME_LEN)
#undef PR_FNAME_LEN
return drgn_error_create(DRGN_ERROR_OTHER,
"pr_fname is not null terminated");
*ret = tmp;
return NULL;
}

struct drgn_error *drgn_thread_dup_internal(const struct drgn_thread *thread,
struct drgn_thread *ret)
{
Expand Down Expand Up @@ -949,6 +978,7 @@ drgn_program_cache_core_dump_notes(struct drgn_program *prog)
uint32_t first_prstatus_tid;
bool found_prpsinfo = false;
uint32_t prpsinfo_pid;
const char *prpsinfo_fname = NULL;

if (prog->core_dump_notes_cached)
return NULL;
Expand Down Expand Up @@ -1011,6 +1041,12 @@ drgn_program_cache_core_dump_notes(struct drgn_program *prog)
&prpsinfo_pid);
if (err)
goto err;
err = get_prpsinfo_fname(prog,
(char *)data->d_buf + desc_offset,
nhdr.n_descsz,
&prpsinfo_fname);
if (err)
goto err;
found_prpsinfo = true;
} else if (nhdr.n_type == NT_PRSTATUS) {
uint32_t tid;
Expand Down Expand Up @@ -1042,6 +1078,7 @@ drgn_program_cache_core_dump_notes(struct drgn_program *prog)
&prpsinfo_pid);
/* If the PID isn't found, then this is NULL. */
prog->main_thread = it.entry;
prog->core_dump_fname_cached = prpsinfo_fname;
}
if (found_prstatus) {
/*
Expand Down Expand Up @@ -1489,6 +1526,77 @@ drgn_thread_object(struct drgn_thread *thread, const struct drgn_object **ret)
return NULL;
}

static struct drgn_error *
drgn_thread_name_linux_kernel(struct drgn_thread *thread, char **ret)
{
struct drgn_error *err;
DRGN_OBJECT(comm, drgn_object_program(&thread->object));
err = drgn_object_member_dereference(&comm, &thread->object, "comm");
if (!err)
err = drgn_object_read_c_string(&comm, ret);
return err;
}

static struct drgn_error *
drgn_thread_name_userspace_live(struct drgn_thread *thread, char **ret)
{
#define FORMAT "/proc/%" PRIu32 "/comm"
char path[sizeof(FORMAT)
- sizeof("%" PRIu32)
+ max_decimal_length(uint32_t)
+ 1];
snprintf(path, sizeof(path), FORMAT, thread->tid);
#undef FORMAT
_cleanup_close_ int fd = open(path, O_RDONLY);
if (fd < 0)
return drgn_error_create_os("open", errno, path);
// While userspace threads use 16 byte buffer, kernel threads use a 64 byte buffer
// https://github.com/torvalds/linux/blob/075dbe9f6e3c21596c5245826a4ee1f1c1676eb8/fs/proc/array.c#L101
char buf[64];
ssize_t bytes_read = read_all(fd, buf, sizeof(buf));
if (bytes_read < 0)
return drgn_error_create_os("read", errno, path);

if (bytes_read > 0 && buf[bytes_read - 1] == '\n')
bytes_read--;
char *tmp = strndup(buf, bytes_read);
if (!tmp)
return &drgn_enomem;
*ret = tmp;
return NULL;
}

static struct drgn_error *
drgn_thread_name_userspace_core(struct drgn_thread *thread, char **ret)
{
struct drgn_error *err = drgn_program_cache_core_dump_notes(thread->prog);
if (err)
return err;
// Core dumps only contain the main thread name so check if this is the main thread.
// Otherwise, set ret to NULL which will return None in Python.
bool is_main_thread = thread->prog->main_thread && thread->prog->main_thread->tid == thread->tid;
if (is_main_thread && thread->prog->core_dump_fname_cached) {
char *tmp = strdup(thread->prog->core_dump_fname_cached);
if (!tmp)
return &drgn_enomem;
*ret = tmp;
} else {
*ret = NULL;
}
return NULL;
}

LIBDRGN_PUBLIC struct drgn_error *
drgn_thread_name(struct drgn_thread *thread, char **ret)
{
if (thread->prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
return drgn_thread_name_linux_kernel(thread, ret);
else if (thread->prog->flags & DRGN_PROGRAM_IS_LIVE)
return drgn_thread_name_userspace_live(thread, ret);
else
return drgn_thread_name_userspace_core(thread, ret);
}

struct drgn_error *drgn_program_find_prstatus(struct drgn_program *prog,
uint32_t tid, struct nstring *ret)
{
Expand Down
1 change: 1 addition & 0 deletions libdrgn/program.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ struct drgn_program {
uint64_t aarch64_insn_pac_mask;
bool core_dump_notes_cached;
bool prefer_orc_unwinder;
const char *core_dump_fname_cached;

/*
* Linux kernel-specific.
Expand Down
13 changes: 13 additions & 0 deletions libdrgn/python/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ static DrgnObject *Thread_get_object(Thread *self)
return_ptr(ret);
}

static PyObject *Thread_get_name(Thread *self)
{
_cleanup_free_ char *ret = NULL;
struct drgn_error *err = drgn_thread_name(&self->thread, &ret);
if (err)
return set_drgn_error(err);
if (!ret) {
Py_RETURN_NONE;
}
return PyUnicode_DecodeFSDefault(ret);
}

static PyObject *Thread_stack_trace(Thread *self)
{
struct drgn_error *err;
Expand All @@ -72,6 +84,7 @@ static PyObject *Thread_stack_trace(Thread *self)
static PyGetSetDef Thread_getset[] = {
{"tid", (getter)Thread_get_tid, NULL, drgn_Thread_tid_DOC},
{"object", (getter)Thread_get_object, NULL, drgn_Thread_object_DOC},
{"name", (getter)Thread_get_name, NULL, drgn_Thread_name_DOC},
{},
};

Expand Down
7 changes: 7 additions & 0 deletions tests/linux_kernel/test_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ def test_crashed_thread(self):
"crashed thread is only defined for core dumps",
self.prog.crashed_thread,
)

def test_thread_name(self):
pid = os.getpid()
with open(f"/proc/{pid}/comm", "r") as f:
comm = f.read().strip()
thread = self.prog.thread(pid)
self.assertEqual(comm, thread.name)
15 changes: 15 additions & 0 deletions tests/test_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ def test_crashed_thread(self):
self.prog.crashed_thread,
)

def test_thread_name(self):
with open(f"/proc/{os.getpid()}/comm", "r") as f:
comm = f.read().strip()
self.assertEqual(self.prog.main_thread().name, comm)
for thread in self.prog.threads():
self.assertIsNotNone(thread.name)


class TestCoreDump(TestCase):
TIDS = (
Expand All @@ -55,6 +62,8 @@ class TestCoreDump(TestCase):
MAIN_TID = 2265413
CRASHED_TID = 2265419

MAIN_THREAD_NAME = "segfault_random"

@classmethod
def setUpClass(cls):
super().setUpClass()
Expand All @@ -79,3 +88,9 @@ def test_main_thread(self):

def test_crashed_thread(self):
self.assertEqual(self.prog.crashed_thread().tid, self.CRASHED_TID)

def test_thread_name(self):
self.assertEqual(self.prog.main_thread().name, self.MAIN_THREAD_NAME)
for tid in self.TIDS:
if tid != self.MAIN_TID:
self.assertIsNone(self.prog.thread(tid).name)

0 comments on commit 739697e

Please sign in to comment.