Skip to content

Commit de9528b

Browse files
committed
AST: add viewstmt to pirinspector
1 parent 6bf1cc2 commit de9528b

File tree

11 files changed

+181
-5
lines changed

11 files changed

+181
-5
lines changed

chb/app/CHVersion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
chbversion: str = "0.3.0-20250722"
1+
chbversion: str = "0.3.0-20250723"

chb/arm/ARMInstruction.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,9 @@ def to_string(
441441
opcodewidth: int = 40,
442442
typingrules: bool = False,
443443
sp: bool = False) -> str:
444+
445+
lines: List[str] = []
444446
if typingrules:
445-
lines: List[str] = []
446447
rulesapplied = self.app.type_constraints.rules_applied_to_instruction(
447448
self.armfunction.faddr, self.iaddr)
448449
for r in sorted(str(r) for r in rulesapplied):

chb/ast/ASTDeserializer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ def low_level_ast(self) -> AST.ASTStmt:
7171
def high_unreduced_ast(self) -> AST.ASTStmt:
7272
return self.asts[1]
7373

74+
def get_stmt(self, stmtid: int) -> AST.ASTStmt:
75+
for node in self.nodes.values():
76+
if node.is_ast_stmt:
77+
node = cast(AST.ASTStmt, node)
78+
if node.stmtid == stmtid:
79+
return node
80+
else:
81+
raise Exception("No stmt found with id: " + str(stmtid))
82+
7483
def get_instruction(self, instrid: int) -> AST.ASTInstruction:
7584
for node in self.nodes.values():
7685
if node.is_ast_instruction:

chb/ast/ASTNOPVisitor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ def visit_question_expression(self, expr: AST.ASTQuestion) -> None:
148148
def visit_address_of_expression(self, expr: AST.ASTAddressOf) -> None:
149149
pass
150150

151+
def visit_start_of_expression(self, expr: AST.ASTStartOf) -> None:
152+
pass
153+
151154
def visit_void_typ(self, typ: AST.ASTTypVoid) -> None:
152155
pass
153156

chb/ast/ASTProvenanceCollector.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# ------------------------------------------------------------------------------
2+
# CodeHawk Binary Analyzer
3+
# Author: Henny Sipma
4+
# ------------------------------------------------------------------------------
5+
# The MIT License (MIT)
6+
#
7+
# Copyright (c) 2021-2025 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+
"""Collects stmt and instruction provenance for pir inspector."""
28+
29+
from typing import Dict, List, TYPE_CHECKING
30+
31+
import chb.ast.ASTNode as AST
32+
from chb.ast.ASTNOPVisitor import ASTNOPVisitor
33+
34+
if TYPE_CHECKING:
35+
from chb.ast.ASTDeserializer import ASTFunctionDeserialization
36+
37+
38+
class ASTProvenanceCollector(ASTNOPVisitor):
39+
40+
def __init__(self, dfn: "ASTFunctionDeserialization") -> None:
41+
self._dfn = dfn
42+
self._hl_ll_instr_mapping: Dict[int, List[int]] = {}
43+
44+
@property
45+
def dfn(self) -> "ASTFunctionDeserialization":
46+
return self._dfn
47+
48+
@property
49+
def provenance(self) -> Dict[int, List[AST.ASTInstruction]]:
50+
result: Dict[int, List[AST.ASTInstruction]] = {}
51+
for hlinstrid in self._hl_ll_instr_mapping:
52+
llinstrids = self._hl_ll_instr_mapping[hlinstrid]
53+
result[hlinstrid] = [self.dfn.get_instruction(i) for i in llinstrids]
54+
return result
55+
56+
def instruction_provenance(
57+
self, stmt: AST.ASTStmt) -> Dict[int, List[AST.ASTInstruction]]:
58+
stmt.accept(self)
59+
return self.provenance
60+
61+
def visit_loop_stmt(self, stmt: AST.ASTLoop) -> None:
62+
stmt.body.accept(self)
63+
64+
def visit_block_stmt(self, stmt: AST.ASTBlock) -> None:
65+
for s in stmt.stmts:
66+
s.accept(self)
67+
68+
def visit_instruction_sequence_stmt(self, stmt: AST.ASTInstrSequence) -> None:
69+
for i in stmt.instructions:
70+
if i.instrid in self.dfn.astree.provenance.instruction_mapping:
71+
self._hl_ll_instr_mapping[i.instrid] = (
72+
self.dfn.astree.provenance.instruction_mapping[i.instrid])
73+
74+
def visit_branch_stmt(self, stmt: AST.ASTBranch) -> None:
75+
stmt.ifstmt.accept(self)
76+
stmt.elsestmt.accept(self)

chb/ast/ASTViewer.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ def to_graph(self, stmt: AST.ASTStmt) -> DU.ASTDotGraph:
9595
stmt.accept(self)
9696
return self.dotgraph
9797

98+
def stmt_to_graph(
99+
self,
100+
stmt: AST.ASTStmt,
101+
provinstrs: Dict[int, List[AST.ASTInstruction]] = {}) -> DU.ASTDotGraph:
102+
stmt.accept(self)
103+
for pl in provinstrs.values():
104+
for p in pl:
105+
p.accept(self)
106+
return self.dotgraph
107+
98108
def instr_to_graph(
99109
self,
100110
instr: AST.ASTInstruction,
@@ -124,6 +134,9 @@ def get_expr_connections(self, expr: AST.ASTExpr) -> str:
124134
if self.astree.provenance.has_reaching_definitions(expr.exprid):
125135
ids = self.astree.provenance.reaching_definitions[expr.exprid]
126136
result += "\\nrdefs:[" + ",".join(str(id) for id in ids) + "]"
137+
if expr.exprid in self.astree.expr_spanmap():
138+
span = self.astree.expr_spanmap()[expr.exprid]
139+
result += "\\ncc:" + span
127140
return result
128141

129142
def stmt_name(self, stmt: AST.ASTStmt) -> str:

chb/ast/AbstractSyntaxTree.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,25 @@ def get_exprid(self, exprid: Optional[int]) -> int:
287287
def add_span(self, span: ASTSpanRecord) -> None:
288288
self._spans.append(span)
289289

290+
def add_stmt_span(
291+
self, locationid: int, spans: List[Tuple[str, str]]) -> None:
292+
"""Add a span for the ast instructions contained in a stmt.
293+
294+
Note: this is currently done only for if statements originating from
295+
predicated instructions.
296+
"""
297+
spaninstances: List[Dict[str, Union[str, int]]] = []
298+
for (iaddr, bytestring) in spans:
299+
span: Dict[str, Union[str, int]] = {}
300+
span["base_va"] = iaddr
301+
span["size"] = len(bytestring) // 2
302+
spaninstances.append(span)
303+
spanrec: Dict[str, Any] = {}
304+
spanrec["locationid"] = locationid
305+
spanrec["spans"] = spaninstances
306+
self.add_span(cast(ASTSpanRecord, spanrec))
307+
308+
290309
def add_instruction_span(
291310
self, locationid: int, base: str, bytestring: str) -> None:
292311
"""Add a span for an ast instruction."""
@@ -1224,7 +1243,7 @@ def _cast_if_needed(self, e: AST.ASTExpr, opunsigned: bool) -> AST.ASTExpr:
12241243
# CIL encodes signedness in the shift operator but C infers
12251244
# the operator flavor from the type of the left operand, so
12261245
# we may need to insert a cast to ensure that serialization
1227-
# through C code preserves the AST.
1246+
# through C code preserves the AST.
12281247

12291248
mb_t = e.ctype(ASTBasicCTyper(self.globalsymboltable))
12301249
force_cast = mb_t is None

chb/ast/astutil.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from chb.ast.ASTCPrettyPrinter import ASTCPrettyPrinter
4040
from chb.ast.ASTDeserializer import ASTDeserializer
4141
import chb.ast.ASTNode as AST
42+
from chb.ast.ASTProvenanceCollector import ASTProvenanceCollector
4243
from chb.ast.ASTViewer import ASTViewer
4344
import chb.ast.astdotutil as DU
4445

@@ -191,6 +192,36 @@ def viewastcmd(args: argparse.Namespace) -> NoReturn:
191192
exit(0)
192193

193194

195+
def viewstmtcmd(args: argparse.Namespace) -> NoReturn:
196+
197+
# arguments
198+
pirfile: str = args.pirfile
199+
function: Optional[str] = args.function
200+
stmtid: int = args.stmtid
201+
provenance: bool = args.provenance
202+
outputfilename: str = args.output
203+
204+
with open(pirfile, "r") as fp:
205+
pirjson = json.load(fp)
206+
207+
faddr = get_function_addr(pirjson, function)
208+
deserializer = ASTDeserializer(pirjson)
209+
(globaltable, dfns) = deserializer.deserialize()
210+
for dfn in dfns:
211+
if dfn.astree.faddr == faddr:
212+
stmt = dfn.get_stmt(stmtid)
213+
viewer = ASTViewer(faddr, dfn.astree)
214+
if provenance:
215+
provcollector = ASTProvenanceCollector(dfn)
216+
provinstrs = provcollector.instruction_provenance(stmt)
217+
g = viewer.stmt_to_graph(stmt, provinstrs)
218+
else:
219+
g = viewer.to_graph(stmt)
220+
221+
DU.print_dot(outputfilename, g)
222+
exit(0)
223+
224+
194225
def viewinstrcmd(args: argparse.Namespace) -> NoReturn:
195226

196227
# arguments

chb/ast/pirinspector

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,24 @@ def parse() -> argparse.Namespace:
9393
)
9494
viewastcmd.set_defaults(func=AU.viewastcmd)
9595

96+
# --- view selected stmt ast
97+
viewstmtcmd = viewparsers.add_parser("stmt")
98+
viewstmtcmd.add_argument("pirfile", help="name of json file with ast information")
99+
viewstmtcmd.add_argument("--function", help="name or address of function to view")
100+
viewstmtcmd.add_argument(
101+
"--stmtid", help="stmt id of statement", type=int)
102+
viewstmtcmd.add_argument(
103+
"--provenance",
104+
help="show associated low-level instructions",
105+
action="store_true",
106+
)
107+
viewstmtcmd.add_argument(
108+
"-o", "--output",
109+
help="name of output graph file (without extension)",
110+
required=True,
111+
)
112+
viewstmtcmd.set_defaults(func=AU.viewstmtcmd)
113+
96114
# --- view single instruction ast
97115
viewinstrcmd = viewparsers.add_parser("instruction")
98116
viewinstrcmd.add_argument("pirfile", help="name of json file with ast information")

chb/astinterface/ASTInterface.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,10 @@ def add_formal(
585585
return nextindex
586586

587587
def add_span(self, span: ASTSpanRecord) -> None:
588-
self.astree.add_span
588+
self.astree.add_span(span)
589+
590+
def add_stmt_span(self, id: int, spans: List[Tuple[str, str]]) -> None:
591+
self.astree.add_stmt_span(id, spans)
589592

590593
def add_instruction_span(self, id: int, base: str, bytestring: str) -> None:
591594
self.astree.add_instruction_span(id, base, bytestring)

0 commit comments

Comments
 (0)