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
14 changes: 12 additions & 2 deletions chb/app/AppAccess.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
# Copyright (c) 2016-2020 Kestrel Technology LLC
# Copyright (c) 2020-2021 Henny Sipma
# Copyright (c) 2021-2023 Aarno Labs LLC
# Copyright (c) 2021-2024 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 Down Expand Up @@ -49,6 +49,7 @@
from chb.api.CallTarget import CallTarget, IndirectTarget, CallbackTableTarget
from chb.api.InterfaceDictionary import InterfaceDictionary

from chb.app.AppCfgInfo import AppCfgInfo
from chb.app.AppResultData import AppResultData
from chb.app.AppResultMetrics import AppResultMetrics
from chb.app.BDictionary import BDictionary
Expand Down Expand Up @@ -110,6 +111,7 @@ def __init__(

# functions
self._appresultdata: Optional[AppResultData] = None
self._appcfginfo: Optional[AppCfgInfo] = None
self._functioninfos: Dict[str, FunctionInfo] = {}

# callgraph
Expand Down Expand Up @@ -256,6 +258,13 @@ def appresultdata(self) -> AppResultData:
self._appresultdata = AppResultData(x)
return self._appresultdata

@property
def appcfginfo(self) -> AppCfgInfo:
if self._appcfginfo is None:
x = UF.get_app_cfg_info_xnode(self.path, self.filename)
self._appcfginfo = AppCfgInfo(x)
return self._appcfginfo

@property
def appfunction_addrs(self) -> Sequence[str]:
"""Return a list of all application function addresses."""
Expand Down Expand Up @@ -401,7 +410,8 @@ def callgraph(self) -> Callgraph:
cbttgts = cbtable.tagged_fields_at_offset(cbtgt.offset)
for (tag, cbfaddr) in cbttgts.items():
if self.has_function_name(cbfaddr):
cbfname = tag + ":" + self.function_name(cbfaddr)
cbfname = (
tag + ":" + self.function_name(cbfaddr))
else:
cbfname = tag + ":" + cbfaddr
apptgtnode = mk_tagged_app_callgraph_node(
Expand Down
103 changes: 103 additions & 0 deletions chb/app/AppCfgInfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# ------------------------------------------------------------------------------
# CodeHawk Binary Analyzer
# Author: Henny Sipma
# ------------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2024 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
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ------------------------------------------------------------------------------
"""List of application function addresses and cfg characteristics."""

import xml.etree.ElementTree as ET

from typing import Dict, List, Optional


class FnCfgInfo:

def __init__(self, xnode: ET.Element) -> None:
self.xnode = xnode

@property
def faddr(self) -> str:
return self.xnode.get("va", "0")

@property
def faddr_i(self) -> int:
return int(self.xnode.get("va", "0"), 16)

@property
def basic_blocks(self) -> int:
return int(self.xnode.get("bc", "0"))

@property
def instructions(self) -> int:
return int(self.xnode.get("ic", "0"))

@property
def loops(self) -> int:
return int(self.xnode.get("lc", "0"))

@property
def max_loopdepth(self) -> int:
return int(self.xnode.get("ld", "0"))

@property
def has_error(self) -> bool:
return self.xnode.get("tr", "ok") == "x"

@property
def name(self) -> Optional[str]:
return self.xnode.get("name")

def __str__(self) -> str:
return (
("bc:" + str(self.basic_blocks)).ljust(10)
+ ("; ic: " + str(self.instructions)).ljust(14)
+ ("" if self.loops == 0 else ("; lc: " + str(self.loops))))


class AppCfgInfo:

def __init__(self, xnode: Optional[ET.Element]) -> None:
self.xnode = xnode
self._function_cfg_infos: Optional[Dict[str, FnCfgInfo]] = None

@property
def function_cfg_infos(self) -> Dict[str, FnCfgInfo]:
if self._function_cfg_infos is None:
self._function_cfg_infos = {}
self._initialize_functions()
return self._function_cfg_infos

@property
def cfg_infos(self) -> List[FnCfgInfo]:
return sorted(
self.function_cfg_infos.values(),
key = lambda c: c.faddr_i)

def _initialize_functions(self) -> None:
self._function_cfg_infos = {}
if self.xnode is not None:
for xf in self.xnode.findall("fn"):
optva = xf.get("va")
if optva is not None:
self._function_cfg_infos[optva] = FnCfgInfo(xf)
8 changes: 4 additions & 4 deletions chb/app/CallbackTables.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ def tag_offset(self) -> int:
return (-1)

@property
def tag(self) -> str:
def tag(self) -> Optional[str]:
if self.tag_offset >= 0:
return self.fields[self.tag_offset][1]
else:
return "?"
return None

def value_at_offset(self, offset: int) -> str:
if offset in self.fields:
Expand Down Expand Up @@ -131,8 +131,8 @@ def tagged_fields_at_offset(self, offset: int) -> Dict[str, str]:
for r in self.records:
counter += 1
tag = r.tag
if tag == "?":
tag = "unknown_" + str(counter)
if tag is None:
tag = str(counter)
result[tag] = r.value_at_offset(offset)
return result

Expand Down
8 changes: 7 additions & 1 deletion chb/app/InstrXData.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ def has_call_target(self) -> bool:
else:
return False

@property
def is_bx_call(self) -> bool:
return "bx-call" in self.tags

def call_target_argument_count(self) -> Optional[int]:
if len(self.tags) >= 3:
if self.tags[1] == "call":
Expand All @@ -330,7 +334,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():
if self.has_call_target() and self.is_bx_call:
return ixd.call_target(self.args[-5])
elif self.has_call_target():
return ixd.call_target(self.args[-1])
else:
raise UF.CHBError(
Expand Down
29 changes: 17 additions & 12 deletions chb/arm/ARMCallOpcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,28 @@ def ast_call_prov(
finfo = xdata.function.finfo
if finfo.has_call_target_info(iaddr):
ctinfo = finfo.call_target_info(iaddr)
fname = ctinfo.target_interface.name
ftype = ctinfo.target_interface.bctype
if ftype is not None:
astfntype = ftype.convert(astree.typconverter)
if astree.globalsymboltable.has_symbol(fname):
tgtvinfo = astree.globalsymboltable.get_symbol(fname)
hl_tgt = astree.mk_vinfo_lval_expression(tgtvinfo)

if xdata.is_bx_call:
# indirect call
hl_tgt = XU.xxpr_to_ast_def_expr(xprs[-1], xdata, iaddr, astree)
else:
gaddr: int = 0
if fname.startswith("sub_"):
gaddr = int("0x" + fname[4:], 16)
fname = ctinfo.target_interface.name
if astree.globalsymboltable.has_symbol(fname):
tgtvinfo = astree.globalsymboltable.get_symbol(fname)
hl_tgt = astree.mk_vinfo_lval_expression(tgtvinfo)
else:
if tgt.is_absolute:
tgtaddr = cast(ARMAbsoluteOp, tgt.opkind)
gaddr = int(tgtaddr.address.get_hex(), 16)
hl_tgt = astree.mk_global_variable_expr(
fname, globaladdress=gaddr, vtype=astfntype)
gaddr: int = 0
if fname.startswith("sub_"):
gaddr = int("0x" + fname[4:], 16)
else:
if tgt.is_absolute:
tgtaddr = cast(ARMAbsoluteOp, tgt.opkind)
gaddr = int(tgtaddr.address.get_hex(), 16)
hl_tgt = astree.mk_global_variable_expr(
fname, globaladdress=gaddr, vtype=astfntype)

if ftype is not None and ftype.is_function:
ftype = cast("BCTypFun", ftype)
Expand Down
3 changes: 2 additions & 1 deletion chb/arm/ARMInstruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,9 @@ def annotation(self) -> str:
aggaddr = self.xdata.subsumed_by()
return f"subsumed by {aggaddr}"
elif self.subsumes:
ann = self.opcode.annotation(self.xdata)
dependents = self.xdata.subsumes()
return "subsumes [" + ", ".join(dependents) + "]"
return ann + " (subsumes [" + ", ".join(dependents) + "])"
else:
return self.opcode.annotation(self.xdata)

Expand Down
15 changes: 14 additions & 1 deletion chb/arm/opcodes/ARMBranchExchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@

from chb.invariants.XXpr import XXpr


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

from chb.util.IndexedTable import IndexedTableValue

Expand Down Expand Up @@ -98,8 +100,19 @@ def call_target(self, xdata: InstrXData) -> "CallTarget":
else:
raise UF.CHBError("Instruction is not a call: " + str(self))

def argument_count(self, xdata: InstrXData) -> int:
if self.is_call_instruction(xdata):
argcount = xdata.call_target_argument_count()
if argcount is not None:
return argcount
chklogger.logger.warning(
"Call instruction does not have argument count")
return 0
else:
raise UF.CHBError("Instruction is not a call: " + str(self))

def arguments(self, xdata: InstrXData) -> Sequence[XXpr]:
return xdata.xprs
return xdata.xprs[:self.argument_count(xdata)]

def annotation(self, xdata: InstrXData) -> str:
"""xdata format: a:x .
Expand Down
3 changes: 2 additions & 1 deletion chb/ast/ASTCPrettyPrinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def __init__(
localsymboltable: "ASTLocalSymbolTable",
indentation: int = 2,
annotations: Dict[int, List[str]] = {},
livevars_on_exit: Dict[int, Set[str]] = {}) -> None:
livevars_on_exit: Dict[int, Set[str]] = {},
hide_annotations: bool = False) -> None:
self._indentation = indentation # indentation amount
self._indent = 0 # current indentation
self._localsymboltable = localsymboltable
Expand Down
16 changes: 15 additions & 1 deletion chb/buffer/LibraryCallCallsites.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,22 @@ class LibraryCallSideeffect:
def __init__(
self,
summary: "FunctionSummary",
faddr: str,
instr: "Instruction",
pre: "PreDerefWrite") -> None:
self._summary = summary
self._faddr = faddr
self._instr = instr
self._pre = pre

@property
def summary(self) -> "FunctionSummary":
return self._summary

@property
def faddr(self) -> str:
return self._faddr

@property
def instr(self) -> "Instruction":
return self._instr
Expand Down Expand Up @@ -286,7 +292,8 @@ def derefwrites(self) -> List[LibraryCallSideeffect]:
for pre in self.preconditions:
if pre.is_deref_write:
pre = cast("PreDerefWrite", pre)
lcwrite = LibraryCallSideeffect(self.summary, self.instr, pre)
lcwrite = LibraryCallSideeffect(
self.summary, self.faddr, self.instr, pre)
result.append(lcwrite)
return result

Expand Down Expand Up @@ -446,6 +453,13 @@ def patch_candidates(self) -> List["Instruction"]:
result.append(c.instr)
return result

def patch_callsites(self) -> List[LibraryCallSideeffect]:
result: List[LibraryCallSideeffect] = []
for (_, ics) in self.callsites.items():
for cs in ics.values():
result.extend(cs.patch_candidates)
return result

def patch_candidates_distribution(self) -> Dict[str, int]:
result: Dict[str, int] = {}
for (_, ics) in self.callsites.items():
Expand Down
5 changes: 4 additions & 1 deletion chb/cmdline/AnalysisManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ def disassemble(
verbose: bool = False,
collectdiagnostics: bool = True,
preamble_cutoff: int = 12,
save_asm: str = "yes") -> None:
save_asm: str = "yes",
save_asm_cfg_info: bool = False) -> None:
cwd = os.getcwd()
chklogger.logger.debug("change directory to %s", self.path)
os.chdir(self.path) # temporary change in directory
Expand All @@ -233,6 +234,8 @@ def disassemble(
cmd.extend(["-specialization", s])
if save_asm == "yes":
cmd.append("-save_asm")
if save_asm_cfg_info:
cmd.append("-save_asm_cfg_info")
if collectdiagnostics:
cmd.append("-diagnostics")
if self.mips:
Expand Down
33 changes: 33 additions & 0 deletions chb/cmdline/XComparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,5 +399,38 @@ def prepare_report(self) -> str:

return "\n".join(lines)

def diffs_as_table(self) -> str:

lines: List[str] = []
lines.append(
"section".ljust(16)
+ "virtual address".rjust(28)
+ "section size (bytes)".rjust(28)
+ " difference")
lines.append("-" * 82)
for (name, (optsh1, optsh2)) in self.sectionheaderpairs.items():
if optsh1 is not None and optsh2 is not None:
if optsh1.vaddr != optsh2.vaddr or optsh1.size != optsh2.size:
if optsh1.vaddr == optsh2.vaddr:
vaddr = optsh1.vaddr
else:
vaddr = (optsh1.vaddr + " => " + optsh2.vaddr)
if optsh1.size == optsh2.size:
size = optsh1.size
strdiff = ""
else:
diff = int(optsh2.size, 16) - int(optsh1.size, 16)
if diff > 0:
strdiff = "(+ " + str(diff) + ")"
else:
strdiff = "(- " + str(-diff) + ")"
size = (optsh1.size + " => " + optsh2.size)
lines.append(
name.ljust(16)
+ vaddr.rjust(28) + size.rjust(28) + strdiff.rjust(10))

return "\n".join(lines)


def __str__(self) -> str:
return self.prepare_report()
Loading