252 redirect logs to stderr add silent mode and introduce final summary in mole headless (#254)

- Write headless log output to `stderr`
- Add log level `none` to suppress log output
- Write machine-readable summary to `stdout`
This commit is contained in:
Damian Pfammatter
2026-02-27 16:22:03 +01:00
committed by GitHub
parent e4f4b7f0cc
commit d133007825
2 changed files with 65 additions and 14 deletions
+53 -3
View File
@@ -8,7 +8,9 @@ import argparse as ap
import binaryninja as bn
import hashlib
import json
import math
import os
import time
import yaml
@@ -29,7 +31,7 @@ def main() -> None:
parser.add_argument("--config_file", help="custom configuration file to use")
parser.add_argument(
"--log_level",
choices=["error", "warning", "info", "debug"],
choices=["error", "warning", "info", "debug", "none"],
default="debug",
help="log level",
)
@@ -72,6 +74,8 @@ def main() -> None:
"--save_bndb", help="save BN database file with analysis results"
)
args = vars(parser.parse_args())
# Time before analysis
start_time = time.time()
# Load and analyze binary with Binary Ninja
try:
bv = bn.load(args["file"])
@@ -86,9 +90,10 @@ def main() -> None:
# Analyze binary with Mole
try:
# Find paths
path_service = PathService(
bv, log, ConfigModel(ConfigService(log, args["config_file"]).load_config())
config_model = ConfigModel(
ConfigService(log, args["config_file"]).load_config()
)
path_service = PathService(bv, log, config_model)
path_service.find_paths(
max_workers=args["max_workers"],
fix_func_type=args["fix_func_type"],
@@ -141,6 +146,51 @@ def main() -> None:
fp = args["save_bndb"]
fp = os.path.abspath(os.path.expanduser(os.path.expandvars(fp)))
bv.create_database(fp)
# Time after analysis
end_time = time.time()
# Calculate path statistics
paths_stats: Dict[str, Dict[str, int]] = {}
for path in paths:
paths_stats[path.src_sym_name][path.snk_sym_name] = (
paths_stats.setdefault(path.src_sym_name, {}).setdefault(
path.snk_sym_name, 0
)
+ 1
)
sources: Dict[str, List[str]] = {}
for lib_name in config_model.get_libraries("Sources").keys():
src_funcs = [
func.name
for func in config_model.get_functions(
lib_name=lib_name, fun_type="Sources", fun_enabled=True
)
]
if src_funcs:
sources.setdefault(lib_name, []).extend(src_funcs)
sinks: Dict[str, List[str]] = {}
for lib_name in config_model.get_libraries("Sinks").keys():
snk_funcs = [
func.name
for func in config_model.get_functions(
lib_name=lib_name, fun_type="Sinks", fun_enabled=True
)
]
if snk_funcs:
sinks.setdefault(lib_name, []).extend(snk_funcs)
# Output summary of results in machine-readable format
print(
json.dumps(
{
"analysis_time_seconds": math.trunc((end_time - start_time) * 1000)
/ 1000,
"paths_total": len(paths),
"paths_stats": paths_stats,
"sources": sources,
"sinks": sinks,
},
indent=2,
)
)
# Close binary
bv.file.close()
except KeyboardInterrupt:
+12 -11
View File
@@ -12,12 +12,12 @@ class Logger:
This class implements a logger that can print messages to Binary Ninja's log and to the console.
"""
_levels = ["debug", "info", "warning", "error"]
_levels = ["debug", "info", "warning", "error", "none"]
def __init__(
self,
bv: bn.BinaryView | None = None,
level: Literal["debug", "info", "warning", "error"] = "debug",
level: Literal["debug", "info", "warning", "error", "none"] = "debug",
) -> None:
"""
This method initializes a logger that can be used to write messages of a given level (and
@@ -92,7 +92,7 @@ class Logger:
on_color: str = "",
print_raw: bool = False,
attrs: List[str] = [],
file=sys.stdout,
file=sys.stderr,
) -> None:
"""
This method prints the given message to the console.
@@ -129,7 +129,7 @@ class Logger:
text = self._tag_msg(tag, msg)
if not self._runs_headless and not self._runs_debugger:
self._logger.log_debug(text)
elif self._level <= 0:
elif self._runs_debugger or self._level <= 0:
self._print(
"DEBG",
text,
@@ -137,8 +137,9 @@ class Logger:
on_color=on_color,
print_raw=print_raw,
attrs=attrs,
file=sys.stdout,
file=sys.stdout if self._runs_debugger else sys.stderr,
)
return
def info(
@@ -156,7 +157,7 @@ class Logger:
text = self._tag_msg(tag, msg)
if not self._runs_headless and not self._runs_debugger:
self._logger.log_info(text)
elif self._level <= 1:
elif self._runs_debugger or self._level <= 1:
self._print(
"INFO",
text,
@@ -164,7 +165,7 @@ class Logger:
on_color=on_color,
print_raw=print_raw,
attrs=attrs,
file=sys.stdout,
file=sys.stdout if self._runs_debugger else sys.stderr,
)
return
@@ -183,7 +184,7 @@ class Logger:
text = self._tag_msg(tag, msg)
if not self._runs_headless and not self._runs_debugger:
self._logger.log_warn(text)
elif self._level <= 2:
elif self._runs_debugger or self._level <= 2:
self._print(
"WARN",
text,
@@ -191,7 +192,7 @@ class Logger:
on_color=on_color,
print_raw=print_raw,
attrs=attrs,
file=sys.stderr,
file=sys.stdout if self._runs_debugger else sys.stderr,
)
return
@@ -210,7 +211,7 @@ class Logger:
text = self._tag_msg(tag, msg)
if not self._runs_headless and not self._runs_debugger:
self._logger.log_error(text)
elif self._level <= 3:
elif self._runs_debugger or self._level <= 3:
self._print(
"ERRO",
text,
@@ -218,6 +219,6 @@ class Logger:
on_color=on_color,
print_raw=print_raw,
attrs=attrs,
file=sys.stderr,
file=sys.stdout if self._runs_debugger else sys.stderr,
)
return