Skip to content

Commit

Permalink
Try to locate the interpreter using debug offsets
Browse files Browse the repository at this point in the history
If we've found the `_Py_DebugOffsets` structure, we can use this to find
the interpreter state quickly and efficiently: the debug offsets are at
the start of the `_PyRuntime` structure, which contains a reference to
the interpreter state, at an offset identified by the debug offsets.

Signed-off-by: Matt Wozniski <[email protected]>
  • Loading branch information
godlygeek committed Aug 17, 2024
1 parent a0ffe51 commit f4a7838
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/pystack/_pystack.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class StackMethod(enum.Enum):
ELF_DATA: int
HEAP: int
SYMBOLS: int
DEBUG_OFFSETS: int

class ProcessManager: ...

Expand Down
9 changes: 7 additions & 2 deletions src/pystack/_pystack.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ class StackMethod(enum.Enum):
BSS = 1 << 2
ANONYMOUS_MAPS = 1 << 3
HEAP = 1 << 4
AUTO = ELF_DATA | SYMBOLS | BSS
DEBUG_OFFSETS = 1 << 5
AUTO = DEBUG_OFFSETS | ELF_DATA | SYMBOLS | BSS
ALL = AUTO | ANONYMOUS_MAPS | HEAP


Expand Down Expand Up @@ -521,6 +522,7 @@ cdef remote_addr_t _get_interpreter_state_addr(
) except*:
cdef remote_addr_t head = 0
possible_methods = [
StackMethod.DEBUG_OFFSETS,
StackMethod.ELF_DATA,
StackMethod.SYMBOLS,
StackMethod.BSS,
Expand All @@ -533,7 +535,10 @@ cdef remote_addr_t _get_interpreter_state_addr(
continue

try:
if possible_method == StackMethod.ELF_DATA:
if possible_method == StackMethod.DEBUG_OFFSETS:
how = "using debug offsets data"
head = manager.findInterpreterStateFromDebugOffsets()
elif possible_method == StackMethod.ELF_DATA:
how = "using ELF data"
head = manager.findInterpreterStateFromElfData()
elif possible_method == StackMethod.SYMBOLS:
Expand Down
30 changes: 30 additions & 0 deletions src/pystack/_pystack/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,36 @@ AbstractProcessManager::findInterpreterStateFromElfData() const
return findInterpreterStateFromPyRuntime(pyruntime);
}

remote_addr_t
AbstractProcessManager::findInterpreterStateFromDebugOffsets() const
{
if (!d_debug_offsets_addr) {
LOG(DEBUG) << "Debug offsets were never found";
return 0;
}

LOG(INFO) << "Searching for PyInterpreterState based on PyRuntime address " << std::hex
<< std::showbase << d_debug_offsets_addr
<< " found when searching for 3.13+ debug offsets";

try {
Structure<py_runtime_v> runtime(shared_from_this(), d_debug_offsets_addr);
remote_addr_t interp_state = runtime.getField(&py_runtime_v::o_interp_head);
LOG(DEBUG) << "Checking interpreter state at " << std::hex << std::showbase << interp_state
<< " found at address "
<< runtime.getFieldRemoteAddress(&py_runtime_v::o_interp_head);
if (isValidInterpreterState(interp_state)) {
LOG(DEBUG) << "Interpreter head reference from debug offsets dereferences successfully";
return interp_state;
}
} catch (...) {
// Swallow exceptions and fall through to return failure
}
LOG(INFO) << "Failed to resolve PyInterpreterState based on PyRuntime address " << std::hex
<< std::showbase << d_debug_offsets_addr;
return 0;
}

ProcessManager::ProcessManager(
pid_t pid,
const std::shared_ptr<ProcessTracer>& tracer,
Expand Down
1 change: 1 addition & 0 deletions src/pystack/_pystack/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class AbstractProcessManager : public std::enable_shared_from_this<AbstractProce
remote_addr_t findInterpreterStateFromPyRuntime(remote_addr_t runtime_addr) const;
remote_addr_t findInterpreterStateFromSymbols() const;
remote_addr_t findInterpreterStateFromElfData() const;
remote_addr_t findInterpreterStateFromDebugOffsets() const;
remote_addr_t findSymbol(const std::string& symbol) const;
ssize_t copyMemoryFromProcess(remote_addr_t addr, size_t size, void* destination) const;
template<typename T>
Expand Down
1 change: 1 addition & 0 deletions src/pystack/_pystack/process.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ cdef extern from "process.h" namespace "pystack":
remote_addr_t scanBSS() except+
remote_addr_t scanHeap() except+
remote_addr_t scanAllAnonymousMaps() except+
remote_addr_t findInterpreterStateFromDebugOffsets() except+
remote_addr_t findInterpreterStateFromSymbols() except+
remote_addr_t findInterpreterStateFromElfData() except+
ssize_t copyMemoryFromProcess(remote_addr_t addr, ssize_t size, void *destination) except+
Expand Down
13 changes: 12 additions & 1 deletion tests/integration/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@
elif sys.version_info < (3, 11): # pragma: no cover
STACK_METHODS = (StackMethod.SYMBOLS, StackMethod.ELF_DATA, StackMethod.HEAP)
CORE_STACK_METHODS = (StackMethod.SYMBOLS, StackMethod.ELF_DATA)
else: # pragma: no cover
elif sys.version_info < (3, 13): # pragma: no cover
STACK_METHODS = (StackMethod.SYMBOLS, StackMethod.ELF_DATA)
CORE_STACK_METHODS = (StackMethod.SYMBOLS, StackMethod.ELF_DATA)
else: # pragma: no cover
STACK_METHODS = (
StackMethod.DEBUG_OFFSETS,
StackMethod.SYMBOLS,
StackMethod.ELF_DATA,
)
CORE_STACK_METHODS = (
StackMethod.DEBUG_OFFSETS,
StackMethod.SYMBOLS,
StackMethod.ELF_DATA,
)


@pytest.mark.parametrize("method", STACK_METHODS)
Expand Down
6 changes: 6 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,15 @@ def generate_all_pystack_combinations(
]: # pragma: no cover
if corefile:
stack_methods = (
StackMethod.DEBUG_OFFSETS,
StackMethod.SYMBOLS,
StackMethod.BSS,
StackMethod.ELF_DATA,
StackMethod.ANONYMOUS_MAPS,
)
else:
stack_methods = (
StackMethod.DEBUG_OFFSETS,
StackMethod.SYMBOLS,
StackMethod.BSS,
StackMethod.HEAP,
Expand All @@ -210,6 +212,10 @@ def generate_all_pystack_combinations(
AVAILABLE_PYTHONS,
):
(major_version, minor_version) = python.version
if method == StackMethod.DEBUG_OFFSETS and (
major_version < 3 or (major_version == 3 and minor_version < 13)
):
continue
if method == StackMethod.BSS and (
major_version > 3 or (major_version == 3 and minor_version >= 10)
):
Expand Down

0 comments on commit f4a7838

Please sign in to comment.