Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add drgn.helpers.drgn #453

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/helpers.rst
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.. drgndoc:: drgn.helpers
:exclude: drgn
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Table of Contents
advanced_usage
api_reference
helpers
dev_helpers
support_matrix
case_studies
getting_debugging_symbols
Expand Down
35 changes: 34 additions & 1 deletion drgn/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,28 @@ def _displayhook(value: Any) -> None:
setattr(builtins, "_", value)


def _add_drgn_helpers_banner(banner: str) -> str:
return (
banner
+ """
>>> from drgn.helpers.drgn import *

>>> self_prog = program_from_self()
>>> oot = get_prog_obj(self_prog, prog)"""
)


def _add_drgn_helpers_globals(ns: Dict[str, Any]) -> Dict[str, Any]:
module = importlib.import_module("drgn.helpers.drgn")
for name in module.__dict__["__all__"]:
ns[name] = getattr(module, name)

self_prog = module.program_from_self()
ns["self_prog"] = self_prog
ns["oot"] = module.get_prog_obj(self_prog, ns["prog"])
return ns


def _main() -> None:
handler = logging.StreamHandler()
handler.setFormatter(
Expand Down Expand Up @@ -245,6 +267,11 @@ def _main() -> None:
nargs=argparse.REMAINDER,
help="script to execute instead of running in interactive mode",
)
parser.add_argument(
"--debug-self",
action="store_true",
help=argparse.SUPPRESS,
)
parser.add_argument("--version", action="version", version=version)

args = parser.parse_args()
Expand Down Expand Up @@ -318,6 +345,12 @@ def _main() -> None:
except drgn.MissingDebugInfoError as e:
logger.warning("%s", e)

banner_func = None
globals_func = None
if args.debug_self:
banner_func = _add_drgn_helpers_banner
globals_func = _add_drgn_helpers_globals

if args.script:
sys.argv = args.script
script = args.script[0]
Expand All @@ -326,7 +359,7 @@ def _main() -> None:
drgn.set_default_prog(prog)
runpy.run_path(script, init_globals={"prog": prog}, run_name="__main__")
else:
run_interactive(prog)
run_interactive(prog, banner_func=banner_func, globals_func=globals_func)


def run_interactive(
Expand Down
127 changes: 127 additions & 0 deletions drgn/helpers/drgn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright (c) 2024 Oracle and/or its affiliates
# SPDX-License-Identifier: LGPL-2.1-or-later
"""
Development Helpers
-------------------

The ``drgn.helpers.drgn`` module provides drgn helpers for debugging drgn
itself. These are mainly useful to drgn's developers. Debugging drgn is similar
to other targets:

- You must have debuginfo for the drgn extension available. Debuginfo for Python
isn't necessarily required.
- You can debug a core dump or a running process.

Unlike other debug targets, you can additionally debug the running drgn process
*from the same process*. When that is happening, we use the following naming
convention:

- ``prog`` - an instance of drgn, which you want to debug
- ``self_prog`` - a :class:`drgn.Program` which is debugging the current process
- ``oot`` - "object of testing" representing ``prog``, the program we're
debugging

For instance, if you have started a drgn instance and would like to debug it,
you could do the following:

>>> from drgn.helpers.drgn import *
>>> self_prog = program_from_self()
>>> oot = get_prog_obj(prog)

Please note that some helpers in this module represent the internal
implementation details of drgn. As such, these helpers should not be considered
a stable API, and they should only be used at your own risk.

"""
import os
from typing import Iterator

from drgn import Object, Program, Type, program_from_pid

__all__ = (
"for_each_created_type",
"get_prog_obj",
"get_type_obj",
"print_type_report",
"program_from_self",
"type_repr",
"vector_for_each",
)


def program_from_self() -> Program:
"""
Return a program for debugging the current drgn process
"""
return program_from_pid(os.getpid())


def get_prog_obj(self_prog: Program, oot_prog: Program) -> Object:
"""
Return the ``struct drgn_program *`` corresponding to ``oot_prog``

When debugging drgn in the same process as it is currently running, use this
function to obtain the :class:`Object` representation of the program that is
under test. Please see the module docstring for a description of the naming
conventions used here.

:param self_prog: a program used to debug the current process
:param prog: the program we are debugging (may be the same as ``self_prog``)
:returns: an :class:`Object` representing ``oot_prog``
"""
return Object(self_prog, "Program *", value=id(oot_prog)).prog


def get_type_obj(self_prog: Program, tp: Type) -> Object:
"""
Return the ``struct drgn_type *`` corresponding to a type
:param self_prog: a program used to debug the current instance of drgn
:param tp: a type object to lookup
:returns: an :class:`Object` representing ``tp``
"""
return Object(self_prog, "DrgnType *", value=id(tp)).type


def vector_for_each(obj: Object, start: int = 0) -> Iterator[Object]:
"""
Iterate over objects in a drgn vector
:param obj: a pointer to the vector object
:param start: the starting index to iterate from
"""
for i in range(start, obj._size.value_()):
yield obj._data[i]


def for_each_created_type(oot: Object, start: int = 0) -> Iterator[Object]:
"""
Iterate over every type in a drgn program
:param oot: an object corresponding to ``struct drgn_program *``
:param start: starting index to iterate from
"""
yield from vector_for_each(oot.created_types, start=start)


def type_repr(tp: Object) -> str:
"""
Format a type's kind, name, and pointer for display

This is not as complete as :meth:`Type.type_name`, but it is a simple way to
print information that can help you identify a specific type.

:param tp: object representing a ``struct drgn_type *``
"""
spelling = tp.prog_["drgn_type_kind_spelling"][tp._kind].string_().decode()
name_obj = tp._name
if name_obj:
name = name_obj.string_().decode()
else:
name = "(anonymous)"
return f"{spelling} {name} (0x{tp.value_():x})"


def print_type_report(oot: Object, start: int = 0) -> None:
"""
Print a report of all loaded types for a program
"""
for i, tp in enumerate(for_each_created_type(oot, start)):
print(f"{i + start:4d} {type_repr(tp)}")
Loading