File: advanced_usage.rst

package info (click to toggle)
drgn 0.0.32-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,096 kB
  • sloc: ansic: 50,186; python: 46,462; awk: 423; makefile: 339; sh: 114
file content (427 lines) | stat: -rw-r--r-- 15,409 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
Advanced Usage
==============

.. highlight:: pycon

The :doc:`user_guide` covers basic usage of drgn, but drgn also supports more
advanced use cases which are covered here.

.. _advanced-modules:

Modules and Debugging Symbols
-----------------------------

drgn tries to determine what executable, libraries, etc. a program uses and
load debugging symbols automatically. As long as :doc:`debugging symbols are
installed <getting_debugging_symbols>`, this should work out of the box on
standard setups.

For non-standard scenarios, drgn allows overriding the defaults with different
levels of control and complexity.

Loading Debugging Symbols From Non-Standard Locations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. program:: drgn

drgn searches standard locations for debugging symbols. If you have debugging
symbols available in a non-standard location, you can provide it to the CLI
with the :option:`-s`/:option:`--symbols` option:

.. code-block:: console

    $ drgn -s ./libfoo.so -s /usr/lib/libbar.so.debug

Or with the :meth:`drgn.Program.load_debug_info()` method::

    >>> prog.load_debug_info(["./libfoo.so", "/usr/lib/libbar.so.debug"])

Loading Debugging Symbols For Specific Modules
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:option:`-s` and :meth:`~drgn.Program.load_debug_info()` try the given files
against all of the modules loaded in the program based on build IDs. You can
also :ref:`look up <api-module-constructors>` a specific module and try a given
file for just that module with :meth:`drgn.Module.try_file()`::

    >>> prog.main_module().try_file("build/vmlinux")

Loading Additional Debugging Symbols
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:option:`-s` and :meth:`~drgn.Program.load_debug_info()` ignore files that
don't correspond to a loaded module. To load debugging symbols from an
arbitrary file, pass :option:`--extra-symbols` to the CLI:

.. code-block:: console

    $ drgn --extra-symbols ./my_extra_symbols.debug

Or create a :class:`drgn.ExtraModule`::

    >>> module = prog.extra_module("my_extra_symbols", create=True)
    >>> module.try_file("./my_extra_symbols.debug")

Listing Modules
^^^^^^^^^^^^^^^

By default, drgn creates a module for everything loaded in the program. You can
disable this in the CLI with :option:`--no-default-symbols`.

You can find or create the loaded modules programmatically with
:meth:`drgn.Program.loaded_modules()`::

    >>> for module, new in prog.loaded_modules():
    ...     print("Created" if new else "Found", module)

You can see all of the created modules with :meth:`drgn.Program.modules()`.

Overriding Modules
^^^^^^^^^^^^^^^^^^

You can create modules with the :ref:`module factory functions
<api-module-constructors>`. You can also modify various attributes of the
:class:`drgn.Module` class.

.. _debugging-information-finders-example:

Debugging Information Finders
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A callback for automatically finding debugging symbols for a set of modules can
be registered with :meth:`drgn.Program.register_debug_info_finder()`. Here is
an example for getting debugging symbols on Fedora Linux using DNF:

.. code-block:: python3

    import subprocess

    import drgn

    # Install debugging symbols using the DNF debuginfo-install plugin. Note that
    # this is mainly for demonstration purposes; debuginfod, which drgn supports
    # out of the box, is more reliable.
    def dnf_debug_info_finder(modules: list[drgn.Module]) -> None:
        # Determine all of the packages for the given modules.
        packages = set()
        for module in modules:
            if not module.wants_debug_file():
                continue

            if not module.name.startswith("/"):
                continue

            proc = subprocess.run(
                ["rpm", "--query", "--file", module.name],
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                text=True,
            )
            if proc.returncode == 0:
                packages.add(proc.stdout.rstrip("\n"))

        # Try installing their debug info.
        subprocess.call(
            ["sudo", "dnf", "debuginfo-install", "--skip-broken", "--"]
            + sorted(packages)
        )

        # Now that it's installed, try the standard locations. Other finders may
        # need to try specific files for specific modules with module.try_file()
        # instead.
        modules[0].prog.find_standard_debug_info(modules)


    prog.register_debug_info_finder("dnf", dnf_debug_info_finder, enable_index=-1)

Custom debugging information finders can even be configured automatically
through the :ref:`plugin system <writing-plugins>`.

.. _writing-plugins:

Writing Plugins
---------------

In order for drgn to load a plugin automatically, it must be registered as an
`entry point <https://packaging.python.org/specifications/entry-points/>`_ for
the ``drgn.plugins`` group. Here is a minimal example. First:

.. code-block:: console

    $ mkdir drgn_plugin_example
    $ cd drgn_plugin_example

Then, create ``pyproject.toml`` with the following contents:

.. code-block:: toml
    :caption: pyproject.toml
    :emphasize-lines: 5-6

    [project]
    name = 'drgn_plugin_example'
    version = '0.0.1'

    [project.entry-points.'drgn.plugins']
    example = 'drgn_plugin_example'

See the `Python Packaging User Guide
<https://packaging.python.org/guides/writing-pyproject-toml/>`_ for a complete
description of ``pyproject.toml``. We are most interested in the last two
lines, which define the entry point. In ``example = 'drgn_plugin_example'``,
``example`` is the plugin name, and ``drgn_plugin_example`` is the plugin
module.

Create ``drgn_plugin_example.py`` with the following contents:

.. code-block:: python3
    :caption: drgn_plugin_example.py

    import drgn

    def example_debug_info_finder(modules: list[drgn.Module]) -> None:
        for module in modules:
            if isinstance(module, drgn.MainModule):
                module.try_file("/my/vmlinux")

    def drgn_prog_set(prog: drgn.Program) -> None:
        if prog.flags & drgn.ProgramFlags.IS_LINUX_KERNEL:
            prog.register_debug_info_finder(
                "example", example_debug_info_finder, enable_index=-1
            )
    # Optional; the default is 50;
    drgn_prog_set.drgn_priority = 100

This is a typical usage of the :func:`drgn_prog_set()` hook to register
finders. See :ref:`plugins` for more details.

After creating the above files, the plugin can be installed with
``pip install .``.

Library
-------

In addition to the CLI, drgn is also available as a library.
:func:`drgn.program_from_core_dump()`, :func:`drgn.program_from_kernel()`, and
:func:`drgn.program_from_pid()` correspond to the :option:`-c`, :option:`-k`,
and :option:`-p` command line options, respectively; they return a
:class:`drgn.Program` that can be used just like the one initialized by the
CLI::

    >>> import drgn
    >>> prog = drgn.program_from_kernel()

C Library
---------

The core functionality of drgn is implemented in C and is available as a C
library, ``libdrgn``. See |drgn.h|_.

.. |drgn.h| replace:: ``drgn.h``
.. _drgn.h: https://github.com/osandov/drgn/blob/main/libdrgn/drgn.h

Full documentation can be generated by running ``doxygen`` in the ``libdrgn``
directory of the source code. Note that the API and ABI are not yet stable.

Custom Programs
---------------

The main components of a :class:`drgn.Program` are the program memory, types,
and objects. The CLI and equivalent library interfaces automatically determine
these. However, it is also possible to create a "blank" ``Program`` and plug in
the main components. The :func:`drgn.cli.run_interactive()` function allows you
to run the same drgn CLI once you've created a :class:`drgn.Program`, so it's
easy to make a custom program which allows interactive debugging.

:meth:`drgn.Program.add_memory_segment()` defines a range of memory and how to
read that memory. The following example uses a Btrfs filesystem image as the
program "memory":

.. code-block:: python3

    import os
    import sys

    import drgn
    from drgn.cli import run_interactive


    def btrfs_debugger(dev):
        file = open(dev, "rb")
        size = file.seek(0, 2)

        def read_file(address, count, offset, physical):
            file.seek(offset)
            return file.read(count)

        platform = drgn.Platform(
            drgn.Architecture.UNKNOWN, drgn.PlatformFlags.IS_LITTLE_ENDIAN
        )
        prog = drgn.Program(platform)
        prog.add_memory_segment(0, size, read_file)
        module = prog.extra_module("btrfs", create=True)
        module.try_file(f"/lib/modules/{os.uname().release}/kernel/fs/btrfs/btrfs.ko")
        return prog


    prog = btrfs_debugger(sys.argv[1] if len(sys.argv) >= 2 else "/dev/sda")
    print(drgn.Object(prog, "struct btrfs_super_block", address=65536))
    run_interactive(prog, banner_func=lambda _: "BTRFS debugger")

:meth:`drgn.Program.register_type_finder()` and
:meth:`drgn.Program.register_object_finder()` are the equivalent methods for
plugging in types and objects.

Environment Variables
---------------------

Some of drgn's behavior can be modified through environment variables:

.. envvar:: DRGN_DISABLE_PLUGINS

    Comma-separated list of plugins to disable. Each item is a glob pattern
    matching plugin entry point names.

.. envvar:: DRGN_PLUGINS

    Comma-separated list of plugins to enable. Each item is either a plugin
    entry point name, a file path, or a module name. Empty items are ignored.

    An item not containing ``=`` is interpreted as a plugin entry point name.
    This takes precedence over :envvar:`DRGN_DISABLE_PLUGINS`.

    An item containing ``=`` is interpreted as an extra plugin to load manually
    instead of via an entry point. The string before ``=`` is the plugin name.
    The string after ``=`` is the value. If the value contains a ``/``, it is
    the file path of a Python module. Otherwise, it is a module name.

    So, ``DRGN_DISABLE_PLUGINS=* DRGN_PLUGINS=foo,bar=/hello/world.py,baz=my.module``
    results in three plugins being loaded: the entry point ``foo``, the file
    ``/hello/world.py`` as ``bar``, and the module ``my.module`` as ``baz``.
    All other plugins are disabled.

.. envvar:: DRGN_MAX_DEBUG_INFO_ERRORS

    The maximum number of warnings about missing debugging information to log
    on CLI startup or from :meth:`drgn.Program.load_debug_info()`. Any
    additional errors are truncated. The default is 5; -1 is unlimited.

.. envvar:: DRGN_PREFER_ORC_UNWINDER

    Whether to prefer using `ORC
    <https://www.kernel.org/doc/html/latest/x86/orc-unwinder.html>`_ over DWARF
    for stack unwinding (0 or 1). The default is 0. Note that drgn will always
    fall back to ORC for functions lacking DWARF call frame information and
    vice versa. This environment variable is mainly intended for testing and
    may be ignored in the future.

.. envvar:: DRGN_USE_LIBKDUMPFILE_FOR_ELF

    Whether drgn should use libkdumpfile for ELF vmcores (0 or 1). The default
    is 0. This functionality will be removed in the future.

.. envvar:: DRGN_USE_SYS_MODULE

    Whether drgn should use ``/sys/module`` to find information about loaded
    kernel modules for the running kernel instead of getting them from the core
    dump (0 or 1). The default is 1. This environment variable is mainly
    intended for testing and may be ignored in the future.

.. envvar:: PYTHON_BASIC_REPL

    If non-empty, don't try to use the `new interactive REPL
    <https://docs.python.org/3/whatsnew/3.13.html#a-better-interactive-interpreter>`_
    added in Python 3.13. drgn makes use of the new REPL through internal
    implementation details since there is `not yet
    <https://github.com/python/cpython/issues/119512>`_ a public API for it. If
    it breaks, this may be used as an escape hatch.

.. _kernel-special-objects:

Linux Kernel Special Objects
----------------------------

When debugging the Linux kernel, there are some special :class:`drgn.Object`\ s
accessible with :meth:`drgn.Program.object()` and :meth:`drgn.Program[]
<drgn.Program.__getitem__>`. Some of these are available even without debugging
information, thanks to metadata called "vmcoreinfo" which is present in kernel
core dumps. These special objects include:

``UTS_RELEASE``
    Object type: ``const char []``

    This corresponds to the ``UTS_RELEASE`` macro in the Linux kernel source
    code. This is the exact kernel release (i.e., the output of ``uname -r``).

    To use this as a Python string, you must convert it::

        >>> release = prog["UTS_RELEASE"].string_().decode("ascii")

    This is available without debugging information.

``PAGE_SIZE``
    Object type: ``unsigned long``

``PAGE_SHIFT``
    Object type: ``unsigned int``

``PAGE_MASK``
    Object type: ``unsigned long``

    These correspond to the macros of the same name in the Linux kernel source
    code. The page size is the smallest contiguous unit of physical memory
    which can be allocated or mapped by the kernel.

    >>> prog['PAGE_SIZE']
    (unsigned long)4096
    >>> prog['PAGE_SHIFT']
    (int)12
    >>> prog['PAGE_MASK']
    (unsigned long)18446744073709547520
    >>> 1 << prog['PAGE_SHIFT'] == prog['PAGE_SIZE']
    True
    >>> ~(prog['PAGE_SIZE'] - 1) == prog['PAGE_MASK']
    True

    These are available without debugging information.

``jiffies``
    Object type: ``volatile unsigned long``

    This is a counter of timer ticks. It is actually an alias of ``jiffies_64``
    on 64-bit architectures, or the least significant 32 bits of ``jiffies_64``
    on 32-bit architectures. Since this alias is defined via the linker, drgn
    handles it specially.

    This is *not* available without debugging information.

``vmemmap``
    Object type: ``struct page *``

    This is a pointer to the "virtual memory map", an array of ``struct page``
    for each physical page of memory. While the purpose and implementation
    details of this array are beyond the scope of this documentation, it is
    enough to say that it is represented in the kernel source in an
    architecture-dependent way, frequently as a macro or constant. The
    definition provided by drgn ensures that users can access it without
    resorting to architecture-specific logic.

    This is *not* available without debugging information.

``VMCOREINFO``
    Object type: ``const char []``

    This is the data contained in the vmcoreinfo note, which is present either
    as an ELF note in ``/proc/kcore`` or ELF vmcores, or as a special data
    section in kdump-formatted vmcores. The vmcoreinfo note contains critical
    data necessary for interpreting the kernel image, such as KASLR offsets and
    data structure locations.

    In the Linux kernel, this data is normally stored in a variable called
    ``vmcoreinfo_data``. However, drgn reads this information from ELF note or
    from the diskdump header. It is possible (in rare cases, usually with
    vmcores created by hypervisors) for a vmcore to contain vmcoreinfo which
    differs from the data in ``vmcoreinfo_data``, so it is important to
    distinguish the contents. For that reason, we use the name ``VMCOREINFO`` to
    distinguish it from the kernel variable ``vmcoreinfo_data``.

    This is available without debugging information.