mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 01:33:06 +00:00
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:
parent
93d7ea9f01
commit
10142f922f
@ -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
|
||||
|
@ -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
|
||||
^^^^^
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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 \
|
||||
|
@ -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, ®);
|
||||
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(®_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(®_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(®_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(®_obj, &frame_obj, "r12", &dwarf_regs[0]);
|
||||
if (err)
|
||||
goto out;
|
||||
err = read_register(®_obj, &frame_obj, "r13", &dwarf_regs[1]);
|
||||
if (err)
|
||||
goto out;
|
||||
err = read_register(®_obj, &frame_obj, "r14", &dwarf_regs[2]);
|
||||
if (err)
|
||||
goto out;
|
||||
err = read_register(®_obj, &frame_obj, "r15", &dwarf_regs[3]);
|
||||
if (err)
|
||||
goto out;
|
||||
err = read_register(®_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(®_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,
|
||||
};
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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. */
|
||||
|
@ -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[];
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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},
|
||||
{},
|
||||
|
142
libdrgn/python/stack_trace.c
Normal file
142
libdrgn/python/stack_trace.c
Normal 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
302
libdrgn/stack_trace.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user