Skip to content

Commit f0f66de

Browse files
authored
Merge pull request #126 from devdanzin/oom-dedup-gcc-asan-frames
oom_dedup: parse GCC-build ASan frames; skip free-plumbing
2 parents d809f56 + 5225abe commit f0f66de

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

fusil/python/oom_dedup.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,14 @@ def match(c, snap):
155155
# " #5 0x.. in _excinfo_clear_type /abs/path/Python/crossinterp.c:1319:15"
156156
# The CPython source dir is matched anywhere in the absolute path; leading libc/pthread
157157
# frames carry no such path and are skipped, as is fatal/dump plumbing (via _BT_SKIP).
158+
# The source path may be absolute (Clang builds: /home/.../Objects/foo.c:12:3) or relative
159+
# (GCC builds: Objects/foo.c:12, also no column). Make the absolute prefix and the column
160+
# optional so GCC-built crashes are parsed too -- otherwise extract_native_sites finds nothing
161+
# in the ASan trace and the dedup falls back to faulthandler's sparser (GCC-inlined) C-stack,
162+
# mislabelling known bugs as new (e.g. a whole OOM-0004 batch keyed on _Py_MergeZeroLocalRefcount).
158163
_ASAN_FRAME = re.compile(
159-
r"#\d+\s+0x[0-9a-fA-F]+\s+in\s+(\w+)\s+\S*?"
160-
r"/((?:Objects|Python|Modules|Include|Parser)/[\w./+-]+\.(?:c|h)):(\d+)"
164+
r"#\d+\s+0x[0-9a-fA-F]+\s+in\s+(\w+)\s+(?:\S*?/)?"
165+
r"((?:Objects|Python|Modules|Include|Parser)/[\w./+-]+\.(?:c|h)):(\d+)"
161166
)
162167
# fatal/assert/dump plumbing -- skip so a recorded frame is the real crash/assert site.
163168
# The debug allocator's free-time checks (_PyMem_DebugCheckAddress / _PyMem_DebugRawFree /
@@ -173,6 +178,14 @@ def match(c, snap):
173178
r"|_Py_NegativeRefcount|_Py_DumpStack|faulthandler\w*"
174179
r"|_Py_DumpExtensionModules"
175180
r"|_PyMem_DebugCheckAddress|_PyMem_DebugRawFree|_PyMem_DebugFree"
181+
# The _testcapi set_nomemory injection hooks and the PyMem_/PyObject_ free/realloc
182+
# wrappers are pass-through allocator plumbing: when one is the innermost frame the real
183+
# defect is the CALLER doing the bad free (e.g. free_list_items = OOM-0004), so skip them
184+
# too. Without this, a double-free caught in the free hook resolves to hook_ffree/PyMem_Free
185+
# (not a catalog site) and the crash is mislabelled oomNEW -- notably on GCC builds, whose
186+
# ASan trace exposes these frames where Clang inlines them away.
187+
r"|hook_fmalloc|hook_fcalloc|hook_frealloc|hook_ffree"
188+
r"|PyMem_Free|PyMem_RawFree|PyObject_Free|PyMem_Realloc|PyMem_RawRealloc|PyObject_Realloc"
176189
r"|tracemalloc_(raw_)?(alloc|calloc|realloc|free))$"
177190
)
178191
# Inlined refcount/atomic helpers live in these headers and show up as the innermost frame

tests/python/test_oom_dedup.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,22 @@ def test_inlined_decref_atomic_header_frames_skipped(self):
272272
# and it matches the catalog bug keyed on that .c line, not labelled oomNEW.
273273
self.assertEqual(self._deduper().decide(bt, source_path=None), (True, "OOM-0003"))
274274

275+
def test_gcc_relative_path_frames_parsed(self):
276+
# GCC-built ASan traces use a RELATIVE source path and no column (Objects/foo.c:68),
277+
# vs Clang's absolute path + column (/abs/Objects/foo.c:68:9). Both must parse, else
278+
# GCC crashes fall back to faulthandler's inlined C-stack and known bugs (here the
279+
# OOM-0003 site) get mislabelled oomNEW.
280+
bt = "\n".join(
281+
[
282+
"==1==ERROR: AddressSanitizer: SEGV on unknown address",
283+
" #5 0x5e in Py_DECREF Include/refcount.h:359",
284+
" #6 0x5e in code_dealloc Objects/codeobject.c:2440",
285+
]
286+
)
287+
sites = oom_dedup.extract_native_sites(bt)
288+
self.assertEqual(sites[0], "code_dealloc@Objects/codeobject.c:2440")
289+
self.assertEqual(self._deduper().decide(bt, source_path=None), (True, "OOM-0003"))
290+
275291

276292
class TestExtractSite(unittest.TestCase):
277293
def test_skips_plumbing_returns_real_site(self):

0 commit comments

Comments
 (0)