Skip to content

Commit 055c061

Browse files
pablogsalgodlygeek
authored andcommitted
Fix handling duplicate symbols when libraries dlopen Python
When libraries like `llvmlite` `dlopen` the Python binary, it creates duplicate mappings of the binary in the process memory. This caused PyStack to use the wrong address for `_PyRuntime`, leading to "Invalid address in remote process" errors. It happens to work if we stop searching as soon as we find the symbol, rather than continuing to iterate over other loaded modules searching for the symbol. This counts on the earliest mapping for the interpreter binary being the initial mapping made by the loader. Signed-off-by: Pablo Galindo Salgado <[email protected]> Signed-off-by: Matt Wozniski <[email protected]>
1 parent e63ec63 commit 055c061

File tree

4 files changed

+62
-2
lines changed

4 files changed

+62
-2
lines changed

news/258.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix handling of duplicate ``_PyRuntime`` symbols when ctypes mmaps a statically linked Python interpreter's binary in order to create a trampoline. Previously this led to "Invalid address in remote process" errors.

src/pystack/_pystack/unwinder.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ module_callback(
378378
module_arg->addr = addr;
379379
LOG(INFO) << "Symbol '" << sname << "' found at address " << std::hex << std::showbase
380380
<< addr;
381-
break;
381+
return DWARF_CB_ABORT;
382382
}
383383
}
384384
return DWARF_CB_OK;
@@ -389,7 +389,7 @@ AbstractUnwinder::getAddressforSymbol(const std::string& symbol, const std::stri
389389
{
390390
LOG(DEBUG) << "Trying to find address for symbol " << symbol;
391391
ModuleArg arg = {symbol.c_str(), modulename.c_str(), 0};
392-
if (dwfl_getmodules(Dwfl(), module_callback, &arg, 0) != 0) {
392+
if (dwfl_getmodules(Dwfl(), module_callback, &arg, 0) == -1) {
393393
throw UnwinderError("Failed to fetch modules!");
394394
}
395395
LOG(DEBUG) << "Address for symbol " << symbol << " resolved to: " << std::hex << std::showbase
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import ctypes
2+
import sys
3+
import time
4+
5+
6+
def first_func():
7+
second_func()
8+
9+
10+
def second_func():
11+
third_func()
12+
13+
14+
def third_func():
15+
# Trigger libffi to re-import the Python binary
16+
global gil_check
17+
gil_check = ctypes.CFUNCTYPE(ctypes.c_int)(ctypes.CDLL(None).PyGILState_Check)
18+
19+
with open(sys.argv[1], "w") as fifo:
20+
fifo.write("ready")
21+
time.sleep(1000)
22+
23+
24+
first_func()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import sys
2+
from pathlib import Path
3+
4+
from pystack.engine import get_process_threads
5+
from tests.utils import spawn_child_process
6+
7+
TEST_DUPLICATE_SYMBOLS_FILE = Path(__file__).parent / "ctypes_program.py"
8+
9+
10+
def test_duplicate_pyruntime_symbol_handling(tmpdir):
11+
"""Test that pystack correctly handles duplicate _PyRuntime symbols.
12+
13+
This can occur when ctypes uses libffi to dlopen the Python binary
14+
in order to create a trampoline (which it only does if the Python binary
15+
was statically linked against libpython).
16+
"""
17+
# GIVEN
18+
with spawn_child_process(
19+
sys.executable, TEST_DUPLICATE_SYMBOLS_FILE, tmpdir
20+
) as child_process:
21+
# WHEN
22+
threads = list(get_process_threads(child_process.pid, stop_process=True))
23+
24+
# THEN
25+
# We should have successfully resolved threads without "Invalid address" errors
26+
assert threads is not None
27+
assert len(threads) > 0
28+
29+
# Verify we can get stack traces (which requires correct _PyRuntime)
30+
for thread in threads:
31+
# Just ensure we can get frames without crashing
32+
frames = list(thread.frames)
33+
# The main thread should have at least one frame
34+
if thread.tid == child_process.pid:
35+
assert len(frames) > 0

0 commit comments

Comments
 (0)