Skip to content

Commit

Permalink
Support searching for kernel modules without depmod metadata
Browse files Browse the repository at this point in the history
For live kernel debugging, you usually have a properly installed kernel
that has run depmod. However, for core dumps, it's common to get an
archive containing all of the kernel module files and no depmod
metadata. Peter Collingbourne also reported that the depmod situation on
Android is complicated: Android doesn't have a modules.dep.bin file, and
modules are split among different images.

Let's add support for walking kernel module directories if
modules.dep.bin isn't found and a debug info option for controlling
whether depmod, walking, or both are tried. We default to using depmod
if it is available and walking if not.

Closes #369.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
  • Loading branch information
osandov committed Jan 31, 2025
1 parent 94b6d39 commit 7dc474d
Show file tree
Hide file tree
Showing 15 changed files with 597 additions and 33 deletions.
57 changes: 57 additions & 0 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,7 @@ class DebugInfoOptions:
try_reuse: bool = ...,
try_supplementary: bool = ...,
kernel_directories: Iterable[Path] = ...,
try_kmod: KmodSearchMethod = ...,
) -> None:
"""
Create a ``DebugInfoOptions``.
Expand Down Expand Up @@ -1512,6 +1513,62 @@ class DebugInfoOptions:
:file:`/boot/vmlinux-{release}`, :file:`/lib/modules/{release}`) absolutely
and under each absolute path in :attr:`directories`.
"""
try_kmod: KmodSearchMethod
"""
How to search for loadable kernel modules.
Defaults to :attr:`KmodSearchMethod.DEPMOD_OR_WALK`.
"""

class KmodSearchMethod(enum.Enum):
"""
Methods of searching for loadable kernel module debugging information.
In addition to searching by build ID, there are currently two methods of
searching for debugging information specific to loadable kernel modules:
1. Using :manpage:`depmod(8)` metadata. This looks for :command:`depmod`
metadata (specifically, :file:`modules.dep.bin`) at the top level of
each directory in :attr:`DebugInfoOptions.kernel_directories` (an empty
path means :file:`/lib/modules/{release}`). The metadata is used to
quickly find the path of each module, which is then checked relative to
each directory specified by :attr:`DebugInfoOptions.kernel_directories`.
This method is faster but typically only applicable to installed
kernels.
2. Walking kernel directories. This traverses each directory specified by
:attr:`DebugInfoOptions.kernel_directories` looking for ``.ko`` files.
Module names are matched to filenames before the ``.ko`` extension and
with dashes (``-``) replaced with underscores (``_``).
This method is slower but not limited to installed kernels.
Debugging information searches can be configured to use one, both, or
neither method.
"""

NONE = ...
"""Don't search using kernel module-specific methods."""
DEPMOD = ...
"""Search using :command:`depmod` metadata."""
WALK = ...
"""Search by walking kernel directories."""
DEPMOD_OR_WALK = ...
"""
Search using :command:`depmod` metadata, falling back to walking kernel
directories only if no :command:`depmod` metadata is found.
Since :command:`depmod` metadata is expected to be reliable if present,
this is the default.
"""
DEPMOD_AND_WALK = ...
"""
Search using :command:`depmod` metadata and by walking kernel directories.
Unlike :attr:`DEPMOD_OR_WALK`, if :command:`depmod` metadata is found but
doesn't result in the desired debugging information, this will still walk
kernel directories.
"""

def get_default_prog() -> Program:
"""
Expand Down
1 change: 1 addition & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Programs
.. drgndoc:: FindObjectFlags

.. drgndoc:: DebugInfoOptions
.. drgndoc:: KmodSearchMethod

.. drgndoc:: Thread

Expand Down
2 changes: 2 additions & 0 deletions drgn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
FaultError,
FindObjectFlags,
IntegerLike,
KmodSearchMethod,
Language,
MainModule,
MissingDebugInfoError,
Expand Down Expand Up @@ -121,6 +122,7 @@
"FaultError",
"FindObjectFlags",
"IntegerLike",
"KmodSearchMethod",
"Language",
"MainModule",
"MissingDebugInfoError",
Expand Down
13 changes: 11 additions & 2 deletions drgn/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,20 @@ def __call__(


class _TryDebugInfoOptionAction(_DebugInfoOptionAction):
_choices = _DebugInfoOptionAction._bool_options(True)
_choices = {
**_DebugInfoOptionAction._bool_options(True),
"kmod=depmod": ("try_kmod", drgn.KmodSearchMethod.DEPMOD),
"kmod=walk": ("try_kmod", drgn.KmodSearchMethod.WALK),
"kmod=depmod-or-walk": ("try_kmod", drgn.KmodSearchMethod.DEPMOD_OR_WALK),
"kmod=depmod-and-walk": ("try_kmod", drgn.KmodSearchMethod.DEPMOD_AND_WALK),
}


class _NoDebugInfoOptionAction(_DebugInfoOptionAction):
_choices = _DebugInfoOptionAction._bool_options(False)
_choices = {
**_DebugInfoOptionAction._bool_options(False),
"kmod": ("try_kmod", drgn.KmodSearchMethod.NONE),
}


def _main() -> None:
Expand Down
1 change: 1 addition & 0 deletions libdrgn/build-aux/gen_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ConstantClass(NamedTuple):
CONSTANTS = (
ConstantClass("Architecture", "Enum", r"DRGN_ARCH_([a-zA-Z0-9_]+)"),
ConstantClass("FindObjectFlags", "Flag", r"DRGN_FIND_OBJECT_([a-zA-Z0-9_]+)"),
ConstantClass("KmodSearchMethod", "Enum", r"DRGN_KMOD_SEARCH_([a-zA-Z0-9_]+)"),
ConstantClass("ModuleFileStatus", "Enum", r"DRGN_MODULE_FILE_([a-zA-Z0-9_]+)"),
ConstantClass(
"PlatformFlags",
Expand Down
12 changes: 11 additions & 1 deletion libdrgn/debug_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -2370,7 +2370,17 @@ drgn_standard_debug_info_find(struct drgn_module * const *modules,
}

_cleanup_(drgn_standard_debug_info_find_state_deinit)
struct drgn_standard_debug_info_find_state state = {};
struct drgn_standard_debug_info_find_state state = {
.modules = modules,
.num_modules = num_modules,
.kmod_walk = {
.modules = HASH_TABLE_INIT,
.stack = VECTOR_INIT,
.path = STRING_BUILDER_INIT,
.visited_dirs = HASH_TABLE_INIT,
.next_kernel_dir = options->kernel_directories,
},
};
for (size_t i = 0; i < num_modules; i++) {
err = drgn_module_try_standard_files(modules[i], options,
&state);
Expand Down
27 changes: 27 additions & 0 deletions libdrgn/debug_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,37 @@ struct depmod_index {
size_t len;
};

DEFINE_VECTOR_TYPE(char_p_vector, char *);

DEFINE_HASH_MAP_TYPE(drgn_kmod_walk_module_map, const char *,
struct char_p_vector);

DEFINE_VECTOR_TYPE(drgn_kmod_walk_stack,
struct drgn_kmod_walk_stack_entry);

struct drgn_kmod_walk_inode {
dev_t dev;
ino_t ino;
};

DEFINE_HASH_SET_TYPE(drgn_kmod_walk_inode_set, struct drgn_kmod_walk_inode);

struct drgn_kmod_walk_state {
struct drgn_kmod_walk_module_map modules;
struct drgn_kmod_walk_stack stack;
struct string_builder path;
struct drgn_kmod_walk_inode_set visited_dirs;
const char * const *next_kernel_dir;
const char * const *next_debug_dir;
};

// State kept by standard debug info finder for all modules it's working on.
// Currently it's only used to cache locations of Linux kernel loadable modules.
struct drgn_standard_debug_info_find_state {
struct drgn_module * const *modules;
size_t num_modules;
struct depmod_index modules_dep;
struct drgn_kmod_walk_state kmod_walk;
};

void
Expand Down
32 changes: 32 additions & 0 deletions libdrgn/debug_info_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,38 @@ static bool drgn_format_debug_info_options_bool(struct string_builder *sb,
&& string_builder_append(sb, value ? "True" : "False");
}

static bool
drgn_kmod_search_method_format(struct string_builder *sb, const char *name,
bool *first, enum drgn_kmod_search_method value,
enum drgn_kmod_search_method default_value)
{
// Skip options set to the default.
if (value == default_value)
return true;
const char *s;
SWITCH_ENUM(value) {
case DRGN_KMOD_SEARCH_NONE:
s = "NONE";
break;
case DRGN_KMOD_SEARCH_DEPMOD:
s = "DEPMOD";
break;
case DRGN_KMOD_SEARCH_WALK:
s = "WALK";
break;
case DRGN_KMOD_SEARCH_DEPMOD_OR_WALK:
s = "DEPMOD_OR_WALK";
break;
case DRGN_KMOD_SEARCH_DEPMOD_AND_WALK:
s = "DEPMOD_AND_WALK";
break;
default:
UNREACHABLE();
}
return drgn_format_debug_info_options_common(sb, name, first)
&& string_builder_append(sb, s);
}

char *drgn_format_debug_info_options(struct drgn_debug_info_options *options)
{
STRING_BUILDER(sb);
Expand Down
4 changes: 3 additions & 1 deletion libdrgn/debug_info_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
BOOL_OPTION(try_embedded_vdso, true) \
BOOL_OPTION(try_reuse, true) \
BOOL_OPTION(try_supplementary, true) \
LIST_OPTION(kernel_directories)
LIST_OPTION(kernel_directories) \
ENUM_OPTION(try_kmod, drgn_kmod_search_method, \
DRGN_KMOD_SEARCH_DEPMOD_OR_WALK)

struct drgn_debug_info_options {
#define LIST_OPTION(name) const char * const *name;
Expand Down
18 changes: 18 additions & 0 deletions libdrgn/drgn.h
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,24 @@ drgn_debug_info_options_set_kernel_directories(struct drgn_debug_info_options *o
const char * const *value)
__attribute__((__nonnull__(1, 2)));

/** Methods of searching for loadable kernel module debugging information. */
enum drgn_kmod_search_method {
DRGN_KMOD_SEARCH_NONE,
DRGN_KMOD_SEARCH_DEPMOD,
DRGN_KMOD_SEARCH_WALK,
DRGN_KMOD_SEARCH_DEPMOD_OR_WALK,
DRGN_KMOD_SEARCH_DEPMOD_AND_WALK,
} __attribute__((__packed__));

/** Get how to search for loadable kernel module debugging information. */
enum drgn_kmod_search_method
drgn_debug_info_options_get_try_kmod(const struct drgn_debug_info_options *options);

/** Set how to search for loadable kernel module debugging information. */
void
drgn_debug_info_options_set_try_kmod(struct drgn_debug_info_options *options,
enum drgn_kmod_search_method value);

/**
* Get the default debugging information options for @p prog.
*
Expand Down
Loading

0 comments on commit 7dc474d

Please sign in to comment.