From 5fa44dc9cbda19cd60e8b2ce256c06a5d2a549e7 Mon Sep 17 00:00:00 2001 From: Henny Sipma Date: Mon, 23 Mar 2026 11:47:47 -0700 Subject: [PATCH 1/4] CMD: add command to view varinfo table --- chb/bctypes/BCDictionary.py | 9 +++++++++ chb/cmdline/chkx | 5 +++++ chb/cmdline/commandutil.py | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/chb/bctypes/BCDictionary.py b/chb/bctypes/BCDictionary.py index 2b608e11..38fae0e4 100644 --- a/chb/bctypes/BCDictionary.py +++ b/chb/bctypes/BCDictionary.py @@ -290,3 +290,12 @@ def f(ix: int, v: IT.IndexedTableValue) -> None: self.compinfo_table.iter(f) return "\n".join(lines) + + def varinfo_table_to_string(self) -> str: + lines: List[str] = [] + + def f(ix: int, v: IT.IndexedTableValue) -> None: + lines.append(str(ix) + ": " + str(self.varinfo(ix))) + + self.varinfo_table.iter(f) + return "\n".join(lines) diff --git a/chb/cmdline/chkx b/chb/cmdline/chkx index 7bbe161e..ec35fffd 100755 --- a/chb/cmdline/chkx +++ b/chb/cmdline/chkx @@ -1752,6 +1752,11 @@ def parse() -> argparse.Namespace: table_type.add_argument("xname", help="name of executable") table_type.set_defaults(func=UCC.show_type_table) + # --- varinfo --- + table_type = tableparsers.add_parser("varinfo") + table_type.add_argument("xname", help="name of executable") + table_type.set_defaults(func=UCC.show_varinfo_table) + # --- compinfo --- table_compinfo = tableparsers.add_parser("compinfo") table_compinfo.add_argument("xname", help="name of executable") diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index c8a850f3..5b152f6b 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -2526,6 +2526,30 @@ def show_type_table(args: argparse.Namespace) -> NoReturn: exit(0) +def show_varinfo_table(args: argparse.Namespace) -> NoReturn: + + # arguments + xname: str = args.xname + + try: + (path, xfile) = get_path_filename(xname) + UF.check_analysis_results(path, xfile) + except UF.CHBError as e: + print(str(e.wrap())) + exit(1) + + xinfo = XI.XInfo() + xinfo.load(path, xfile) + + app = get_app(path, xfile, xinfo) + + bcdictionary = app.bcdictionary + + print(bcdictionary.varinfo_table_to_string()) + + exit(0) + + def show_compinfo_table(args: argparse.Namespace) -> NoReturn: # arguments From 0f4d8e4a7a78046f81f67cfce95d0e181457912d Mon Sep 17 00:00:00 2001 From: Henny Sipma Date: Mon, 23 Mar 2026 11:48:34 -0700 Subject: [PATCH 2/4] CMD: add construct_signatures to interface --- chb/cmdline/AnalysisManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chb/cmdline/AnalysisManager.py b/chb/cmdline/AnalysisManager.py index 6a020842..6dd3429f 100644 --- a/chb/cmdline/AnalysisManager.py +++ b/chb/cmdline/AnalysisManager.py @@ -545,7 +545,7 @@ def _analyze_until_stable( if isfinished: chklogger.logger.debug("execute zip command %s", " ".join(zipcmd)) subprocess.call(zipcmd, stderr=subprocess.STDOUT, cwd=analysisdir) - fincmd = cmd + ["-collectdata"] + fincmd = cmd + ["-collectdata", "-construct_signatures"] if self.use_ssa: fincmd = fincmd + ["-ssa"] if self.no_varinvs: From b702b876155f1a2f464a7731f5d96a1ff0ef644a Mon Sep 17 00:00:00 2001 From: Henny Sipma Date: Mon, 23 Mar 2026 11:48:50 -0700 Subject: [PATCH 3/4] PATCH: add new function to patch results --- chb/cmdline/PatchResults.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chb/cmdline/PatchResults.py b/chb/cmdline/PatchResults.py index f128f533..aee95079 100644 --- a/chb/cmdline/PatchResults.py +++ b/chb/cmdline/PatchResults.py @@ -320,6 +320,10 @@ def logicalva(self) -> str: def is_trampoline(self) -> bool: return self.patchkind == "Trampoline" + @property + def is_new_function(self) -> bool: + return self.patchkind == "NewFunction" + @property def is_supported(self) -> bool: return self.is_trampoline @@ -431,6 +435,7 @@ def trampoline_addresses(self) -> List[Dict[str, str]]: if e.case_fallthrough_jump is not None: r["fallthrough-jump"] = e.case_fallthrough_jump result.append(r) + return result From 3dea42509de8519ac89016b31fc9360ea06fb6f3 Mon Sep 17 00:00:00 2001 From: Henny Sipma Date: Tue, 24 Mar 2026 23:57:17 -0700 Subject: [PATCH 4/4] CMD: add support for function signatures --- chb/app/CHVersion.py | 4 +-- chb/bctypes/BCAttrParam.py | 22 +++++++++++++++++ chb/bctypes/BCAttribute.py | 20 ++++++++++++++- chb/bctypes/BCDictionary.py | 18 ++++++++++++++ chb/bctypes/BCVarInfo.py | 45 ++++++++++++++++++++++++++++++++-- chb/cmdline/AnalysisManager.py | 9 +++++-- chb/cmdline/chkx | 9 +++++++ chb/cmdline/commandutil.py | 28 ++++++++++++++++++++- 8 files changed, 147 insertions(+), 8 deletions(-) diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index 2fd9f4a0..76b145e3 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1,3 +1,3 @@ -chbversion: str = "0.3.0-20260218" +chbversion: str = "0.3.0-20260324" -minimum_required_chb_version = "0.6.0_20260122" +minimum_required_chb_version = "0.6.0_20260324" diff --git a/chb/bctypes/BCAttrParam.py b/chb/bctypes/BCAttrParam.py index 4465078e..803364c5 100644 --- a/chb/bctypes/BCAttrParam.py +++ b/chb/bctypes/BCAttrParam.py @@ -74,6 +74,10 @@ def __init__( def is_int(self) -> bool: return False + @property + def to_c_string(self) -> str: + return str(self) + def __str__(self) -> str: return "bc-attrparam:" + self.tags[0] @@ -96,6 +100,10 @@ def intvalue(self) -> int: def is_int(self) -> bool: return True + @property + def to_c_string(self) -> str: + return str(self.intvalue) + def __str__(self) -> str: return "aint(" + str(self.intvalue) + ")" @@ -114,6 +122,10 @@ def __init__( def strvalue(self) -> str: return self.bcd.string(self.args[0]) + @property + def to_c_string(self) -> str: + return self.strvalue + def __str__(self) -> str: return "astr(" + self.strvalue + ")" @@ -136,6 +148,16 @@ def name(self) -> str: def params(self) -> List["BCAttrParam"]: return [self.bcd.attrparam(i) for i in self.args] + @property + def to_c_string(self) -> str: + if len(self.params) == 0: + return self.name + else: + return ( + self.name + "(" + + ", ".join(p.to_c_string for p in self.params) + + ")") + def __str__(self) -> str: return ( "acons(" diff --git a/chb/bctypes/BCAttribute.py b/chb/bctypes/BCAttribute.py index a51f2ca4..950b4af8 100644 --- a/chb/bctypes/BCAttribute.py +++ b/chb/bctypes/BCAttribute.py @@ -57,6 +57,10 @@ def params(self) -> List["BCAttrParam"]: def is_volatile(self) -> bool: return self.name == "volatile" + @property + def to_c_string(self) -> str: + return self.name + "(" + ", ".join(p.to_c_string for p in self.params) + ")" + def __str__(self) -> str: return self.name + "(" + ", ".join(str(p) for p in self.params) + ")" @@ -73,5 +77,19 @@ def __init__( def attrs(self) -> List[BCAttribute]: return [self.bcd.attribute(i) for i in self.args] + @property + def is_empty(self) -> bool: + return len(self.attrs) == 0 + + @property + def to_c_string(self) -> str: + return ( + "__attribute__((" + + "\n ".join(a.to_c_string for a in self.attrs) + + "\n ))") + def __str__(self) -> str: - return ", ".join(str(a) for a in self.attrs) + return ( + "__attribute__((" + + ", ".join(str(a) for a in self.attrs) + + "))" ) diff --git a/chb/bctypes/BCDictionary.py b/chb/bctypes/BCDictionary.py index 38fae0e4..4532c942 100644 --- a/chb/bctypes/BCDictionary.py +++ b/chb/bctypes/BCDictionary.py @@ -299,3 +299,21 @@ def f(ix: int, v: IT.IndexedTableValue) -> None: self.varinfo_table.iter(f) return "\n".join(lines) + + def varinfo_table_to_declarations(self) -> str: + lines: List[str] = [] + + def f(ix: int, v: IT.IndexedTableValue) -> None: + lines.append(self.varinfo(ix).to_c_string) + + self.varinfo_table.iter(f) + return "\n\n".join(lines) + + def attributes_table_to_string(self) -> str: + lines: List[str] = [] + + def f(ix: int, v: IT.IndexedTableValue) -> None: + lines.append(str(ix) + ": " + str(self.attributes(ix))) + + self.attributes_table.iter(f) + return "\n".join(lines) diff --git a/chb/bctypes/BCVarInfo.py b/chb/bctypes/BCVarInfo.py index 02af1870..4a31c900 100644 --- a/chb/bctypes/BCVarInfo.py +++ b/chb/bctypes/BCVarInfo.py @@ -25,10 +25,11 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, cast, Dict, List, TYPE_CHECKING +from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING import chb.ast.ASTNode as AST +from chb.bctypes.BCAttribute import BCAttributes from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionaryRecord import BCDictionaryRecord from chb.bctypes.BCVisitor import BCVisitor @@ -65,9 +66,44 @@ def vtype(self) -> "BCTyp": def vparam(self) -> int: return self.args[7] + @property + def attributes(self) -> Optional["BCAttributes"]: + return self.bcd.attributes(self.args[2]) + def convert(self, converter: "BCConverter") -> AST.ASTVarInfo: return converter.convert_varinfo(self) + @property + def to_c_string(self) -> str: + if self.vtype.is_array: + atype = cast("BCTypArray", self.vtype) + if atype.has_constant_size(): + asize = atype.sizevalue + return ( + str(atype.tgttyp) + + " " + + self.vname + + "[" + + str(asize) + + "];") + else: + return str(self.vtype) + " " + self.vname + elif self.vtype.is_function: + ftype = cast("BCTypFun", self.vtype) + if ftype.argtypes is not None: + argtypes = str(ftype.argtypes) + else: + argtypes = "()" + fattrs = self.attributes + if fattrs is not None and not fattrs.is_empty: + pfattrs = "\n" + fattrs.to_c_string + else: + pfattrs = "" + return (str(ftype.returntype) + " " + self.vname + argtypes + pfattrs) + ";" + else: + return str(self.vtype) + " " + self.vname + ";" + + def __str__(self) -> str: if self.vtype.is_array: atype = cast("BCTypArray", self.vtype) @@ -88,6 +124,11 @@ def __str__(self) -> str: argtypes = str(ftype.argtypes) else: argtypes = "()" - return (str(ftype.returntype) + " " + self.vname + argtypes) + fattrs = self.attributes + if fattrs is not None: + pfattrs = " " + str(fattrs) + else: + pfattrs = "" + return (str(ftype.returntype) + " " + self.vname + argtypes + pfattrs) else: return str(self.vtype) + " " + self.vname diff --git a/chb/cmdline/AnalysisManager.py b/chb/cmdline/AnalysisManager.py index 6dd3429f..6fc102f6 100644 --- a/chb/cmdline/AnalysisManager.py +++ b/chb/cmdline/AnalysisManager.py @@ -6,7 +6,7 @@ # # Copyright (c) 2016-2020 Kestrel Technology LLC # Copyright (c) 2020 Henny Sipma -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -296,6 +296,7 @@ def analyze( ignore_stable: bool = False, save_asm: bool = False, construct_all_functions: bool = False, + construct_signatures: bool = False, mem: bool = False, timeout: Optional[int] = None, preamble_cutoff: int = 12) -> int: @@ -310,6 +311,7 @@ def analyze( timeout=timeout, verbose=verbose, construct_all_functions=construct_all_functions, + construct_signatures=construct_signatures, collectdiagnostics=collectdiagnostics, failonfunctionfailure=failonfunctionfailure, preamble_cutoff=preamble_cutoff) @@ -450,6 +452,7 @@ def _analyze_until_stable( timeout: Optional[int] = None, verbose: bool = False, construct_all_functions: bool = False, + construct_signatures: bool = False, collectdiagnostics: bool = False, failonfunctionfailure: bool = False, preamble_cutoff: int = 12) -> int: @@ -545,7 +548,9 @@ def _analyze_until_stable( if isfinished: chklogger.logger.debug("execute zip command %s", " ".join(zipcmd)) subprocess.call(zipcmd, stderr=subprocess.STDOUT, cwd=analysisdir) - fincmd = cmd + ["-collectdata", "-construct_signatures"] + fincmd = cmd + ["-collectdata"] + if construct_signatures: + fincmd = fincmd + ["-construct_signatures"] if self.use_ssa: fincmd = fincmd + ["-ssa"] if self.no_varinvs: diff --git a/chb/cmdline/chkx b/chb/cmdline/chkx index ec35fffd..c1f7fbb1 100755 --- a/chb/cmdline/chkx +++ b/chb/cmdline/chkx @@ -386,6 +386,10 @@ def parse() -> argparse.Namespace: "--construct_all_functions", action="store_true", help="construct all functions even if not all functions are analyzed") + analyzecmd.add_argument( + "--construct_signatures", + action="store_true", + help="infer and construct function signatures for functions without them") analyzecmd.add_argument( "--collect_diagnostics", action="store_true", @@ -1752,6 +1756,11 @@ def parse() -> argparse.Namespace: table_type.add_argument("xname", help="name of executable") table_type.set_defaults(func=UCC.show_type_table) + # --- attributes --- + table_type = tableparsers.add_parser("attributes") + table_type.add_argument("xname", help="name of executable") + table_type.set_defaults(func=UCC.show_attributes_table) + # --- varinfo --- table_type = tableparsers.add_parser("varinfo") table_type.add_argument("xname", help="name of executable") diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index 5b152f6b..e7108e79 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -419,6 +419,7 @@ def analyzecmd(args: argparse.Namespace) -> NoReturn: analyze_range_entry_points: List[str] = args.analyze_range_entry_points gc_compact: int = args.gc_compact construct_all_functions: bool = args.construct_all_functions + construct_signatures: bool = args.construct_signatures show_function_timing: bool = args.show_function_timing lineq_instr_cutoff: int = args.lineq_instr_cutoff lineq_block_cutoff: int = args.lineq_block_cutoff @@ -601,6 +602,7 @@ def analyzecmd(args: argparse.Namespace) -> NoReturn: verbose=verbose, save_asm=save_asm, construct_all_functions=construct_all_functions, + construct_signatures=construct_signatures, collectdiagnostics=collectdiagnostics, failonfunctionfailure=failonfunctionfailure, preamble_cutoff=preamble_cutoff) @@ -2526,6 +2528,30 @@ def show_type_table(args: argparse.Namespace) -> NoReturn: exit(0) +def show_attributes_table(args: argparse.Namespace) -> NoReturn: + + # arguments + xname: str = args.xname + + try: + (path, xfile) = get_path_filename(xname) + UF.check_analysis_results(path, xfile) + except UF.CHBError as e: + print(str(e.wrap())) + exit(1) + + xinfo = XI.XInfo() + xinfo.load(path, xfile) + + app = get_app(path, xfile, xinfo) + + bcdictionary = app.bcdictionary + + print(bcdictionary.attributes_table_to_string()) + + exit(0) + + def show_varinfo_table(args: argparse.Namespace) -> NoReturn: # arguments @@ -2545,7 +2571,7 @@ def show_varinfo_table(args: argparse.Namespace) -> NoReturn: bcdictionary = app.bcdictionary - print(bcdictionary.varinfo_table_to_string()) + print(bcdictionary.varinfo_table_to_declarations()) exit(0)