Document with Sphinx

drgn has pretty thorough in-program documentation, but it doesn't have a
nice overview or introduction to the basic concepts. This commit adds
that using Sphinx. In order to avoid documenting everything in two
places, the libdrgn bindings have their docstrings generated from the
API documentation. The alternative would be to use Sphinx's autodoc
extension, but that's not as flexible and would also require building
the extension to build the docs. The documentation for the helpers is
generated using autodoc and a small custom extension.
This commit is contained in:
Omar Sandoval 2019-04-04 01:27:23 -07:00
parent 9be2627418
commit 393a1f3149
38 changed files with 2409 additions and 853 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@
/build
/coverage.info
/dist
/docs/_build
/drgn.egg-info
/htmlcov
__pycache__

3
.readthedocs.yml Normal file
View File

@ -0,0 +1,3 @@
version: 2
sphinx:
configuration: docs/conf.py

View File

@ -1 +1,2 @@
recursive-include docs *.py *.rst
recursive-include tests *.py

View File

@ -1,95 +0,0 @@
drgn
====
`drgn` is a debugger-as-a-library. In contrast to existing debuggers like
[GDB](https://www.gnu.org/software/gdb/) which excel in breakpoint-based
debugging, drgn focuses on live introspection. drgn exposes the types and data
in a program for easy, expressive scripting.
drgn was developed for debugging the Linux kernel (as an alternative to the
[crash](http://people.redhat.com/anderson/) utility), but it can also debug
userspace programs written in C. C++ support is planned.
Python is the main interface for drgn, although an experimental C library,
`libdrgn`, is also provided.
Installation
------------
drgn is built with setuptools. Build it like so:
```
$ python3 setup.py build_ext -i
```
Then, you can either run it locally:
```
$ python3 -m drgn --help
```
Or install it and run it:
```
$ sudo python3 setup.py install
$ drgn --help
```
Or, pick your favorite Python package installation method.
Getting Started
---------------
drgn can be used as a command line tool or as a library. The rest of this
section describes using the CLI; the CLI is basically a wrapper around the
library which provides a nice interface, including history and tab completion.
To debug the running kernel, run `sudo drgn -k`. To debug a running program,
run `sudo drgn -p $PID`. To debug a core dump (either a kernel vmcore or a
userspace core dump), run `drgn -c $PATH`.
The drgn CLI has an interactive mode and a script mode. If no arguments are
passed, drgn runs in interactive mode; otherwise, the given script is run with
the given arguments. The drgn CLI is actually just the Python interpreter
initialized with a `prog` object representing the debugged program:
```
$ sudo drgn -k
>>> prog.type('struct list_head')
struct list_head {
struct list_head *next;
struct list_head *prev;
}
>>> prog['modules']
(struct list_head){
.next = (struct list_head *)0xffffffffc0b91048,
.prev = (struct list_head *)0xffffffffc0066148,
}
>>> prog['init_task'].pid
(pid_t)0
>>> from drgn.helpers.linux import list_for_each_entry
>>> for mod in list_for_each_entry('struct module', prog['modules'].address_of_(), 'list'):
... if mod.refcnt.counter > 10:
... print(mod.name)
...
(char [56])"snd"
(char [56])"evdev"
(char [56])"i915"
```
See the in-program documentation in interactive mode with `help(drgn)` for more
information. See `examples` and `drgn/helpers` for some examples.
License
-------
Copyright 2018-2019 - Omar Sandoval
Licensed under the GPLv3 or later
Acknowledgements
----------------
drgn is named after
[this](https://giraffesgiraffes.bandcamp.com/track/drgnfkr-2) because dragons
eat [dwarves](http://dwarfstd.org/).

99
README.rst Normal file
View File

@ -0,0 +1,99 @@
drgn
====
.. image:: https://readthedocs.org/projects/drgn/badge/?version=latest
:target: https://drgn.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. start-introduction
drgn is a debugger-as-a-library. In contrast to existing debuggers like `GDB
<https://www.gnu.org/software/gdb/>`_ which focus on breakpoint-based
debugging, drgn excels in live introspection. drgn exposes the types and
variables in a program for easy, expressive scripting in Python. For example,
you can debug the Linux kernel:
.. code-block:: pycon
>>> from drgn.helpers.linux import list_for_each_entry
>>> for mod in list_for_each_entry('struct module',
... prog['modules'].address_of_(),
... 'list'):
... if mod.refcnt.counter > 10:
... print(mod.name)
...
(char [56])"snd"
(char [56])"evdev"
(char [56])"i915"
drgn was developed for debugging the Linux kernel (as an alternative to the
`crash <http://people.redhat.com/anderson/>`_ utility), but it can also debug
userspace programs written in C. C++ support is planned.
.. end-introduction
Documentation can be found at `drgn.readthedocs.io
<https://drgn.readthedocs.io>`_.
Installation
------------
Install the following dependencies:
* Python 3.6 or newer
* elfutils development libraries (libelf and libdw)
* GNU autotools (autoconf, automake, and libtool) and pkgconf
Then, run:
.. code-block:: console
$ git clone https://github.com/osandov/drgn.git
$ cd drgn
$ python3 setup.py build
$ sudo python3 setup.py install
See the `installation documentation
<https://drgn.readthedocs.io/en/latest/installation.html>`_ for more details.
Quick Start
-----------
.. start-quick-start
To debug the running kernel, run ``sudo drgn -k``. To debug a running program,
run ``sudo drgn -p $PID``. To debug a core dump (either a kernel vmcore or a
userspace core dump), run ``drgn -c $PATH``. The program must have debugging
symbols available.
Then, you can access variables in the program with ``prog['name']``, access
structure members with ``.``, use various predefined helpers, and more:
.. code-block:: pycon
$ sudo drgn -k
>>> prog['init_task'].comm
(char [16])"swapper/0"
>>> d_path(fget(find_task(prog, 1), 0).f_path.address_of_())
b'/dev/null'
>>> max(task.stime for task in for_each_task(prog))
(u64)4192109975952
>>> sum(disk.gendisk.part0.nr_sects for disk in for_each_disk(prog))
(sector_t)999705952
.. end-quick-start
See the `user guide <https://drgn.readthedocs.io/en/latest/user_guide.html>`_
for more information.
License
-------
.. start-license
Copyright 2018-2019 Omar Sandoval
drgn is licensed under the `GPLv3
<https://www.gnu.org/licenses/gpl-3.0.en.html>`_ or later.
.. end-license

892
docs/api_reference.rst Normal file
View File

@ -0,0 +1,892 @@
API Reference
=============
.. module:: drgn
Programs
--------
.. class:: Program
A ``Program`` represents a crashed or running program. It can be used to
lookup type definitions, access variables, and read arbitrary memory.
The main functionality of a ``Program`` is looking up objects (i.e.,
variables, constants, or functions). This is usually done with the
:meth:`[] <__getitem__>` operator.
This class cannot be constructed directly. Instead, use one of the
:ref:`api-program-constructors`.
.. attribute:: flags
Flags which apply to this program.
:vartype: ProgramFlags
.. attribute:: word_size
Size of a word in this program in bytes.
:vartype: int
.. attribute:: byteorder
Byte order (a.k.a. endianness) in this program (either ``'little'``
or ``'big'``).
:vartype: str
.. attribute:: __getitem__(name)
Implement ``self[name]``. Get the object (variable, constant, or
function) with the given name.
If there are multiple objects with the same name, one is returned
arbitrarily. In this case, the :meth:`variable()`, :meth:`constant()`,
or :meth:`function()` methods can be used instead.
>>> prog['jiffies']
Object(prog, 'volatile unsigned long', address=0xffffffff94c05000)
:param str name: The object name.
:rtype: Object
.. attribute:: variable(name, filename=None)
Get the variable with the given name.
>>> prog.variable('jiffies')
Object(prog, 'volatile unsigned long', address=0xffffffff94c05000)
:param str name: The variable name.
:param filename: The source code file that contains the definition. See
:ref:`api-filenames`.
:type filename: str or None
:rtype: Object
:raises LookupError: if no variables with the given name are found in
the given file
.. attribute:: constant(name, filename=None)
Get the constant (e.g., enumeration constant) with the given name.
Note that support for macro constants is not yet implemented for DWARF
files, and most compilers don't generate macro debugging information
by default anyways.
>>> prog.constant('PIDTYPE_MAX')
Object(prog, 'enum pid_type', value=4)
:param str name: The constant name.
:param filename: The source code file that contains the definition. See
:ref:`api-filenames`.
:type filename: str or None
:rtype: Object
:raises LookupError: if no constants with the given name are found in
the given file
.. attribute:: function(name, filename=None)
Get the function with the given name.
>>> prog.function('schedule')
Object(prog, 'void (void)', address=0xffffffff94392370)
:param str name: The function name.
:param filename: The source code file that contains the definition. See
:ref:`api-filenames`.
:type filename: str or None
:rtype: Object
:raises LookupError: if no functions with the given name are found in
the given file
.. attribute:: type(name, filename=None)
Get the type with the given name.
>>> prog.type('long')
int_type(name='long', size=8, is_signed=True)
:param str name: The type name.
:param filename: The source code file that contains the definition. See
:ref:`api-filenames`.
:type filename: str or None
:rtype: Type
:raises LookupError: if no types with the given name are found in
the given file
.. attribute:: read(address, size, physical=False)
Read *size* bytes of memory starting at *address* in the program. The
address may be virtual (the default) or physical if the program
supports it.
>>> prog.read(0xffffffffbe012b40, 16)
b'swapper/0\x00\x00\x00\x00\x00\x00\x00'
:param int address: The starting address.
:param int size: The number of bytes to read.
:param bool physical: Whether *address* is a physical memory address.
If ``False``, then it is a virtual memory address. Physical memory
can usually only be read when the program is an operating system
kernel.
:rtype: bytes
:raises FaultError: if the address range is invalid or the type of
address (physical or virtual) is not supported by the program
:raises ValueError: if *size* is negative
.. class:: ProgramFlags
``ProgramFlags`` is an :class:`enum.IntFlag` of flags that can apply to a
:class:`Program` (e.g., about what kind of program it is).
.. attribute:: IS_LINUX_KERNEL
The program is the Linux kernel.
.. _api-filenames:
Filenames
^^^^^^^^^
The :meth:`Program.type()`, :meth:`Program.variable()`,
:meth:`Program.constant()`, and :meth:`Program.function()` methods all take a
*filename* parameter to distinguish between multiple definitions with the same
name. The filename refers to the source code file that contains the definition.
``None`` matches any definition. Otherwise, the filename is matched from right
to left, so ``'stdio.h'``, ``'include/stdio.h'``, ``'usr/include/stdio.h'``,
and ``'/usr/include/stdio.h'`` would all match a definition in
``/usr/include/stdio.h``. If multiple definitions match, one is returned
arbitrarily.
.. _api-program-constructors:
Program Constructors
^^^^^^^^^^^^^^^^^^^^
The drgn command line interface automatically creates a :class:`Program` named
``prog``. However, drgn may also be used as a library without the CLI, in which
case a ``Program`` must be created manually.
.. function:: program_from_core_dump(path, verbose=False)
Create a :class:`Program` from a core dump file. The type of program (e.g.,
userspace or kernel) is determined automatically.
:param str path: Core dump file path.
:param bool verbose: Whether to print non-fatal errors to stderr (e.g.,
about not being able to find debugging symbols).
:rtype: Program
.. function:: program_from_kernel(verbose=False)
Create a :class:`Program` from the running operating system kernel. This
requires root privileges.
:param bool verbose: Whether to print non-fatal errors to stderr (e.g.,
about not being able to find kernel modules or debugging symbols).
:rtype: Program
.. function:: program_from_pid(pid)
Create a :class:`Program` from a running program with the given PID. This
requires appropriate permissions (on Linux, :manpage:`ptrace(2)` attach
permissions).
:param int pid: Process ID of the program to debug.
:rtype: Program
Objects
-------
.. class:: Object(prog, type=None, *, address=None, value=None, byteorder=None, bit_offset=None, bit_field_size=None)
An ``Object`` represents a symbol or value in a program. An object may
exist in the memory of the program (a *reference*), or it may be a
temporary computed value (a *value*).
All instances of this class have two attributes: :attr:`prog_`, the program
that the object is from; and :attr:`type_`, the type of the object.
Reference objects also have an :attr:`address_` attribute. Objects may also
have a :attr:`byteorder_`, :attr:`bit_offset_`, and
:attr:`bit_field_size_`.
:func:`repr()` of an object returns a Python representation of the object:
>>> print(repr(prog['jiffies']))
Object(prog, 'volatile long unsigned int', address=0xffffffffbf005000)
:class:`str() <str>` returns a representation of the object in programming
language syntax:
>>> print(prog['jiffies'])
(volatile long unsigned int)4326237045
Note that the drgn CLI is set up so that objects are displayed with
``str()`` instead of ``repr()`` (the latter is the default behavior of
Python's interactive mode). This means that in the drgn CLI, the call to
``print()`` in the second example above is not necessary.
Objects support the following operators:
* Arithmetic operators: ``+``, ``-``, ``*``, ``/``, ``%``
* Bitwise operators: ``<<``, ``>>``, ``&``, ``|``, ``^``, ``~``
* Relational operators: ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=``
* Subscripting: :meth:`[] <__getitem__>` (Python does not have a unary
``*`` operator, so pointers are dereferenced with ``ptr[0]``)
* Member access: :meth:`. <__getattribute__>` (Python does not have a
``->`` operator, so ``.`` is also used to access members of pointers to
structures)
* The address-of operator: :meth:`drgn.Object.address_of_()` (this is a
method because Python does not have a ``&`` operator)
* Array length: :meth:`len() <__len__>`
These operators all have the semantics of the program's programming
language. For example, adding two objects from a program written in C
results in an object with a type and value according to the rules of C:
>>> Object(prog, 'unsigned long', value=2**64 - 1) + Object(prog, 'int', value=1)
Object(prog, 'unsigned long', value=0)
If only one operand to a binary operator is an object, the other operand
will be converted to an object according to the language's rules for
literals:
>>> Object(prog, 'char', value=0) - 1
Object(prog, 'int', value=-1)
The standard :class:`int() <int>`, :class:`float() <float>`, and
:class:`bool() <bool>` functions convert an object to that Python type.
Conversion to ``bool`` uses the programming language's notion of
"truthiness". Additionally, certain Python functions will automatically
coerce an object to the appropriate Python type (e.g., :func:`hex()`,
:func:`round()`, and :meth:`list subscripting <object.__getitem__>`).
Object attributes and methods are named with a trailing underscore to avoid
conflicting with structure or union members. The attributes and methods
always take precedence; use :meth:`member_()` if there is a conflict.
Objects are usually obtained directly from a :class:`Program`, but they can
be constructed manually, as well (for example, if you got a variable
address from a log file).
:param Program prog: The program to create this object in.
:param type: The type of the object. If omitted, this is deduced from
*value* according to the language's rules for literals.
:type type: str or Type
:param int address: The address of this object in the program. Either this
or *value* must be given, but not both.
:param value: The value of this object. See :meth:`value_()`.
:param byteorder: Byte order of the object. This should be ``'little'`` or
``'big'``. The default is ``None``, which indicates the program byte
order. This must be ``None`` for primitive values.
:type byteorder: str or None
:param bit_offset: Offset in bits from the object's address to the
beginning of the object. The default is ``None``, which means no
offset. This must be ``None`` for primitive values.
:type bit_offset: int or None
:param bit_field_size: Size in bits of this object if it is a bit field.
The default is ``None``, which means the object is not a bit field.
:type bit_field_size: int or None
.. attribute:: prog_
Program that this object is from.
:vartype: Program
.. attribute:: type_
Type of this object.
:vartype: Type
.. attribute:: address_
Address of this object if it is a reference, ``None`` if it is a value.
:vartype: int or None
.. attribute:: byteorder_
Byte order of this object (either ``'little'`` or ``'big'``) if it is a
reference or a non-primitive value, ``None`` otherwise.
:vartype: str or None
.. attribute:: bit_offset_
Offset in bits from this object's address to the beginning of the
object if it is a reference or a non-primitive value, ``None``
otherwise.
:vartype: int or None
.. attribute:: bit_field_size_
Size in bits of this object if it is a bit field, ``None`` if it is
not.
:vartype: int or None
.. method:: __getattribute__(name)
Implement ``self.name``.
If *name* is an attribute of the :class:`Object` class, then this
returns that attribute. Otherwise, it is equivalent to
:meth:`member_()`.
>>> print(prog['init_task'].pid)
(pid_t)0
:param str name: Attribute name.
.. method:: __getitem__(idx)
Implement ``self[idx]``. Get the array element at the given index.
>>> print(prog['init_task'].comm[0])
(char)115
This is only valid for pointers and arrays.
:param int idx: The array index.
:rtype: Object
:raises TypeError: if this object is not a pointer or array
.. method:: __len__()
Implement ``len(self)``. Get the number of elements in this object.
>>> len(prog['init_task'].comm)
16
This is only valid for arrays.
:rtype: int
:raises TypeError: if this object is not an array with complete type
.. method:: value_()
Get the value of this object as a Python object.
For basic types (integer, floating-point, boolean), this returns an
object of the directly corresponding Python type (``int``, ``float``,
``bool``). For pointers, this returns the address value of the pointer.
For enums, this returns an ``int``. For structures and unions, this
returns a ``dict`` of members. For arrays, this returns a ``list`` of
values.
:raises FaultError: if reading the object causes a bad memory access
:raises TypeError: if this object has an unreadable type (e.g.,
``void``)
.. method:: string_()
Read a null-terminated string pointed to by this object.
This is only valid for pointers and arrays. The element type is
ignored; this operates byte-by-byte.
For pointers and flexible arrays, this stops at the first null byte.
For complete arrays, this stops at the first null byte or at the end of
the array.
:rtype: bytes
:raises FaultError: if reading the string causes a bad memory access
:raises TypeError: if this object is not a pointer or array
.. method:: member_(name)
Get a member of this object.
This is valid for structures, unions, and pointers to either.
Normally the dot operator (``.``) can be used to accomplish the same
thing, but this method can be used if there is a name conflict with an
Object member or method.
:param str name: Name of the member.
:rtype: Object
:raises TypeError: if this object is not a structure, union, or a
pointer to either
:raises LookupError: if this object does not have a member with the
given name
.. method:: address_of_()
Get a pointer to this object.
This corresponds to the address-of (``&``) operator in C. It is only
possible for reference objects, as value objects don't have an address
in the program.
As opposed to :attr:`address_`, this returns an ``Object``, not an
``int``.
:rtype: Object
:raises ValueError: if this object is a value
.. method:: read_()
Read this object (which may be a reference or a value) and return it as
a value object.
This is useful if the object can change in the running program (but of
course nothing stops the program from modifying the object while it is
being read).
As opposed to :meth:`value_()`, this returns an ``Object``, not a
standard Python type.
:rtype: Object
:raises FaultError: if reading this object causes a bad memory access
:raises TypeError: if this object has an unreadable type (e.g.,
``void``)
.. function:: NULL(prog, type)
Get an object representing ``NULL`` casted to the given type.
This is equivalent to ``Object(prog, type, value=0)``.
:param Program prog: The program.
:param type: The type.
:type type: str or Type
:rtype: Object
.. function:: cast(type, obj)
Get the value of the given object casted to another type.
Objects with a scalar type (integer, boolean, enumerated, floating-point,
or pointer) can be casted to a different scalar type. Other objects can
only be casted to the same type. This always results in a value object. See
also :func:`drgn.reinterpret()`.
:param type: The type to cast to.
:type type: str or Type
:param Object obj: The object to cast.
:rtype: Object
.. function:: reinterpret(type, obj, byteorder=None)
Get a copy of the given object reinterpreted as another type and/or byte
order.
This reinterprets the raw memory of the object, so an object can be
reinterpreted as any other type. However, value objects with a scalar type
cannot be reinterpreted, as their memory layout in the program is not
known. Reinterpreting a reference results in a reference, and
reinterpreting a value results in a value. See also :func:`drgn.cast()`.
:param type: The type to reinterpret as.
:type type: str or Type
:param Object obj: The object to reinterpret.
:param byteorder: The byte order to reinterpret as. This should be
``'little'`` or ``'big'``. The default is ``None``, which indicates the
program byte order.
:type byteorder: str or None
:rtype: Object
.. function:: container_of(ptr, type, member)
Get the containing object of a pointer object.
This corresponds to the ``container_of()`` macro in C.
:param Object ptr: The pointer.
:param type: The type of the containing object.
:type type: str or Type
:param str member: The name of the member in ``type``.
:raises TypeError: if the object is not a pointer or the type is not a
structure or union type
:raises LookupError: If the type does not have a member with the given name
.. _api-reference-types:
Types
-----
.. class:: Type
A ``Type`` object describes a type in a program. Each kind of type (e.g.,
integer, structure) has different attributes (e.g., name, size). Types can
also have qualifiers (e.g., constant, atomic). Accessing an attribute which
does not apply to a type raises an :exc:`AttributeError`.
:func:`repr()` of a Type returns a Python representation of the type:
>>> print(repr(prog.type('sector_t')))
typedef_type(name='sector_t', type=int_type(name='unsigned long', size=8, is_signed=False))
:class:`str() <str>` returns a representation of the type in programming
language syntax:
>>> print(prog.type('sector_t'))
typedef unsigned long sector_t
The drgn CLI is set up so that types are displayed with ``str()`` instead
of ``repr()`` by default.
This class cannot be constructed directly. Instead, use one of the
:ref:`api-type-constructors`.
.. attribute:: kind
Kind of this type.
:vartype: TypeKind
.. attribute:: qualifiers
Bitmask of this type's qualifier.
:vartype: Qualifiers
.. attribute:: name
Name of this type. This is present for integer, boolean,
floating-point, complex, and typedef types.
:vartype: str
.. attribute:: tag
Tag of this type, or ``None`` if this is an anonymous type. This is
present for structure, union, and enumerated types.
:vartype: str or None
.. attribute:: size
Size of this type in bytes, or ``None`` if this is an incomplete type.
This is present for integer, boolean, floating-point, complex,
structure, union, and pointer types.
:vartype: int or None
.. attribute:: length
Number of elements in this type, or ``None`` if this is an incomplete
type. This is only present for array types.
:vartype: int or None
.. attribute:: is_signed
Whether this type is signed. This is only present for integer types.
:vartype: bool
.. attribute:: type
Type underlying this type, defined as follows:
* For complex types, the corresponding the real type.
* For typedef types, the aliased type.
* For enumerated types, the compatible integer type, which is ``None``
if this is an incomplete type.
* For pointer types, the referenced type.
* For array types, the element type.
* For function types, the return type.
For other types, this attribute is not present.
:vartype: Type
.. attribute:: members
List of members of this type, or ``None`` if this is an incomplete
type. This is present for structure and union types.
Each member is a (type, name, bit offset, bit field size) tuple. The
name is ``None`` if the member is unnamed; the bit field size is zero
if the member is not a bit field.
:vartype: list[tuple(Type, str or None, int, int)]
.. attribute:: enumerators
List of enumeration constants of this type, or ``None`` if this is an
incomplete type. This is only present for enumerated types.
Each enumeration constant is a (name, value) tuple.
:vartype: list[tuple(str, int)] or None
.. attribute:: parameters
List of parameters of this type. This is only present for function
types.
Each parameter is a (type, name) tuple. The name is ``None`` if the
parameter is unnamed.
:vartype: list[tuple(Type, str or None)]
.. attribute:: is_variadic
Whether this type takes a variable number of arguments. This is only
present for function types.
:vartype: bool
.. method:: type_name()
Get a descriptive full name of this type.
:rtype: str
.. method:: is_complete()
Get whether this type is complete (i.e., the type definition is known).
This is always ``False`` for void types. It may be ``False`` for
structure, union, enumerated, and array types, as well as typedef types
where the underlying type is one of those. Otherwise, it is always
``True``.
:rtype: bool
.. method:: qualified(qualifiers)
Get a copy of this type with different qualifiers.
Note that the original qualifiers are replaced, not added to.
:param qualifiers: New type qualifiers.
:type qualifiers: Qualifiers or None
:rtype: Type
.. method:: unqualified()
Get a copy of this type with no qualifiers.
:rtype: Type
.. class:: TypeKind
``TypeKind`` is an :class:`enum.Enum` of the different kinds of types.
.. attribute:: VOID
Void type.
.. attribute:: INT
Integer type.
.. attribute:: BOOL
Boolean type.
.. attribute:: FLOAT
Floating-point type.
.. attribute:: COMPLEX
Complex type.
.. attribute:: STRUCT
Structure type.
.. attribute:: UNION
Union type.
.. attribute:: ENUM
Enumerated type.
.. attribute:: TYPEDEF
Type definition (a.k.a. alias) type.
.. attribute:: POINTER
Pointer type.
.. attribute:: ARRAY
Array type.
.. attribute:: FUNCTION
Function type.
.. class:: Qualifiers
``Qualifiers`` is an :class:`enum.IntFlag` of type qualifiers.
.. attribute:: CONST
Constant type.
.. attribute:: VOLATILE
Volatile type.
.. attribute:: RESTRICT
`Restrict <https://en.cppreference.com/w/c/language/restrict>`_ type.
.. attribute:: ATOMIC
Atomic type.
.. _api-type-constructors:
Type Constructors
^^^^^^^^^^^^^^^^^
Custom drgn types can be created with the following factory functions. These
can be used just like types obtained from :meth:`Program.type()`.
.. function:: void_type(qualifiers=None)
Create a new void type. It has kind :attr:`TypeKind.VOID`.
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: int_type(name, size, is_signed, qualifiers=None)
Create a new integer type. It has kind :attr:`TypeKind.INT`.
:param str name: :attr:`Type.name`
:param int size: :attr:`Type.size`
:param bool is_signed: :attr:`Type.is_signed`
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: bool_type(name, size, qualifiers=None)
Create a new boolean type. It has kind :attr:`TypeKind.BOOL`.
:param str name: :attr:`Type.name`
:param int size: :attr:`Type.size`
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: float_type(name, size, qualifiers=None)
Create a new floating-point type. It has kind :attr:`TypeKind.FLOAT`.
:param str name: :attr:`Type.name`
:param int size: :attr:`Type.size`
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: complex_type(name, size, type, qualifiers=None)
Create a new complex type. It has kind :attr:`TypeKind.COMPLEX`.
:param str name: :attr:`Type.name`
:param int size: :attr:`Type.size`
:param Type type: The corresponding real type (:attr:`Type.type`)
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: struct_type(tag, size, members, qualifiers=None)
Create a new structure type. It has kind :attr:`TypeKind.STRUCT`.
:param tag: :attr:`Type.tag`
:type tag: str or None
:param int size: :attr:`Type.size`
:param list[tuple] members: :attr:`Type.members`. The type of a member may
be given as a callable returning a ``Type``; it will be called the
first time that the member is accessed. The name, bit offset, and bit
field size may be omitted; they default to ``None``, 0, and 0,
respectively.
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: union_type(tag, size, members, qualifiers=None)
Create a new union type. It has kind :attr:`TypeKind.UNION`. Otherwise,
this is the same as :func:`struct_type()`.
.. function:: enum_type(tag, type, enumerators, qualifiers=None)
Create a new enumerated type. It has kind :attr:`TypeKind.ENUM`.
:param tag: :attr:`Type.tag`
:type tag: str or None
:param type: The compatible integer type (:attr:`Type.type`)
:type param Type or None:
:param enumerators: :attr:`Type.enumerators`
:type enumerators: list[tuple] or None
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: typedef_type(name, type, qualifiers=None)
Create a new typedef type. It has kind :attr:`TypeKind.TYPEDEF`.
:param str name: :attr:`Type.name`
:param Type type: The aliased type (:attr:`Type.type`)
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: pointer_type(size, type, qualifiers=None)
Create a new pointer type. It has kind :attr:`TypeKind.POINTER`,
:param int size: :attr:`Type.size`
:param Type type: The referenced type (:attr:`Type.type`)
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: array_type(length, type, qualifiers=None)
Create a new array type. It has kind :attr:`TypeKind.ARRAY`.
:param length: :attr:`Type.length`
:type length: int or None
:param Type type: The element type (:attr:`Type.type`)
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
.. function:: function_type(type, parameters, is_variadic=False, qualifiers=None)
Create a new function type. It has kind :attr:`TypeKind.FUNCTION`.
:param Type type: The return type (:attr:`Type.type`)
:param list[tuple] parameters: :attr:`Type.parameters`. The type of a
parameter may be given as a callable returning a ``Type``; it will be
called the first time that the parameter is accessed. The name may be
omitted and defaults to ``None``.
:param bool is_variadic: :attr:`Type.is_variadic`
:param qualifiers: :attr:`Type.qualifiers`
:type qualifiers: Qualifiers or None
:rtype: Type
Exceptions
----------
.. exception:: FaultError
This error is raised when a bad memory access is attempted (i.e., when
accessing a memory address which is not valid in a program, or when
accessing out of bounds of a value object).
.. exception:: FileFormatError
This is error raised when a file cannot be parsed according to its expected
format (e.g., ELF or DWARF).

38
docs/conf.py Normal file
View File

@ -0,0 +1,38 @@
import os.path
import sys
sys.path.append(os.path.abspath('..'))
sys.path.append(os.path.abspath('exts'))
master_doc = 'index'
extensions = [
'autopackage',
'setuptools_config',
'sphinx.ext.autodoc',
'sphinx.ext.extlinks',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
autodoc_mock_imports = ['_drgn']
extlinks = {
'linux': ('https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/%s', ''),
}
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
}
manpages_url = 'http://man7.org/linux/man-pages/man{section}/{page}.{section}.html'
html_theme = 'alabaster'
html_theme_options = {
'description': 'Debugger-as-a-library',
'github_user': 'osandov',
'github_repo': 'drgn',
'github_button': True,
'github_type': 'star',
}

65
docs/exts/autopackage.py Normal file
View File

@ -0,0 +1,65 @@
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
import docutils.nodes
from docutils.statemachine import StringList
import importlib
import pkgutil
import sphinx.ext.autodoc
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import nested_parse_with_titles
# sphinx.ext.autodoc doesn't recursively document packages, so we need our own
# directive to do that.
class AutopackageDirective(SphinxDirective):
required_arguments = 1
optional_arguments = 0
def run(self):
sourcename = ''
def aux(name):
module = importlib.import_module(name)
contents = StringList()
contents.append(f'.. automodule:: {name}', sourcename)
if hasattr(module, '__all__'):
module_attrs = [
attr_name for attr_name in module.__all__
if getattr(module, attr_name).__module__ == name
]
if module_attrs:
contents.append(f" :members: {', '.join(module_attrs)}",
sourcename)
else:
contents.append(' :members:', sourcename)
contents.append('', sourcename)
node = docutils.nodes.section()
nested_parse_with_titles(self.state, contents, node)
# If this module defines any sections, then submodules should go
# inside of the last one.
section = node
for child in node.children:
if isinstance(child, docutils.nodes.section):
section = child
if hasattr(module, '__path__'):
submodules = sorted(module_info.name for module_info in
pkgutil.iter_modules(module.__path__,
prefix=name + '.'))
for submodule in submodules:
section.extend(aux(submodule))
return node.children
with sphinx.ext.autodoc.mock(self.env.config.autodoc_mock_imports):
return aux(self.arguments[0])
def setup(app):
app.setup_extension('sphinx.ext.autodoc')
app.add_directive('autopackage', AutopackageDirective)
return {'parallel_read_safe': True}

View File

@ -0,0 +1,63 @@
# https://pypi.org/project/jaraco.packaging/
#
# Copyright Jason R. Coombs
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import unicode_literals
import os
import sys
import subprocess
if 'check_output' not in dir(subprocess):
import subprocess32 as subprocess
def setup(app):
app.add_config_value('package_url', '', '')
app.connect('builder-inited', load_config_from_setup)
app.connect('html-page-context', add_package_url)
def load_config_from_setup(app):
"""
Replace values in app.config from package metadata
"""
# for now, assume project root is one level up
root = os.path.join(app.confdir, '..')
setup_script = os.path.join(root, 'setup.py')
fields = ['--name', '--version', '--url', '--author']
dist_info_cmd = [sys.executable, setup_script] + fields
output = subprocess.check_output(
dist_info_cmd,
cwd=root,
universal_newlines=True,
)
outputs = output.strip().split('\n')
project, version, url, author = outputs
app.config.project = project
app.config.version = app.config.release = version
app.config.package_url = url
app.config.author = app.config.copyright = author
def add_package_url(app, pagename, templatename, context, doctree):
context['package_url'] = app.config.package_url

1
docs/helpers.rst Normal file
View File

@ -0,0 +1 @@
.. autopackage:: drgn.helpers

37
docs/index.rst Normal file
View File

@ -0,0 +1,37 @@
drgn
====
.. include:: ../README.rst
:start-after: start-introduction
:end-before: end-introduction
In addition to the main Python API, an experimental C library, ``libdrgn``, is
also available.
See the :doc:`installation` instructions. Then, start with the
:doc:`user_guide`.
License
-------
.. include:: ../README.rst
:start-after: start-license
:end-before: end-license
Acknowledgements
----------------
drgn is named after `this
<https://giraffesgiraffes.bandcamp.com/track/drgnfkr-2>`_ because dragons eat
`dwarves <http://dwarfstd.org/>`_.
Table of Contents
-----------------
.. toctree::
:maxdepth: 3
installation
user_guide
api_reference
helpers

47
docs/installation.rst Normal file
View File

@ -0,0 +1,47 @@
Installation
============
.. highlight:: console
drgn depends on `Python <https://www.python.org/>`_ 3.6 or newer as well as
`elfutils <https://sourceware.org/elfutils/>`_. The build requires `GCC
<https://gcc.gnu.org/>`_ or `Clang <https://clang.llvm.org/>`_, `pkgconf
<http://pkgconf.org/>`_, and `setuptools
<https://pypi.org/project/setuptools/>`_. A build from a Git checkout also
requires the GNU Autotools (`autoconf
<https://www.gnu.org/software/autoconf/>`_, `automake
<https://www.gnu.org/software/automake/automake.html>`_, and `libtool
<https://www.gnu.org/software/libtool/libtool.html>`_). Install those
dependencies:
Arch Linux::
$ sudo pacman -S --needed autoconf automake libtool gcc pkgconf libelf python python-setuptools
Debian/Ubuntu::
$ sudo apt-get install autoconf automake libtool gcc pkgconf libelf-dev libdw-dev python3 python3-setuptools
Note that Debian, Ubuntu Trusty, and Ubuntu Xenial ship Python versions which
are too old, so a newer version must be installed manually.
Due to a packaging `bug
<https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=885071>`_, the following may
also be required::
$ sudo apt-get install liblzma-dev zlib1g-dev
Fedora::
$ sudo dnf install autoconf automake libtool gcc pkgconf elfutils-devel python3 python3-setuptools
Then, drgn can be built and installed::
$ python3 setup.py build
$ sudo python3 setup.py install
$ drgn --help
Or, it can be be built and run locally::
$ python3 setup.py build_ext -i
$ python3 -m drgn --help

284
docs/user_guide.rst Normal file
View File

@ -0,0 +1,284 @@
User Guide
==========
Quick Start
-----------
.. include:: ../README.rst
:start-after: start-quick-start
:end-before: end-quick-start
Core Concepts
-------------
.. highlight:: pycon
Programs
^^^^^^^^
A program being debugged is represented by an instance of the
:class:`drgn.Program` class. The drgn CLI is initialized with a ``Program``
named ``prog``; unless you are using the drgn library directly, this is usually
the only ``Program`` you will need.
A ``Program`` is used to look up type definitions, access variables, and read
arbitrary memory::
>>> prog.type('unsigned long')
int_type(name='unsigned long', size=8, is_signed=False)
>>> prog['jiffies']
Object(prog, 'volatile unsigned long', address=0xffffffffbe405000)
>>> prog.read(0xffffffffbe411e10, 16)
b'swapper/0\x00\x00\x00\x00\x00\x00\x00'
The :meth:`drgn.Program.type()`, :meth:`drgn.Program.variable()`,
:meth:`drgn.Program.constant()`, and :meth:`drgn.Program.function()` methods
look up those various things in a program. :meth:`drgn.Program.read()` reads
memory from the program's address space. The :meth:`[]
<drgn.Program.__getitem__>` operator looks up a variable, constant, or
function::
>>> prog['jiffies'] == prog.variable('jiffies')
True
It is usually more convenient to use the ``[]`` operator rather than the
``variable()``, ``constant()``, or ``function()`` methods unless the program
has multiple objects with the same name, in which case the methods provide more
control.
Objects
^^^^^^^
Variables, constants, functions, and computed values are all called *objects*
in drgn. Objects are represented by the :class:`drgn.Object` class. An object
may exist in the memory of the program (a *reference*)::
>>> Object(prog, 'int', address=0xffffffffc09031a0)
Or, an object may be a temporary computed value (a *value*)::
>>> Object(prog, 'int', value=4)
What makes drgn scripts expressive is that objects can be used almost exactly
like they would be in the program's own source code. For example, structure
members can be accessed with the dot (``.``) operator, arrays can be
subscripted with ``[]``, arithmetic can be performed, and objects can be
compared::
>>> print(prog['init_task'].comm[0])
(char)115
>>> print(repr(prog['init_task'].nsproxy.mnt_ns.mounts + 1))
Object(prog, 'unsigned int', value=34)
>>> prog['init_task'].nsproxy.mnt_ns.pending_mounts > 0
False
A common use case is converting a ``drgn.Object`` to a Python value so it can
be used by a standard Python library. There are a few ways to do this:
* The :meth:`drgn.Object.value_()` method gets the value of the object with the
directly corresponding Python type (i.e., integers and pointers become
``int``, floating-point types become ``float``, booleans become ``bool``,
arrays become ``list``, structures and unions become ``dict``).
* The :meth:`drgn.Object.string_()` method gets a null-terminated string as
``bytes`` from an array or pointer.
* The :class:`int() <int>`, :class:`float() <float>`, and :class:`bool()
<bool>` functions do an explicit conversion to that Python type.
Objects have several attributes; the most important are
:attr:`drgn.Object.prog_` and :attr:`drgn.Object.type_`. The former is the
:class:`drgn.Program` that the object is from, and the latter is the
:class:`drgn.Type` of the object.
Note that all attributes and methods of the ``Object`` class end with an
underscore (``_``) in order to avoid conflicting with structure or union
members. The ``Object`` attributes and methods always take precedence; use
:meth:`drgn.Object.member_()` if there is a conflict.
References vs. Values
"""""""""""""""""""""
The main difference between reference objects and value objects is how they are
evaluated. References are read from the program's memory every time they are
evaluated; values simply return the stored value (:meth:`drgn.Object.read_()`
reads a reference object and returns it as a value object)::
>>> import time
>>> jiffies = prog['jiffies']
>>> jiffies.value_()
4391639989
>>> time.sleep(1)
>>> jiffies.value_()
4391640290
>>> jiffies2 = jiffies.read_()
>>> jiffies2.value_()
4391640291
>>> time.sleep(1)
>>> jiffies2.value_()
4391640291
>>> jiffies.value_()
4391640593
References have a :attr:`drgn.Object.address_` attribute, which is the object's
address as a Python ``int``. This is slightly different from the
:meth:`drgn.Object.address_of_()` method, which returns the address as a
``drgn.Object``. Of course, both references and values can have a pointer type;
``address_`` refers to the address of the pointer object itself, and
:meth:`drgn.Object.value_()` refers to the value of the pointer (i.e., the
address it points to)::
>>> address = prog['jiffies'].address_
>>> type(address)
<class 'int'>
>>> print(hex(address))
0xffffffffbe405000
>>> jiffiesp = prog['jiffies'].address_of_()
>>> jiffiesp
Object(prog, 'volatile unsigned long *', value=0xffffffffbe405000)
>>> print(hex(jiffiesp.value_()))
0xffffffffbe405000
Types
^^^^^
drgn automatically obtains type definitions from the program. Types are
represented by the :class:`drgn.Type` class and created by various factory
functions like :func:`int_type()`::
>>> prog.type('int')
int_type(name='int', size=4, is_signed=True)
You won't usually need to work with types directly, but see
:ref:`api-reference-types` if you do.
Helpers
^^^^^^^
Some programs have common data structures that you may want to examine. For
example, consider linked lists in the Linux kernel:
.. code-block:: c
struct list_head {
struct list_head *next, *prev;
};
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
When working with these lists, you'd probably want to define a function:
.. code-block:: python3
def list_for_each(head):
pos = head.next
while pos != head:
yield pos
pos = pos.next
Then, you could use it like so for any list you need to look at::
>>> for pos in list_for_each(head):
... do_something_with(pos)
Of course, it would be a waste of time and effort for everyone to have to
define these helpers for themselves, so drgn includes a collection of helpers
for many use cases. See :doc:`helpers`.
Command Line Interface
----------------------
The drgn CLI is basically a wrapper around the drgn library which automatically
creates a :class:`drgn.Program`. The CLI can be run in interactive mode or
script mode.
Script Mode
^^^^^^^^^^^
Script mode is useful for reusable scripts. Simply pass the path to the script
along with any arguments:
.. code-block:: console
$ cat script.py
import sys
from drgn.helpers.linux import find_task
pid = int(sys.argv[1])
uid = find_task(prog, pid).cred.uid.val.value_()
print(f'PID {pid} is being run by UID {uid}')
$ sudo drgn -k script.py 601
PID 601 is being run by UID 1000
It's even possible to run drgn scripts directly with the proper `shebang
<https://en.wikipedia.org/wiki/Shebang_(Unix)>`_::
$ cat script2.py
#!/usr/bin/drgn -k
mounts = prog['init_task'].nsproxy.mnt_ns.mounts.value_()
print(f'You have {mounts} filesystems mounted')
$ sudo ./script2.py
You have 36 filesystems mounted
You usually shouldn't depend on an executable being installed at a specific
absolute path. With newer versions of GNU coreutils (since v8.30), you can
use |env -S|_:
.. |env -S| replace:: ``env --split-string``
.. _env -S: https://www.gnu.org/software/coreutils/manual/html_node/env-invocation.html#g_t_002dS_002f_002d_002dsplit_002dstring-usage-in-scripts
.. code-block:: sh
#!/usr/bin/env -S drgn -k
Interactive Mode
^^^^^^^^^^^^^^^^
Interactive mode uses the Python interpreter's `interactive mode
<https://docs.python.org/3/tutorial/interpreter.html#interactive-mode>`_ and
adds a few nice features, including:
* History
* Tab completion
* Automatic import of relevant helpers
* Pretty printing of objects and types
The default behavior of the Python `REPL
<https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop>`_ is to
print the output of :func:`repr()`. For :class:`drgn.Object` and
:class:`drgn.Type`, this is a raw representation::
>>> print(repr(prog['jiffies']))
Object(prog, 'volatile unsigned long', address=0xffffffffbe405000)
>>> print(repr(prog.type('atomic_t')))
typedef_type(name='atomic_t', type=struct_type(tag=None, size=4, members=((int_type(name='int', size=4, is_signed=True), 'counter', 0, 0),)))
The standard :func:`print()` function uses the output of :func:`str()`. For
drgn objects and types, this is a representation in programming language
syntax::
>>> print(prog['jiffies'])
(volatile unsigned long)4395387628
>>> print(prog.type('atomic_t'))
typedef struct {
int counter;
} atomic_t
In interactive mode, the drgn CLI automatically uses ``str()`` instead of
``repr()`` for objects and types, so you don't need to call ``print()``
explicitly::
$ sudo drgn -k
>>> prog['jiffies']
(volatile unsigned long)4395387628
>>> prog.type('atomic_t')
typedef struct {
int counter;
} atomic_t
Next Steps
----------
Refer to the :doc:`api_reference`. Look through the :doc:`helpers`. Browse
through the official `examples
<https://github.com/osandov/drgn/tree/master/examples>`_.

View File

@ -40,18 +40,17 @@ that package should be considered implementation details and should not
be used.
"""
from typing import Union
from _drgn import (
__version__,
FaultError,
FileFormatError,
NULL,
Object,
Program,
ProgramFlags,
Qualifiers,
Type,
TypeKind,
__version__,
array_type,
bool_type,
cast,
@ -102,13 +101,3 @@ __all__ = [
'union_type',
'void_type',
]
def NULL(prog: Program, type: Union[str, Type]) -> Object:
"""
Return an Object representing NULL cast to the given type. The type can
be a string or a Type object.
This is equivalent to Object(prog, type, value=0).
"""
return Object(prog, type, value=0)

View File

@ -1,11 +1,18 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Common program helpers
Helpers
-------
This package contains subpackages which provide helpers for working with
particular types of programs.
The ``drgn.helpers`` package contains subpackages which provide helpers for
working with particular types of programs. Currently, there are only helpers
for the Linux kernel. In the future, there may be helpers for, e.g., glibc and
libstdc++.
Parameter types and return types are :class:`drgn.Object` unless noted
otherwise. Many helpers include a C function signature indicating the expected
object types.
"""
from typing import Iterable

View File

@ -1,21 +1,33 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel helpers
Linux Kernel
------------
This package contains several modules for working with data structures and
subsystems in the Linux kernel. The helpers are available from the individual
modules in which they are defined and from this top-level package. E.g., the
following are both valid:
The ``drgn.helpers.linux`` package contains several modules for working with
data structures and subsystems in the Linux kernel. The helpers are available
from the individual modules in which they are defined and from this top-level
package. E.g., the following are both valid:
>>> from drgn.helpers.linux.list import list_for_each_entry
>>> from drgn.helpers.linux import list_for_each_entry
In interactive mode, the following is done automatically when debugging the
Linux kernel:
Iterator macros (``for_each_foo``) are a common idiom in the Linux kernel. The
equivalent drgn helpers are implemented as Python :ref:`generators
<python:tut-generators>`. For example, the following code in C:
>>> from drgn.helpers.linux import *
.. code-block:: c
list_for_each(pos, head)
do_something_with(pos);
Translates to the following code in Python:
.. code-block:: python3
for pos in list_for_each(head):
do_something_with(pos)
"""
from drgn.helpers.linux.block import *

View File

@ -1,92 +1,110 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel block layer helpers
Block Layer
-----------
This module provides helpers for working with the Linux block layer, including
disks (struct gendisk) and partitions (struct hd_struct).
The ``drgn.helpers.linux.block`` module provides helpers for working with the
Linux block layer, including disks (``struct gendisk``) and partitions
(``struct hd_struct``).
"""
import typing
from drgn import Object, container_of
from drgn.helpers import escape_string
from drgn.helpers.linux.device import MAJOR, MINOR
from drgn.helpers.linux.device import MAJOR, MINOR, MKDEV
from drgn.helpers.linux.list import list_for_each_entry
__all__ = [
'Disk',
'Partition',
'disk_devt',
'disk_name',
'for_each_disk',
'print_disks',
'part_devt',
'part_name',
'for_each_partition',
'print_partitions',
]
class Disk(typing.NamedTuple):
"""A disk. gendisk is a struct gendisk * object."""
major: int
minor: int
name: bytes
gendisk: Object
def disk_devt(disk):
"""
.. c:function:: dev_t disk_devt(struct gendisk *disk)
Get a disk's device number.
"""
return MKDEV(disk.major, disk.first_minor)
def disk_name(disk):
"""
.. c:function:: char *disk_name(struct gendisk *disk)
Get the name of a disk (e.g., ``sda``).
:rtype: bytes
"""
return disk.disk_name.string_()
def for_each_disk(prog):
"""
for_each_disk() -> Iterator[Disk]
Iterate over all disks in the system.
Return an iterator over all disks in the system.
:return: Iterator of ``struct gendisk *`` objects.
"""
devices = prog['block_class'].p.klist_devices.k_list.address_of_()
disk_type = prog['disk_type'].address_of_()
for device in list_for_each_entry('struct device', devices, 'knode_class.n_node'):
for device in list_for_each_entry('struct device', devices,
'knode_class.n_node'):
if device.type == disk_type:
obj = container_of(device, 'struct gendisk', 'part0.__dev')
dev = device.devt.value_()
yield Disk(MAJOR(dev), MINOR(dev), device.kobj.name.string_(), obj)
yield container_of(device, 'struct gendisk', 'part0.__dev')
def print_disks(prog):
"""Print all of the disks in the system."""
for disk in for_each_disk(prog):
major = disk.major.value_()
minor = disk.first_minor.value_()
name = escape_string(disk_name(disk), escape_backslash=True)
print(f'{major}:{minor} {name} ({disk.type_.type_name()})0x{disk.value_():x}')
def part_devt(part):
"""
print_disks()
.. c:function:: dev_t part_devt(struct hd_struct *part)
Print all of the disks in the system.
Get a partition's device number.
"""
for major, minor, name, obj in for_each_disk(prog):
name = escape_string(name, escape_backslash=True)
print(f'{major}:{minor} {name} ({obj.type_.type_name()})0x{obj.value_():x}')
return part.__dev.devt
class Partition(typing.NamedTuple):
"""A disk partition. hd_struct is a struct hd_struct * object."""
major: int
minor: int
name: bytes
hd_struct: Object
def part_name(part):
"""
.. c:function:: char *part_name(struct hd_struct *part)
Get the name of a partition (e.g., ``sda1``).
:rtype: bytes
"""
return part.__dev.kobj.name.string_()
def for_each_partition(prog):
"""
for_each_partition() -> Iterator[Partition]
Iterate over all partitions in the system.
Return an iterator over all partitions in the system.
:return: Iterator of ``struct hd_struct *`` objects.
"""
devices = prog['block_class'].p.klist_devices.k_list.address_of_()
for device in list_for_each_entry('struct device', devices, 'knode_class.n_node'):
obj = container_of(device, 'struct hd_struct', '__dev')
dev = device.devt.value_()
yield Partition(MAJOR(dev), MINOR(dev), device.kobj.name.string_(),
obj)
for device in list_for_each_entry('struct device', devices,
'knode_class.n_node'):
yield container_of(device, 'struct hd_struct', '__dev')
def print_partitions(prog):
"""
print_partitions()
Print all of the partitions in the system.
"""
for major, minor, name, obj in for_each_partition(prog):
name = escape_string(name, escape_backslash=True)
print(f'{major}:{minor} {name} ({obj.type_.type_name()})0x{obj.value_():x}')
"""Print all of the partitions in the system."""
for part in for_each_partition(prog):
devt = part_devt(part).value_()
name = escape_string(part_name(part), escape_backslash=True)
print(f'{MAJOR(devt)}:{MINOR(devt)} {name} ({part.type_.type_name()})0x{part.value_():x}')

View File

@ -1,10 +1,12 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel CPU mask helpers
CPU Masks
---------
This module provides helpers for working with CPU masks from "linux/cpumask.h".
The ``drgn.helpers.linux.cpumask`` module provides helpers for working with CPU
masks from :linux:`include/linux/cpumask.h`.
"""
__all__ = [
@ -17,9 +19,11 @@ __all__ = [
def for_each_cpu(mask):
"""
for_each_cpu(struct cpumask)
.. c:function:: for_each_cpu(struct cpumask mask)
Return an iterator over all of the CPUs in the given mask, as ints.
Iterate over all of the CPUs in the given mask.
:rtype: Iterator[int]
"""
bits = mask.bits
word_bits = 8 * bits.type_.type.sizeof()
@ -32,26 +36,26 @@ def for_each_cpu(mask):
def for_each_possible_cpu(prog):
"""
for_each_possible_cpu()
Iterate over all possible CPUs.
Return an iterator over all possible CPUs, as ints.
:rtype: Iterator[int]
"""
return for_each_cpu(prog['__cpu_possible_mask'])
def for_each_online_cpu(prog):
"""
for_each_online_cpu()
Iterate over all online CPUs.
Return an iterator over all online CPUs, as ints.
:rtype: Iterator[int]
"""
return for_each_cpu(prog['__cpu_online_mask'])
def for_each_present_cpu(prog):
"""
for_each_present_cpu()
Iterate over all present CPUs.
Return an iterator over all present CPUs, as ints.
:rtype: Iterator[int]
"""
return for_each_cpu(prog['__cpu_present_mask'])

View File

@ -1,14 +1,15 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel device helpers
Devices
-------
This module provides helpers for working with Linux devices, including the
kernel encoding of dev_t.
The ``drgn.helpers.linux.device`` module provides helpers for working with
Linux devices, including the kernel encoding of ``dev_t``.
"""
from drgn import cast, Object
from drgn import Object, cast
__all__ = [
'MAJOR',
@ -24,9 +25,9 @@ _MINORMASK = ((1 << _MINORBITS) - 1)
def MAJOR(dev):
"""
unsigned int MAJOR(dev_t)
.. c:function:: unsigned int MAJOR(dev_t dev)
Return the major ID of a kernel dev_t.
Return the major ID of a kernel ``dev_t``.
"""
major = dev >> _MINORBITS
if isinstance(major, Object):
@ -36,9 +37,9 @@ def MAJOR(dev):
def MINOR(dev):
"""
unsigned int MINOR(dev_t)
.. c:function:: unsigned int MINOR(dev_t dev)
Return the major ID of a kernel dev_t.
Return the minor ID of a kernel ``dev_t``.
"""
minor = dev & _MINORMASK
if isinstance(minor, Object):
@ -48,9 +49,9 @@ def MINOR(dev):
def MKDEV(major, minor):
"""
dev_t MKDEV(unsigned int major, unsigned int minor)
.. c:function:: dev_t MKDEV(unsigned int major, unsigned int minor)
Return a kernel dev_t from the major and minor IDs.
Return a kernel ``dev_t`` from the major and minor IDs.
"""
dev = (major << _MINORBITS) | minor
if isinstance(dev, Object):

View File

@ -1,26 +1,28 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel filesystem helpers
Virtual Filesystem Layer
------------------------
This module provides helpers for working with the Linux virtual filesystem
(VFS) layer, including mounts, dentries, and inodes.
The ``drgn.helpers.linux.fs`` module provides helpers for working with the
Linux virtual filesystem (VFS) layer, including mounts, dentries, and inodes.
"""
import os
import typing
from drgn import Object, Program, container_of
from drgn.helpers import escape_string
from drgn.helpers.linux.list import hlist_for_each_entry, list_for_each_entry
__all__ = [
'Mount',
'd_path',
'dentry_path',
'inode_path',
'inode_paths',
'mount_src',
'mount_dst',
'mount_fstype',
'for_each_mount',
'print_mounts',
'fget',
@ -31,10 +33,10 @@ __all__ = [
def d_path(path_or_vfsmnt, dentry=None):
"""
char *d_path(struct path *)
char *d_path(struct vfsmount *, struct dentry *)
.. c:function:: char *d_path(struct path *path)
.. c:function:: char *d_path(struct vfsmount *vfsmnt, struct dentry *dentry)
Return the full path of a dentry given a struct path or a mount and a
Return the full path of a dentry given a ``struct path *`` or a mount and a
dentry.
"""
type_name = str(path_or_vfsmnt.type_.type_name())
@ -72,7 +74,7 @@ def d_path(path_or_vfsmnt, dentry=None):
def dentry_path(dentry):
"""
char *dentry_path(struct dentry *)
.. c:function:: char *dentry_path(struct dentry *dentry)
Return the path of a dentry from the root of its filesystem.
"""
@ -88,7 +90,7 @@ def dentry_path(dentry):
def inode_path(inode):
"""
char *inode_path(struct inode *)
.. c:function:: char *inode_path(struct inode *inode)
Return any path of an inode from the root of its filesystem.
"""
@ -98,34 +100,70 @@ def inode_path(inode):
def inode_paths(inode):
"""
inode_paths(struct inode *)
.. c:function:: inode_paths(struct inode *inode)
Return an iterator over all of the paths of an inode from the root of its
filesystem.
:rtype: Iterator[bytes]
"""
return (
dentry_path(dentry) for dentry in
hlist_for_each_entry('struct dentry', inode.i_dentry.address_of_(), 'd_u.d_alias')
hlist_for_each_entry('struct dentry', inode.i_dentry.address_of_(),
'd_u.d_alias')
)
class Mount(typing.NamedTuple):
"""A mounted filesystem. mount is a struct mount * object."""
src: bytes
dst: bytes
fstype: bytes
mount: Object
def mount_src(mnt):
"""
.. c:function:: char *mount_src(struct mount *mnt)
Get the source device name for a mount.
:rtype: bytes
"""
return mnt.mnt_devname.string_()
def mount_dst(mnt):
"""
.. c:function:: char *mount_dst(struct mount *mnt)
Get the path of a mount point.
:rtype: bytes
"""
return d_path(mnt.mnt.address_of_(), mnt.mnt.mnt_root)
def mount_fstype(mnt):
"""
.. c:function:: char *mount_fstype(struct mount *mnt)
Get the filesystem type of a mount.
:rtype: bytes
"""
sb = mnt.mnt.mnt_sb.read_()
fstype = sb.s_type.name.string_()
subtype = sb.s_subtype.read_()
if subtype:
subtype = subtype.string_()
if subtype:
fstype += b'.' + subtype
return fstype
def for_each_mount(prog_or_ns, src=None, dst=None, fstype=None):
"""
for_each_mount(struct mnt_namespace *, char *src, char *dst,
char *fstype) -> Iterator[Mount]
.. c:function:: for_each_mount(struct mnt_namespace *ns, char *src, char *dst, char *fstype)
Return an iterator over all of the mounts in a given namespace. If given a
Program object instead, the initial mount namespace is used. The returned
Iterate over all of the mounts in a given namespace. If given a
:class:`Program` instead, the initial mount namespace is used. returned
mounts can be filtered by source, destination, or filesystem type, all of
which are encoded using os.fsencode().
which are encoded using :func:`os.fsencode()`.
:return: Iterator of ``struct mount *`` objects.
"""
if isinstance(prog_or_ns, Program):
ns = prog_or_ns['init_task'].nsproxy.mnt_ns
@ -139,57 +177,44 @@ def for_each_mount(prog_or_ns, src=None, dst=None, fstype=None):
fstype = os.fsencode(fstype)
for mnt in list_for_each_entry('struct mount', ns.list.address_of_(),
'mnt_list'):
mnt_src = mnt.mnt_devname.string_()
if src is not None and mnt_src != src:
continue
mnt_dst = d_path(mnt.mnt.address_of_(), mnt.mnt.mnt_root)
if dst is not None and mnt_dst != dst:
continue
sb = mnt.mnt.mnt_sb.read_()
mnt_fstype = sb.s_type.name.string_()
subtype = sb.s_subtype.read_()
if subtype:
subtype = subtype.string_()
if subtype:
mnt_fstype += b'.' + subtype
if fstype is not None and mnt_fstype != fstype:
continue
yield Mount(mnt_src, mnt_dst, mnt_fstype, mnt)
if ((src is None or mount_src(mnt) == src) and
(dst is None or mount_dst(mnt) == dst) and
(fstype is None or mount_fstype(mnt) == fstype)):
yield mnt
def print_mounts(prog_or_ns, src=None, dst=None, fstype=None):
"""
print_mounts(struct mnt_namespace *, char *src, char *dst, char *fstype)
.. c:function:: print_mounts(struct mnt_namespace *ns, char *src, char *dst, char *fstype)
Print the mount table of a given namespace. The arguments are the same as
for_each_mount(). The output format is similar to /proc/mounts but prints
the value of each struct mount *.
:func:`for_each_mount()`. The output format is similar to ``/proc/mounts``
but prints the value of each ``struct mount *``.
"""
for mnt_src, mnt_dst, mnt_fstype, mnt in for_each_mount(prog_or_ns, src,
dst, fstype):
mnt_src = escape_string(mnt_src, escape_backslash=True)
mnt_dst = escape_string(mnt_dst, escape_backslash=True)
mnt_fstype = escape_string(mnt_fstype, escape_backslash=True)
for mnt in for_each_mount(prog_or_ns, src, dst, fstype):
mnt_src = escape_string(mount_src(mnt), escape_backslash=True)
mnt_dst = escape_string(mount_dst(mnt), escape_backslash=True)
mnt_fstype = escape_string(mount_fstype(mnt), escape_backslash=True)
print(f'{mnt_src} {mnt_dst} {mnt_fstype} ({mnt.type_.type_name()})0x{mnt.value_():x}')
def fget(task, fd):
"""
struct file *fget(struct task_struct *, int fd)
.. c:function:: struct file *fget(struct task_struct *task, int fd)
Return the kernel file descriptor (struct file *) of the fd of a given
task.
Return the kernel file descriptor of the fd of a given task.
"""
return task.files.fdt.fd[fd]
def for_each_file(task):
"""
for_each_file(struct task_struct *)
.. c:function:: for_each_file(struct task_struct *task)
Return an iterator over all of the files open in a given task. The
generated values are (fd, path, struct file *) tuples. The path is returned
as bytes.
Iterate over all of the files open in a given task.
:return: Iterator of (fd, ``struct file *``) tuples.
:rtype: Iterator[tuple[int, Object]]
"""
fdt = task.files.fdt.read_()
bits_per_long = 8 * fdt.open_fds.type_.type.size
@ -199,16 +224,17 @@ def for_each_file(task):
if word & (1 << j):
fd = i * bits_per_long + j
file = fdt.fd[fd].read_()
yield fd, d_path(file.f_path), file
yield fd, file
def print_files(task):
"""
print_files(struct task_struct *)
.. c:function:: print_files(struct task_struct *task)
Print the open files of a given task.
"""
for fd, path, file in for_each_file(task):
for fd, file in for_each_file(task):
path = d_path(file.f_path)
if path is None:
path = file.f_inode.i_sb.s_type.name.string_()
path = escape_string(path, escape_backslash=True)

View File

@ -1,13 +1,14 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel IDR helpers
IDR
---
This module provides helpers for working with the IDR data structure in
"linux/idr.h". An IDR provides a mapping from an ID to a pointer. This
currently only supports Linux v4.11+; before this, IDRs were not based on radix
trees.
The ``drgn.helpers.linux.idr`` module provides helpers for working with the IDR
data structure in :linux:`include/linux/idr.h`. An IDR provides a mapping from
an ID to a pointer. This currently only supports Linux v4.11+; before this,
IDRs were not based on radix trees.
"""
from drgn.helpers.linux.radixtree import radix_tree_for_each, radix_tree_lookup
@ -21,10 +22,10 @@ __all__ = [
def idr_find(idr, id):
"""
void *idr_find(struct idr *, unsigned long id)
.. c:function:: void *idr_find(struct idr *idr, unsigned long id)
Look up the entry with the given id in an IDR. If it is not found, this
returns a NULL object.
returns a ``NULL`` object.
"""
# idr_base was added in v4.16.
try:
@ -36,10 +37,12 @@ def idr_find(idr, id):
def idr_for_each(idr):
"""
idr_for_each(struct idr *)
.. c:function:: idr_for_each(struct idr *idr)
Return an iterator over all of the entries in an IDR. The generated values
are (index, entry) tuples.
Iterate over all of the entries in an IDR.
:return: Iterator of (index, ``void *``) tuples.
:rtype: Iterator[tuple[int, Object]]
"""
try:
base = idr.idr_base.value_()

View File

@ -1,11 +1,13 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel linked list helpers
Linked Lists
------------
This module provides helpers for working with the doubly-linked list
implementations in "linux/list.h".
The ``drgn.helpers.linux.list`` module provides helpers for working with the
doubly-linked list implementations (``struct list_head`` and ``struct
hlist_head``) in :linux:`include/linux/list.h`.
"""
from drgn import container_of
@ -26,7 +28,7 @@ __all__ = [
def list_empty(head):
"""
bool list_empty(struct list_head *)
.. c:function:: bool list_empty(struct list_head *head)
Return whether a list is empty.
"""
@ -36,7 +38,7 @@ def list_empty(head):
def list_is_singular(head):
"""
bool list_is_singular(struct list_head *)
.. c:function:: bool list_is_singular(struct list_head *head)
Return whether a list has only one element.
"""
@ -47,9 +49,11 @@ def list_is_singular(head):
def list_for_each(head):
"""
list_for_each(struct list_head *)
.. c:function:: list_for_each(struct list_head *head)
Return an iterator over all of the nodes in a list.
Iterate over all of the nodes in a list.
:return: Iterator of ``struct list_head *`` objects.
"""
head = head.read_()
pos = head.next.read_()
@ -60,9 +64,11 @@ def list_for_each(head):
def list_for_each_reverse(head):
"""
list_for_each_reverse(struct list_head *)
.. c:function:: list_for_each_reverse(struct list_head *head)
Return an iterator over all of the nodes in a list in reverse order.
Iterate over all of the nodes in a list in reverse order.
:return: Iterator of ``struct list_head *`` objects.
"""
head = head.read_()
pos = head.prev.read_()
@ -73,10 +79,12 @@ def list_for_each_reverse(head):
def list_for_each_entry(type, head, member):
"""
list_for_each_entry(type, struct list_head *, member)
.. c:function:: list_for_each_entry(type, struct list_head *head, member)
Return an iterator over all of the entries in a list, given the type of the
entry and the struct list_head member in that type.
Iterate over all of the entries in a list, given the type of the entry and
the ``struct list_head`` member in that type.
:return: Iterator of ``type *`` objects.
"""
for pos in list_for_each(head):
yield container_of(pos, type, member)
@ -84,10 +92,12 @@ def list_for_each_entry(type, head, member):
def list_for_each_entry_reverse(type, head, member):
"""
list_for_each_entry_reverse(type, struct list_head *, member)
.. c:function:: list_for_each_entry_reverse(type, struct list_head *head, member)
Return an iterator over all of the entries in a list in reverse order,
given the type of the entry and the struct list_head member in that type.
Iterate over all of the entries in a list in reverse order, given the type
of the entry and the ``struct list_head`` member in that type.
:return: Iterator of ``type *`` objects.
"""
for pos in list_for_each_reverse(head):
yield container_of(pos, type, member)
@ -95,7 +105,7 @@ def list_for_each_entry_reverse(type, head, member):
def hlist_empty(head):
"""
bool hlist_empty(struct hlist_head *)
.. c:function:: bool hlist_empty(struct hlist_head *head)
Return whether a hash list is empty.
"""
@ -104,9 +114,11 @@ def hlist_empty(head):
def hlist_for_each(head):
"""
hlist_for_each(struct hlist_head *)
.. c:function:: hlist_for_each(struct hlist_head *head)
Return an iterator over all of the nodes in a hash list.
Iterate over all of the nodes in a hash list.
:return: Iterator of ``struct hlist_node *`` objects.
"""
pos = head.first.read_()
while pos:
@ -116,10 +128,12 @@ def hlist_for_each(head):
def hlist_for_each_entry(type, head, member):
"""
hlist_for_each_entry(type, struct hlist_head *, member)
.. c:function:: hlist_for_each_entry(type, struct hlist_head *head, member)
Return an iterator over all of the entries in a has list, given the type of
the entry and the struct hlist_node member in that type.
Iterate over all of the entries in a has list, given the type of the entry
and the ``struct hlist_node`` member in that type.
:return: Iterator of ``type *`` objects.
"""
for pos in hlist_for_each(head):
yield container_of(pos, type, member)

View File

@ -1,14 +1,15 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel memory management helpers
Memory Management
-----------------
This module provides helpers for working with the Linux memory management (mm)
subsystem. Only x86-64 support is currently implemented.
The ``drgn.helpers.linux.mm`` provides helpers for working with the Linux
memory management (MM) subsystem. Only x86-64 support is currently implemented.
"""
from drgn import cast, Object
from drgn import Object, cast
__all__ = [
@ -42,9 +43,9 @@ def _page_offset(prog):
def for_each_page(prog):
"""
for_each_page()
Iterate over all pages in the system.
Return an iterator over each struct page * in the system.
:return: Iterator of ``struct page *`` objects.
"""
vmemmap = _vmemmap(prog)
for i in range(prog['max_pfn']):
@ -53,7 +54,7 @@ def for_each_page(prog):
def page_to_pfn(page):
"""
unsigned long page_to_pfn(struct page *)
.. c:function:: unsigned long page_to_pfn(struct page *page)
Get the page frame number (PFN) of a page.
"""
@ -62,10 +63,10 @@ def page_to_pfn(page):
def pfn_to_page(prog_or_pfn, pfn=None):
"""
struct page *pfn_to_page(unsigned long)
.. c:function:: struct page *pfn_to_page(unsigned long pfn)
Get the page with the given page frame number (PFN). This can take the PFN
as an Object or a Program and the PFN as an int.
as an :class:`Object`, or a :class:`Program` and the PFN as an ``int``.
"""
if pfn is None:
prog = prog_or_pfn.prog_
@ -77,26 +78,28 @@ def pfn_to_page(prog_or_pfn, pfn=None):
def virt_to_pfn(prog_or_addr, addr=None):
"""
unsigned long virt_to_pfn(void *)
.. c:function:: unsigned long virt_to_pfn(void *addr)
Get the page frame number (PFN) of a directly mapped virtual address. This
can take the address as an Object or a Program and the address as an int.
can take the address as an :class:`Object`, or a :class:`Program` and the
address as an ``int``.
"""
if addr is None:
prog = prog_or_addr.prog_
addr = prog_or_addr.value_()
else:
prog = prog_or_addr
return Object(prog, 'unsigned long', value=(addr - _page_offset(prog)) >> 12)
return Object(prog, 'unsigned long',
value=(addr - _page_offset(prog)) >> 12)
def pfn_to_virt(prog_or_pfn, pfn=None):
"""
void *pfn_to_virt(unsigned long)
.. c:function:: void *pfn_to_virt(unsigned long pfn)
Get the directly mapped virtual address of the given page frame number
(PFN). This can take the PFN as an Object or a Program and the PFN as an
int.
(PFN). This can take the PFN as an :class:`Object`, or a :class:`Program`
and the PFN as an ``int``.
"""
if pfn is None:
prog = prog_or_pfn.prog_
@ -108,7 +111,7 @@ def pfn_to_virt(prog_or_pfn, pfn=None):
def page_to_virt(page):
"""
void *page_to_virt(struct page *)
.. c:function:: void *page_to_virt(struct page *page)
Get the directly mapped virtual address of a page.
"""
@ -117,9 +120,10 @@ def page_to_virt(page):
def virt_to_page(prog_or_addr, addr=None):
"""
struct page *virt_to_page(void *)
.. c:function:: struct page *virt_to_page(void *addr)
Get the page containing a directly mapped virtual address. This can take
the address as an Object or a Program and the address as an int.
the address as an :class:`Object`, or a :class:`Program` and the address as
an ``int``.
"""
return pfn_to_page(virt_to_pfn(prog_or_addr, addr))

View File

@ -1,11 +1,13 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel per-CPU helpers
Per-CPU
-------
This module provides helpers for working with per-CPU allocations from
"linux/percpu.h" and per-CPU counters from "linux/percpu_counter.h".
The ``drgn.helpers.linux.percpu`` module provides helpers for working with
per-CPU allocations from :linux:`include/linux/percpu.h` and per-CPU counters
from :linux:`include/linux/percpu_counter.h`.
"""
from drgn import Object
@ -20,7 +22,7 @@ __all__ = [
def per_cpu_ptr(ptr, cpu):
"""
type *per_cpu_ptr(type __percpu *ptr, int cpu)
.. c:function:: type *per_cpu_ptr(type __percpu *ptr, int cpu)
Return the per-CPU pointer for a given CPU.
"""
@ -30,7 +32,7 @@ def per_cpu_ptr(ptr, cpu):
def percpu_counter_sum(fbc):
"""
s64 percpu_counter_sum(struct percpu_counter *fbc)
.. c:function:: s64 percpu_counter_sum(struct percpu_counter *fbc)
Return the sum of a per-CPU counter.
"""

View File

@ -1,13 +1,15 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel process ID helpers
Process IDS
-----------
This module provides helpers for looking up process IDs.
The ``drgn.helpers.linux.pid`` module provides helpers for looking up process
IDs and processes.
"""
from drgn import cast, container_of, NULL, Program
from drgn import NULL, Program, cast, container_of
from drgn.helpers.linux.idr import idr_find, idr_for_each
from drgn.helpers.linux.list import hlist_for_each_entry
@ -22,10 +24,11 @@ __all__ = [
def find_pid(prog_or_ns, nr):
"""
struct pid *find_pid(struct pid_namespace *, int)
.. c:function:: struct pid *find_pid(struct pid_namespace *ns, int nr)
Return the struct pid for the given PID in the given namespace. If given a
Program object instead, the initial PID namespace is used.
Return the ``struct pid *`` for the given PID number in the given
namespace. If given a :class:`Program` instead, the initial PID namespace
is used.
"""
if isinstance(prog_or_ns, Program):
prog = prog_or_ns
@ -53,11 +56,12 @@ def find_pid(prog_or_ns, nr):
def for_each_pid(prog_or_ns):
"""
for_each_pid(struct pid_namespace *)
.. c:function:: for_each_pid(struct pid_namespace *ns)
Return an iterator over all of the PIDs in the given namespace. If given a
Program object instead, the initial PID namespace is used. The generated
values are struct pid * objects.
Iterate over all of the PIDs in the given namespace. If given a
:class:`Program` instead, the initial PID namespace is used.
:return: Iterator of ``struct pid *`` objects.
"""
if isinstance(prog_or_ns, Program):
prog = prog_or_ns
@ -81,10 +85,10 @@ def for_each_pid(prog_or_ns):
def pid_task(pid, pid_type):
"""
struct task_struct *pid_task(struct pid *, enum pid_type)
.. c:function:: struct task_struct *pid_task(struct pid *pid, enum pid_type pid_type)
Return the struct task_struct containing the given struct pid of the given
type.
Return the ``struct task_struct *`` containing the given ``struct pid *``
of the given type.
"""
if not pid:
return NULL(pid.prog_, 'struct task_struct *')
@ -101,10 +105,10 @@ def pid_task(pid, pid_type):
def find_task(prog_or_ns, pid):
"""
struct task_struct *find_task(struct pid_namespace *, int pid)
.. c:function:: struct task_struct *find_task(struct pid_namespace *ns, int pid)
Return the task with the given PID in the given namespace. If given a
Program object instead, the initial PID namespace is used.
:class:`Program` instead, the initial PID namespace is used.
"""
if isinstance(prog_or_ns, Program):
prog = prog_or_ns
@ -115,11 +119,12 @@ def find_task(prog_or_ns, pid):
def for_each_task(prog_or_ns):
"""
for_each_task(struct pid_namespace *)
.. c:function:: for_each_task(struct pid_namespace *ns)
Return an iterator over all of the tasks visible in the given namespace. If
given a Program object instead, the initial PID namespace is used. The
generated values are struct task_struct * objects.
Iterate over all of the tasks visible in the given namespace. If given a
:class:`Program` instead, the initial PID namespace is used.
:return: Iterator of ``struct task_struct *`` objects.
"""
if isinstance(prog_or_ns, Program):
prog = prog_or_ns

View File

@ -1,14 +1,15 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel radix tree helpers
Radix Trees
-----------
This module provides helpers for working with radix trees from
"linux/radix-tree.h".
The ``drgn.helpers.linux.radixtree`` module provides helpers for working with
radix trees from :linux:`include/linux/radix-tree.h`.
"""
from drgn import cast, Object
from drgn import Object, cast
__all__ = [
@ -38,10 +39,10 @@ def _radix_tree_root_node(root):
def radix_tree_lookup(root, index):
"""
void *radix_tree_lookup(struct radix_tree_root *, unsigned long index)
.. c:function:: void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
Look up the entry at a given index in a radix tree. If it is not found,
this returns a NULL object.
this returns a ``NULL`` object.
"""
node, RADIX_TREE_INTERNAL_NODE = _radix_tree_root_node(root)
RADIX_TREE_MAP_MASK = node.slots.type_.length - 1
@ -56,10 +57,12 @@ def radix_tree_lookup(root, index):
def radix_tree_for_each(root):
"""
radix_tree_for_each(struct radix_tree_root *)
.. c:function:: radix_tree_for_each(struct radix_tree_root *root)
Return an iterator over all of the entries in a radix tree. The generated
values are (index, entry) tuples.
Iterate over all of the entries in a radix tree.
:return: Iterator of (index, ``void *``) tuples.
:rtype: Iterator[tuple[int, Object]]
"""
node, RADIX_TREE_INTERNAL_NODE = _radix_tree_root_node(root)
def aux(node, index):

View File

@ -1,14 +1,15 @@
# Copyright 2018 - Omar Sandoval
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
"""
Linux kernel red-black tree helpers
Red-Black Trees
---------------
This module provides helpers for working with red-black trees from
"linux/rbtree.h"
The ``drgn.helpers.linux.rbtree`` module provides helpers for working with
red-black trees from :linux:`include/linux/rbtree.h`.
"""
from drgn import container_of, Object
from drgn import Object, container_of
__all__ = [
@ -26,7 +27,7 @@ __all__ = [
def RB_EMPTY_NODE(node):
"""
bool RB_EMPTY_NODE(struct rb_node *)
.. c:function:: bool RB_EMPTY_NODE(struct rb_node *node)
Return whether a red-black tree node is empty, i.e., not inserted in a
tree.
@ -36,7 +37,7 @@ def RB_EMPTY_NODE(node):
def rb_parent(node):
"""
struct rb_node *rb_parent(struct rb_node *)
.. c:function:: struct rb_node *rb_parent(struct rb_node *node)
Return the parent node of a red-black tree node.
"""
@ -46,10 +47,10 @@ def rb_parent(node):
def rb_first(root):
"""
struct rb_node *rb_first(struct rb_root *)
.. c:function:: struct rb_node *rb_first(struct rb_root *root)
Return the first node (in sort order) in a red-black tree, or a NULL object
if the tree is empty.
Return the first node (in sort order) in a red-black tree, or a ``NULL``
object if the tree is empty.
"""
node = root.rb_node.read_()
if not node:
@ -63,10 +64,10 @@ def rb_first(root):
def rb_last(root):
"""
struct rb_node *rb_last(struct rb_root *)
.. c:function:: struct rb_node *rb_last(struct rb_root *root)
Return the last node (in sort order) in a red-black tree, or a NULL object
if the tree is empty.
Return the last node (in sort order) in a red-black tree, or a ``NULL``
object if the tree is empty.
"""
node = root.rb_node.read_()
if not node:
@ -80,9 +81,9 @@ def rb_last(root):
def rb_next(node):
"""
struct rb_node *rb_next(struct rb_node *)
.. c:function:: struct rb_node *rb_next(struct rb_node *node)
Return the next node (in sort order) after a red-black node, or a NULL
Return the next node (in sort order) after a red-black node, or a ``NULL``
object if the node is the last node in the tree or is empty.
"""
node = node.read_()
@ -108,10 +109,10 @@ def rb_next(node):
def rb_prev(node):
"""
struct rb_node *rb_prev(struct rb_node *)
.. c:function:: struct rb_node *rb_prev(struct rb_node *node)
Return the previous node (in sort order) before a red-black node, or a NULL
object if the node is the first node in the tree or is empty.
Return the previous node (in sort order) before a red-black node, or a
``NULL`` object if the node is the first node in the tree or is empty.
"""
node = node.read_()
@ -136,10 +137,11 @@ def rb_prev(node):
def rbtree_inorder_for_each(root):
"""
rbtree_inorder_for_each(struct rb_root *)
.. c:function:: rbtree_inorder_for_each(struct rb_root *root)
Return an iterator over all of the nodes in a red-black tree, in sort
order.
Iterate over all of the nodes in a red-black tree, in sort order.
:return: Iterator of ``struct rb_node *`` objects.
"""
def aux(node):
if node:
@ -151,11 +153,13 @@ def rbtree_inorder_for_each(root):
def rbtree_inorder_for_each_entry(type, root, member):
"""
rbtree_inorder_for_each_entry(type, struct rb_root *, member)
.. c:function:: rbtree_inorder_for_each_entry(type, struct rb_root *root, member)
Return an iterator over all of the entries in a red-black tree, given the
type of the entry and the struct list_head member in that type. The entries
are returned in sort order.
Iterate over all of the entries in a red-black tree, given the type of the
entry and the ``struct rb_node`` member in that type. The entries are
returned in sort order.
:return: Iterator of ``type *`` objects.
"""
for node in rbtree_inorder_for_each(root):
yield container_of(node, type, member)
@ -163,13 +167,13 @@ def rbtree_inorder_for_each_entry(type, root, member):
def rb_find(type, root, member, key, cmp):
"""
type *rb_find(type, struct rb_root *, member,
key_type key, int (*cmp)(key_type, type *))
.. c:function:: type *rb_find(type, struct rb_root *root, member, key_type key, int (*cmp)(key_type, type *))
Find an entry in a red-black tree, given a key and a comparator function
which takes the key and an entry. The comparator should return -1 if the
key is less than the entry, 1 if it is greater than the entry, or 0 if it
matches the entry. This returns a NULL object if no entry matches the key.
which takes the key and an entry. The comparator should return < 0 if the
key is less than the entry, > 0 if it is greater than the entry, or 0 if it
matches the entry. This returns a ``NULL`` object if no entry matches the
key.
Note that this function does not have an analogue in the Linux kernel
source code, as tree searches are all open-coded.

1
libdrgn/.gitignore vendored
View File

@ -14,3 +14,4 @@
/html
/libtool
/python/constants.c
/python/docstrings.c

View File

@ -55,7 +55,10 @@ if WITH_PYTHON
noinst_LTLIBRARIES += _drgn.la
endif
_drgn_la_SOURCES = python/drgnpy.h \
CLEANFILES = python/constants.c python/docstrings.c
_drgn_la_SOURCES = python/docstrings.h \
python/drgnpy.h \
python/module.c \
python/object.c \
python/program.c \
@ -63,17 +66,23 @@ _drgn_la_SOURCES = python/drgnpy.h \
python/type.c \
python/util.c
nodist__drgn_la_SOURCES = python/constants.c
nodist__drgn_la_SOURCES = python/constants.c python/docstrings.c
_drgn_la_CFLAGS = $(PYTHON_CFLAGS) -D_GNU_SOURCE -fvisibility=hidden
_drgn_la_LDFLAGS = -avoid-version -module -shared -rpath $(pkgpyexecdir) \
-Wl,--exclude-libs,ALL
_drgn_la_LIBADD = libdrgnimpl.la
$(top_builddir)/python/constants.c: $(top_srcdir)/drgn.h $(top_srcdir)/build-aux/gen_constants.py
$(PYTHON) $(top_srcdir)/build-aux/gen_constants.py $(top_srcdir)/python < $< > $@
GEN_CONSTANTS = $(top_srcdir)/build-aux/gen_constants.py
GEN_DOCSTRINGS = $(top_srcdir)/build-aux/gen_docstrings.py
EXTRA_DIST = $(top_srcdir)/build-aux/gen_constants.py
$(top_builddir)/python/constants.c: $(top_srcdir)/drgn.h $(GEN_CONSTANTS)
$(PYTHON) $(GEN_CONSTANTS) $(top_srcdir)/python < $< > $@
$(top_builddir)/python/docstrings.c: $(top_srcdir)/../docs/api_reference.rst $(GEN_DOCSTRINGS)
$(PYTHON) $(GEN_DOCSTRINGS) < $< > $@
EXTRA_DIST = $(GEN_CONSTANTS) $(GEN_DOCSTRINGS)
# This target lists all of the files that need to be distributed for setup.py
# sdist. It is based on the automake distdir target.

View File

@ -1,4 +1,5 @@
*
!/.gitignore
!/gen_constants.py
!/gen_docstrings.py
!/version.sh

View File

@ -1,3 +1,6 @@
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
import os.path
import re
import sys

View File

@ -0,0 +1,182 @@
# Copyright 2018-2019 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
import re
import sys
from types import SimpleNamespace
def strictstartswith(a, b):
return a.startswith(b) and a != b
# Quick and dirty reStructuredText parser. It probably can't handle anything
# other than the input in this repository.
def parse_rst(input_file):
stack = [
SimpleNamespace(name='', state='CONTENT', lines=None,
directive_indentation='', content_indentation='')
]
state = None
for line in input_file:
line = line.rstrip()
indentation = re.match(r'\s*', line).group()
while True:
top = stack[-1]
if top.state == 'DIRECTIVE':
if not line:
top.state = 'BLANK_LINE'
break
elif strictstartswith(indentation, top.directive_indentation):
top.content_indentation = indentation
top.state = 'OPTIONS'
break
elif top.state == 'BLANK_LINE':
if not line:
break
elif strictstartswith(indentation, top.directive_indentation):
top.content_indentation = indentation
top.state = 'CONTENT'
break
elif top.state == 'OPTIONS':
if not line:
top.state = 'OPTIONS_BLANK_LINE'
break
elif indentation.startswith(top.content_indentation):
break
else:
if top.state == 'OPTIONS_BLANK_LINE':
top.state = 'CONTENT'
assert top.state == 'CONTENT'
if (not line or
indentation.startswith(top.content_indentation)):
break
# The current line is indented less than the current indentation,
# so pop the top directive.
if top.lines is not None:
yield top
del stack[-1]
assert top is stack[-1]
if top.state != 'CONTENT':
continue
if line:
assert line.startswith(top.content_indentation)
line = line[len(top.content_indentation):]
match = re.match(r'\s*..\s*(?:py:)?([-a-zA-Z0-9_+:.]+)::\s*(.*)', line)
if match:
directive = match.group(1)
argument = match.group(2)
if directive == 'module' or directive == 'currentmodule':
stack[0].name = argument
else:
name = top.name
if directive in {'attribute', 'class', 'exception', 'function',
'method'}:
lines = []
paren = argument.find('(')
if paren != -1:
# If the argument includes a signature, add it along
# with the signature end marker used by CPython.
lines.append(argument)
lines.append('--')
lines.append('')
argument = argument[:paren]
if name:
name += '.'
name += argument
else:
lines = None
entry = SimpleNamespace(name=name, state='DIRECTIVE',
lines=lines,
directive_indentation=indentation,
content_indentation=None)
stack.append(entry)
elif top.lines is not None:
top.lines.append(line)
while len(stack) > 1:
entry = stack.pop()
if entry.lines is not None:
yield entry
escapes = []
for c in range(256):
if c == 0:
e = r'\0'
elif c == 7:
e = r'\a'
elif c == 8:
e = r'\b'
elif c == 9:
e = r'\t'
elif c == 10:
e = r'\n'
elif c == 11:
e = r'\v'
elif c == 12:
e = r'\f'
elif c == 13:
e = r'\r'
elif c == 34:
e = r'\"'
elif c == 92:
e = r'\\'
elif 32 <= c <= 126:
e = chr(c)
else:
e = f'\\x{c:02x}'
escapes.append(e)
def escape_string(s):
return ''.join([escapes[c] for c in s.encode('utf-8')])
def gen_docstrings(input_file, output_file, header=False):
path = 'libdrgn/build-aux/gen_docstrings.py'
if header:
output_file.write(f"""\
/*
* Generated by {path} -H.
*
* Note that this is generated manually because automake and other build systems
* have trouble with generated headers. Regenerate this if a new docstring is
* added. The docstring contents themselves are automatically regenerated by the
* build system.
*
* Before Python 3.7, various docstring fields were defined as char * (see
* https://bugs.python.org/issue28761). We still want the strings to be
* read-only, so just cast away the const.
*/
""")
else:
output_file.write(f'/* Generated by {path}. */\n\n')
directives = sorted(parse_rst(input_file), key=lambda x: x.name)
for directive in directives:
while directive.lines and not directive.lines[-1]:
del directive.lines[-1]
name = directive.name.replace('.', '_') + '_DOC'
if header:
output_file.write('extern ')
output_file.write(f"const char {name}[]")
if not header:
output_file.write(' =')
if directive.lines:
for i, line in enumerate(directive.lines):
output_file.write(f'\n\t"{escape_string(line)}')
if i != len(directive.lines) - 1:
output_file.write('\\n')
output_file.write('"')
else:
output_file.write(' ""')
output_file.write(';\n')
if header:
output_file.write(f'#define {name} (char *){name}\n')
if __name__ == '__main__':
gen_docstrings(sys.stdin, sys.stdout, '-H' in sys.argv[1:])

179
libdrgn/python/docstrings.h Normal file
View File

@ -0,0 +1,179 @@
/*
* Generated by libdrgn/build-aux/gen_docstrings.py -H.
*
* Note that this is generated manually because automake and other build systems
* have trouble with generated headers. Regenerate this if a new docstring is
* added. The docstring contents themselves are automatically regenerated by the
* build system.
*
* Before Python 3.7, various docstring fields were defined as char * (see
* https://bugs.python.org/issue28761). We still want the strings to be
* read-only, so just cast away the const.
*/
extern const char drgn_FaultError_DOC[];
#define drgn_FaultError_DOC (char *)drgn_FaultError_DOC
extern const char drgn_FileFormatError_DOC[];
#define drgn_FileFormatError_DOC (char *)drgn_FileFormatError_DOC
extern const char drgn_NULL_DOC[];
#define drgn_NULL_DOC (char *)drgn_NULL_DOC
extern const char drgn_Object_DOC[];
#define drgn_Object_DOC (char *)drgn_Object_DOC
extern const char drgn_Object___getattribute___DOC[];
#define drgn_Object___getattribute___DOC (char *)drgn_Object___getattribute___DOC
extern const char drgn_Object___getitem___DOC[];
#define drgn_Object___getitem___DOC (char *)drgn_Object___getitem___DOC
extern const char drgn_Object___len___DOC[];
#define drgn_Object___len___DOC (char *)drgn_Object___len___DOC
extern const char drgn_Object_address__DOC[];
#define drgn_Object_address__DOC (char *)drgn_Object_address__DOC
extern const char drgn_Object_address_of__DOC[];
#define drgn_Object_address_of__DOC (char *)drgn_Object_address_of__DOC
extern const char drgn_Object_bit_field_size__DOC[];
#define drgn_Object_bit_field_size__DOC (char *)drgn_Object_bit_field_size__DOC
extern const char drgn_Object_bit_offset__DOC[];
#define drgn_Object_bit_offset__DOC (char *)drgn_Object_bit_offset__DOC
extern const char drgn_Object_byteorder__DOC[];
#define drgn_Object_byteorder__DOC (char *)drgn_Object_byteorder__DOC
extern const char drgn_Object_member__DOC[];
#define drgn_Object_member__DOC (char *)drgn_Object_member__DOC
extern const char drgn_Object_prog__DOC[];
#define drgn_Object_prog__DOC (char *)drgn_Object_prog__DOC
extern const char drgn_Object_read__DOC[];
#define drgn_Object_read__DOC (char *)drgn_Object_read__DOC
extern const char drgn_Object_string__DOC[];
#define drgn_Object_string__DOC (char *)drgn_Object_string__DOC
extern const char drgn_Object_type__DOC[];
#define drgn_Object_type__DOC (char *)drgn_Object_type__DOC
extern const char drgn_Object_value__DOC[];
#define drgn_Object_value__DOC (char *)drgn_Object_value__DOC
extern const char drgn_Program_DOC[];
#define drgn_Program_DOC (char *)drgn_Program_DOC
extern const char drgn_Program___getitem___DOC[];
#define drgn_Program___getitem___DOC (char *)drgn_Program___getitem___DOC
extern const char drgn_Program_byteorder_DOC[];
#define drgn_Program_byteorder_DOC (char *)drgn_Program_byteorder_DOC
extern const char drgn_Program_constant_DOC[];
#define drgn_Program_constant_DOC (char *)drgn_Program_constant_DOC
extern const char drgn_Program_flags_DOC[];
#define drgn_Program_flags_DOC (char *)drgn_Program_flags_DOC
extern const char drgn_Program_function_DOC[];
#define drgn_Program_function_DOC (char *)drgn_Program_function_DOC
extern const char drgn_Program_read_DOC[];
#define drgn_Program_read_DOC (char *)drgn_Program_read_DOC
extern const char drgn_Program_type_DOC[];
#define drgn_Program_type_DOC (char *)drgn_Program_type_DOC
extern const char drgn_Program_variable_DOC[];
#define drgn_Program_variable_DOC (char *)drgn_Program_variable_DOC
extern const char drgn_Program_word_size_DOC[];
#define drgn_Program_word_size_DOC (char *)drgn_Program_word_size_DOC
extern const char drgn_ProgramFlags_DOC[];
#define drgn_ProgramFlags_DOC (char *)drgn_ProgramFlags_DOC
extern const char drgn_ProgramFlags_IS_LINUX_KERNEL_DOC[];
#define drgn_ProgramFlags_IS_LINUX_KERNEL_DOC (char *)drgn_ProgramFlags_IS_LINUX_KERNEL_DOC
extern const char drgn_Qualifiers_DOC[];
#define drgn_Qualifiers_DOC (char *)drgn_Qualifiers_DOC
extern const char drgn_Qualifiers_ATOMIC_DOC[];
#define drgn_Qualifiers_ATOMIC_DOC (char *)drgn_Qualifiers_ATOMIC_DOC
extern const char drgn_Qualifiers_CONST_DOC[];
#define drgn_Qualifiers_CONST_DOC (char *)drgn_Qualifiers_CONST_DOC
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_Type_DOC[];
#define drgn_Type_DOC (char *)drgn_Type_DOC
extern const char drgn_Type_enumerators_DOC[];
#define drgn_Type_enumerators_DOC (char *)drgn_Type_enumerators_DOC
extern const char drgn_Type_is_complete_DOC[];
#define drgn_Type_is_complete_DOC (char *)drgn_Type_is_complete_DOC
extern const char drgn_Type_is_signed_DOC[];
#define drgn_Type_is_signed_DOC (char *)drgn_Type_is_signed_DOC
extern const char drgn_Type_is_variadic_DOC[];
#define drgn_Type_is_variadic_DOC (char *)drgn_Type_is_variadic_DOC
extern const char drgn_Type_kind_DOC[];
#define drgn_Type_kind_DOC (char *)drgn_Type_kind_DOC
extern const char drgn_Type_length_DOC[];
#define drgn_Type_length_DOC (char *)drgn_Type_length_DOC
extern const char drgn_Type_members_DOC[];
#define drgn_Type_members_DOC (char *)drgn_Type_members_DOC
extern const char drgn_Type_name_DOC[];
#define drgn_Type_name_DOC (char *)drgn_Type_name_DOC
extern const char drgn_Type_parameters_DOC[];
#define drgn_Type_parameters_DOC (char *)drgn_Type_parameters_DOC
extern const char drgn_Type_qualified_DOC[];
#define drgn_Type_qualified_DOC (char *)drgn_Type_qualified_DOC
extern const char drgn_Type_qualifiers_DOC[];
#define drgn_Type_qualifiers_DOC (char *)drgn_Type_qualifiers_DOC
extern const char drgn_Type_size_DOC[];
#define drgn_Type_size_DOC (char *)drgn_Type_size_DOC
extern const char drgn_Type_tag_DOC[];
#define drgn_Type_tag_DOC (char *)drgn_Type_tag_DOC
extern const char drgn_Type_type_DOC[];
#define drgn_Type_type_DOC (char *)drgn_Type_type_DOC
extern const char drgn_Type_type_name_DOC[];
#define drgn_Type_type_name_DOC (char *)drgn_Type_type_name_DOC
extern const char drgn_Type_unqualified_DOC[];
#define drgn_Type_unqualified_DOC (char *)drgn_Type_unqualified_DOC
extern const char drgn_TypeKind_DOC[];
#define drgn_TypeKind_DOC (char *)drgn_TypeKind_DOC
extern const char drgn_TypeKind_ARRAY_DOC[];
#define drgn_TypeKind_ARRAY_DOC (char *)drgn_TypeKind_ARRAY_DOC
extern const char drgn_TypeKind_BOOL_DOC[];
#define drgn_TypeKind_BOOL_DOC (char *)drgn_TypeKind_BOOL_DOC
extern const char drgn_TypeKind_COMPLEX_DOC[];
#define drgn_TypeKind_COMPLEX_DOC (char *)drgn_TypeKind_COMPLEX_DOC
extern const char drgn_TypeKind_ENUM_DOC[];
#define drgn_TypeKind_ENUM_DOC (char *)drgn_TypeKind_ENUM_DOC
extern const char drgn_TypeKind_FLOAT_DOC[];
#define drgn_TypeKind_FLOAT_DOC (char *)drgn_TypeKind_FLOAT_DOC
extern const char drgn_TypeKind_FUNCTION_DOC[];
#define drgn_TypeKind_FUNCTION_DOC (char *)drgn_TypeKind_FUNCTION_DOC
extern const char drgn_TypeKind_INT_DOC[];
#define drgn_TypeKind_INT_DOC (char *)drgn_TypeKind_INT_DOC
extern const char drgn_TypeKind_POINTER_DOC[];
#define drgn_TypeKind_POINTER_DOC (char *)drgn_TypeKind_POINTER_DOC
extern const char drgn_TypeKind_STRUCT_DOC[];
#define drgn_TypeKind_STRUCT_DOC (char *)drgn_TypeKind_STRUCT_DOC
extern const char drgn_TypeKind_TYPEDEF_DOC[];
#define drgn_TypeKind_TYPEDEF_DOC (char *)drgn_TypeKind_TYPEDEF_DOC
extern const char drgn_TypeKind_UNION_DOC[];
#define drgn_TypeKind_UNION_DOC (char *)drgn_TypeKind_UNION_DOC
extern const char drgn_TypeKind_VOID_DOC[];
#define drgn_TypeKind_VOID_DOC (char *)drgn_TypeKind_VOID_DOC
extern const char drgn_array_type_DOC[];
#define drgn_array_type_DOC (char *)drgn_array_type_DOC
extern const char drgn_bool_type_DOC[];
#define drgn_bool_type_DOC (char *)drgn_bool_type_DOC
extern const char drgn_cast_DOC[];
#define drgn_cast_DOC (char *)drgn_cast_DOC
extern const char drgn_complex_type_DOC[];
#define drgn_complex_type_DOC (char *)drgn_complex_type_DOC
extern const char drgn_container_of_DOC[];
#define drgn_container_of_DOC (char *)drgn_container_of_DOC
extern const char drgn_enum_type_DOC[];
#define drgn_enum_type_DOC (char *)drgn_enum_type_DOC
extern const char drgn_float_type_DOC[];
#define drgn_float_type_DOC (char *)drgn_float_type_DOC
extern const char drgn_function_type_DOC[];
#define drgn_function_type_DOC (char *)drgn_function_type_DOC
extern const char drgn_int_type_DOC[];
#define drgn_int_type_DOC (char *)drgn_int_type_DOC
extern const char drgn_pointer_type_DOC[];
#define drgn_pointer_type_DOC (char *)drgn_pointer_type_DOC
extern const char drgn_program_from_core_dump_DOC[];
#define drgn_program_from_core_dump_DOC (char *)drgn_program_from_core_dump_DOC
extern const char drgn_program_from_kernel_DOC[];
#define drgn_program_from_kernel_DOC (char *)drgn_program_from_kernel_DOC
extern const char drgn_program_from_pid_DOC[];
#define drgn_program_from_pid_DOC (char *)drgn_program_from_pid_DOC
extern const char drgn_reinterpret_DOC[];
#define drgn_reinterpret_DOC (char *)drgn_reinterpret_DOC
extern const char drgn_struct_type_DOC[];
#define drgn_struct_type_DOC (char *)drgn_struct_type_DOC
extern const char drgn_typedef_type_DOC[];
#define drgn_typedef_type_DOC (char *)drgn_typedef_type_DOC
extern const char drgn_union_type_DOC[];
#define drgn_union_type_DOC (char *)drgn_union_type_DOC
extern const char drgn_void_type_DOC[];
#define drgn_void_type_DOC (char *)drgn_void_type_DOC

View File

@ -9,6 +9,7 @@
#include <Python.h>
#include "structmember.h"
#include "docstrings.h"
#include "../drgn.h"
#include "../program.h"
@ -117,6 +118,7 @@ static inline DrgnObject *DrgnObject_alloc(Program *prog)
int Program_hold_type(Program *prog, DrgnType *type);
PyObject *DrgnObject_NULL(PyObject *self, PyObject *args, PyObject *kwds);
DrgnObject *cast(PyObject *self, PyObject *args, PyObject *kwds);
DrgnObject *reinterpret(PyObject *self, PyObject *args, PyObject *kwds);
DrgnObject *DrgnObject_container_of(PyObject *self, PyObject *args,

View File

@ -58,163 +58,61 @@ DRGNPY_PUBLIC PyObject *set_drgn_error(struct drgn_error *err)
}
static PyMethodDef drgn_methods[] = {
{"NULL", (PyCFunction)DrgnObject_NULL, METH_VARARGS | METH_KEYWORDS,
drgn_NULL_DOC},
{"cast", (PyCFunction)cast, METH_VARARGS | METH_KEYWORDS,
"cast(type: Union[str, Type], obj: Object) -> Object\n"
"\n"
"Return the value of the given object casted to another type.\n"
"\n"
"Objects with a scalar type (integer, boolean, enumerated,\n"
"floating-point, or pointer) can be casted to a different scalar type.\n"
"Other objects can only be casted to the same type. This always results\n"
"in a value object. See also reinterpret()."},
drgn_cast_DOC},
{"reinterpret", (PyCFunction)reinterpret, METH_VARARGS | METH_KEYWORDS,
"reinterpret(type: Union[str, Type], obj: Object,\n"
" byteorder: Optional[str] = None) -> Object\n"
"\n"
"Return a copy of the given object reinterpreted as another type and/or\n"
"byte order. If byte order is None, it defaults to the program byte\n"
"order.\n"
"\n"
"This reinterprets the raw memory of the object, so an object can be\n"
"reinterpreted as any other type. However, value objects with a scalar\n"
"type cannot be reinterpreted, as their memory layout in the program is\n"
"not known. Reinterpreting a reference results in a reference, and\n"
"reinterpreting a value results in a value. See also cast()."},
drgn_reinterpret_DOC},
{"container_of", (PyCFunction)DrgnObject_container_of,
METH_VARARGS | METH_KEYWORDS,
"container_of(ptr: Object, type: Union[str, Type], member: str) -> Object\n"
"\n"
"Return the containing object of the object pointed to by the given\n"
"pointer object. The given type is the type of the containing object, and\n"
"the given member is the name of the member in that type. This\n"
"corresponds to the container_of() macro in C."},
METH_VARARGS | METH_KEYWORDS, drgn_container_of_DOC},
{"mock_program", (PyCFunction)mock_program,
METH_VARARGS | METH_KEYWORDS,
"mock_program(word_size: int, byteorder: str,\n"
" segments: Optional[Sequence[MockMemorySegment]] = None,\n"
" types: Optional[Sequence[MockType]] = None,\n"
" objects: Optional[Sequence[MockObject]] = None) -> Program\n"
"mock_program(word_size, byteorder, segments=None, types=None, objects=None)\n"
"--\n"
"\n"
"Return a \"mock\" Program from the given word size, byteorder, and lists\n"
"of MockMemorySegment, MockType, and MockObject. This is usually used for\n"
"testing."},
"Create a mock :class:`Program` for testing.\n"
"\n"
":param int word_size: :attr:`Program.word_size`\n"
":param str byteorder: :attr:`Program.byteorder`\n"
":param segments: Memory segments.\n"
":type segments: list[MockMemorySegment] or None\n"
":param types: Type definitions.\n"
":type types: list[MockType] or None\n"
":param objects: Object definitions.\n"
":type objects: list[MockObject] or None\n"
":rtype: Program"},
{"program_from_core_dump", (PyCFunction)program_from_core_dump,
METH_VARARGS | METH_KEYWORDS,
"program_from_core_dump(path: str, verbose: bool = False) -> Program\n"
"\n"
"Create a Program from a core dump file. The type of program (e.g.,\n"
"userspace or kernel) will be determined automatically.\n"
"\n"
"If verbose is True, this will print messages to stderr about not being\n"
"able to find debugging symbols, etc."},
METH_VARARGS | METH_KEYWORDS, drgn_program_from_core_dump_DOC},
{"program_from_kernel", (PyCFunction)program_from_kernel,
METH_VARARGS | METH_KEYWORDS,
"program_from_kernel(verbose: bool = False) -> Program\n"
"\n"
"Create a Program from the running operating system kernel. This requires\n"
"root privileges.\n"
"\n"
"If verbose is True, this will print messages to stderr about not being\n"
"able to find kernel modules, debugging symbols, etc."},
METH_VARARGS | METH_KEYWORDS, drgn_program_from_kernel_DOC},
{"program_from_pid", (PyCFunction)program_from_pid,
METH_VARARGS | METH_KEYWORDS,
"program_from_pid(pid: int) -> Program\n"
"\n"
"Create a Program from a running program with the given PID. This\n"
"requires appropriate permissions (on Linux, ptrace(2) attach\n"
"permissions)."},
METH_VARARGS | METH_KEYWORDS, drgn_program_from_pid_DOC},
{"void_type", (PyCFunction)void_type, METH_VARARGS | METH_KEYWORDS,
"void_type(qualifiers: int = 0) -> Type\n"
"\n"
"Return a new void type. It has kind TypeKind.VOID."},
drgn_void_type_DOC},
{"int_type", (PyCFunction)int_type, METH_VARARGS | METH_KEYWORDS,
"int_type(name: str, size: int, is_signed: bool,\n"
" qualifiers: int = 0) -> Type\n"
"\n"
"Return a new integer type. It has kind TypeKind.INT, a name, a size, and\n"
"a signedness."},
drgn_int_type_DOC},
{"bool_type", (PyCFunction)bool_type, METH_VARARGS | METH_KEYWORDS,
"bool_type(name: str, size: int, qualifiers: int = 0) -> Type\n"
"\n"
"Return a new boolean type. It has kind TypeKind.BOOL, a name, and a\n"
"size."},
drgn_bool_type_DOC},
{"float_type", (PyCFunction)float_type, METH_VARARGS | METH_KEYWORDS,
"float_type(name, size, qualifiers=0) -> new floating-point type\n"
"\n"
"Return a new floating-point type. It has kind TypeKind.FLOAT, a string\n"
"name, and an integer size."},
drgn_float_type_DOC},
{"complex_type", (PyCFunction)complex_type,
METH_VARARGS | METH_KEYWORDS,
"complex_type(name: str, size: int, type: Type,\n"
" qualifiers: int = 0) -> Type\n"
"\n"
"Return a new complex type. It has kind TypeKind.COMPLEX, a name, a\n"
"size, and a corresponding real type, which must be an unqualified\n"
"floating-point or integer Type object."},
METH_VARARGS | METH_KEYWORDS, drgn_complex_type_DOC},
{"struct_type", (PyCFunction)struct_type, METH_VARARGS | METH_KEYWORDS,
"struct_type(tag: Optional[str], size: int, members: Optional[Sequence],\n"
" qualifiers: int = 0) -> Type\n"
"\n"
"Return a new structure type. It has kind TypeKind.STRUCT, a tag, a size,\n"
"and a list of members. The tag may be None, which indicates an anonymous\n"
"type. The members may be None, which indicates an incomplete type; in\n"
"this case, the size must be zero. Otherwise, the members must be a list\n"
"of (type, string name, integer bit offset, integer bit field size)\n"
"tuples. The type of a member must be a Type object or a callable\n"
"returning a Type object. In the latter case, the callable will be called\n"
"the first time that the member is accessed. The name of a member may be\n"
"None, which indicates an unnamed member. The bit field size should be\n"
"non-zero for bit fields and zero otherwise. The name, bit offset, and\n"
"bit field size can be omitted; the name defaults to None and the bit\n"
"offset and bit field size default to zero."},
drgn_struct_type_DOC},
{"union_type", (PyCFunction)union_type, METH_VARARGS | METH_KEYWORDS,
"union_type(tag: Optional[str], size: int, members: Optional[Sequence],\n"
" qualifiers: int = 0) -> Type\n"
"\n"
"Return a new union type. It has kind TypeKind.UNION, a tag, a size, and\n"
"a list of members. See struct_type()."},
drgn_union_type_DOC},
{"enum_type", (PyCFunction)enum_type, METH_VARARGS | METH_KEYWORDS,
"enum_type(tag: Optional[str], type: Optional[Type],\n"
" enumerators: Optional[Sequence[Tuple[str, int]],\n"
" qualifiers: int = 0) -> Type\n"
"\n"
"Return a new enumerated type. It has kind TypeKind.ENUM, a tag, a\n"
"compatible integer type, and a list of enumerators. The tag may be None,\n"
"which indicates an anonymous type. The type and enumerators may be None,\n"
"which indicates an incomplete type. Otherwise, the type must be an\n"
"integer Type object and the enumerators must be a list of (string name,\n"
"integer value) tuples."},
drgn_enum_type_DOC},
{"typedef_type", (PyCFunction)typedef_type,
METH_VARARGS | METH_KEYWORDS,
"typedef_type(name: str, type: Type, qualifiers: int = 0) -> Type\n"
"\n"
"Return a new typedef type. It has kind TypeKind.TYPEDEF, a name, and an\n"
"aliased type."},
drgn_typedef_type_DOC},
{"pointer_type", (PyCFunction)pointer_type,
METH_VARARGS | METH_KEYWORDS,
"pointer_type(size: int, type: Type, qualifiers: int = 0) -> Type\n"
"\n"
"Return a new pointer type. It has kind TypeKind.POINTER, a size, and a\n"
"referenced type."},
METH_VARARGS | METH_KEYWORDS, drgn_pointer_type_DOC},
{"array_type", (PyCFunction)array_type, METH_VARARGS | METH_KEYWORDS,
"array_type(length: Optional[int], type: Type,\n"
" qualifiers: int = 0) -> Type\n"
"\n"
"Return a new array type. It has kind TypeKind.ARRAY, a length, and an\n"
"element type. The length may be None, which indicates an incomplete\n"
"array type."},
drgn_array_type_DOC},
{"function_type", (PyCFunction)function_type,
METH_VARARGS | METH_KEYWORDS,
"function_type(type: Type, parameters: Sequence,\n"
" is_variadic: bool = False, qualifiers: int = 0) -> Type\n"
"\n"
"Return a new function type. It has kind TypeKind.FUNCTION, a return\n"
"type, a list of parameters, and may be variadic. The parameters must be\n"
"a list of (type, string name) tuples. Each parameter type must be a Type\n"
"object or a callable returning a Type object. In the latter case, the\n"
"callable will be called the first time that the parameter is accessed. A\n"
"parameter name may be None, which indicates an unnamed parameter. The\n"
"parameter name is optional and defaults to None."},
METH_VARARGS | METH_KEYWORDS, drgn_function_type_DOC},
{},
};
@ -228,19 +126,6 @@ static struct PyModuleDef drgnmodule = {
drgn_methods,
};
#define FaultError_DOC \
"Bad memory access.\n" \
"\n" \
"This error is raised when a memory access is attempted to an address\n" \
"which is not valid in a program, or when accessing out of bounds of a\n" \
"value object."
#define FileFormatError_DOC \
"Invalid file.\n" \
"\n" \
"This is error raised when a file cannot be parsed according to its\n" \
"expected format (e.g., ELF or DWARF)."
DRGNPY_PUBLIC PyMODINIT_FUNC PyInit__drgn(void)
{
PyObject *m;
@ -260,14 +145,14 @@ DRGNPY_PUBLIC PyMODINIT_FUNC PyInit__drgn(void)
PyModule_AddObject(m, "__version__", version);
FaultError = PyErr_NewExceptionWithDoc("_drgn.FaultError",
FaultError_DOC, NULL, NULL);
drgn_FaultError_DOC, NULL, NULL);
if (!FaultError)
goto err;
PyModule_AddObject(m, "FaultError", FaultError);
FileFormatError = PyErr_NewExceptionWithDoc("_drgn.FileFormatError",
FileFormatError_DOC, NULL,
NULL);
drgn_FileFormatError_DOC,
NULL, NULL);
if (!FileFormatError)
goto err;
PyModule_AddObject(m, "FileFormatError", FileFormatError);

View File

@ -464,7 +464,7 @@ static int DrgnObject_init(DrgnObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {
"prog", "type", "value", "address", "byteorder",
"bit_offset", "bit_field_size",NULL,
"bit_offset", "bit_field_size", NULL,
};
struct drgn_error *err;
Program *prog;
@ -1691,96 +1691,40 @@ static PyObject *DrgnObject_dir(DrgnObject *self)
}
static PyGetSetDef DrgnObject_getset[] = {
{"prog_", (getter)DrgnObject_get_prog, NULL,
"Program\n"
"\n"
"Program that this object is from"},
{"type_", (getter)DrgnObject_get_type, NULL,
"Type\n"
"\n"
"Type of this object"},
{"prog_", (getter)DrgnObject_get_prog, NULL, drgn_Object_prog__DOC},
{"type_", (getter)DrgnObject_get_type, NULL, drgn_Object_type__DOC},
{"address_", (getter)DrgnObject_get_address, NULL,
"Optional[int]\n"
"\n"
"Address of this object if it is a reference, None if it is a value"},
drgn_Object_address__DOC},
{"byteorder_", (getter)DrgnObject_get_byteorder, NULL,
"Optional[str]\n"
"\n"
"Byte order of this object (either 'little' or 'big') if it is a\n"
"reference or a non-primitive value, None otherwise"},
drgn_Object_byteorder__DOC},
{"bit_offset_", (getter)DrgnObject_get_bit_offset, NULL,
"Optional[int]\n"
"\n"
"Offset in bits from this object's address to the beginning of the object\n"
"if it is a reference or a non-primitive value, None otherwise"},
drgn_Object_bit_offset__DOC},
{"bit_field_size_", (getter)DrgnObject_get_bit_field_size, NULL,
"Optional[int]\n"
"\n"
"Size in bits of this object if it is a bit field, None if not"},
drgn_Object_bit_field_size__DOC},
{},
};
static PyMethodDef DrgnObject_methods[] = {
{"__getitem__", (PyCFunction)DrgnObject_subscript,
METH_O | METH_COEXIST,
"__getitem__(self, idx) -> Object\n"
"\n"
"Implement self[idx]. Return an Object representing the array element at\n"
"the given index.\n"
"\n"
"This is only valid for pointers and arrays."},
METH_O | METH_COEXIST, drgn_Object___getitem___DOC},
{"value_", (PyCFunction)DrgnObject_value, METH_NOARGS,
"value_(self) -> Any\n"
"\n"
"Return the value of this object as a Python object.\n"
"\n"
"For basic types (int, bool, etc.), this returns an object of the\n"
"directly corresponding Python type. For pointers, this returns the\n"
"address value of the pointer. For enums, this returns an int. For\n"
"structures and unions, this returns a dict of members. For arrays, this\n"
"returns a list of values."},
drgn_Object_value__DOC},
{"string_", (PyCFunction)DrgnObject_string, METH_NOARGS,
"string_(self) -> bytes\n"
"\n"
"Return the null-terminated string pointed to by this object as bytes.\n"
"\n"
"This is only valid for pointers and arrays."},
drgn_Object_string__DOC},
{"member_", (PyCFunction)DrgnObject_member,
METH_VARARGS | METH_KEYWORDS,
"member_(self, name: str) -> Object\n"
"\n"
"Return an Object representing the given structure or union member.\n"
"\n"
"This is only valid for structs, unions, and pointers to either. Normally\n"
"the dot operator (\".\") can be used to accomplish the same thing, but\n"
"this method can be used if there is a name conflict with an Object\n"
"member or method."},
METH_VARARGS | METH_KEYWORDS, drgn_Object_member__DOC},
{"address_of_", (PyCFunction)DrgnObject_address_of, METH_NOARGS,
"address_of_(self) -> Object\n"
"\n"
"Return an Object pointing to this object.\n"
"\n"
"This corresponds to the address-of (\"&\") operator in C. It is only\n"
"possible for reference objects, as value objects don't have an address\n"
"in the program."},
{"read_", (PyCFunction)DrgnObject_read,
METH_NOARGS,
"read_(self) -> Object\n"
"\n"
"Read this object (which may be a reference or a value) and return it as\n"
"a value object. This is useful if the object can change in the running\n"
"program (but of course nothing stops the program from modifying the\n"
"object while it is being read)."},
drgn_Object_address_of__DOC},
{"read_", (PyCFunction)DrgnObject_read, METH_NOARGS,
drgn_Object_read__DOC},
{"__round__", (PyCFunction)DrgnObject_round,
METH_VARARGS | METH_KEYWORDS},
{"__trunc__", (PyCFunction)DrgnObject_trunc, METH_NOARGS},
{"__floor__", (PyCFunction)DrgnObject_floor, METH_NOARGS},
{"__ceil__", (PyCFunction)DrgnObject_ceil, METH_NOARGS},
{"__dir__", (PyCFunction)DrgnObject_dir,
METH_NOARGS,
{"__dir__", (PyCFunction)DrgnObject_dir, METH_NOARGS,
"dir() implementation which includes structure and union members."},
{"__format__", (PyCFunction)DrgnObject_format,
METH_O,
{"__format__", (PyCFunction)DrgnObject_format, METH_O,
"Object formatter."},
{},
};
@ -1827,63 +1771,6 @@ static PyMappingMethods DrgnObject_as_mapping = {
(binaryfunc)DrgnObject_subscript, /* mp_subscript */
};
#define DrgnObject_DOC \
"An Object represents a symbol or value in a program. The object may be\n" \
"in the memory of the program (a \"reference\").\n" \
"\n" \
">>> Object(prog, 'int', address=0xffffffffc09031a0)\n" \
"\n" \
"It can also be a temporary computed value (a \"value\").\n" \
"\n" \
">>> Object(prog, 'int', value=4)\n" \
"\n" \
"All Object instances have two members: prog_, the program that the\n" \
"object is from; and type_, the type of the object. Reference objects\n" \
"have an address_ member. Objects may also have a byteorder_,\n" \
"bit_offset_, and bit_field_size.\n" \
"\n" \
"repr() of an Object returns a Python representation of the object.\n" \
"\n" \
">>> print(repr(prog['jiffies']))\n" \
"Object(prog, 'volatile long unsigned int', address=0xffffffffbf005000)\n" \
"\n" \
"str() returns a representation of the object in programming language\n" \
"syntax.\n" \
"\n" \
">>> print(prog['jiffies'])\n" \
"(volatile long unsigned int)4326237045\n" \
"\n" \
"Note that the drgn CLI is set up so that Objects are displayed with\n" \
"str() instead of repr() (the latter is the default behavior of Python's\n" \
"interactive mode). This means that in the drgn CLI, the call to print()\n" \
"in the second example above is not necessary.\n" \
"\n" \
"Objects support their programming language's operators wherever\n" \
"possible. E.g., structure members can be accessed with the dot (\".\")\n" \
"operator, arrays can be subscripted with \"[]\", arithmetic can be\n" \
"performed, and objects can be compared.\n" \
"\n" \
">>> print(prog['init_task'].pid)\n" \
"(pid_t)0\n" \
">>> print(prog['init_task'].comm[0])\n" \
"(char)115\n" \
">>> print(repr(prog['init_task'].nsproxy.mnt_ns.mounts + 1))\n" \
"Object(prog, 'unsigned int', value=34)\n" \
">>> prog['init_task'].nsproxy.mnt_ns.pending_mounts > 0\n" \
"False\n" \
"\n" \
"Note that because the C structure dereference operator (\"->\") is not\n" \
"valid syntax in Python, \".\" is also used to access members of pointers\n" \
"to structures. Similarly, the indirection operator (\"*\") is not valid\n" \
"syntax in Python, so pointers can be dereferenced with \"[0]\" (e.g.,\n" \
"write \"p[0]\" instead of \"*p\"). The address-of operator (\"&\") is\n" \
"available as the address_of_() method.\n" \
"\n" \
"Object members and methods are named with a trailing underscore to avoid\n" \
"conflicting with structure or union members. The helper methods always\n" \
"take precedence over structure members; use member_() if there is a\n" \
"conflict."
PyTypeObject DrgnObject_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_drgn.Object", /* tp_name */
@ -1905,7 +1792,7 @@ PyTypeObject DrgnObject_type = {
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
DrgnObject_DOC, /* tp_doc */
drgn_Object_DOC, /* tp_doc */
NULL, /* tp_traverse */
NULL, /* tp_clear */
DrgnObject_richcompare, /* tp_richcompare */
@ -1926,6 +1813,30 @@ PyTypeObject DrgnObject_type = {
};
PyObject *DrgnObject_NULL(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"prog", "type", NULL};
PyObject *prog_obj, *type_obj;
PyObject *a, *k, *ret;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:NULL", keywords,
&prog_obj, &type_obj))
return NULL;
a = Py_BuildValue("OO", prog_obj, type_obj);
if (!a)
return NULL;
k = Py_BuildValue("{s:i}", "value", 0);
if (!k) {
Py_DECREF(a);
return NULL;
}
ret = PyObject_Call((PyObject *)&DrgnObject_type, a, k);
Py_DECREF(k);
Py_DECREF(a);
return ret;
}
DrgnObject *cast(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"type", "obj", NULL};

View File

@ -291,103 +291,26 @@ static PyObject *Program_get_byteorder(Program *self, void *arg)
static PyMethodDef Program_methods[] = {
{"__getitem__", (PyCFunction)Program_subscript, METH_O | METH_COEXIST,
"__getitem__(self, name) -> Object\n"
"\n"
"Implement self[name]. Return an Object (variable, constant, or function)\n"
"with the given name.\n"
"\n"
"If there are multiple objects with the same name, one is returned\n"
"arbitrarily. In this case, the constant(), function(), or variable()\n"
"methods can be used instead."},
drgn_Program___getitem___DOC},
{"read", (PyCFunction)Program_read, METH_VARARGS | METH_KEYWORDS,
"read(self, address: int, size: int, physical: bool = False) -> bytes\n"
"\n"
"Return size bytes of memory starting at address in the program. The\n"
"address may be virtual (the default) or physical if the program supports\n"
"it.\n"
"\n"
">>> prog.read(0xffffffffbe012b40, 16)\n"
"b'swapper/0\\x00\\x00\\x00\\x00\\x00\\x00\\x00'"},
drgn_Program_read_DOC},
{"type", (PyCFunction)Program_find_type, METH_VARARGS | METH_KEYWORDS,
"type(self, name: str, filename: Optional[str] = None) -> Type\n"
"\n"
"Return a Type object for the type with the given name.\n"
"\n"
"If there are multiple types with the given name, they can be\n"
"distinguished by passing the filename that the desired identifier was\n"
"defined in. If no filename is given, it is undefined which one is\n"
"returned.\n"
"\n"
"If no matches are found, this raises a LookupError.\n"
"\n"
">>> prog.type('long')\n"
"int_type(name='long', size=8, is_signed=True)"},
drgn_Program_type_DOC},
{"constant", (PyCFunction)Program_constant,
METH_VARARGS | METH_KEYWORDS,
"constant(self, name: str, filename: Optional[str] = None) -> Object\n"
"\n"
"Return an Object representing the constant (e.g., enumeration constant\n"
"or macro) with the given name.\n"
"\n"
"If there are multiple constants with the given name, they can be\n"
"distinguished by passing the filename that the desired constant was\n"
"defined in. If no filename is given, it is undefined which one is\n"
"returned.\n"
"\n"
"If no matches are found, this raises a LookupError.\n"
"\n"
"Note that support for macro constants is not yet implemented for DWARF\n"
"files, and most compilers don't generate macro debugging information\n"
"by default anyways.\n"
"\n"
">>> prog.constant('PIDTYPE_MAX')\n"
"Object(prog, 'enum pid_type', value=4)"},
METH_VARARGS | METH_KEYWORDS, drgn_Program_constant_DOC},
{"function", (PyCFunction)Program_function,
METH_VARARGS | METH_KEYWORDS,
"function(self, name: str, filename: Optional[str] = None) -> Object\n"
"\n"
"Return an Object representing the function with the given name.\n"
"\n"
"If there are multiple functions with the given name, they can be\n"
"distinguished by passing the filename that the desired function was\n"
"defined in. If no filename is given, it is undefined which one is\n"
"returned.\n"
"\n"
"If no matches are found, this raises a LookupError.\n"
"\n"
">>> prog.function('schedule')\n"
"Object(prog, 'void (void)', address=0xffffffff94392370)"},
METH_VARARGS | METH_KEYWORDS, drgn_Program_function_DOC},
{"variable", (PyCFunction)Program_variable,
METH_VARARGS | METH_KEYWORDS,
"variable(self, name: str, filename: Optional[str] = None) -> Object\n"
"\n"
"Return an Object representing the variable with the given name.\n"
"\n"
"If there are multiple variables with the given name, they can be\n"
"distinguished by passing the filename that the desired variable was\n"
"defined in. If no filename is given, it is undefined which one is\n"
"returned.\n"
"\n"
"If no matches are found, this raises a LookupError.\n"
"\n"
">>> prog.variable('jiffies')\n"
"Object(prog, 'volatile unsigned long', address=0xffffffff94c05000)"},
METH_VARARGS | METH_KEYWORDS, drgn_Program_variable_DOC},
{},
};
static PyGetSetDef Program_getset[] = {
{"flags", (getter)Program_get_flags, NULL,
"ProgramFlags\n"
"\n"
"flags which apply to this program"},
{"flags", (getter)Program_get_flags, NULL, drgn_Program_flags_DOC},
{"word_size", (getter)Program_get_word_size, NULL,
"int\n"
"\n"
"size of a word in this program in bytes"},
drgn_Program_word_size_DOC},
{"byteorder", (getter)Program_get_byteorder, NULL,
"str\n"
"\n"
"byte order in this program (either 'little' or 'big')"},
drgn_Program_byteorder_DOC},
{},
};
@ -396,20 +319,6 @@ static PyMappingMethods Program_as_mapping = {
(binaryfunc)Program_subscript, /* mp_subscript */
};
#define Program_DOC \
"A Program represents a crashed or running program. It can be used to lookup\n" \
"type definitions, access variables, and read arbitrary memory.\n" \
"\n" \
"The main functionality of a Program is looking up objects (i.e.,\n" \
"variables, constants, or functions). This is done with the \"[]\"\n" \
"operator.\n" \
"\n" \
">>> print(prog['pid_max'])\n" \
"(int)32768\n" \
"\n" \
"A Program cannot be constructed directly. Instead, use\n" \
"program_from_core_dump(), program_from_kernel(), or program_from_pid()."
PyTypeObject Program_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_drgn.Program", /* tp_name */
@ -431,7 +340,7 @@ PyTypeObject Program_type = {
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
Program_DOC, /* tp_doc */
drgn_Program_DOC, /* tp_doc */
(traverseproc)Program_traverse, /* tp_traverse */
(inquiry)Program_clear, /* tp_clear */
NULL, /* tp_richcompare */

View File

@ -551,72 +551,35 @@ static PyObject *DrgnType_getter(DrgnType *self, struct DrgnType_Attr *attr)
static PyGetSetDef DrgnType_getset[] = {
{"_ptr", (getter)DrgnType_get_ptr, NULL,
"int\n"
"Address of underlying ``struct drgn_type``.\n"
"\n"
"address of underlying struct drgn_type"},
"This is used for testing.\n"
"\n"
":vartype: int"},
{"kind", (getter)DrgnType_getter, NULL,
"TypeKind\n"
"\n"
"kind of this type", &DrgnType_attr_kind},
{"qualifiers", (getter)DrgnType_getter, NULL,
"Qualifiers\n"
"\n"
"bitmask of this type's qualifiers", &DrgnType_attr_qualifiers},
{"name", (getter)DrgnType_getter, NULL,
"str\n"
"\n"
"name of this integer, boolean, floating-point, complex, or typedef type\n",
drgn_Type_kind_DOC, &DrgnType_attr_kind},
{"qualifiers", (getter)DrgnType_getter, NULL, drgn_Type_qualifiers_DOC,
&DrgnType_attr_qualifiers},
{"name", (getter)DrgnType_getter, NULL, drgn_Type_name_DOC,
&DrgnType_attr_name},
{"tag", (getter)DrgnType_getter, NULL,
"Optional[str]\n"
"\n"
"tag of this structure, union, or enumerated type or None if this is an\n"
"anonymous type", &DrgnType_attr_tag},
{"size", (getter)DrgnType_getter, NULL,
"Optional[int]\n"
"\n"
"size in bytes of this integer, boolean, floating-point, complex,\n"
"structure, union, or pointer type, or None if this is an incomplete\n"
"structure or union type", &DrgnType_attr_size},
{"length", (getter)DrgnType_getter, NULL,
"Optional[int]\n"
"\n"
"number of elements in this array type or None if this is an incomplete\n"
"array type", &DrgnType_attr_length},
{"is_signed", (getter)DrgnType_getter, NULL,
"bool\n"
"\n"
"whether this integer type is signed", &DrgnType_attr_is_signed},
{"type", (getter)DrgnType_getter, NULL,
"Optional[Type]\n"
"\n"
"type underlying this type (i.e., the type denoted by a typedef type, the\n"
"compatible integer type of an enumerated type [which is None if this is\n"
"an incomplete type], the type referenced by a pointer type, the element\n"
"type of an array, or the return type of a function type)\n",
{"tag", (getter)DrgnType_getter, NULL, drgn_Type_tag_DOC,
&DrgnType_attr_tag},
{"size", (getter)DrgnType_getter, NULL, drgn_Type_size_DOC,
&DrgnType_attr_size},
{"length", (getter)DrgnType_getter, NULL, drgn_Type_length_DOC,
&DrgnType_attr_length},
{"is_signed", (getter)DrgnType_getter, NULL, drgn_Type_is_signed_DOC,
&DrgnType_attr_is_signed},
{"type", (getter)DrgnType_getter, NULL, drgn_Type_type_DOC,
&DrgnType_attr_type},
{"members", (getter)DrgnType_getter, NULL,
"Optional[List[Tuple[Type, Optional[str], int, int]]]\n"
"\n"
"list of members of this structure or union type as (type, name, bit\n"
"offset, bit field size) tuples, or None if this is an incomplete type",
{"members", (getter)DrgnType_getter, NULL, drgn_Type_members_DOC,
&DrgnType_attr_members},
{"enumerators", (getter)DrgnType_getter, NULL,
"Optional[List[Tuple[str, int]]]\n"
"\n"
"list of enumeration constants of this enumerated type as (name, value)\n"
"tuples, or None if this is an incomplete type",
&DrgnType_attr_enumerators},
{"parameters", (getter)DrgnType_getter, NULL,
"List[Tuple[Type, Optional[str]]]\n"
"\n"
"list of parameters of this function type as (type, name) tuples",
drgn_Type_enumerators_DOC, &DrgnType_attr_enumerators},
{"parameters", (getter)DrgnType_getter, NULL, drgn_Type_parameters_DOC,
&DrgnType_attr_parameters},
{"is_variadic", (getter)DrgnType_getter, NULL,
"bool\n"
"\n"
"whether this function type takes a variable number of arguments",
&DrgnType_attr_is_variadic},
drgn_Type_is_variadic_DOC, &DrgnType_attr_is_variadic},
{},
};
@ -979,23 +942,29 @@ static PyObject *DrgnType_is_complete(DrgnType *self)
static int qualifiers_converter(PyObject *arg, void *result)
{
PyObject *value;
unsigned long qualifiers;
if (!PyObject_TypeCheck(arg, (PyTypeObject *)Qualifiers_class)) {
PyErr_SetString(PyExc_TypeError,
"qualifiers must be Qualifiers");
return 0;
if (arg == Py_None) {
qualifiers = 0;
} else {
PyObject *value;
if (!PyObject_TypeCheck(arg,
(PyTypeObject *)Qualifiers_class)) {
PyErr_SetString(PyExc_TypeError,
"qualifiers must be Qualifiers or None");
return 0;
}
value = PyObject_GetAttrString(arg, "value");
if (!value)
return 0;
qualifiers = PyLong_AsUnsignedLong(value);
Py_DECREF(value);
if (qualifiers == (unsigned long)-1 && PyErr_Occurred())
return 0;
}
value = PyObject_GetAttrString(arg, "value");
if (!value)
return 0;
qualifiers = PyLong_AsUnsignedLong(value);
Py_DECREF(value);
if (qualifiers == (unsigned long)-1 && PyErr_Occurred())
return 0;
*(unsigned char *)result = qualifiers;
return 1;
}
@ -1053,38 +1022,15 @@ static PyObject *DrgnType_richcompare(DrgnType *self, PyObject *other, int op)
Py_RETURN_FALSE;
}
#define DrgnType_DOC \
"Type descriptor\n" \
"\n" \
"A Type object represents a type in a program. Each kind of type (e.g.,\n" \
"integer, structure) has different descriptors (e.g., name, size). Types\n" \
"can also have qualifiers (e.g., constant, atomic). Accessing a\n" \
"descriptor which does not apply to a type raises an exception.\n" \
"\n" \
"This class cannot be constructed directly. Instead, use one of the\n" \
"*_type() factory functions."
static PyMethodDef DrgnType_methods[] = {
{"type_name", (PyCFunction)DrgnType_type_name, METH_NOARGS,
"type_name(self) -> str\n"
"\n"
"Get a descriptive full name of this type."},
drgn_Type_type_name_DOC},
{"is_complete", (PyCFunction)DrgnType_is_complete, METH_NOARGS,
"is_complete(self) -> bool\n"
"\n"
"Get whether this type is complete (i.e., the type definition is known).\n"
"This is always False for void types. It may be False for structure,\n"
"union, enumerated, and array types, as well as typedef types where the\n"
"underlying type is one of those. Otherwise, it is always True."},
drgn_Type_is_complete_DOC},
{"qualified", (PyCFunction)DrgnType_qualified,
METH_VARARGS | METH_KEYWORDS,
"qualified(self, qualifiers: Qualifiers) -> Type\n"
"\n"
"Return a copy of this type with different qualifiers."},
METH_VARARGS | METH_KEYWORDS, drgn_Type_qualified_DOC},
{"unqualified", (PyCFunction)DrgnType_unqualified, METH_NOARGS,
"unqualified(self) -> Type\n"
"\n"
"Return a copy of this type with no qualifiers."},
drgn_Type_unqualified_DOC},
{},
};
@ -1116,7 +1062,7 @@ PyTypeObject DrgnType_type = {
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
DrgnType_DOC, /* tp_doc */
drgn_Type_DOC, /* tp_doc */
(traverseproc)DrgnType_traverse, /* tp_traverse */
(inquiry)DrgnType_clear, /* tp_clear */
(richcmpfunc)DrgnType_richcompare, /* tp_richcompare */