From 1a7f42b896b340582e874baa814a761d56cee47c Mon Sep 17 00:00:00 2001 From: jonsnow Date: Sat, 11 Jul 2026 13:04:46 -0400 Subject: [PATCH 1/2] fix: disable Non-Returning Functions analyzer under the large-binary fast profile FindNoReturnFunctionsAnalyzer's ClearFlowAndRepair walks the instruction-flow graph (a CodeManager DB lookup per node) to repair downstream flow whenever a routine is flagged non-returning. On a monolith with dense or mis-disassembled code that traversal explodes: observed wedged for >40h in FindNoReturnFunctionsAnalyzer.repairDamagedLocations on a ~900 MB image, still burning CPU (genuinely advancing through the graph, but the reachable set is astronomically large) with no forward progress persisted. Add "Non-Returning Functions" (both "- Discovered" and "- Known") to _slow_analyzer so the >=100 MB fast profile disables it, alongside the already-dropped Call-Fixup Installer / decompiler / constant-prop passes. Flow past no-return calls stays intact; we lose only the automatic no-return marking, which does not affect HexGraph's on-demand xrefs/decompiles. Test updated to lock in both variants disabled while the dotted sub-option and call-graph analyzers stay enabled. Co-Authored-By: Claude Opus 4.8 --- src/hexgraph/sandbox/probes/pyghidra_lib.py | 8 ++++++++ tests/test_ghidra_fast_profile.py | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/hexgraph/sandbox/probes/pyghidra_lib.py b/src/hexgraph/sandbox/probes/pyghidra_lib.py index aa97a43..162db48 100644 --- a/src/hexgraph/sandbox/probes/pyghidra_lib.py +++ b/src/hexgraph/sandbox/probes/pyghidra_lib.py @@ -198,6 +198,14 @@ def _slow_analyzer(name: str) -> bool: if name in ("Call-Fixup Installer", "Decompiler Parameter ID", "Decompiler Switch Analysis", "Aggressive Instruction Finder"): return True + # Non-Returning Functions ("- Discovered"/"- Known"): when a heavily-called routine is flagged + # non-returning, the analyzer's ClearFlowAndRepair walks the instruction-flow graph (a CodeManager + # DB lookup per node) to repair downstream flow. On a monolith with dense/mis-disassembled code + # that traversal explodes — observed wedged for >40h in FindNoReturnFunctionsAnalyzer.repairDamagedLocations + # on a ~900 MB image. Flow past no-return calls stays intact; we lose only the auto no-return marking, + # which does not affect on-demand xrefs/decompiles. + if name.startswith("Non-Returning Functions"): + return True return name.endswith("Constant Reference Analyzer") or name.endswith("Scalar Operand References") diff --git a/tests/test_ghidra_fast_profile.py b/tests/test_ghidra_fast_profile.py index cddc71d..fbba938 100644 --- a/tests/test_ghidra_fast_profile.py +++ b/tests/test_ghidra_fast_profile.py @@ -25,6 +25,11 @@ def test_fast_profile_disables_the_proven_slow_passes(): # processor-agnostic match for the constant-propagation passes ("PowerPC/ARM/x86 … "): assert L._slow_analyzer("PowerPC Constant Reference Analyzer") assert L._slow_analyzer("ARM Scalar Operand References") + # Non-Returning Functions analyzers: ClearFlowAndRepair wedges on a monolith (both variants). + assert L._slow_analyzer("Non-Returning Functions - Discovered") + assert L._slow_analyzer("Non-Returning Functions - Known") + # ...but a dotted SUB-option of it is still never disabled (the "." rule wins): + assert not L._slow_analyzer("Non-Returning Functions - Discovered.Create Analysis Bookmarks") def test_fast_profile_keeps_the_call_graph_analyzers(): From 4f279a59a988e877e4820ec3956de1b60e8be731 Mon Sep 17 00:00:00 2001 From: branover Date: Sat, 11 Jul 2026 13:16:51 -0400 Subject: [PATCH 2/2] docs: enumerate Non-Returning Functions in fast-profile docstrings The _slow_analyzer docstring and the test module docstring listed the fast-profile disabled set but omitted the newly-added Non-Returning Functions analyzers (doc drift flagged in PR #284 review). No behavior change. Co-Authored-By: Claude Opus 4.8 --- src/hexgraph/sandbox/probes/pyghidra_lib.py | 11 ++++++----- tests/test_ghidra_fast_profile.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/hexgraph/sandbox/probes/pyghidra_lib.py b/src/hexgraph/sandbox/probes/pyghidra_lib.py index 162db48..cc9dc78 100644 --- a/src/hexgraph/sandbox/probes/pyghidra_lib.py +++ b/src/hexgraph/sandbox/probes/pyghidra_lib.py @@ -188,11 +188,12 @@ def open_target(artifact, *, cold_analyze=True, read_only=False): def _slow_analyzer(name: str) -> bool: - """The auto-analysis passes disabled under the fast profile — matched by suffix so it's - architecture-agnostic ("PowerPC/ARM/x86 … Constant Reference Analyzer"). Mirrors the Jython - FAST_PROFILE_SCRIPT: drop the O(n^2) Call-Fixup Installer, the per-processor constant/scalar - propagation, and the decompile-EVERY-function passes; KEEP function/call-graph/reference - discovery (HexGraph decompiles on demand).""" + """The auto-analysis passes disabled under the fast profile — matched by name, with the + constant-prop passes matched by suffix so it's architecture-agnostic ("PowerPC/ARM/x86 … + Constant Reference Analyzer"). Mirrors the Jython FAST_PROFILE_SCRIPT: drop the O(n^2) + Call-Fixup Installer, the per-processor constant/scalar propagation, the decompile-EVERY-function + passes, and the Non-Returning Functions analyzers (their ClearFlowAndRepair wedges on a monolith); + KEEP function/call-graph/reference discovery (HexGraph decompiles on demand).""" if "." in name: return False if name in ("Call-Fixup Installer", "Decompiler Parameter ID", "Decompiler Switch Analysis", diff --git a/tests/test_ghidra_fast_profile.py b/tests/test_ghidra_fast_profile.py index fbba938..e3be90e 100644 --- a/tests/test_ghidra_fast_profile.py +++ b/tests/test_ghidra_fast_profile.py @@ -1,6 +1,7 @@ """Ghidra's analysis of a 100 MB+ monolith is bounded by a fast profile that disables the passes proven pathological on a huge binary (Call-Fixup Installer's O(n^2) AddressSet, the per-processor -Constant Reference Analyzer, the decompile-every-function passes) while KEEPING the call-graph/ +Constant Reference Analyzer, the decompile-every-function passes, and the Non-Returning Functions +analyzers whose ClearFlowAndRepair wedges on a monolith) while KEEPING the call-graph/ reference analyzers. Since the PyGhidra re-platform these are pure host-side helpers in `pyghidra_lib` (`_slow_analyzer` + `_FAST_PROFILE_BYTES`), applied in-process by `_analyze` before AutoAnalysisManager runs. The analysis otherwise runs to completion — `re_analyze` runs it detached with a generous budget