0.0.27 (Released July 1st, 2024)¶
These are some of the highlights of drgn 0.0.27. See the GitHub release for the full release notes, including more improvements and bug fixes.
Finding the Type Member at an Offset¶
This release added member_at_offset()
, which
returns the name of the member at an offset in a type:
>>> prog.type('struct list_head')
struct list_head {
struct list_head *next;
struct list_head *prev;
}
>>> member_at_offset(prog.type('struct list_head'), 0)
'next'
>>> member_at_offset(prog.type('struct list_head'), 8)
'prev'
It also handles more complicated cases, like nested structures, arrays, unions, and padding.
It is particularly useful in combination with
identify_address()
or
slab_object_info()
:
>>> identify_address(0xffff984fc7cc6708)
'slab object: fuse_inode+0x188'
>>> member_at_offset(prog.type("struct fuse_inode"), 0x188)
'inode.i_data.i_pages.xa_head'
(Note that in some cases, the slab cache name isn’t identical to the type name.
Slab merging also complicates this; see
slab_cache_is_merged()
. In those cases, this
trick requires some extra effort.)
Identifying Memory¶
This release added
print_annotated_memory()
, which dumps a
range of memory, annotating values that can be identified:
>>> print_annotated_memory(0xffff985163300698, 64)
ADDRESS VALUE
ffff985163300698: ffff984f415456a0 [slab object: mnt_cache+0x20]
ffff9851633006a0: ffff984f587b7840 [slab object: dentry+0x0]
ffff9851633006a8: ffff984f404bfa38 [slab object: inode_cache+0x0]
ffff9851633006b0: ffffffff8b4890c0 [object symbol: signalfd_fops+0x0]
ffff9851633006b8: 0000000000000000
ffff9851633006c0: ffff984f9307c078 [slab object: lsm_file_cache+0x0]
ffff9851633006c8: ffff984f8afe3980 [slab object: kmalloc-8+0x0]
ffff9851633006d0: ffff984f414730f0 [slab object: ep_head+0x0]
(This is similar to print_annotated_stack()
but for arbitrary memory ranges.)
identify_address()
(used by
print_annotated_memory()
and
print_annotated_stack()
) can now also
identify vmap addresses and vmap kernel stacks:
>>> print(identify_address(0xffffffffc0536540))
vmap: 0xffffffffc0536000-0xffffffffc0545000 caller load_module+0x811
>>> print(identify_address(0xffffbb88e2283f58))
vmap stack: 2220305 (python3) +0x3f58
Configurable Type and Object Finders¶
drgn already supported registering custom callbacks that could satisfy type and
object lookups: Program.add_type_finder()
and
Program.add_object_finder()
. However, there was no way to disable
previously added callbacks or control the order in which they are called. This
release adds an interface for doing so.
Program.registered_object_finders()
returns the set of registered
object finders:
>>> prog.registered_object_finders()
{'dwarf', 'linux'}
Program.enabled_object_finders()
returns the list of enabled
object finders in the order that they are called:
>>> prog.enabled_object_finders()
['linux', 'dwarf']
Program.register_object_finder()
registers and optionally enables
a finder:
>>> def my_object_finder(prog, name, flags, filename):
... ...
...
>>> prog.register_object_finder("foo", my_object_finder)
>>> prog.registered_object_finders()
{'foo', 'dwarf', 'linux'}
>>> prog.enabled_object_finders()
['linux', 'dwarf']
>>> def my_object_finder2(prog, name, flags, filename):
... ...
...
>>> prog.register_object_finder("bar", my_object_finder2, enable_index=0)
>>> prog.registered_object_finders()
{'foo', 'dwarf', 'bar', 'linux'}
>>> prog.enabled_object_finders()
['bar', 'linux', 'dwarf']
Program.set_enabled_object_finders()
sets the list of enabled
finders. This can enable, disable, and reorder finders.
>>> prog.set_enabled_object_finders(['dwarf', 'foo'])
>>> prog.enabled_object_finders()
['dwarf', 'foo']
Type finders have equivalent methods: Program.registered_type_finders()
,
Program.enabled_type_finders()
, Program.register_type_finder()
, and
Program.set_enabled_type_finders()
.
The old interface is now deprecated.
Symbol Finders¶
Previously, symbols could only be looked up using the ELF symbol table. In this
release, Stephen Brennan added support for custom symbol finders:
Program.registered_symbol_finders()
,
Program.enabled_symbol_finders()
, Program.register_symbol_finder()
,
and Program.set_enabled_symbol_finders()
.
contrib
Directory¶
A few new scripts were added to the contrib
directory, and others were
updated.
contrib/search_kernel_memory.py
¶
This script does a brute force search through kernel RAM for a given byte string and prints all of the addresses where it is found. It’s useful as a last resort for finding what is referencing an object, for example.
>>> folio = stack_trace(task)[5]["folio"]
>>> search_memory(prog, folio.value_().to_bytes(8, "little"))
0xffff8882f67539e8 vmap stack: 2232297 (io_thread) +0x39e8
0xffff8882f6753a18 vmap stack: 2232297 (io_thread) +0x3a18
0xffff8882f6753a60 vmap stack: 2232297 (io_thread) +0x3a60
0xffff8882f6753ac8 vmap stack: 2232297 (io_thread) +0x3ac8
0xffff888300405530 slab object: kmalloc-16+0x0
0xffff8883b8c6ca38
contrib/gcore.py
¶
This script creates a core dump of a live process. This works even if the process is stuck in D state (Uninterruptible Sleep), which normally causes debuggers attempting to attach to the process to hang, too. The generated core dump can be debugged with GDB, LLDB, or even drgn.
By default, gcore.py
reads the task’s memory through /proc/$pid/mem
.
However, if mmap_lock
/mmap_sem
is stuck, then this will also hang. If
the --no-procfs
flag is used, drgn bypasses this, too, by reading the
process’s page tables and reading the memory directly. This has a couple of big
downsides: paged out memory will be skipped, and it’s a lot slower. But if the
task is badly stuck in memory management, --no-procfs
is a great escape
hatch.
gcore.py
can also extract userspace core dumps out of a kernel core dump,
but note that makedumpfile(8) is
normally configured to filter out userspace memory.
contrib/negdentdelete.py
¶
Negative dentries are a cache of failed filename lookups. They can take up a lot of memory, and it’s difficult to get rid of them by normal means. Stephen Brennan contributed a script that can be used to get rid of negative dentries in a directory.
contrib/btrfs_tree.py
¶
This script contains work-in-progress helpers for reading Btrfs metadata. It was added in drgn 0.0.23, but this release expanded and improved it. It will likely be adapted into proper helpers in a future release.
This script was used to investigate a bug, culminating in Linux kernel commit 9d274c19a71b (“btrfs: fix crash on racing fsync and size-extending write into prealloc”).
contrib/bpf_inspect.py
¶
Leon Hwang made many improvements to this script, including adding more detailed information, new commands, and updating it for recent kernels.
Linux 6.9 and 6.10 Support¶
Changes in Linux 6.9 and 6.10 broke a few drgn helpers. Here are some errors you might see with older versions of drgn that are fixed in this release.
From stack_depot_fetch()
:
AttributeError: 'union handle_parts' has no member 'pool_index'
From for_each_disk()
:
AttributeError: 'struct block_device' has no member 'bd_partno'
Additionally,
slab_cache_for_each_allocated_object()
,
slab_object_info()
, and
find_containing_slab_cache()
may fail to find
anything.