Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to use drgn with IPython/Jupyter #200

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,7 @@ class Object:
value (i.e., for C, zero-initialized). Defaults to ``False``.
"""
...
def _repr_pretty_(self, p: Any, cycle: bool) -> None: ...
def __iter__(self) -> Iterator[Object]: ...
def __bool__(self) -> bool: ...
def __lt__(self, other: Any) -> bool: ...
Expand Down Expand Up @@ -1621,6 +1622,7 @@ class StackTrace:
"""Program that this stack trace is from."""

def __getitem__(self, idx: IntegerLike) -> StackFrame: ...
def _repr_pretty_(self, p: Any, cycle: bool) -> None: ...

class StackFrame:
"""
Expand Down Expand Up @@ -1741,6 +1743,7 @@ class StackFrame:
dictionary with the register names as keys.
"""
...
def _repr_pretty_(self, p: Any, cycle: bool) -> None: ...

class Type:
"""
Expand Down Expand Up @@ -1913,6 +1916,7 @@ class Type:
:raises TypeError: if this type is not a structure, union, or class
type
"""
def _repr_pretty_(self, p: Any, cycle: bool) -> None: ...

class TypeMember:
"""
Expand Down
56 changes: 56 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,62 @@ explicitly::
int counter;
} atomic_t

Jupyter
^^^^^^^

drgn can also be use in `Jupyter <https://jupyter.org>`_ with the `IPython
kernel <https://ipython.org>`_, where the former provides the follows features,
similar to `Interactive Mode`_:

* History
* Tab completion
* Pretty printing of objects and types
* Helpers

* Saving session ``%save``
* Running shell command ``!``
* `and more <https://ipython.readthedocs.io/en/stable/interactive/magics.html>`_

It shares much of the same features as `Interactive Mode`_ plus `a few extra
tricks <https://ipython.readthedocs.io/en/stable/interactive/magics.html>`_,
albeit much more heavy-weight compaired to the built-in counterpart.

Similarly, ``str()`` is used instead of ``repr()`` to save one from having to
call ``print()`` for objects and types.::

$ pip install ipython
...

$ ipython
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import drgn
In [2]: prog = drgn.Program()
In [3]: !ps aux | grep bash | head -n1 # Run shell command
drgn 2022 0.0 0.0 20000 10000 pts/1 Ss Aug16 0:00 /bin/bash
In [4]: prog.set_pid(2022)
In [5]: prog.load_default_debug_info()
In [6]: %save session.py # Save the session inside a .py file
The following commands were written to file `session.py`:
...
In [7]: exit
$ ipython
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: %run -i drgn_session.py # Re-execute all previous statements
In [2]: prog['main']
Out[2]: (int (int argc, char **argv, char **env))<absent>

For a fully-fledges Jupyter experience, it is recommended to use `Jupyter
notebook
<https://jupyter.org/try-jupyter/retro/notebooks/?path=notebooks/Intro.ipynb>`_,
which is a web-based interactive programming interface that supports richer
formats such as `Markdown
<https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Working%20With%20Markdown%20Cells.html>`_
and can be `easily shared
<https://github.blog/2015-05-07-github-jupyter-notebooks-3/>`_.

Next Steps
----------

Expand Down
3 changes: 3 additions & 0 deletions libdrgn/python/drgnpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ DrgnType *Program_function_type(Program *self, PyObject *args, PyObject *kwds);
int append_string(PyObject *parts, const char *s);
int append_format(PyObject *parts, const char *format, ...);
PyObject *join_strings(PyObject *parts);
PyObject *repr_pretty_from_str(PyObject *self, PyObject *args, PyObject *kwds);
PyDoc_STRVAR(repr_pretty_DOC, "Helper method used for pretty printing in "
"Jupyter notebook.");

struct index_arg {
bool allow_none;
Expand Down
2 changes: 2 additions & 0 deletions libdrgn/python/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,8 @@ static PyMethodDef DrgnObject_methods[] = {
drgn_Object_from_bytes__DOC},
{"format_", (PyCFunction)DrgnObject_format,
METH_VARARGS | METH_KEYWORDS, drgn_Object_format__DOC},
{"_repr_pretty_", (PyCFunction)repr_pretty_from_str,
METH_VARARGS | METH_KEYWORDS, repr_pretty_DOC},
{"__round__", (PyCFunction)DrgnObject_round,
METH_VARARGS | METH_KEYWORDS},
{"__trunc__", (PyCFunction)DrgnObject_trunc, METH_NOARGS},
Expand Down
9 changes: 9 additions & 0 deletions libdrgn/python/stack_trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ static StackFrame *StackTrace_item(StackTrace *self, Py_ssize_t i)
return ret;
}

static PyMethodDef StackTrace_methods[] = {
{"_repr_pretty_", (PyCFunction)repr_pretty_from_str,
METH_VARARGS | METH_KEYWORDS, repr_pretty_DOC},
{},
};

static PySequenceMethods StackTrace_as_sequence = {
.sq_length = (lenfunc)StackTrace_length,
.sq_item = (ssizeargfunc)StackTrace_item,
Expand All @@ -87,6 +93,7 @@ PyTypeObject StackTrace_type = {
.tp_str = (reprfunc)StackTrace_str,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = drgn_StackTrace_DOC,
.tp_methods = StackTrace_methods,
.tp_getset = StackTrace_getset,
};

Expand Down Expand Up @@ -300,6 +307,8 @@ static PyMethodDef StackFrame_methods[] = {
METH_O, drgn_StackFrame_register_DOC},
{"registers", (PyCFunction)StackFrame_registers,
METH_NOARGS, drgn_StackFrame_registers_DOC},
{"_repr_pretty_", (PyCFunction)repr_pretty_from_str,
METH_VARARGS | METH_KEYWORDS, repr_pretty_DOC},
{},
};

Expand Down
2 changes: 2 additions & 0 deletions libdrgn/python/type.c
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,8 @@ static PyMethodDef DrgnType_methods[] = {
drgn_Type_member_DOC},
{"has_member", (PyCFunction)DrgnType_has_member,
METH_VARARGS | METH_KEYWORDS, drgn_Type_has_member_DOC},
{"_repr_pretty_", (PyCFunction)repr_pretty_from_str,
METH_VARARGS | METH_KEYWORDS, repr_pretty_DOC},
{},
};

Expand Down
23 changes: 23 additions & 0 deletions libdrgn/python/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ PyObject *join_strings(PyObject *parts)
return ret;
}

PyObject *repr_pretty_from_str(PyObject *self, PyObject *args,
PyObject *kwds)
{
static char *keywords[] = {"p", "cycle", NULL};
PyObject *p, *str_obj, *ret;
int cycle;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "Op:_repr_pretty_", keywords,
&p, &cycle))
return NULL;

if (cycle)
return PyObject_CallMethod(p, "text", "s", "...");

str_obj = PyObject_Str(self);
if (!str_obj)
return NULL;

ret = PyObject_CallMethod(p, "text", "O", str_obj);
Py_DECREF(str_obj);
return ret;
}

int index_converter(PyObject *o, void *p)
{
struct index_arg *arg = p;
Expand Down
11 changes: 11 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import functools
from typing import Any, NamedTuple, Optional
import unittest
from unittest.mock import Mock

from drgn import (
Architecture,
Expand Down Expand Up @@ -108,6 +109,16 @@ def mock_object_find(prog, name, flags, filename):
return prog


def assertReprPrettyEqualsStr(obj):
pretty_printer_mock = Mock()

obj._repr_pretty_(pretty_printer_mock, False)
pretty_printer_mock.text.assert_called_with(str(obj))

obj._repr_pretty_(p=pretty_printer_mock, cycle=True)
pretty_printer_mock.text.assert_called_with("...")


def identical(a, b):
"""
Return whether two objects are "identical".
Expand Down
11 changes: 11 additions & 0 deletions tests/linux_kernel/test_stack_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from drgn import Object, Program, cast
from drgn.helpers.linux.pid import find_task
from tests import assertReprPrettyEqualsStr
from tests.linux_kernel import (
LinuxKernelTestCase,
fork_and_pause,
Expand Down Expand Up @@ -97,3 +98,13 @@ def test_prog(self):
self.prog.stack_trace(Object(self.prog, "struct pt_regs", value={})).prog,
self.prog,
)

def test_stack__repr_pretty_(self):
osandov marked this conversation as resolved.
Show resolved Hide resolved
pid = fork_and_pause()
wait_until(proc_blocked, pid)
trace = self.prog.stack_trace(pid)
assertReprPrettyEqualsStr(trace)
for frame in trace:
assertReprPrettyEqualsStr(frame)
os.kill(pid, signal.SIGKILL)
os.waitpid(pid, 0)
11 changes: 10 additions & 1 deletion tests/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
reinterpret,
sizeof,
)
from tests import MockMemorySegment, MockProgramTestCase, mock_program
from tests import (
MockMemorySegment,
MockProgramTestCase,
assertReprPrettyEqualsStr,
mock_program,
)


class TestInit(MockProgramTestCase):
Expand Down Expand Up @@ -1718,3 +1723,7 @@ def test_iter(self):
iter,
Object(self.prog, "int []", address=0),
)

def test__repr_pretty_(self):
obj = Object(self.prog, "int", value=0)
assertReprPrettyEqualsStr(obj)
11 changes: 10 additions & 1 deletion tests/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
offsetof,
sizeof,
)
from tests import DEFAULT_LANGUAGE, MockProgramTestCase, mock_program
from tests import (
DEFAULT_LANGUAGE,
MockProgramTestCase,
assertReprPrettyEqualsStr,
mock_program,
)


class TestType(MockProgramTestCase):
Expand Down Expand Up @@ -973,6 +978,10 @@ def test_language_repr(self):
"prog.void_type(language=Language.CPP)",
)

def test__repr_pretty_(self):
t = self.prog.void_type()
assertReprPrettyEqualsStr(t)

def test_different_programs_compound(self):
self.assertRaisesRegex(
ValueError,
Expand Down