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

Allow naming and configuring order of type, object, and symbol finders #393

Merged
merged 6 commits into from
Jun 5, 2024
Merged
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
209 changes: 176 additions & 33 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ libdrgn bindings
Don't use this module directly. Instead, use the drgn package.
"""

import collections.abc
import enum
import os
import sys
Expand All @@ -21,6 +22,7 @@ from typing import (
Mapping,
Optional,
Sequence,
Set,
Tuple,
Union,
overload,
Expand Down Expand Up @@ -435,64 +437,185 @@ class Program:
another :ref:`buffer <python:binaryseq>` type.
"""
...
def add_type_finder(
self, fn: Callable[[TypeKind, str, Optional[str]], Optional[Type]]
def register_type_finder(
self,
name: str,
fn: Callable[[Program, TypeKindSet, str, Optional[str]], Optional[Type]],
*,
enable_index: Optional[int] = None,
) -> None:
"""
Register a callback for finding types in the program.

Callbacks are called in reverse order of the order they were added
until the type is found. So, more recently added callbacks take
precedence.
This does not enable the finder unless *enable_index* is given.

:param fn: Callable taking a :class:`TypeKind`, name, and filename:
``(kind, name, filename)``. The filename should be matched with
:func:`filename_matches()`. This should return a :class:`Type`
or ``None`` if not found.
:param name: Finder name.
:param fn: Callable taking the program, a :class:`TypeKindSet`, name,
and filename: ``(prog, kinds, name, filename)``. The filename
should be matched with :func:`filename_matches()`. This should
return a :class:`Type` or ``None`` if not found.
:param enable_index: Insert the finder into the list of enabled type
finders at the given index. If -1 or greater than the number of
enabled finders, insert it at the end. If ``None`` or not given,
don't enable the finder.
:raises ValueError: if there is already a finder with the given name
"""
...
def add_object_finder(
def registered_type_finders(self) -> Set[str]:
"""Return the names of all registered type finders."""
...
def set_enabled_type_finders(self, names: Sequence[str]) -> None:
"""
Set the list of enabled type finders.

Finders are called in the same order as the list until a type is found.

Finders that are not in the list are not called.

:param names: Names of finders to enable, in order.
:raises ValueError: if no finder has a given name or the same name is
given more than once
"""
...
def enabled_type_finders(self) -> List[str]:
"""Return the names of enabled type finders, in order."""
...
def register_object_finder(
self,
name: str,
fn: Callable[[Program, str, FindObjectFlags, Optional[str]], Optional[Object]],
*,
enable_index: Optional[int] = None,
) -> None:
"""
Register a callback for finding objects in the program.

Callbacks are called in reverse order of the order they were added
until the object is found. So, more recently added callbacks take
precedence.
This does not enable the finder unless *enable_index* is given.

:param fn: Callable taking a program, name, :class:`FindObjectFlags`,
:param name: Finder name.
:param fn: Callable taking the program, name, :class:`FindObjectFlags`,
and filename: ``(prog, name, flags, filename)``. The filename
should be matched with :func:`filename_matches()`. This should
return an :class:`Object` or ``None`` if not found.
:param enable_index: Insert the finder into the list of enabled object
finders at the given index. If -1 or greater than the number of
enabled finders, insert it at the end. If ``None`` or not given,
don't enable the finder.
:raises ValueError: if there is already a finder with the given name
"""
...
def registered_object_finders(self) -> Set[str]:
"""Return the names of all registered object finders."""
...
def set_enabled_object_finders(self, names: Sequence[str]) -> None:
"""
Set the list of enabled object finders.

Finders are called in the same order as the list until an object is found.

Finders that are not in the list are not called.

:param names: Names of finders to enable, in order.
:raises ValueError: if no finder has a given name or the same name is
given more than once
"""
...
def add_symbol_finder(
self, fn: Callable[[Optional[str], Optional[int], bool], Sequence[Symbol]]
def enabled_object_finders(self) -> List[str]:
"""Return the names of enabled object finders, in order."""
...
def register_symbol_finder(
self,
name: str,
fn: Callable[[Program, Optional[str], Optional[int], bool], Sequence[Symbol]],
*,
enable_index: Optional[int] = None,
) -> None:
"""
Register a callback for finding symbols in the program.

The callback should take three arguments: a search name, a search
address, and a boolean flag 'one' indicating whether to return only
the single best match. When the 'one' flag is True, the callback should
return a list containing at most one :class:`Symbol`. When the flag is
False, the callback should return a list of all matching
:class:`Symbol`\\ s. Both the name and address arguments are optional.
If both are provided, then the result(s) should match both. If neither
are provided, the finder should return all available symbols. If no
result is found, the return should be an empty list.
This does not enable the finder unless *enable_index* is given.

The callback should take four arguments: the program, a *name*, an
*address*, and a boolean flag *one*. It should return a list of symbols
or an empty list if no matches are found.

If *name* is not ``None``, then only symbols with that name should be
returned. If *address* is not ``None``, then only symbols containing
that address should be returned. If neither is ``None``, then the
returned symbols must match both. If both are ``None``, then all
symbols should be considered matching.

When the *one* flag is ``False``, the callback should return a list of
all matching symbols. When it is ``True``, it should return a list with
at most one symbol which is the best match.

:param name: Finder name.
:param fn: Callable taking ``(prog, name, address, one)`` and returning
a sequence of :class:`Symbol`\\ s.
:param enable_index: Insert the finder into the list of enabled finders
at the given index. If -1 or greater than the number of enabled
finders, insert it at the end. If ``None`` or not given, don't
enable the finder.
:raises ValueError: if there is already a finder with the given name
"""
...
def registered_symbol_finders(self) -> Set[str]:
"""Return the names of all registered symbol finders."""
...
def set_enabled_symbol_finders(self, names: Sequence[str]) -> None:
"""
Set the list of enabled symbol finders.

Finders are called in the same order as the list. When the *one* flag
is set, the search will short-circuit after the first finder which
returns a result, and subsequent finders will not be called. Otherwise,
all callbacks will be called, and all results will be returned.

Finders that are not in the list are not called.

:param names: Names of finders to enable, in order.
:raises ValueError: if no finder has a given name or the same name is
given more than once
"""
...
def enabled_symbol_finders(self) -> List[str]:
"""Return the names of enabled symbol finders, in order."""
...
def add_type_finder(
self, fn: Callable[[TypeKind, str, Optional[str]], Optional[Type]]
) -> None:
"""
Deprecated method to register and enable a callback for finding types
in the program.

.. deprecated:: 0.0.27
Use :meth:`register_type_finder()` instead.

The differences from :meth:`register_type_finder()` are:

Callbacks are called in reverse order of the order they were added
(i.e,, the most recently added callback is called first). When the
'one' flag is set, the search will short-circuit after the first
finder which returns a result, and subsequent finders will not be
called. Otherwise, all callbacks will be called, and all results will be
returned.
1. *fn* is not passed *prog*.
2. *fn* is passed a :class:`TypeKind` instead of a
:class:`TypeKindSet`. If multiple kinds are being searched for, *fn*
will be called multiple times.
3. A name for the finder is generated from *fn*.
4. The finder is always enabled before any existing finders.
"""
...
def add_object_finder(
self,
fn: Callable[[Program, str, FindObjectFlags, Optional[str]], Optional[Object]],
) -> None:
"""
Deprecated method to register and enable a callback for finding objects
in the program.

.. deprecated:: 0.0.27
Use :meth:`register_object_finder()` instead.

:param fn: Callable taking name, address, and 'one' flag, and
returning a sequence of :class:`Symbol`\\ s.
The differences from :meth:`register_object_finder()` are:

1. A name for the finder is generated from *fn*.
2. The finder is always enabled before any existing finders.
"""
...
def set_core_dump(self, path: Union[Path, int]) -> None:
Expand Down Expand Up @@ -2297,6 +2420,26 @@ class TypeKind(enum.Enum):
FUNCTION = ...
"""Function type."""

class TypeKindSet(collections.abc.Set[TypeKind]):
"""
Immutable set of :class:`TypeKind`\\ s.

>>> kinds = TypeKindSet({TypeKind.STRUCT, TypeKind.CLASS})
>>> TypeKind.STRUCT in kinds
True
>>> TypeKind.INT in kinds
False
>>> for kind in kinds:
... print(kind)
...
TypeKind.STRUCT
TypeKind.CLASS
"""

def __contains__(self, __x: object) -> bool: ...
def __iter__(self) -> Iterator[TypeKind]: ...
def __len__(self) -> int: ...

class PrimitiveType(enum.Enum):
"""A ``PrimitiveType`` represents a primitive type known to drgn."""

Expand Down
4 changes: 2 additions & 2 deletions docs/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ program "memory":
print(drgn.Object(prog, 'struct btrfs_super_block', address=65536))
run_interactive(prog, banner_func=lambda _: "BTRFS debugger")

:meth:`drgn.Program.add_type_finder()` and
:meth:`drgn.Program.add_object_finder()` are the equivalent methods for
: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
Expand Down
1 change: 1 addition & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Types
.. drgndoc:: TypeParameter
.. drgndoc:: TypeTemplateParameter
.. drgndoc:: TypeKind
.. drgndoc:: TypeKindSet
.. drgndoc:: PrimitiveType
.. drgndoc:: Qualifiers
.. drgndoc:: offsetof
Expand Down
2 changes: 2 additions & 0 deletions drgn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
Type,
TypeEnumerator,
TypeKind,
TypeKindSet,
TypeMember,
TypeParameter,
TypeTemplateParameter,
Expand Down Expand Up @@ -129,6 +130,7 @@
"Type",
"TypeEnumerator",
"TypeKind",
"TypeKindSet",
"TypeMember",
"TypeParameter",
"TypeTemplateParameter",
Expand Down
5 changes: 3 additions & 2 deletions libdrgn/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ libdrgnimpl_la_SOURCES = $(ARCH_DEFS_PYS:_defs.py=.c) \
error.c \
error.h \
generics.h \
handler.c \
handler.h \
hash_table.c \
hash_table.h \
helpers.h \
Expand All @@ -86,8 +88,6 @@ libdrgnimpl_la_SOURCES = $(ARCH_DEFS_PYS:_defs.py=.c) \
nstring.h \
object.c \
object.h \
object_index.c \
object_index.h \
openmp.c \
openmp.h \
orc.h \
Expand Down Expand Up @@ -170,6 +170,7 @@ _drgn_la_SOURCES = python/constants.c \
python/test.c \
python/thread.c \
python/type.c \
python/type_kind_set.c \
python/util.c

_drgn_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden
Expand Down
12 changes: 12 additions & 0 deletions libdrgn/bitops.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@
#define ilog2_impl(arg, suffix, x) \
((8 * sizeof(0u##suffix) - 1) ^ __builtin_clz##suffix(x))

/**
* Bit population count.
*
* Return the number of 1-bits in @p x.
*
* ```
* popcount(8) == 1
* popcount(3) == 2
* ```
*/
#define popcount(x) generic_bitop(x, PP_UNIQUE(_x), builtin_bitop_impl, popcount)

#define builtin_bitop_impl(arg, suffix, x) __builtin_##arg##suffix(x)
#define generic_bitop(x, unique_x, impl, impl_arg) ({ \
__auto_type unique_x = (x); \
Expand Down
25 changes: 18 additions & 7 deletions libdrgn/debug_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -2181,13 +2181,24 @@ void drgn_debug_info_init(struct drgn_debug_info *dbinfo,
// unlikely to fail anwyays, so don't bother propagating an error up.
if (!dbinfo->dwfl)
abort();
drgn_program_add_type_finder_impl(prog, &dbinfo->type_finder,
drgn_debug_info_find_type, dbinfo);
drgn_program_add_object_finder_impl(prog, &dbinfo->object_finder,
drgn_debug_info_find_object,
dbinfo);
drgn_program_add_symbol_finder_impl(prog, &dbinfo->symbol_finder,
elf_symbols_search, prog);
const struct drgn_type_finder_ops type_finder_ops = {
.find = drgn_debug_info_find_type,
};
drgn_program_register_type_finder_impl(prog, &dbinfo->type_finder,
"dwarf", &type_finder_ops,
dbinfo, 0);
const struct drgn_object_finder_ops object_finder_ops = {
.find = drgn_debug_info_find_object,
};
drgn_program_register_object_finder_impl(prog, &dbinfo->object_finder,
"dwarf", &object_finder_ops,
dbinfo, 0);
const struct drgn_symbol_finder_ops symbol_finder_ops = {
.find = elf_symbols_search,
};
drgn_program_register_symbol_finder_impl(prog, &dbinfo->symbol_finder,
"elf", &symbol_finder_ops,
prog, 0);
drgn_module_table_init(&dbinfo->modules);
c_string_set_init(&dbinfo->module_names);
drgn_dwarf_info_init(dbinfo);
Expand Down
6 changes: 3 additions & 3 deletions libdrgn/debug_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "drgn.h"
#include "dwarf_info.h"
#include "hash_table.h"
#include "object_index.h"
#include "object.h"
#include "orc_info.h"
#include "string_builder.h"
#include "symbol.h"
Expand Down Expand Up @@ -253,13 +253,13 @@ struct drgn_error *
drgn_debug_info_main_language(struct drgn_debug_info *dbinfo,
const struct drgn_language **ret);

/** @ref drgn_type_find_fn() that uses debugging information. */
/** @ref drgn_type_finder_ops::find() that uses debugging information. */
struct drgn_error *drgn_debug_info_find_type(uint64_t kinds, const char *name,
size_t name_len,
const char *filename, void *arg,
struct drgn_qualified_type *ret);

/** @ref drgn_object_find_fn() that uses debugging information. */
/** @ref drgn_object_finder_ops::find() that uses debugging information. */
struct drgn_error *
drgn_debug_info_find_object(const char *name, size_t name_len,
const char *filename,
Expand Down
Loading