From f2473ff692bc21162a5cc85af4ed227165eb33a4 Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Wed, 22 Jul 2026 15:40:32 -0400 Subject: [PATCH] Tweak our symbol resolution again I've found another case where the libpython gets mmap'ed by a process and breaks pystack's ability to find `_PyRuntime`. This time, I noticed it happening when Memray is symbolizing native stacks, which can trigger elfutils to mmap the text section of libpython if one of those native stack frames resolves to an address inside libpython. When this happened, the symbol resolution callback was finding an address for the symbol, and discovering that that address was mapped, but not noticing that it fell within a different module entirely, since only a small part of libpython was mapped at that first mapping. Address this by preferring matches which fall within the module we searched in over mappings which don't, while continuing to prefer a match in an earlier module over one in a later one. Signed-off-by: Matt Wozniski --- src/pystack/_pystack/unwinder.cpp | 36 ++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/pystack/_pystack/unwinder.cpp b/src/pystack/_pystack/unwinder.cpp index 091b551f..0cbd2602 100644 --- a/src/pystack/_pystack/unwinder.cpp +++ b/src/pystack/_pystack/unwinder.cpp @@ -352,6 +352,7 @@ AbstractUnwinder::dwarfModuleAddrDie(Dwarf_Addr pc_adjusted, Dwfl_Module* mod, D struct ModuleArg { + Dwfl* dwfl; const char* symbol; const char* modulename; remote_addr_t addr; @@ -382,10 +383,35 @@ module_callback( for (int i = 0; i < n_syms; i++) { const char* sname = dwfl_module_getsym_info(mod, i, &sym, &addr, nullptr, nullptr, nullptr); if (strcmp(sname, module_arg->symbol) == 0) { - module_arg->addr = addr; - LOG(INFO) << "Symbol '" << sname << "' found at address " << std::hex << std::showbase - << addr; - return DWARF_CB_ABORT; + Dwfl_Module* actual_module = dwfl_addrmodule(module_arg->dwfl, addr); + if (actual_module != mod) { + // Save this address if it's the first one we've seen, but keep looking for a match + // that resolves to the expected module. + if (!module_arg->addr) { + module_arg->addr = addr; + } + const char* actual_module_name = dwfl_module_info( + actual_module, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + LOG(INFO) << "Symbol '" << sname << "' found at address " << std::hex << std::showbase + << addr << " which falls in " + << (actual_module_name ? actual_module_name : "a non-file-backed mapping") + << " rather than the expected module " << module_arg->modulename + << " so we will keep looking for a better match"; + return DWARF_CB_OK; + } else { + // Otherwise, stop looking and use this match. + module_arg->addr = addr; + LOG(INFO) << "Symbol '" << sname << "' found at address " << std::hex << std::showbase + << addr; + return DWARF_CB_ABORT; + } } } return DWARF_CB_OK; @@ -395,7 +421,7 @@ remote_addr_t AbstractUnwinder::getAddressforSymbol(const std::string& symbol, const std::string& modulename) const { LOG(DEBUG) << "Trying to find address for symbol " << symbol; - ModuleArg arg = {symbol.c_str(), modulename.c_str(), 0}; + ModuleArg arg = {Dwfl(), symbol.c_str(), modulename.c_str(), 0}; if (dwfl_getmodules(Dwfl(), module_callback, &arg, 0) == -1) { throw UnwinderError("Failed to fetch modules!"); }