Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 40 additions & 16 deletions plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,35 @@ def _collect_from_frame(vf):
return found_fns

def _tick():
# --- Drain the main-thread dispatch queue ---
try:
from .core.binary_operations import _main_thread_queue
from .core import binary_operations as _bo_mod
import threading as _threading
if _bo_mod._main_thread_id is None:
_bo_mod._main_thread_id = _threading.current_thread().ident
while not _main_thread_queue.empty():
try:
func, done_event, result_holder = _main_thread_queue.get_nowait()
try:
result_holder[0] = func()
except Exception as e:
result_holder[1] = e
finally:
done_event.set()
except Exception:
break
except Exception:
pass

try:
ops = (
plugin.server.binary_ops
if (plugin.server and plugin.server.binary_ops)
else None
)
if not ops:
bn.log_debug("MCP _tick: ops is None")
return

# First, prune internal weakrefs and get a snapshot of tracked views
Expand All @@ -542,17 +564,13 @@ def _tick():

# Discover all open BVs from UI and sync registry (returns filenames)
try:
_discover_all_open_bvs(ops) or set()
except Exception:
pass
found = _discover_all_open_bvs(ops) or set()
if found:
bn.log_debug(f"MCP _tick: discovered BVs: {found}")
except Exception as e:
bn.log_debug(f"MCP _tick: discover error: {e}")

# Do not prune solely based on UI heuristics; UI enumeration may miss open tabs.
# Rely on explicit close notifications and weakref pruning in ops.

# Keep MCP-selected active view independent of UI focus.
# Only adopt a UI-active view if there is no current selection
# (e.g., after the previously selected view was actually closed
# and pruned by weakrefs).
# Always try to adopt UI-active view if current_view is None
try:
if ops.current_view is None:
try:
Expand All @@ -562,22 +580,28 @@ def _tick():
act_bv = None
if act_ctx:
vf = act_ctx.getCurrentViewFrame()
bn.log_debug(f"MCP _tick: viewFrame={vf}, has getCurrentBinaryView={hasattr(vf, 'getCurrentBinaryView') if vf else 'N/A'}")
if vf and hasattr(vf, "getCurrentBinaryView"):
act_bv = vf.getCurrentBinaryView()
ops.current_view = act_bv
if act_bv:
bn.log_debug(f"MCP _tick: getCurrentBinaryView returned: {act_bv}, type={type(act_bv).__name__ if act_bv else 'None'}")
else:
bn.log_debug("MCP _tick: no active UIContext")
if act_bv is not None:
ops.current_view = act_bv
ops.register_view(act_bv)
except Exception:
# If UI is unavailable or no active view, leave as None
pass
bn.log_info(f"MCP _tick: auto-set current_view to {act_bv.file.filename}")
else:
bn.log_debug("MCP _tick: current_view is None and no active BV found")
except Exception as e:
bn.log_debug(f"MCP _tick: error acquiring BV: {e}")
except Exception:
pass
except Exception:
# Never raise out of the timer
pass

_bv_monitor_timer = QTimer()
_bv_monitor_timer.setInterval(1000) # 1s; light periodic sync
_bv_monitor_timer.setInterval(200) # 200ms; fast dispatch for HTTP API calls
_bv_monitor_timer.timeout.connect(_tick)

def _start():
Expand Down
38 changes: 21 additions & 17 deletions plugin/api/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,25 +210,29 @@ def search_functions(
if not search_term:
return []

matches = []
for func in self.binary_ops.current_view.functions:
if search_term.lower() in func.name.lower():
matches.append(
{
"name": func.name,
"address": hex(func.start),
"raw_name": func.raw_name if hasattr(func, "raw_name") else func.name,
"symbol": {
"type": str(func.symbol.type) if func.symbol else None,
"full_name": func.symbol.full_name if func.symbol else None,
from ..core.binary_operations import _run_on_main_thread

def _inner():
matches = []
for func in self.binary_ops.current_view.functions:
if search_term.lower() in func.name.lower():
matches.append(
{
"name": func.name,
"address": hex(func.start),
"raw_name": func.raw_name if hasattr(func, "raw_name") else func.name,
"symbol": {
"type": str(func.symbol.type) if func.symbol else None,
"full_name": func.symbol.full_name if func.symbol else None,
}
if func.symbol
else None,
}
if func.symbol
else None,
}
)
)
matches.sort(key=lambda x: x["name"])
return matches[offset : offset + limit]

matches.sort(key=lambda x: x["name"])
return matches[offset : offset + limit]
return _run_on_main_thread(_inner)

def decompile_function(self, identifier: str) -> str | None:
"""Decompile a function by name or address"""
Expand Down
Loading
Loading