Skip to content

Functionnames #224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 9, 2025
Merged
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
18 changes: 17 additions & 1 deletion chb/app/AppResultFunctionMetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion chb/app/AppResultMetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]:
Expand Down
2 changes: 1 addition & 1 deletion chb/app/CHVersion.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
chbversion: str = "0.3.0-20250502"
chbversion: str = "0.3.0-20250608"
4 changes: 4 additions & 0 deletions chb/app/InstrXData.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:]
Expand Down
6 changes: 5 additions & 1 deletion chb/arm/ARMCallOpcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions chb/arm/opcodes/ARMAdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 28 additions & 0 deletions chb/arm/opcodes/ARMBranch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions chb/arm/opcodes/ARMMove.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions chb/cmdline/chkx
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
58 changes: 58 additions & 0 deletions chb/cmdline/commandutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions chb/invariants/XXpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down