diff --git a/chb/app/AppResultFunctionMetrics.py b/chb/app/AppResultFunctionMetrics.py index a7002b6e..c2d675b6 100644 --- a/chb/app/AppResultFunctionMetrics.py +++ b/chb/app/AppResultFunctionMetrics.py @@ -28,7 +28,9 @@ # ------------------------------------------------------------------------------ import xml.etree.ElementTree as ET -from typing import Dict, List, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING + +from chb.jsoninterface.JSONResult import JSONResult import chb.util.fileutil as UF @@ -214,6 +216,20 @@ def name(self) -> str: def has_name(self) -> bool: return "name" in self.xnode.attrib + def to_json_result(self) -> JSONResult: + content: Dict[str, Any] = {} + content["faddr"] = self.faddr + if self.has_name(): + content["name"] = self.name + content["instrs"] = self.instruction_count + content["blocks"] = self.block_count + content["time"] = self.time + if self.call_count > 0: + content["callcount"] = self.call_count + if self.unresolved_call_count > 0: + content["unrcalls"] = self.unresolved_call_count + return JSONResult("functionmetrics", content, "ok") + def as_dictionary(self) -> Dict[str, str]: result: Dict[str, str] = {} result['faddr'] = self.faddr diff --git a/chb/app/AppResultMetrics.py b/chb/app/AppResultMetrics.py index 4d62c7f0..565f003a 100644 --- a/chb/app/AppResultMetrics.py +++ b/chb/app/AppResultMetrics.py @@ -401,7 +401,7 @@ def f(fn: AppResultFunctionMetrics) -> None: self.iter(f) return names - def to_json_result(self) -> JSONResult: + def to_json_result(self, includefns: bool = False) -> JSONResult: content: Dict[str, Any] = {} content["instructions"] = int(self.instruction_count) content["unknowninstrs"] = int(self.unknown_instructions) @@ -420,6 +420,10 @@ def to_json_result(self) -> JSONResult: content["iterations"] = self.run_count content["analysisdate"] = self.date_time content["fns-excluded"] = self.fns_excluded + if includefns: + content["functions"] = [] + for f in self.get_function_results(): + content["functions"].append(f.to_json_result().content) return JSONResult("analysisstats", content, "ok") def as_dictionary(self) -> Dict[str, Any]: diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index ffbbaf73..e28791c6 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1 +1 @@ -chbversion: str = "0.3.0-20250502" +chbversion: str = "0.3.0-20250608" diff --git a/chb/app/InstrXData.py b/chb/app/InstrXData.py index 768da68d..eeea1ffa 100644 --- a/chb/app/InstrXData.py +++ b/chb/app/InstrXData.py @@ -327,6 +327,10 @@ def _expand(self) -> None: return if self.tags[0] == "nop": return + if self.tags[0] == "subsumes": + chklogger.logger.error( + "InstrXData: subsumes tag not yet supported") + return key = self.tags[0] if key.startswith("a:"): keyletters = key[2:] diff --git a/chb/arm/ARMCallOpcode.py b/chb/arm/ARMCallOpcode.py index 04d9e7e2..ba52897c 100644 --- a/chb/arm/ARMCallOpcode.py +++ b/chb/arm/ARMCallOpcode.py @@ -331,7 +331,11 @@ def ast_call_prov( # Note that defuses[0] may be None even when defuseshigh[0] is not # None. This happens when the return value's only use is as a # return value of the caller's function itself. - if rtype.is_void or ((defuses[0] is None) and (defuseshigh[0] is None)): + if ( + rtype.is_void + or ((defuses[0] is None) + and (defuseshigh[0] is None) + and not self.return_value)): chklogger.logger.info( "Unused: introduced ssa-variable: %s for return value of %s " + "at address %s", diff --git a/chb/arm/opcodes/ARMAdd.py b/chb/arm/opcodes/ARMAdd.py index 396faffc..80a794cc 100644 --- a/chb/arm/opcodes/ARMAdd.py +++ b/chb/arm/opcodes/ARMAdd.py @@ -259,6 +259,12 @@ def ast_prov( annotations: List[str] = [iaddr, "ADD"] + if xdata.is_aggregate_jumptable: + chklogger.logger.warning( + "ADD: aggregate jumptable at address %s not yet handled", + iaddr) + return ([], []) + # low-level assignment (ll_lhs, _, _) = self.operands[0].ast_lvalue(astree) diff --git a/chb/arm/opcodes/ARMBranch.py b/chb/arm/opcodes/ARMBranch.py index 27fe8821..9cd452bb 100644 --- a/chb/arm/opcodes/ARMBranch.py +++ b/chb/arm/opcodes/ARMBranch.py @@ -149,6 +149,21 @@ def is_xtgt_ok(self) -> bool: else: return self.is_xpr_ok(4) + def has_return_xpr(self) -> bool: + return self.xdata.has_return_xpr() + + def returnval(self) -> "XXpr": + return self.xdata.get_return_xpr() + + def rreturnval(self) -> "XXpr": + return self.xdata.get_return_xxpr() + + def has_creturnval(self) -> bool: + return self.xdata.has_return_cxpr() + + def creturnval(self) -> "XXpr": + return self.xdata.get_return_cxpr() + @property def annotation(self) -> str: if self.is_conditional: @@ -234,6 +249,19 @@ def jump_target(self, xdata: InstrXData) -> Optional["XXpr"]: else: return xdata.xprs[0] + def is_return_instruction(self, xdata: InstrXData) -> bool: + return ARMBranchXData(xdata).has_return_xpr() + + def return_value(self, xdata: InstrXData) -> Optional[XXpr]: + xd = ARMBranchXData(xdata) + if xd.has_return_xpr(): + if xd.has_creturnval(): + return xd.creturnval() + else: + return xd.rreturnval() + else: + return None + def annotation(self, xdata: InstrXData) -> str: xd = ARMBranchXData(xdata) if xd.is_ok: diff --git a/chb/arm/opcodes/ARMMove.py b/chb/arm/opcodes/ARMMove.py index a32016a4..8a6c773c 100644 --- a/chb/arm/opcodes/ARMMove.py +++ b/chb/arm/opcodes/ARMMove.py @@ -220,6 +220,12 @@ def ast_prov( if xdata.instruction_is_subsumed(): return self.ast_prov_subsumed(astree, iaddr, bytestring, xdata) + if xdata.instruction_subsumes(): + chklogger.logger.warning( + "MOV instruction at %s is part of an aggregate that is not yet supported", + iaddr) + return ([], []) + # low-level assignment (ll_lhs, _, _) = self.opargs[0].ast_lvalue(astree) diff --git a/chb/cmdline/chkx b/chb/cmdline/chkx index 3424947d..2309085f 100755 --- a/chb/cmdline/chkx +++ b/chb/cmdline/chkx @@ -579,6 +579,13 @@ def parse() -> argparse.Namespace: ddatagvars.add_argument("xname", help="name of executable") ddatagvars.set_defaults(func=UCC.ddata_gvars) + # -- ddata userdata -- + ddatauserdata = ddataparsers.add_parser("userdata") + ddatauserdata.add_argument("xname", help="name of executable") + ddatauserdata.add_argument( + "--output", "-o", required=True, help="name of file to save results") + ddatauserdata.set_defaults(func=UCC.ddata_userdata) + # -- ddata md5s -- ddatamd5s = ddataparsers.add_parser("md5s") ddatamd5s.add_argument("xname", help="name of executable") @@ -616,6 +623,10 @@ def parse() -> argparse.Namespace: "--opcodes", help=("json filename (without extension) to save opcode distribution " + "stats (for instructions within analyzed functions)")) + resultsstats.add_argument( + "--functionmetrics", + help=("json filename (without extension) to save function metrics in " + + "json format")) resultsstats.add_argument( "--tagfile", help="name of json file that has function tags") diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index 3bfede7a..d6662554 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -619,6 +619,7 @@ def results_stats(args: argparse.Namespace) -> NoReturn: sortby: str = args.sortby timeshare: int = args.timeshare opcodes: str = args.opcodes + functionmetrics: str = args.functionmetrics hide: List[str] = args.hide tagfile: Optional[str] = args.tagfile loglevel: str = args.loglevel @@ -696,6 +697,17 @@ def results_stats(args: argparse.Namespace) -> NoReturn: print("-----------------------") print("Total".ljust(14) + "{:4.2f}".format(100.0 * toptotal).rjust(6)) + if functionmetrics: + filename = functionmetrics + ".json" + content: Dict[str, Any] = {} + content["filename"] = xname + content["functions"] = [] + for f in sorted(stats.get_function_results(), key=lambda f: f.faddr): + content["functions"].append(f.to_json_result().content) + jsonok = JU.jsonok("functionmetrics", content) + with open(filename, "w") as fp: + json.dump(jsonok, fp, indent=4) + if opcodes: filename = opcodes + ".json" opcstats = app.mnemonic_stats() @@ -2530,6 +2542,52 @@ def ddata_gvars(args: argparse.Namespace) -> NoReturn: exit(0) +def ddata_userdata(args: argparse.Namespace) -> NoReturn: + + # arguments + xname: str = str(args.xname) + xoutput: str = str(args.output) + + try: + (path, xfile) = get_path_filename(xname) + except UF.CHBError as e: + print(str(e.wrap())) + exit(1) + + xinfo = XI.XInfo() + xinfo.load(path, xfile) + + app = get_app(path, xfile, xinfo) + + result: Dict[str, Dict[str, Dict[str, str]]] = {} + userdata = result["userdata"] = {} + fnnames = userdata["function-names"] = {} + symaddrs = userdata["symbolic-addresses"] = {} + + functionsdata = app.systeminfo.functionsdata.functions + for (faddr, fdata) in sorted(functionsdata.items(), key=lambda t: int(t[0], 16)): + if fdata.has_name(): + fnnames[faddr] = fdata.name + + memmap = app.globalmemorymap + glocs = memmap.locations + for gaddr in sorted(glocs, key=lambda g: int(g, 16)): + gloc = glocs[gaddr] + if gloc.gtype is not None: + symaddrs[gaddr] = gloc.name + + with open(xoutput, "w") as fp: + json.dump(result, fp, indent=4) + + print("\nSaved userdata with " + + str(len(fnnames)) + + " function names and " + + str(len(symaddrs)) + + " symbolic addresses") + + exit(0) + + def ddata_md5s(args: argparse.Namespace) -> NoReturn: # arguments diff --git a/chb/invariants/XXpr.py b/chb/invariants/XXpr.py index cbcf4c4d..e5ce90b7 100644 --- a/chb/invariants/XXpr.py +++ b/chb/invariants/XXpr.py @@ -1035,6 +1035,8 @@ def __str__(self) -> str: if self.operator in xpr_operator_strings: return ( "(" + xpr_operator_strings[self.operator] + str(args[0]) + ")") + elif self.operator == "xf_addressofvar": + return "&(" + str(args[0]) + ")" else: return "(" + self.operator + " " + str(args[0]) + ")" elif len(args) == 2: