Skip to content

Commit 6cd8808

Browse files
committed
REL: add facility to compare cfg info
1 parent 800e41a commit 6cd8808

File tree

10 files changed

+346
-6
lines changed

10 files changed

+346
-6
lines changed

chb/app/AppAccess.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#
77
# Copyright (c) 2016-2020 Kestrel Technology LLC
88
# Copyright (c) 2020-2021 Henny Sipma
9-
# Copyright (c) 2021-2023 Aarno Labs LLC
9+
# Copyright (c) 2021-2024 Aarno Labs LLC
1010
#
1111
# Permission is hereby granted, free of charge, to any person obtaining a copy
1212
# of this software and associated documentation files (the "Software"), to deal
@@ -49,6 +49,7 @@
4949
from chb.api.CallTarget import CallTarget, IndirectTarget, CallbackTableTarget
5050
from chb.api.InterfaceDictionary import InterfaceDictionary
5151

52+
from chb.app.AppCfgInfo import AppCfgInfo
5253
from chb.app.AppResultData import AppResultData
5354
from chb.app.AppResultMetrics import AppResultMetrics
5455
from chb.app.BDictionary import BDictionary
@@ -110,6 +111,7 @@ def __init__(
110111

111112
# functions
112113
self._appresultdata: Optional[AppResultData] = None
114+
self._appcfginfo: Optional[AppCfgInfo] = None
113115
self._functioninfos: Dict[str, FunctionInfo] = {}
114116

115117
# callgraph
@@ -256,6 +258,13 @@ def appresultdata(self) -> AppResultData:
256258
self._appresultdata = AppResultData(x)
257259
return self._appresultdata
258260

261+
@property
262+
def appcfginfo(self) -> AppCfgInfo:
263+
if self._appcfginfo is None:
264+
x = UF.get_app_cfg_info_xnode(self.path, self.filename)
265+
self._appcfginfo = AppCfgInfo(x)
266+
return self._appcfginfo
267+
259268
@property
260269
def appfunction_addrs(self) -> Sequence[str]:
261270
"""Return a list of all application function addresses."""

chb/app/AppCfgInfo.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# ------------------------------------------------------------------------------
2+
# CodeHawk Binary Analyzer
3+
# Author: Henny Sipma
4+
# ------------------------------------------------------------------------------
5+
# The MIT License (MIT)
6+
#
7+
# Copyright (c) 2024 Aarno Labs LLC
8+
#
9+
# Permission is hereby granted, free of charge, to any person obtaining a copy
10+
# of this software and associated documentation files (the "Software"), to deal
11+
# in the Software without restriction, including without limitation the rights
12+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
# copies of the Software, and to permit persons to whom the Software is
14+
# furnished to do so, subject to the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be included in all
17+
# copies or substantial portions of the Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
# SOFTWARE.
26+
# ------------------------------------------------------------------------------
27+
"""List of application function addresses and cfg characteristics."""
28+
29+
import xml.etree.ElementTree as ET
30+
31+
from typing import Dict, List, Optional
32+
33+
34+
class FnCfgInfo:
35+
36+
def __init__(self, xnode: ET.Element) -> None:
37+
self.xnode = xnode
38+
39+
@property
40+
def faddr(self) -> str:
41+
return self.xnode.get("va", "0")
42+
43+
@property
44+
def faddr_i(self) -> int:
45+
return int(self.xnode.get("va", "0"), 16)
46+
47+
@property
48+
def basic_blocks(self) -> int:
49+
return int(self.xnode.get("bc", "0"))
50+
51+
@property
52+
def instructions(self) -> int:
53+
return int(self.xnode.get("ic", "0"))
54+
55+
@property
56+
def loops(self) -> int:
57+
return int(self.xnode.get("lc", "0"))
58+
59+
@property
60+
def max_loopdepth(self) -> int:
61+
return int(self.xnode.get("ld", "0"))
62+
63+
@property
64+
def has_error(self) -> bool:
65+
return self.xnode.get("tr", "ok") == "x"
66+
67+
@property
68+
def name(self) -> Optional[str]:
69+
return self.xnode.get("name")
70+
71+
def __str__(self) -> str:
72+
return (
73+
("bc:" + str(self.basic_blocks)).ljust(10)
74+
+ ("; ic: " + str(self.instructions)).ljust(14)
75+
+ ("" if self.loops == 0 else ("; lc: " + str(self.loops))))
76+
77+
78+
class AppCfgInfo:
79+
80+
def __init__(self, xnode: Optional[ET.Element]) -> None:
81+
self.xnode = xnode
82+
self._function_cfg_infos: Optional[Dict[str, FnCfgInfo]] = None
83+
84+
@property
85+
def function_cfg_infos(self) -> Dict[str, FnCfgInfo]:
86+
if self._function_cfg_infos is None:
87+
self._function_cfg_infos = {}
88+
self._initialize_functions()
89+
return self._function_cfg_infos
90+
91+
@property
92+
def cfg_infos(self) -> List[FnCfgInfo]:
93+
return sorted(
94+
self.function_cfg_infos.values(),
95+
key = lambda c: c.faddr_i)
96+
97+
def _initialize_functions(self) -> None:
98+
self._function_cfg_infos = {}
99+
if self.xnode is not None:
100+
for xf in self.xnode.findall("fn"):
101+
optva = xf.get("va")
102+
if optva is not None:
103+
self._function_cfg_infos[optva] = FnCfgInfo(xf)

chb/ast/ASTCPrettyPrinter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ def __init__(
8080
localsymboltable: "ASTLocalSymbolTable",
8181
indentation: int = 2,
8282
annotations: Dict[int, List[str]] = {},
83-
livevars_on_exit: Dict[int, Set[str]] = {}) -> None:
83+
livevars_on_exit: Dict[int, Set[str]] = {},
84+
hide_annotations: bool = False) -> None:
8485
self._indentation = indentation # indentation amount
8586
self._indent = 0 # current indentation
8687
self._localsymboltable = localsymboltable

chb/cmdline/AnalysisManager.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ def disassemble(
220220
verbose: bool = False,
221221
collectdiagnostics: bool = True,
222222
preamble_cutoff: int = 12,
223-
save_asm: str = "yes") -> None:
223+
save_asm: str = "yes",
224+
save_asm_cfg_info: bool = False) -> None:
224225
cwd = os.getcwd()
225226
chklogger.logger.debug("change directory to %s", self.path)
226227
os.chdir(self.path) # temporary change in directory
@@ -233,6 +234,8 @@ def disassemble(
233234
cmd.extend(["-specialization", s])
234235
if save_asm == "yes":
235236
cmd.append("-save_asm")
237+
if save_asm_cfg_info:
238+
cmd.append("-save_asm_cfg_info")
236239
if collectdiagnostics:
237240
cmd.append("-diagnostics")
238241
if self.mips:

chb/cmdline/XComparison.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,5 +399,38 @@ def prepare_report(self) -> str:
399399

400400
return "\n".join(lines)
401401

402+
def diffs_as_table(self) -> str:
403+
404+
lines: List[str] = []
405+
lines.append(
406+
"section".ljust(16)
407+
+ "virtual address".rjust(28)
408+
+ "section size (bytes)".rjust(28)
409+
+ " difference")
410+
lines.append("-" * 82)
411+
for (name, (optsh1, optsh2)) in self.sectionheaderpairs.items():
412+
if optsh1 is not None and optsh2 is not None:
413+
if optsh1.vaddr != optsh2.vaddr or optsh1.size != optsh2.size:
414+
if optsh1.vaddr == optsh2.vaddr:
415+
vaddr = optsh1.vaddr
416+
else:
417+
vaddr = (optsh1.vaddr + " => " + optsh2.vaddr)
418+
if optsh1.size == optsh2.size:
419+
size = optsh1.size
420+
strdiff = ""
421+
else:
422+
diff = int(optsh2.size, 16) - int(optsh1.size, 16)
423+
if diff > 0:
424+
strdiff = "(+ " + str(diff) + ")"
425+
else:
426+
strdiff = "(- " + str(-diff) + ")"
427+
size = (optsh1.size + " => " + optsh2.size)
428+
lines.append(
429+
name.ljust(16)
430+
+ vaddr.rjust(28) + size.rjust(28) + strdiff.rjust(10))
431+
432+
return "\n".join(lines)
433+
434+
402435
def __str__(self) -> str:
403436
return self.prepare_report()

chb/cmdline/astcmds.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def buildast(args: argparse.Namespace) -> NoReturn:
143143
hints: List[str] = args.hints # names of json files
144144
xpatchresultsfile = args.patch_results_file
145145
hide_globals: bool = args.hide_globals
146+
hide_annotations: bool = args.hide_annotations
146147
remove_edges: List[str] = args.remove_edges
147148
add_edges: List[str] = args.add_edges
148149
verbose: bool = args.verbose
@@ -353,8 +354,11 @@ def buildast(args: argparse.Namespace) -> NoReturn:
353354

354355
print("\n// Lifted code for function " + faddr)
355356
print("// --------------------------------------------------")
357+
annotations: Dict[int, List[str]] = {}
358+
if not hide_annotations:
359+
annotations = astinterface.annotations
356360
prettyprinter = ASTCPrettyPrinter(
357-
localsymboltable, annotations=astinterface.annotations)
361+
localsymboltable, annotations=annotations)
358362
print(prettyprinter.to_c(asts[0], include_globals=(not hide_globals)))
359363
functions_lifted += 1
360364

chb/cmdline/chkx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,10 @@ def parse() -> argparse.Namespace:
379379
default="yes",
380380
choices=["yes", "no"],
381381
help='save asm listing in analysis directory')
382+
analyzecmd.add_argument(
383+
"--save_asm_cfg_info",
384+
action="store_true",
385+
help="save list of functions with cfg info in xml (may be slow)")
382386
analyzecmd.add_argument(
383387
"--construct_all_functions",
384388
action="store_true",
@@ -674,6 +678,10 @@ def parse() -> argparse.Namespace:
674678
"--hide_globals",
675679
help="do not include global declarations and definitions in printed output",
676680
action="store_true")
681+
buildast.add_argument(
682+
"--hide_annotations",
683+
help="do not include annotations in printed C code",
684+
action="store_true")
677685
buildast.add_argument(
678686
"--remove_edges",
679687
nargs="*",
@@ -1494,6 +1502,32 @@ def parse() -> argparse.Namespace:
14941502
"xname2", help="name of second (patched) executable (assumed analyzed)")
14951503
relationalcomparemd5s.set_defaults(func=R.relational_compare_md5s_cmd)
14961504

1505+
# --- relational_compare elfdata
1506+
relationalcompareelf = relationalcompareparsers.add_parser("elfdata")
1507+
relationalcompareelf.add_argument(
1508+
"xname1",
1509+
help="name of first (original) executable (assumed disassembled)")
1510+
relationalcompareelf.add_argument(
1511+
"xname2",
1512+
help="name of second (patched) executable (assumed disassembled)")
1513+
relationalcompareelf.set_defaults(func=R.relational_compare_elfdata)
1514+
1515+
# --- relational_compare cfg-info
1516+
relationalcomparecfginfo = relationalcompareparsers.add_parser("cfg_info")
1517+
relationalcomparecfginfo.add_argument(
1518+
"xname1",
1519+
help="name of first (original) executable (assumed disassembled)")
1520+
relationalcomparecfginfo.add_argument(
1521+
"xname2",
1522+
help="name of second (patched) executable (asseumed disassembled)"),
1523+
relationalcomparecfginfo.add_argument(
1524+
"--newfunctions",
1525+
nargs="*",
1526+
default=[],
1527+
help="list of functions that are new in xname2")
1528+
relationalcomparecfginfo.set_defaults(
1529+
func=R.relational_compare_cfg_info)
1530+
14971531
# ------------------------------------------------------ simulate subcommand
14981532
parser_simulate = subparsers.add_parser("simulate")
14991533
parser_simulate.add_argument("xname", help="name of executable")

chb/cmdline/commandutil.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def analyzecmd(args: argparse.Namespace) -> NoReturn:
397397
verbose: bool = args.verbose
398398
collectdiagnostics: bool = args.collect_diagnostics
399399
save_asm: str = args.save_asm
400+
save_asm_cfg_info: bool = args.save_asm_cfg_info
400401
thumb: List[str] = args.thumb
401402
preamble_cutoff: int = args.preamble_cutoff
402403
iterations: int = args.iterations
@@ -517,7 +518,8 @@ def analyzecmd(args: argparse.Namespace) -> NoReturn:
517518
verbose=verbose,
518519
collectdiagnostics=collectdiagnostics,
519520
preamble_cutoff=preamble_cutoff,
520-
save_asm=save_asm)
521+
save_asm=save_asm,
522+
save_asm_cfg_info=save_asm_cfg_info)
521523
except subprocess.CalledProcessError as e:
522524
print_error(str(e.output))
523525
print_error(str(e))

0 commit comments

Comments
 (0)