Add basic stack trace support

For now, we only support stack traces for the Linux kernel (at least
v4.9) on x86-64, and we only support getting the program counter and
corresponding function symbol from each stack frame.
This commit is contained in:
Omar Sandoval 2019-07-29 14:41:33 -07:00
parent 93d7ea9f01
commit 10142f922f
15 changed files with 769 additions and 9 deletions

View File

@ -136,6 +136,18 @@ Programs
:rtype: Symbol
:raises LookupError: if no symbol contains the given address
.. method:: stack_trace(thread)
Get the stack trace for a given thread in the program. Currently, this
is only implemented for the Linux kernel.
Note that this currently doesn't verify that the thread is inactive. If
the thread is running, the returned stack trace will not be accurate.
:param Object thread: The ``struct task_struct *`` object of the
thread. See :func:`drgn.helpers.linux.pid.find_task()`.
:rtype: StackTrace
.. method:: type(name, filename=None)
Get the type with the given name.
@ -790,6 +802,60 @@ Symbols
:vartype: int
Stack Traces
------------
.. class:: StackTrace
A ``StackTrace`` is a :ref:`sequence <python:typesseq-common>` of
:class:`StackFrame`.
``len(trace)`` is the number of stack frames in the trace. ``trace[0]`` is
the innermost stack frame, ``trace[1]`` is its caller, and
``trace[len(trace) - 1]`` is the outermost frame. Negative indexing also
works: ``trace[-1]`` is the outermost frame and ``trace[-len(trace)]`` is
the innermost frame. It is also iterable:
.. code-block:: python3
for frame in trace:
if frame.symbol().name == 'io_schedule':
print('Thread is doing I/O')
:class:`str() <str>` returns a pretty-printed stack trace:
>>> print(prog.stack_trace(find_task(prog, 1))
#0 __schedule+0x25c/0x8ba
#1 schedule+0x3c/0x7e
#2 schedule_hrtimeout_range_clock+0x10c/0x118
#3 ep_poll+0x3ca/0x40a
#4 do_epoll_wait+0xb0/0xc6
#5 __x64_sys_epoll_wait+0x1a/0x1d
#6 do_syscall_64+0x55/0x17c
#7 entry_SYSCALL_64+0x7c/0x156
The drgn CLI is set up so that stack traces are displayed with ``str()`` by
default.
.. class:: StackFrame
A ``StackFrame`` represents a single *frame* (i.e., function call) in a
thread's call stack.
.. attribute:: pc
The return address at this stack frame, i.e., the value of the program
counter when control returns to this frame.
:vartype: int
.. method:: symbol()
Get the function symbol at this stack frame. This is equivalent to
:meth:`prog.symbol(frame.pc) <Program.symbol>`.
:rtype: Symbol
.. _api-reference-types:
Types

View File

@ -184,7 +184,14 @@ Symbols
The symbol table of a program is a list of identifiers along with their address
and size. drgn represents symbols with the :class:`drgn.Symbol` class, which is
returned by :meth:`Program.symbol()`.
returned by :meth:`drgn.Program.symbol()`.
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.
Types
^^^^^

View File

@ -57,6 +57,8 @@ from _drgn import (
Program,
ProgramFlags,
Qualifiers,
StackFrame,
StackTrace,
Symbol,
Type,
TypeKind,
@ -98,6 +100,8 @@ __all__ = [
'Program',
'ProgramFlags',
'Qualifiers',
'StackFrame',
'StackTrace',
'Symbol',
'Type',
'TypeKind',

View File

@ -24,7 +24,7 @@ def displayhook(value: Any) -> None:
if isinstance(value, drgn.Object):
columns = shutil.get_terminal_size((0, 0)).columns
text = f'{value:.{columns}}'
elif isinstance(value, drgn.Type):
elif isinstance(value, (drgn.StackTrace, drgn.Type)):
text = str(value)
else:
text = repr(value)

View File

@ -41,6 +41,7 @@ libdrgnimpl_la_SOURCES = arch_x86_64.c \
serialize.h \
siphash.h \
splay_tree.c \
stack_trace.c \
string_builder.c \
string_builder.h \
symbol.c \
@ -82,6 +83,7 @@ _drgn_la_SOURCES = python/docstrings.h \
python/object.c \
python/platform.c \
python/program.c \
python/stack_trace.c \
python/symbol.c \
python/test.c \
python/type.c \

View File

@ -1,11 +1,110 @@
// Copyright 2019 - Omar Sandoval
// SPDX-License-Identifier: GPL-3.0+
#include "internal.h"
#include "platform.h"
static inline struct drgn_error *read_register(struct drgn_object *reg_obj,
struct drgn_object *frame_obj,
const char *name,
Dwarf_Addr *ret)
{
struct drgn_error *err;
uint64_t reg;
err = drgn_object_member_dereference(reg_obj, frame_obj, name);
if (err)
return err;
err = drgn_object_read_unsigned(reg_obj, &reg);
if (err)
return err;
*ret = reg;
return NULL;
}
static struct drgn_error *
linux_kernel_set_initial_registers_x86_64(Dwfl_Thread *thread,
struct drgn_object *task_obj)
{
struct drgn_error *err;
struct drgn_program *prog = task_obj->prog;
struct drgn_object frame_obj, reg_obj;
struct drgn_qualified_type frame_type;
Dwarf_Word dwarf_regs[5];
uint64_t sp;
drgn_object_init(&frame_obj, prog);
drgn_object_init(&reg_obj, prog);
/*
* This depends on Linux kernel commit 0100301bfdf5 ("sched/x86: Rewrite
* the switch_to() code") (in v4.9).
*/
err = drgn_object_member_dereference(&frame_obj, task_obj, "thread");
if (err)
goto out;
err = drgn_object_member(&frame_obj, &frame_obj, "sp");
if (err)
goto out;
err = drgn_program_find_type(prog, "struct inactive_task_frame *", NULL,
&frame_type);
if (err)
goto out;
err = drgn_object_cast(&frame_obj, frame_type, &frame_obj);
if (err)
goto out;
err = read_register(&reg_obj, &frame_obj, "bx", &dwarf_regs[0]);
if (err)
goto out;
/* rbx is register 3. */
if (!dwfl_thread_state_registers(thread, 3, 1, dwarf_regs)) {
err = drgn_error_libdwfl();
goto out;
}
err = read_register(&reg_obj, &frame_obj, "bp", &dwarf_regs[0]);
if (err)
goto out;
err = drgn_object_read_unsigned(&frame_obj, &sp);
if (err)
goto out;
dwarf_regs[1] = sp;
/* rbp and rsp are registers 6 and 7, respectively. */
if (!dwfl_thread_state_registers(thread, 6, 2, dwarf_regs)) {
err = drgn_error_libdwfl();
goto out;
}
err = read_register(&reg_obj, &frame_obj, "r12", &dwarf_regs[0]);
if (err)
goto out;
err = read_register(&reg_obj, &frame_obj, "r13", &dwarf_regs[1]);
if (err)
goto out;
err = read_register(&reg_obj, &frame_obj, "r14", &dwarf_regs[2]);
if (err)
goto out;
err = read_register(&reg_obj, &frame_obj, "r15", &dwarf_regs[3]);
if (err)
goto out;
err = read_register(&reg_obj, &frame_obj, "ret_addr", &dwarf_regs[4]);
if (err)
goto out;
/* r12-r15 are registers 12-15; register 16 is the return address. */
if (!dwfl_thread_state_registers(thread, 12, 5, dwarf_regs))
err = drgn_error_libdwfl();
out:
drgn_object_deinit(&reg_obj);
drgn_object_deinit(&frame_obj);
return err;
}
const struct drgn_architecture_info arch_info_x86_64 = {
.name = "x86-64",
.arch = DRGN_ARCH_X86_64,
.default_flags = (DRGN_PLATFORM_IS_64_BIT |
DRGN_PLATFORM_IS_LITTLE_ENDIAN),
.linux_kernel_set_initial_registers = linux_kernel_set_initial_registers_x86_64,
};

View File

@ -2239,4 +2239,66 @@ bool drgn_symbol_eq(struct drgn_symbol *a, struct drgn_symbol *b);
/** @} */
/**
* @defgroup StackTraces Stack traces
*
* Call stacks and stack frames.
*
* @{
*/
struct drgn_stack_trace;
struct drgn_stack_frame;
/** Destroy a @ref drgn_stack_trace. */
void drgn_stack_trace_destroy(struct drgn_stack_trace *trace);
/** Get the number of stack frames in a stack trace. */
size_t drgn_stack_trace_num_frames(struct drgn_stack_trace *trace);
/**
* Get the stack at index @p i, where 0 is the innermost stack frame, 1 is its
* caller, etc.
*
* @return On success, the stack frame, which is valid until this stack trace is
* destroyed. If <tt>i >= drgn_stack_trace_num_frames(trace)</tt>, @c NULL.
*/
struct drgn_stack_frame *drgn_stack_trace_frame(struct drgn_stack_trace *trace,
size_t i);
/**
* Pretty-print a stack trace.
*
* @param[out] ret Returned string. On success, it must be freed with @c free().
* On error, its contents are undefined.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_pretty_print_stack_trace(struct drgn_stack_trace *trace,
char **ret);
/** Get the return address at a stack frame. */
uint64_t drgn_stack_frame_pc(struct drgn_stack_frame *frame);
/**
* Get the function symbol at a stack frame.
*
* @param[out] ret Returned symbol. On success, it should be freed with @ref
* drgn_symbol_destroy(). On error, its contents are undefined.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_stack_frame_symbol(struct drgn_stack_frame *frame,
struct drgn_symbol **ret);
/**
* Get a stack trace for the thread represented by @p obj.
*
* @param[out] ret Returned stack trace. On success, it should be freed with
* @ref drgn_stack_trace_destroy(). On error, its contents are undefined.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_object_stack_trace(const struct drgn_object *obj,
struct drgn_stack_trace **ret);
/** @} */
#endif /* DRGN_H */

View File

@ -4,7 +4,7 @@
#ifndef DRGN_PLATFORM_H
#define DRGN_PLATFORM_H
#include <gelf.h>
#include <elfutils/libdwfl.h>
#include "drgn.h"
@ -12,6 +12,8 @@ struct drgn_architecture_info {
const char *name;
enum drgn_architecture arch;
enum drgn_platform_flags default_flags;
struct drgn_error *(*linux_kernel_set_initial_registers)(Dwfl_Thread *,
struct drgn_object *);
};
extern const struct drgn_architecture_info arch_info_unknown;

View File

@ -73,11 +73,16 @@ struct drgn_program {
#endif
Dwfl *_dwfl;
struct drgn_dwarf_info_cache *_dicache;
/* See @ref drgn_object_stack_trace_next_thread(). */
const struct drgn_object *stack_trace_obj;
/* See @ref drgn_object_stack_trace(). */
struct drgn_error *stack_trace_err;
int core_fd;
enum drgn_program_flags flags;
struct drgn_platform platform;
bool has_platform;
bool added_vmcoreinfo_object_finder;
bool attached_dwfl_state;
};
/** Initialize a @ref drgn_program. */

View File

@ -151,6 +151,8 @@ extern const char drgn_Program_set_kernel_DOC[];
#define drgn_Program_set_kernel_DOC (char *)drgn_Program_set_kernel_DOC
extern const char drgn_Program_set_pid_DOC[];
#define drgn_Program_set_pid_DOC (char *)drgn_Program_set_pid_DOC
extern const char drgn_Program_stack_trace_DOC[];
#define drgn_Program_stack_trace_DOC (char *)drgn_Program_stack_trace_DOC
extern const char drgn_Program_symbol_DOC[];
#define drgn_Program_symbol_DOC (char *)drgn_Program_symbol_DOC
extern const char drgn_Program_type_DOC[];
@ -173,6 +175,14 @@ extern const char drgn_Qualifiers_RESTRICT_DOC[];
#define drgn_Qualifiers_RESTRICT_DOC (char *)drgn_Qualifiers_RESTRICT_DOC
extern const char drgn_Qualifiers_VOLATILE_DOC[];
#define drgn_Qualifiers_VOLATILE_DOC (char *)drgn_Qualifiers_VOLATILE_DOC
extern const char drgn_StackFrame_DOC[];
#define drgn_StackFrame_DOC (char *)drgn_StackFrame_DOC
extern const char drgn_StackFrame_pc_DOC[];
#define drgn_StackFrame_pc_DOC (char *)drgn_StackFrame_pc_DOC
extern const char drgn_StackFrame_symbol_DOC[];
#define drgn_StackFrame_symbol_DOC (char *)drgn_StackFrame_symbol_DOC
extern const char drgn_StackTrace_DOC[];
#define drgn_StackTrace_DOC (char *)drgn_StackTrace_DOC
extern const char drgn_Symbol_DOC[];
#define drgn_Symbol_DOC (char *)drgn_Symbol_DOC
extern const char drgn_Symbol_address_DOC[];

View File

@ -83,6 +83,18 @@ typedef struct {
PyObject *cache;
} Program;
typedef struct {
PyObject_HEAD
Program *prog;
struct drgn_stack_trace *trace;
} StackTrace;
typedef struct {
PyObject_HEAD
StackTrace *trace;
struct drgn_stack_frame *frame;
} StackFrame;
typedef struct {
PyObject_HEAD
Program *prog;
@ -101,6 +113,8 @@ extern PyTypeObject DrgnType_type;
extern PyTypeObject ObjectIterator_type;
extern PyTypeObject Platform_type;
extern PyTypeObject Program_type;
extern PyTypeObject StackFrame_type;
extern PyTypeObject StackTrace_type;
extern PyTypeObject Symbol_type;
extern PyObject *FaultError;
extern PyObject *FileFormatError;
@ -139,6 +153,7 @@ int Program_type_arg(Program *prog, PyObject *type_obj, bool can_be_none,
Program *program_from_core_dump(PyObject *self, PyObject *args, PyObject *kwds);
Program *program_from_kernel(PyObject *self);
Program *program_from_pid(PyObject *self, PyObject *args, PyObject *kwds);
Symbol *Program_find_symbol(Program *self, uint64_t address);
static inline PyObject *DrgnType_parent(DrgnType *type)
{

View File

@ -160,6 +160,16 @@ DRGNPY_PUBLIC PyMODINIT_FUNC PyInit__drgn(void)
Py_INCREF(&Program_type);
PyModule_AddObject(m, "Program", (PyObject *)&Program_type);
if (PyType_Ready(&StackFrame_type) < 0)
goto err;
Py_INCREF(&StackFrame_type);
PyModule_AddObject(m, "StackFrame", (PyObject *)&StackFrame_type);
if (PyType_Ready(&StackTrace_type) < 0)
goto err;
Py_INCREF(&StackTrace_type);
PyModule_AddObject(m, "StackTrace", (PyObject *)&StackTrace_type);
if (PyType_Ready(&Symbol_type) < 0)
goto err;
Py_INCREF(&Symbol_type);

View File

@ -670,17 +670,39 @@ static DrgnObject *Program_variable(Program *self, PyObject *args,
DRGN_FIND_OBJECT_VARIABLE);
}
static Symbol *Program_symbol(Program *self, PyObject *args, PyObject *kwds)
static StackTrace *Program_stack_trace(Program *self, PyObject *args,
PyObject *kwds)
{
static char *keywords[] = {"thread", NULL};
struct drgn_error *err;
DrgnObject *task;
struct drgn_stack_trace *trace;
StackTrace *ret;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!:stack_trace", keywords,
&DrgnObject_type, &task))
return NULL;
err = drgn_object_stack_trace(&task->obj, &trace);
if (err)
return set_drgn_error(err);
ret = (StackTrace *)StackTrace_type.tp_alloc(&StackTrace_type, 0);
if (!ret) {
drgn_stack_trace_destroy(trace);
return NULL;
}
ret->trace = trace;
ret->prog = self;
Py_INCREF(self);
return ret;
}
Symbol *Program_find_symbol(Program *self, uint64_t address)
{
static char *keywords[] = {"address", NULL};
struct drgn_error *err;
unsigned long long address;
struct drgn_symbol *sym;
Symbol *ret;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "K", keywords, &address))
return NULL;
err = drgn_program_find_symbol(&self->prog, address, &sym);
if (err)
return set_drgn_error(err);
@ -695,6 +717,16 @@ static Symbol *Program_symbol(Program *self, PyObject *args, PyObject *kwds)
return ret;
}
static Symbol *Program_symbol(Program *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"address", NULL};
unsigned long long address;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "K", keywords, &address))
return NULL;
return Program_find_symbol(self, address);
}
static DrgnObject *Program_subscript(Program *self, PyObject *key)
{
struct drgn_error *err;
@ -819,6 +851,8 @@ static PyMethodDef Program_methods[] = {
METH_VARARGS | METH_KEYWORDS, drgn_Program_function_DOC},
{"variable", (PyCFunction)Program_variable,
METH_VARARGS | METH_KEYWORDS, drgn_Program_variable_DOC},
{"stack_trace", (PyCFunction)Program_stack_trace,
METH_VARARGS | METH_KEYWORDS, drgn_Program_stack_trace_DOC},
{"symbol", (PyCFunction)Program_symbol, METH_VARARGS | METH_KEYWORDS,
drgn_Program_symbol_DOC},
{},

View File

@ -0,0 +1,142 @@
// Copyright 2019 - Omar Sandoval
// SPDX-License-Identifier: GPL-3.0+
#include "drgnpy.h"
static void StackTrace_dealloc(StackTrace *self)
{
drgn_stack_trace_destroy(self->trace);
Py_XDECREF(self->prog);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *StackTrace_str(StackTrace *self)
{
struct drgn_error *err;
PyObject *ret;
char *str;
err = drgn_pretty_print_stack_trace(self->trace, &str);
if (err)
return set_drgn_error(err);
ret = PyUnicode_FromString(str);
free(str);
return ret;
}
static Py_ssize_t StackTrace_length(StackTrace *self)
{
return drgn_stack_trace_num_frames(self->trace);
}
static StackFrame *StackTrace_item(StackTrace *self, Py_ssize_t i)
{
struct drgn_stack_frame *frame;
StackFrame *ret;
if (i < 0 || !(frame = drgn_stack_trace_frame(self->trace, i))) {
PyErr_SetString(PyExc_IndexError,
"stack frame index out of range");
return NULL;
}
ret = (StackFrame *)StackFrame_type.tp_alloc(&StackFrame_type, 0);
if (!ret)
return NULL;
ret->frame = frame;
ret->trace = self;
Py_INCREF(self);
return ret;
}
static PySequenceMethods StackTrace_as_sequence = {
(lenfunc)StackTrace_length, /* sq_length */
NULL, /* sq_concat */
NULL, /* sq_repeat */
(ssizeargfunc)StackTrace_item, /* sq_item */
};
PyTypeObject StackTrace_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_drgn.StackTrace", /* tp_name */
sizeof(StackTrace), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)StackTrace_dealloc, /* tp_dealloc */
NULL, /* tp_print */
NULL, /* tp_getattr */
NULL, /* tp_setattr */
NULL, /* tp_as_async */
NULL, /* tp_repr */
NULL, /* tp_as_number */
&StackTrace_as_sequence, /* tp_as_sequence */
NULL, /* tp_as_mapping */
NULL, /* tp_hash */
NULL, /* tp_call */
(reprfunc)StackTrace_str, /* tp_str */
NULL, /* tp_getattro */
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
drgn_StackTrace_DOC, /* tp_doc */
};
static void StackFrame_dealloc(StackFrame *self)
{
Py_XDECREF(self->trace);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static Symbol *StackFrame_symbol(StackFrame *self)
{
return Program_find_symbol(self->trace->prog,
drgn_stack_frame_pc(self->frame));
}
static PyObject *StackFrame_get_pc(StackFrame *self, void *arg)
{
return PyLong_FromUnsignedLongLong(drgn_stack_frame_pc(self->frame));
}
static PyMethodDef StackFrame_methods[] = {
{"symbol", (PyCFunction)StackFrame_symbol, METH_NOARGS,
drgn_StackFrame_symbol_DOC},
{},
};
static PyGetSetDef StackFrame_getset[] = {
{"pc", (getter)StackFrame_get_pc, NULL, drgn_StackFrame_pc_DOC},
{},
};
PyTypeObject StackFrame_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_drgn.StackFrame", /* tp_name */
sizeof(StackFrame), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)StackFrame_dealloc, /* tp_dealloc */
NULL, /* tp_print */
NULL, /* tp_getattr */
NULL, /* tp_setattr */
NULL, /* tp_as_async */
NULL, /* tp_repr */
NULL, /* tp_as_number */
NULL, /* tp_as_sequence */
NULL, /* tp_as_mapping */
NULL, /* tp_hash */
NULL, /* tp_call */
NULL, /* tp_str */
NULL, /* tp_getattro */
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
drgn_StackFrame_DOC, /* tp_doc */
NULL, /* tp_traverse */
NULL, /* tp_clear */
NULL, /* tp_richcompare */
0, /* tp_weaklistoffset */
NULL, /* tp_iter */
NULL, /* tp_iternext */
StackFrame_methods, /* tp_methods */
NULL, /* tp_members */
StackFrame_getset, /* tp_getset */
};

302
libdrgn/stack_trace.c Normal file
View File

@ -0,0 +1,302 @@
// Copyright 2019 - Omar Sandoval
// SPDX-License-Identifier: GPL-3.0+
#include <elfutils/libdwfl.h>
#include <endian.h>
#include <inttypes.h>
#include <stdlib.h>
#include "internal.h"
#include "program.h"
#include "string_builder.h"
#include "symbol.h"
struct drgn_stack_frame {
struct drgn_program *prog;
uint64_t pc;
};
struct drgn_stack_trace {
size_t num_frames;
struct drgn_stack_frame frames[];
};
LIBDRGN_PUBLIC void drgn_stack_trace_destroy(struct drgn_stack_trace *trace)
{
free(trace);
}
LIBDRGN_PUBLIC
size_t drgn_stack_trace_num_frames(struct drgn_stack_trace *trace)
{
return trace->num_frames;
}
LIBDRGN_PUBLIC struct drgn_stack_frame *
drgn_stack_trace_frame(struct drgn_stack_trace *trace, size_t i)
{
if (i >= trace->num_frames)
return NULL;
return &trace->frames[i];
}
LIBDRGN_PUBLIC struct drgn_error *
drgn_pretty_print_stack_trace(struct drgn_stack_trace *trace, char **ret)
{
struct drgn_error *err;
struct string_builder str = {};
size_t i;
for (i = 0; i < trace->num_frames; i++) {
struct drgn_stack_frame *frame = &trace->frames[i];
uint64_t pc;
struct drgn_symbol sym;
pc = drgn_stack_frame_pc(frame);
err = drgn_program_find_symbol_internal(frame->prog, pc, &sym);
if (err && err != &drgn_not_found)
goto err;
if (!string_builder_appendf(&str, "#%-2zu ", i)) {
err = &drgn_enomem;
goto err;
}
if (err) {
if (!string_builder_appendf(&str, "0x%" PRIx64, pc)) {
err = &drgn_enomem;
goto err;
}
} else {
if (!string_builder_appendf(&str,
"%s+0x%" PRIx64 "/0x%" PRIx64,
sym.name, pc - sym.address,
sym.size)) {
err = &drgn_enomem;
goto err;
}
}
if (i != trace->num_frames - 1 &&
!string_builder_appendc(&str, '\n')) {
err = &drgn_enomem;
goto err;
}
}
if (!string_builder_finalize(&str, ret)) {
err = &drgn_enomem;
goto err;
}
return NULL;
err:
free(str.str);
return err;
}
LIBDRGN_PUBLIC uint64_t drgn_stack_frame_pc(struct drgn_stack_frame *frame)
{
return frame->pc;
}
LIBDRGN_PUBLIC struct drgn_error *
drgn_stack_frame_symbol(struct drgn_stack_frame *frame,
struct drgn_symbol **ret)
{
return drgn_program_find_symbol(frame->prog, frame->pc, ret);
}
static bool drgn_thread_memory_read(Dwfl *dwfl, Dwarf_Addr addr,
Dwarf_Word *result, void *dwfl_arg)
{
struct drgn_error *err;
struct drgn_program *prog = dwfl_arg;
bool is_little_endian = drgn_program_is_little_endian(prog);
if (drgn_program_is_64_bit(prog)) {
uint64_t u64;
err = drgn_program_read_memory(prog, &u64, addr, sizeof(u64),
false);
if (err)
goto err;
*result = is_little_endian ? le64toh(u64) : be64toh(u64);
} else {
uint32_t u32;
err = drgn_program_read_memory(prog, &u32, addr, sizeof(u32),
false);
if (err)
goto err;
*result = is_little_endian ? le32toh(u32) : be32toh(u32);
}
return true;
err:
if (err->code == DRGN_ERROR_FAULT) {
/*
* This could be the end of the stack trace, so it shouldn't be
* fatal.
*/
drgn_error_destroy(err);
} else {
drgn_error_destroy(prog->stack_trace_err);
prog->stack_trace_err = err;
}
return false;
}
/*
* For drgn_object_stack_trace(), we only care about the thread
* prog->stack_trace_obj. We return it with an arbitrary PID.
*/
#define STACK_TRACE_OBJ_PID 1
static pid_t drgn_object_stack_trace_next_thread(Dwfl *dwfl, void *dwfl_arg,
void **thread_argp)
{
struct drgn_program *prog = dwfl_arg;
if (*thread_argp || !prog->stack_trace_obj)
return 0;
*thread_argp = (void *)prog->stack_trace_obj;
return STACK_TRACE_OBJ_PID;
}
static bool drgn_linux_kernel_set_initial_registers(Dwfl_Thread *thread,
void *thread_arg)
{
struct drgn_error *err;
struct drgn_object *task_obj = thread_arg;
struct drgn_program *prog = task_obj->prog;
err = prog->platform.arch->linux_kernel_set_initial_registers(thread,
task_obj);
if (err) {
drgn_error_destroy(prog->stack_trace_err);
prog->stack_trace_err = err;
return false;
}
return true;
}
struct drgn_stack_trace_builder {
struct drgn_program *prog;
struct drgn_stack_trace *trace;
size_t capacity;
};
static int drgn_append_stack_frame(Dwfl_Frame *dwfl_frame, void *arg)
{
struct drgn_error *err;
struct drgn_stack_trace_builder *builder = arg;
struct drgn_program *prog = builder->prog;
struct drgn_stack_trace *trace = builder->trace;
struct drgn_stack_frame *frame;
Dwarf_Addr pc;
if (!dwfl_frame_pc(dwfl_frame, &pc, NULL)) {
err = drgn_error_libdwfl();
goto err;
}
if (trace->num_frames >= builder->capacity) {
size_t new_capacity, bytes;
if (__builtin_mul_overflow(2U, builder->capacity,
&new_capacity) ||
__builtin_mul_overflow(new_capacity,
sizeof(trace->frames[0]), &bytes) ||
__builtin_add_overflow(bytes, sizeof(*trace), &bytes) ||
!(trace = realloc(trace, bytes))) {
err = &drgn_enomem;
goto err;
}
builder->trace = trace;
builder->capacity = new_capacity;
}
frame = &trace->frames[trace->num_frames++];
frame->prog = prog;
frame->pc = pc;
return DWARF_CB_OK;
err:
drgn_error_destroy(prog->stack_trace_err);
prog->stack_trace_err = err;
return DWARF_CB_ABORT;
}
static const Dwfl_Thread_Callbacks drgn_linux_kernel_thread_callbacks = {
.next_thread = drgn_object_stack_trace_next_thread,
.memory_read = drgn_thread_memory_read,
.set_initial_registers = drgn_linux_kernel_set_initial_registers,
};
struct drgn_error *drgn_object_stack_trace(const struct drgn_object *obj,
struct drgn_stack_trace **ret)
{
struct drgn_error *err;
struct drgn_program *prog = obj->prog;
Dwfl *dwfl;
struct drgn_stack_trace_builder builder;
struct drgn_stack_trace *trace;
if (!prog->has_platform) {
return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT,
"cannot unwind stack without platform");
}
if (!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)) {
return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT,
"stack unwinding is currently only supported for the Linux kernel");
}
if (!prog->platform.arch->linux_kernel_set_initial_registers) {
return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT,
"stack unwinding is not supported for %s architecture",
prog->platform.arch->name);
}
err = drgn_program_get_dwfl(prog, &dwfl);
if (err)
return err;
if (!prog->attached_dwfl_state) {
if (!dwfl_attach_state(dwfl, NULL, 0,
&drgn_linux_kernel_thread_callbacks,
prog))
return drgn_error_libdwfl();
prog->attached_dwfl_state = true;
}
builder.prog = prog;
builder.trace = malloc(sizeof(*builder.trace) +
sizeof(builder.trace->frames[0]));
if (!builder.trace)
return &drgn_enomem;
builder.trace->num_frames = 0;
builder.capacity = 1;
prog->stack_trace_obj = obj;
dwfl_getthread_frames(dwfl, STACK_TRACE_OBJ_PID,
drgn_append_stack_frame, &builder);
prog->stack_trace_obj = NULL;
/*
* The error reporting for dwfl_getthread_frames() is not great. The
* documentation says that some of its unwinder implementations always
* return an error. So, we do our own error reporting for fatal errors
* through prog->stack_trace_err.
*/
if (prog->stack_trace_err) {
err = prog->stack_trace_err;
prog->stack_trace_err = NULL;
goto err;
}
/* Shrink the trace to fit if we can, but don't fail if we can't. */
trace = realloc(builder.trace,
sizeof(*builder.trace) +
builder.trace->num_frames *
sizeof(builder.trace->frames[0]));
if (!trace)
trace = builder.trace;
*ret = trace;
return NULL;
err:
free(builder.trace);
return err;
}