Skip to content

Commit

Permalink
Merge branch 'refs/heads/upstream-HEAD' into repo-HEAD
Browse files Browse the repository at this point in the history
  • Loading branch information
Delphix Engineering committed Dec 17, 2024
2 parents 63cb294 + ecebeca commit 924145a
Show file tree
Hide file tree
Showing 15 changed files with 389 additions and 198 deletions.
15 changes: 8 additions & 7 deletions docs/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,20 @@ Some of drgn's behavior can be modified through environment variables:
Whether drgn should use libkdumpfile for ELF vmcores (0 or 1). The default
is 0. This functionality will be removed in the future.

``DRGN_USE_PYREPL``
Whether drgn should attempt to use the improved REPL (pyrepl) from Python
3.13. This provides colored output and multiline editing, among other
features. The default is 1. Unfortunately, Python has no public API to use
these features, so drgn must rely on internal implementation details. Set
this to 0 to disable this feature.

``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.

``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
Expand Down
7 changes: 6 additions & 1 deletion drgn/helpers/common/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ def _identify_kernel_address(
cache_name = escape_ascii_string(
slab_info.slab_cache.name.string_(), escape_backslash=True
)
maybe_free = "" if slab_info.allocated else "free "
if slab_info.allocated:
maybe_free = ""
elif slab_info.allocated is None:
maybe_free = "corrupted "
else:
maybe_free = "free "
return f"{maybe_free}slab object: {cache_name}+{hex(addr - slab_info.address)}"
else:
return _identify_kernel_vmap(prog, addr, cache)
Expand Down
71 changes: 58 additions & 13 deletions drgn/helpers/linux/slab.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
container_of,
sizeof,
)
from drgn.helpers import ValidationError
from drgn.helpers.common.format import escape_ascii_string
from drgn.helpers.common.prog import takes_program_or_default
from drgn.helpers.linux.cpumask import for_each_online_cpu
Expand Down Expand Up @@ -205,6 +206,19 @@ def print_slab_caches(prog: Program) -> None:
print(f"{name} ({s.type_.type_name()})0x{s.value_():x}")


class SlabCorruptionError(ValidationError):
"""
Error raised when a corruption is encountered in a slab allocator data
structure.
"""


class SlabFreelistCycleError(SlabCorruptionError):
"""
Error raised when a cycle is encountered in a slab allocator freelist.
"""


# Between SLUB, SLAB, their respective configuration options, and the
# differences between kernel versions, there is a lot of state that we need to
# keep track of to inspect the slab allocator. It isn't pretty, but this class
Expand All @@ -214,13 +228,17 @@ class _SlabCacheHelper:
def __init__(self, slab_cache: Object) -> None:
self._prog = slab_cache.prog_
self._slab_cache = slab_cache.read_()
self._freelist_error: Optional[Exception] = None

def _page_objects(
self, page: Object, slab: Object, pointer_type: Type
) -> Iterator[Object]:
raise NotImplementedError()

def for_each_allocated_object(self, type: Union[str, Type]) -> Iterator[Object]:
if self._freelist_error:
raise self._freelist_error

pointer_type = self._prog.pointer_type(self._prog.type(type))
slab_type = _get_slab_type(self._prog)
# Get the underlying implementation directly to avoid overhead on each
Expand Down Expand Up @@ -307,9 +325,17 @@ def _try_hardened_freelist_dereference(ptr_addr: int) -> int:

self._freelist_dereference = _try_hardened_freelist_dereference

def _slub_get_freelist(freelist: Object, freelist_set: Set[int]) -> None:
def _slub_get_freelist(
freelist_name: Callable[[], str], freelist: Object, freelist_set: Set[int]
) -> None:
ptr = freelist.value_()
while ptr:
if ptr in freelist_set:
raise SlabFreelistCycleError(
f"{fsdecode(slab_cache.name.string_())} {freelist_name()} "
"freelist contains cycle; "
"may be corrupted or in the middle of update"
)
freelist_set.add(ptr)
ptr = self._freelist_dereference(ptr + freelist_offset)

Expand All @@ -325,11 +351,16 @@ def _slub_get_freelist(freelist: Object, freelist_set: Set[int]) -> None:
# slab for a CPU is `struct slab *slab`. Before that, it is `struct
# page *page`.
cpu_slab_attr = "slab" if hasattr(cpu_slab, "slab") else "page"
for cpu in for_each_online_cpu(self._prog):
this_cpu_slab = per_cpu_ptr(cpu_slab, cpu)
slab = getattr(this_cpu_slab, cpu_slab_attr).read_()
if slab and slab.slab_cache == slab_cache:
_slub_get_freelist(this_cpu_slab.freelist, cpu_freelists)
try:
for cpu in for_each_online_cpu(self._prog):
this_cpu_slab = per_cpu_ptr(cpu_slab, cpu)
slab = getattr(this_cpu_slab, cpu_slab_attr).read_()
if slab and slab.slab_cache == slab_cache:
_slub_get_freelist(
lambda: f"cpu {cpu}", this_cpu_slab.freelist, cpu_freelists
)
except (SlabCorruptionError, FaultError) as e:
self._freelist_error = e

self._slub_get_freelist = _slub_get_freelist
self._cpu_freelists = cpu_freelists
Expand All @@ -338,7 +369,7 @@ def _page_objects(
self, page: Object, slab: Object, pointer_type: Type
) -> Iterator[Object]:
freelist: Set[int] = set()
self._slub_get_freelist(slab.freelist, freelist)
self._slub_get_freelist(lambda: f"slab {hex(slab)}", slab.freelist, freelist)
addr = page_to_virt(page).value_() + self._red_left_pad
end = addr + self._slab_cache_size * slab.objects
while addr < end:
Expand All @@ -353,11 +384,22 @@ def object_info(self, page: Object, slab: Object, addr: int) -> "SlabObjectInfo"
+ (addr - first_addr) // self._slab_cache_size * self._slab_cache_size
)
if address in self._cpu_freelists:
allocated = False
allocated: Optional[bool] = False
else:
freelist: Set[int] = set()
self._slub_get_freelist(slab.freelist, freelist)
allocated = address not in freelist
try:
self._slub_get_freelist(
lambda: f"slab {hex(slab)}", slab.freelist, freelist
)
except (SlabCorruptionError, FaultError):
allocated = False if address in freelist else None
else:
if address in freelist:
allocated = False
elif self._freelist_error:
allocated = None
else:
allocated = True
return SlabObjectInfo(self._slab_cache, slab, address, allocated)


Expand Down Expand Up @@ -536,11 +578,14 @@ class SlabObjectInfo:
address: int
"""Address of the slab object."""

allocated: bool
"""``True`` if the object is allocated, ``False`` if it is free."""
allocated: Optional[bool]
"""
``True`` if the object is allocated, ``False`` if it is free, or ``None``
if not known because the slab cache is corrupted.
"""

def __init__(
self, slab_cache: Object, slab: Object, address: int, allocated: bool
self, slab_cache: Object, slab: Object, address: int, allocated: Optional[bool]
) -> None:
self.slab_cache = slab_cache
self.slab = slab
Expand Down
9 changes: 5 additions & 4 deletions drgn/internal/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
# in the "code" module. We'd like to give the best experience possible, so we'll
# detect _pyrepl and try to use it where possible.
try:
# Since this mucks with internals, add a knob that can be used to disable it
# and use the traditional REPL.
if os.environ.get("DRGN_USE_PYREPL") in ("0", "n", "N", "false", "False"):
# The official Python interpreter honors this environment variable to
# disable the new REPL. We do the same, which also gives users an escape
# hatch if any of the internals we're messing with change.
if os.environ.get("PYTHON_BASIC_REPL"):
raise ModuleNotFoundError()

# Unfortunately, the typeshed library behind mypy explicitly removed type
Expand All @@ -39,7 +40,7 @@ def interact(local: Dict[str, Any], banner: str) -> None:
print(banner, file=sys.stderr)
run_multiline_interactive_console(console)

except (ModuleNotFoundError, ImportError):
except (ModuleNotFoundError, ImportError, AttributeError):
import code
import readline

Expand Down
2 changes: 2 additions & 0 deletions libdrgn/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ libdrgnimpl_la_SOURCES = $(ARCH_DEFS_PYS:_defs.py=.c) \
dwarf_info.h \
elf_file.c \
elf_file.h \
elf_notes.c \
elf_notes.h \
elf_sections.h \
error.c \
error.h \
Expand Down
45 changes: 11 additions & 34 deletions libdrgn/debug_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "cleanup.h"
#include "debug_info.h"
#include "elf_file.h"
#include "elf_notes.h"
#include "error.h"
#include "linux_kernel.h"
#include "openmp.h"
Expand Down Expand Up @@ -465,7 +466,7 @@ drgn_debug_info_report_elf(struct drgn_debug_info_load_state *load,

struct drgn_error *err;
const void *build_id;
ssize_t build_id_len = dwelf_elf_gnu_build_id(elf, &build_id);
ssize_t build_id_len = drgn_elf_gnu_build_id(elf, &build_id);
if (build_id_len < 0) {
err = drgn_debug_info_report_error(load, path, NULL,
drgn_error_libelf());
Expand Down Expand Up @@ -708,7 +709,7 @@ static bool build_id_matches(Elf *elf, const void *build_id,
size_t build_id_len)
{
const void *elf_build_id;
ssize_t elf_build_id_len = dwelf_elf_gnu_build_id(elf, &elf_build_id);
ssize_t elf_build_id_len = drgn_elf_gnu_build_id(elf, &elf_build_id);
if (elf_build_id_len < 0)
return false;
return (elf_build_id_len == build_id_len &&
Expand Down Expand Up @@ -924,32 +925,6 @@ static void read_phdr(const void *phdr_buf, size_t i, bool is_64_bit,
}
}

static const char *read_build_id(const void *buf, size_t buf_len,
unsigned int align, bool bswap,
size_t *len_ret)
{
/*
* Build IDs are usually 16 or 20 bytes (MD5 or SHA-1, respectively), so
* these arbitrary limits are generous.
*/
static const uint32_t build_id_min_size = 2;
static const uint32_t build_id_max_size = 1024;
Elf32_Nhdr nhdr;
const char *name;
const void *desc;
while (next_elf_note(&buf, &buf_len, align, bswap, &nhdr, &name, &desc)) {
if (nhdr.n_namesz == sizeof("GNU") &&
memcmp(name, "GNU", sizeof("GNU")) == 0 &&
nhdr.n_type == NT_GNU_BUILD_ID &&
nhdr.n_descsz >= build_id_min_size &&
nhdr.n_descsz <= build_id_max_size) {
*len_ret = nhdr.n_descsz;
return desc;
}
}
return NULL;
}

struct core_get_phdr_arg {
const void *phdr_buf;
bool is_64_bit;
Expand Down Expand Up @@ -1081,12 +1056,14 @@ userspace_core_identify_file(struct drgn_program *prog,
return err;
}
}
ret->build_id = read_build_id(core->segment_buf,
phdr.p_filesz,
phdr.p_align == 8 ? 8 : 4,
arg.bswap,
&ret->build_id_len);
if (ret->build_id)
ret->build_id_len =
parse_gnu_build_id_from_notes(core->segment_buf,
phdr.p_filesz,
phdr.p_align == 8
? 8 : 4,
arg.bswap,
&ret->build_id);
if (ret->build_id_len)
break;
}
}
Expand Down
10 changes: 0 additions & 10 deletions libdrgn/debug_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <elfutils/version.h>
#include <libelf.h>

#include "cfi.h"
Expand Down Expand Up @@ -302,15 +301,6 @@ struct drgn_error *find_elf_file(char **path_ret, int *fd_ret, Elf **elf_ret,
struct drgn_error *elf_address_range(Elf *elf, uint64_t bias,
uint64_t *start_ret, uint64_t *end_ret);

static inline Elf_Type note_header_type(uint64_t p_align)
{
#if _ELFUTILS_PREREQ(0, 175)
if (p_align == 8)
return ELF_T_NHDR8;
#endif
return ELF_T_NHDR;
}

/** @} */

#endif /* DRGN_DEBUG_INFO_H */
1 change: 1 addition & 0 deletions libdrgn/dwarf_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <byteswap.h>
#include <elf.h>
#include <elfutils/libdw.h>
#include <elfutils/version.h>
#include <gelf.h>
#include <inttypes.h>
#include <limits.h>
Expand Down
48 changes: 0 additions & 48 deletions libdrgn/elf_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,51 +281,3 @@ struct drgn_error *drgn_elf_file_section_buffer_error(struct binary_buffer *bb,
return drgn_elf_file_section_error(buffer->file, buffer->scn,
buffer->data, ptr, message);
}

bool next_elf_note(const void **p, size_t *size, unsigned int align, bool bswap,
Elf32_Nhdr *nhdr_ret, const char **name_ret,
const void **desc_ret)
{
uint64_t align_mask = align - 1;

if (*size < sizeof(*nhdr_ret))
return false;
memcpy(nhdr_ret, *p, sizeof(*nhdr_ret));
if (bswap) {
nhdr_ret->n_namesz = bswap_32(nhdr_ret->n_namesz);
nhdr_ret->n_descsz = bswap_32(nhdr_ret->n_descsz);
nhdr_ret->n_type = bswap_32(nhdr_ret->n_type);
}

if (nhdr_ret->n_namesz > *size - sizeof(*nhdr_ret))
return false;
uint64_t aligned_namesz = (nhdr_ret->n_namesz + align_mask) & ~align_mask;
if (nhdr_ret->n_descsz > 0
&& (aligned_namesz > *size - sizeof(*nhdr_ret)
|| nhdr_ret->n_descsz > *size - sizeof(*nhdr_ret) - aligned_namesz))
return false;

*p = (const char *)*p + sizeof(*nhdr_ret);
*size -= sizeof(*nhdr_ret);

*name_ret = *p;
if (aligned_namesz > *size) {
*p = (const char *)*p + *size;
*size = 0;
} else {
*p = (const char *)*p + aligned_namesz;
*size -= aligned_namesz;
}

*desc_ret = *p;
uint64_t aligned_descsz = (nhdr_ret->n_descsz + align_mask) & ~align_mask;
if (aligned_descsz > *size) {
*p = (const char *)*p + *size;
*size = 0;
} else {
*p = (const char *)*p + aligned_descsz;
*size -= aligned_descsz;
}

return true;
}
Loading

0 comments on commit 924145a

Please sign in to comment.