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
55 changes: 54 additions & 1 deletion src/binassist_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,59 @@ def get_function_low_level_il(filename: str, address_or_name: str, ctx: Context)
tools = BinAssistMCPTools(binary_view)
return tools.get_function_low_level_il(address_or_name)

@mcp.tool(annotations=READ_ONLY_ANNOTATIONS)
def get_function_analysis_limits(filename: str, function_name_or_address: str, ctx: Context) -> dict:
"""Get the effective per-function analysis limit overrides plus current IL availability.

Args:
filename: Name of the binary file
function_name_or_address: Function identifier (name or hex address)

Returns:
Dictionary with effective expressionValueComputeMaxDepth,
maxFunctionAnalysisTime, analysis_skipped state, and IL availability flags
"""
context_manager: BinAssistMCPBinaryContextManager = ctx.request_context.lifespan_context
guard = _check_analysis_guard(context_manager, filename)
if guard:
return guard
binary_view = context_manager.get_binary(filename)
tools = BinAssistMCPTools(binary_view)
return tools.get_function_analysis_limits(function_name_or_address)

@mcp.tool(annotations=ANALYSIS_ANNOTATIONS)
def reanalyze_function(filename: str, function_name_or_address: str, ctx: Context,
expression_depth: Optional[int] = None,
max_analysis_time: Optional[int] = None) -> dict:
"""Force a targeted reanalysis of one function, optionally overriding its
per-function analysis limits (Function Resource Settings, stored in the BNDB).

Use this when get_code() / get_function_low_level_il() keep returning
fallback_used=True or "<IL> not available" for one specific function.
Escalate expression_depth gradually (e.g. 8192, 16384, 32768, 65536)
instead of jumping straight to a very large value.

Args:
filename: Name of the binary file
function_name_or_address: Function identifier (name or hex address)
expression_depth: Override for analysis.limits.expressionValueComputeMaxDepth
for this function only (omit to leave the BinaryView default in effect)
max_analysis_time: Override for analysis.limits.maxFunctionAnalysisTime in
milliseconds for this function only (0 disables the time limit)

Returns:
Dictionary with the applied overrides and resulting IL availability
"""
context_manager: BinAssistMCPBinaryContextManager = ctx.request_context.lifespan_context
guard = _check_analysis_guard(context_manager, filename)
if guard:
return guard
binary_view = context_manager.get_binary(filename)
tools = BinAssistMCPTools(binary_view)
result = tools.reanalyze_function(function_name_or_address, expression_depth, max_analysis_time)
context_manager.update_analysis_status(filename)
return result

@mcp.tool(annotations=READ_ONLY_ANNOTATIONS)
def search_strings(filename: str, pattern: str, ctx: Context,
case_sensitive: bool = False,
Expand Down Expand Up @@ -1518,7 +1571,7 @@ def bookmarks(filename: str, action: str, ctx: Context,
return f"Error: cannot resolve '{address}'"
tt = binary_view.tag_types.get("Bookmarks")
if tt is None:
tt = binary_view.create_tag_type("Bookmarks", "")
tt = binary_view.create_tag_type("Bookmarks", "\u2b50")
text = comment or "Bookmark"
tag = binary_view.create_tag(tt, text, True)
func = binary_view.get_function_at(addr)
Expand Down
103 changes: 103 additions & 0 deletions src/binassist_mcp/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2759,6 +2759,109 @@ def get_function_low_level_il(self, address_or_name: str) -> str:

return "LLIL not available for this function"

@handle_exceptions
@require_binja
def get_function_analysis_limits(self, function_name_or_address: str) -> Dict[str, Any]:
"""Get the effective per-function analysis limit overrides plus current IL availability.

Use this before/after reanalyze_function() to see whether a Function Resource
Setting override is already in place and whether HLIL/MLIL/LLIL are available yet.

Args:
function_name_or_address: Function identifier (name or hex address)

Returns:
Dictionary with the effective expressionValueComputeMaxDepth and
maxFunctionAnalysisTime (BinaryView default unless overridden for this
function), analysis_skipped state, and IL availability flags.
"""
func = self._get_function_by_name_or_address(function_name_or_address)
if not func:
raise ValueError(f"Function not found: {function_name_or_address}")

settings = bn.Settings()
return {
"function": func.name,
"address": hex(func.start),
"expression_depth": settings.get_integer(
"analysis.limits.expressionValueComputeMaxDepth", func),
"max_analysis_time": settings.get_integer(
"analysis.limits.maxFunctionAnalysisTime", func),
Comment on lines +2786 to +2789
"analysis_skipped": func.analysis_skipped,
"hlil_available": func.hlil_if_available is not None,
"mlil_available": func.mlil_if_available is not None,
"llil_available": func.llil_if_available is not None,
}

@handle_exceptions
@require_binja
def reanalyze_function(self, function_name_or_address: str,
expression_depth: Optional[int] = None,
max_analysis_time: Optional[int] = None) -> Dict[str, Any]:
"""Force a targeted reanalysis of one function, optionally overriding its
per-function analysis limits (stored as Function Resource Settings in the BNDB,
inherited from the BinaryView otherwise).

Use this when get_code()/get_function_low_level_il() report fallback_used=True
or "<IL> not available for this function" for one specific function and Binary
Ninja's log shows an internal analysis limit being hit (e.g. "exceeded maximum
recursion depth for Analyze requests" or a deferred/too-large-function message).
Escalate expression_depth in steps (e.g. 8192 -> 16384 -> 32768 -> 65536) rather
than jumping straight to a very large value, since this setting affects analysis
cost for this function on every future reanalysis.

Args:
function_name_or_address: Function identifier (name or hex address)
expression_depth: If set, overrides analysis.limits.expressionValueComputeMaxDepth
for this function only (BinaryView default is commonly 512-4096)
max_analysis_time: If set, overrides analysis.limits.maxFunctionAnalysisTime
in milliseconds for this function only (0 disables the time limit for
this function - only use for functions already known to terminate)

Returns:
Dictionary with the applied overrides and resulting IL availability
"""
func = self._get_function_by_name_or_address(function_name_or_address)
if not func:
raise ValueError(f"Function not found: {function_name_or_address}")

settings = bn.Settings()
overrides_applied: Dict[str, int] = {}

if expression_depth is not None:
settings.set_integer(
"analysis.limits.expressionValueComputeMaxDepth",
expression_depth,
resource=func,
scope=bn.SettingsScope.SettingsResourceScope,
)
overrides_applied["expression_depth"] = expression_depth

if max_analysis_time is not None:
settings.set_integer(
"analysis.limits.maxFunctionAnalysisTime",
max_analysis_time,
resource=func,
scope=bn.SettingsScope.SettingsResourceScope,
)
overrides_applied["max_analysis_time"] = max_analysis_time

# reanalyze() is a no-op while analysis_skipped is True (see BN API docs)
if func.analysis_skipped:
func.analysis_skipped = False

func.reanalyze()
self.bv.update_analysis_and_wait()

return {
"function": func.name,
"address": hex(func.start),
"overrides_applied": overrides_applied,
"hlil_available": func.hlil_if_available is not None,
"mlil_available": func.mlil_if_available is not None,
"llil_available": func.llil_if_available is not None,
}

@handle_exceptions
@require_binja
def search_strings(self, pattern: str, case_sensitive: bool = False,
Expand Down