Skip to content
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
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-20251022"
chbversion: str = "0.3.0-20260122"
8 changes: 5 additions & 3 deletions chb/app/InstrXData.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,9 @@ def has_call_target(self) -> bool:
key = self.tags[0]
if key.startswith("a:"):
keyletters = key[2:]
return len(self.args) == len(keyletters) + 1
return (
len(self.args) == len(keyletters) + 1
and self.args[-1] > 0)
else:
return False
elif len(self.tags) >= 2 and self.tags[1] == "call":
Expand Down Expand Up @@ -470,9 +472,9 @@ def has_indirect_call_target_exprs(self) -> bool:
return (len(self.tags) == 2 and self.tags[1] == "u" and len(self.args) > 1)

def call_target(self, ixd: "InterfaceDictionary") -> "CallTarget":
if self.has_call_target() and self.is_bx_call:
if self.has_call_target() and self.is_bx_call and self.args[-5] > 0:
return ixd.call_target(self.args[-5])
elif self.has_call_target():
elif self.has_call_target() and self.args[-1] > 0:
return ixd.call_target(self.args[-1])
else:
raise UF.CHBError(
Expand Down
34 changes: 29 additions & 5 deletions chb/arm/opcodes/ARMPreloadData.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2021 Aarno Labs LLC
# Copyright (c) 2021-2025 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
Expand All @@ -30,9 +30,11 @@
from chb.app.InstrXData import InstrXData

from chb.arm.ARMDictionaryRecord import armregistry
from chb.arm.ARMOpcode import ARMOpcode, simplify_result
from chb.arm.ARMOpcode import ARMOpcode, ARMOpcodeXData, simplify_result
from chb.arm.ARMOperand import ARMOperand

from chb.invariants.XXpr import XXpr

import chb.util.fileutil as UF

from chb.util.IndexedTable import IndexedTableValue
Expand All @@ -41,6 +43,29 @@
import chb.arm.ARMDictionary


class ARMPreloadDataXData(ARMOpcodeXData):
"""Data format:
- expressions:
0: xbase
1: xmem
"""

def __init__(self, xdata: InstrXData) -> None:
ARMOpcodeXData.__init__(self, xdata)

@property
def xbase(self) -> "XXpr":
return self.xpr(0, "xbase")

@property
def xmem(self) -> "XXpr":
return self.xpr(1, "xmem")

@property
def annotation(self) -> str:
return "Preload-data(" + str(self.xmem)


@armregistry.register_tag("PLDW", ARMOpcode)
@armregistry.register_tag("PLD", ARMOpcode)
class ARMPreloadData(ARMOpcode):
Expand Down Expand Up @@ -72,6 +97,5 @@ def annotation(self, xdata: InstrXData) -> str:
xprs[0]: value of base register
xprs[1]: value of memory location
"""

rhs = str(xdata.xprs[1])
return "Preload-data(" + rhs + ")"
xd = ARMPreloadDataXData(xdata)
return xd.annotation
11 changes: 9 additions & 2 deletions chb/astinterface/ASTIProvenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,10 +446,17 @@ def resolve_reaching_defs(self) -> None:
# Allow for change of name of return value
if str(instr.lhs) == v or v == "R0" or v == "S0":
self.add_reaching_definition(xid, instrid)
elif instr.lhs is None:
chklogger.logger.info(
"Lhs variable %s is suppressed in call to "
"%s for reaching def address %s",
v, str(instr.tgt), addr)
self.add_reaching_definition(xid, instrid)
else:
chklogger.logger.warning(
"Variable names don't match: %s vs %s",
str(instr.lhs), v)
"Lhs variable names don't match: %s vs %s"
+ " to %s for reaching def address %s",
str(instr.lhs), v, str(instr.tgt), addr)
else:
chklogger.logger.warning(
"Expression is defined by unknown instruction: "
Expand Down
15 changes: 9 additions & 6 deletions chb/astinterface/ASTInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,12 +1016,7 @@ def introduce_stack_variables(
stackvartypes: Dict[int, "BCTyp"]) -> None:
"""Creates stack variables/buffers for all stack offsets with types."""

# local variable stack offsets from the type inference are positive,
# so they must be negated here. For the same reason, to capture the
# largest extent of every varinfo, offsets must be traversed in reverse
# order.
for (offset, bctype) in sorted(stackvartypes.items(), reverse=True):
offset = -offset
for (offset, bctype) in sorted(stackvartypes.items()):
vtype = bctype.convert(self.typconverter)
self.mk_stack_variable_lval(offset, vtype=vtype)

Expand Down Expand Up @@ -1115,6 +1110,7 @@ def mk_stack_variable_lval(
if varinfo.vtype is None:
return lval

# create stack variables for all fields and array elements
if varinfo.vtype.is_compound:
structtyp = cast(AST.ASTTypComp, varinfo.vtype)
ckey = structtyp.compkey
Expand Down Expand Up @@ -1159,6 +1155,13 @@ def mk_stack_variable_lval(
self._stack_variables[elementoffset + cfoff] = fieldlval
elementoffset += elsize

else:
elementoffset = offset
for i in range(arraysize):
indexoffset = self.mk_scalar_index_offset(i)
elemlval = self.astree.mk_vinfo_lval(varinfo, offset=indexoffset)
self._stack_variables[elementoffset] = elemlval
elementoffset += elsize
return lval


Expand Down
15 changes: 15 additions & 0 deletions chb/cmdline/chkx
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,14 @@ def parse() -> argparse.Namespace:
resultsclassifyfunctions.add_argument(
"classification_file",
help="name of json classification file")
resultsclassifyfunctions.add_argument(
"--output", "-o",
required=True,
help="name of file to save results")
resultsclassifyfunctions.add_argument(
"--showapicalls",
action="store_true",
help="list classified functions individually in output file")
resultsclassifyfunctions.set_defaults(func=UCC.results_classifyfunctions)

# --- results functions ---
Expand Down Expand Up @@ -1202,6 +1210,13 @@ def parse() -> argparse.Namespace:
+ " source for callgraph path"))
report_calls.set_defaults(func=REP.report_calls_cmd)

# -- report arguments
report_arguments = reportparsers.add_parser("string_arguments")
report_arguments.add_argument("xname", help="name of executable")
report_arguments.add_argument(
"--output", "-o", required=True, help="name of json output file")
report_arguments.set_defaults(func=REP.report_string_arguments)

# -- report function api's
report_functionapis = reportparsers.add_parser("function_apis")
report_functionapis.add_argument("xname", help="name of executable")
Expand Down
94 changes: 67 additions & 27 deletions chb/cmdline/commandutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,8 @@ def results_classifyfunctions(args: argparse.Namespace) -> NoReturn:

xname: str = str(args.xname)
classificationfile: str = str(args.classification_file)
showapicalls: bool = args.showapicalls
outputfilename: str = args.output

with open(classificationfile, "r") as fp:
classifier = json.load(fp)
Expand All @@ -953,44 +955,76 @@ def results_classifyfunctions(args: argparse.Namespace) -> NoReturn:
fns = app.appfunction_addrs

classification: Dict[str, Dict[str, int]] = {} # faddr -> libcat -> count
classificationapi: Dict[str, Dict[str, Dict[str, int]]] = {}

for faddr in fns:
classification.setdefault(faddr, {})
if showapicalls:
classificationapi.setdefault(faddr, {})
else:
classification.setdefault(faddr, {})
f = app.function(faddr)
fcalls = f.call_instructions()
for baddr in fcalls:
for instr in fcalls[baddr]:
tgtname = instr.call_target.name
if tgtname in revclassifier:
category = revclassifier[tgtname]
classification[faddr].setdefault(category, 0)
classification[faddr][category] += 1
if showapicalls:
category = revclassifier[tgtname]
classificationapi[faddr].setdefault(category, {})
classificationapi[faddr][category].setdefault(tgtname, 0)
classificationapi[faddr][category][tgtname] += 1
else:
category = revclassifier[tgtname]
classification[faddr].setdefault(category, 0)
classification[faddr][category] += 1

catfprevalence: Dict[str, int] = {}
catcprevalence: Dict[str, int] = {}
catstats: Dict[int, int] = {}
singlecat: Dict[str, int] = {}
doublecat: Dict[Tuple[str, str], int] = {}
for faddr in classification:
for cat in classification[faddr]:
catfprevalence.setdefault(cat, 0)
catcprevalence.setdefault(cat, 0)
catfprevalence[cat] += 1
catcprevalence[cat] += classification[faddr][cat]

numcats = len(classification[faddr])
catstats.setdefault(numcats, 0)
catstats[numcats] += 1
if numcats == 1:
cat = list(classification[faddr].keys())[0]
singlecat.setdefault(cat, 0)
singlecat[cat] += 1

if numcats == 2:
cats = sorted(list(classification[faddr].keys()))
cattuple = (cats[0], cats[1])
doublecat.setdefault(cattuple, 0)
doublecat[cattuple] += 1

if showapicalls:
for faddr in classificationapi:
for cat in classificationapi[faddr]:
catfprevalence.setdefault(cat, 0)
catcprevalence.setdefault(cat, 0)
catfprevalence[cat] += 1
catcprevalence[cat] += sum(classificationapi[faddr][cat].values())
numcats = len(classificationapi[faddr])
catstats.setdefault(numcats, 0)
catstats[numcats] += 1
if numcats == 1:
cat = list(classificationapi[faddr].keys())[0]
singlecat.setdefault(cat, 0)
singlecat[cat] = 1

if numcats == 2:
cats = sorted(list(classificationapi[faddr].keys()))
cattuple = (cats[0], cats[1])
doublecat.setdefault(cattuple, 0)
doublecat[cattuple] += 1
else:

for faddr in classification:
for cat in classification[faddr]:
catfprevalence.setdefault(cat, 0)
catcprevalence.setdefault(cat, 0)
catfprevalence[cat] += 1
catcprevalence[cat] += classification[faddr][cat]
numcats = len(classification[faddr])
catstats.setdefault(numcats, 0)
catstats[numcats] += 1
if numcats == 1:
cat = list(classification[faddr].keys())[0]
singlecat.setdefault(cat, 0)
singlecat[cat] += 1

if numcats == 2:
cats = sorted(list(classification[faddr].keys()))
cattuple = (cats[0], cats[1])
doublecat.setdefault(cattuple, 0)
doublecat[cattuple] += 1

for (m, c) in sorted(catstats.items()):
print(str(m).rjust(5) + ": " + str(c).rjust(5))
Expand All @@ -1006,10 +1040,16 @@ def results_classifyfunctions(args: argparse.Namespace) -> NoReturn:
classificationresults: Dict[str, Any] = {}
classificationresults["catfprevalence"] = catfprevalence
classificationresults["catcprevalence"] = catcprevalence
classificationresults["functions"] = classification
if showapicalls:
classificationresults["functions"] = classificationapi
else:
classificationresults["functions"] = classification

jresult = JU.jsonok("none", classificationresults)
jresult["meta"]["app"] = JU.jsonappdata(xinfo, includepath=False)

with open("classification_results.json", "w") as fp:
json.dump(classificationresults, fp, indent=2)
with open(outputfilename, "w") as fp:
json.dump(jresult, fp, indent=2)

exit(0)

Expand Down
5 changes: 3 additions & 2 deletions chb/cmdline/jsonresultutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ def jsonok(schemaname: str, content: Dict[str, Any]) -> Dict[str, Any]:
return jresult


def jsonappdata(xinfo: "XInfo") -> Dict[str, str]:
def jsonappdata(xinfo: "XInfo", includepath=True) -> Dict[str, str]:
result: Dict[str, str] = {}
result["path"] = xinfo.path
if includepath:
result["path"] = xinfo.path
result["file"] = xinfo.file
result["md5"] = xinfo.md5
result["arch"] = xinfo.architecture
Expand Down
54 changes: 54 additions & 0 deletions chb/cmdline/reportcmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
from chb.app.AppAccess import AppAccess
from chb.app.BasicBlock import BasicBlock
from chb.app.Instruction import Instruction
from chb.invariants.XConstant import XIntConst
from chb.mips.MIPSInstruction import MIPSInstruction
from chb.models.BTerm import BTerm, BTermArithmetic
from chb.models.FunctionSummary import FunctionSummary
Expand Down Expand Up @@ -602,6 +603,59 @@ def report_calls_cmd(args: argparse.Namespace) -> NoReturn:
exit(1)


def report_string_arguments(args: argparse.Namespace) -> NoReturn:

# arguments
xname: str = args.xname
outputfilename: str = args.output

try:
(path, xfile) = UC.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 = UC.get_app(path, xfile, xinfo)
fns = app.functions

argvals: Dict[str, Dict[str, Any]] = {}

for (faddr, f) in fns.items():
fcalls = f.call_instructions()
for baddr in fcalls:
for instr in fcalls[baddr]:
callee = instr.call_target.name
callargs = instr.call_arguments
for (index, callarg) in enumerate(callargs):
if callarg.is_string_reference:
constcallarg = cast("XprConstant", callarg).constant
intcallarg = cast("XIntConst", constcallarg)
argvals.setdefault(faddr, {})
argvals[faddr].setdefault("call-string-args", [])
argrec = {
"iaddr": instr.iaddr,
"callee": callee,
"index": index + 1,
"value": intcallarg.string_reference()
}
argvals[faddr]["call-string-args"].append(argrec)

result: Dict[str, Any] = {}
result["functions"] = argvals

jresult = JU.jsonok("none", result)
jresult["meta"]["app"] = JU.jsonappdata(xinfo, includepath=False)

with open(outputfilename, "w") as fp:
json.dump(jresult, fp, indent=2)

exit(0)


def report_function_apis(args: argparse.Namespace) -> NoReturn:

# arguments
Expand Down
3 changes: 2 additions & 1 deletion chb/invariants/FnVarDictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import chb.util.fileutil as UF
import chb.util.IndexedTable as IT
from chb.util.loggingutil import chklogger

if TYPE_CHECKING:
from chb.api.InterfaceDictionary import InterfaceDictionary
Expand Down Expand Up @@ -201,4 +202,4 @@ def initialize(self, xnode: ET.Element) -> None:
t.reset()
t.read_xml(xtable, "n")
else:
raise UF.CHBError("Var dictionary table " + t.name + " not found")
chklogger.logger.error("Var dictionary table %s not found", t.name)
Loading