Skip to content

Commit 6239d00

Browse files
committed
XPR: handle sideeffect values
1 parent 49a9ccf commit 6239d00

File tree

9 files changed

+287
-17
lines changed

9 files changed

+287
-17
lines changed

chb/api/BTerm.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,25 @@ def __str__(self) -> str:
147147
return "index-size(" + str(self.bterm) + ")"
148148

149149

150+
@apiregistry.register_tag("r", BTerm)
151+
class BTermReturnValue(BTerm):
152+
"""Return value
153+
154+
args[0]: index of bterm in interface dictionary
155+
"""
156+
157+
def __init__(
158+
self, ixd: "InterfaceDictionary", ixval: IndexedTableValue) -> None:
159+
BTerm.__init__(self, ixd, ixval)
160+
161+
@property
162+
def bterm(self) -> BTerm:
163+
return self.id.bterm(self.args[0])
164+
165+
def __str__(self) -> str:
166+
return "return-value(" + str(self.bterm) + ")"
167+
168+
150169
@apiregistry.register_tag("c", BTerm)
151170
class BTermNumConstant(BTerm):
152171
"""Constant numerical term.
@@ -164,3 +183,22 @@ def constant(self) -> int:
164183

165184
def __str__(self) -> str:
166185
return str(self.constant)
186+
187+
188+
@apiregistry.register_tag("n", BTerm)
189+
class BTermNamedConstant(BTerm):
190+
"""String constant.
191+
192+
tags[1]: numerical value represented as a string
193+
"""
194+
195+
def __init__(
196+
self, ixd: "InterfaceDictionary", ixval: IndexedTableValue) -> None:
197+
BTerm.__init__(self, ixd, ixval)
198+
199+
@property
200+
def constant(self) -> str:
201+
return self.bd.string(self.args[0])
202+
203+
def __str__(self) -> str:
204+
return str(self.constant)

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-20250801"
1+
chbversion: str = "0.3.0-20250804"

chb/app/FunctionStackframe.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
from chb.app.Register import Register
4141
from chb.bctypes.BCDictionary import BCDictionary
4242
from chb.bctypes.BCTyp import BCTyp
43+
from chb.invariants.FnStackAccess import FnStackAccess
44+
from chb.invariants.FnVarDictionary import FnVarDictionary
4345

4446

4547
class Stackslot:
@@ -87,8 +89,9 @@ def is_spill(self) -> bool:
8789
return self.spill is not None
8890

8991
def __str__(self) -> str:
90-
pty = " (" + str(self.btype) + "}" if self.btype is not None else ""
91-
return str(self.offset) + ": " + self.name + pty
92+
pty = " (" + str(self.btype) + ")" if self.btype is not None else ""
93+
psi = " (size: " + str(self.size) + ")" if self.size is not None else ""
94+
return self.name + pty + psi
9295

9396

9497
class FunctionStackframe:
@@ -97,6 +100,7 @@ def __init__(self, fn: "Function", xnode: ET.Element) -> None:
97100
self._fn = fn
98101
self._xnode = xnode
99102
self._stackslots: Optional[Dict[int, Stackslot]] = None
103+
self._accesses: Optional[Dict[int, List[Tuple[str, "FnStackAccess"]]]] = None
100104

101105
@property
102106
def function(self) -> "Function":
@@ -111,7 +115,11 @@ def bcdictionary(self) -> "BCDictionary":
111115
return self.function.bcd
112116

113117
@property
114-
def saved_registers(self) -> Dict[int, Register]:
118+
def vardictionary(self) -> "FnVarDictionary":
119+
return self.function.vardictionary
120+
121+
@property
122+
def saved_registers(self) -> Dict[int, "Register"]:
115123
result: Dict[int, Register] = {}
116124
for (offset, stackslot) in self.stackslots.items():
117125
if stackslot.spill is not None:
@@ -124,7 +132,7 @@ def stackslots(self) -> Dict[int, Stackslot]:
124132
self._stackslots = {}
125133
xsnode = self._xnode.find("stack-slots")
126134
if xsnode is not None:
127-
xslots = xsnode.findall("slots")
135+
xslots = xsnode.findall("slot")
128136
for xslot in xslots:
129137
stackslot = Stackslot(self, xslot)
130138
self._stackslots[stackslot.offset] = stackslot
@@ -138,8 +146,30 @@ def stackslot(self, offset: int) -> Optional[Stackslot]:
138146

139147
return self.stackslots.get(offset, None)
140148

149+
@property
150+
def accesses(self) -> Dict[int, List[Tuple[str, "FnStackAccess"]]]:
151+
if self._accesses is None:
152+
self._accesses = {}
153+
sanode = self._xnode.find("stack-accesses")
154+
if sanode is not None:
155+
for xoff in sanode.findall("offset"):
156+
offset = xoff.get("n")
157+
if offset is not None:
158+
stackoffset = int(offset)
159+
offsetacc: List[Tuple[str, FnStackAccess]] = []
160+
self._accesses[stackoffset] = offsetacc
161+
for xsa in xoff.findall("sa"):
162+
iaddr = xsa.get("addr", "0x0")
163+
sa = self.vardictionary.read_xml_stack_access(xsa)
164+
offsetacc.append((iaddr, sa))
165+
return self._accesses
166+
141167
def __str__(self) -> str:
142168
lines: List[str] = []
143-
for (offset, stackslot) in self.stackslots.items():
169+
for (offset, stackslot) in sorted(self.stackslots.items(), reverse=True):
144170
lines.append(str(offset).rjust(5) + " " + str(stackslot))
171+
if offset in self.accesses:
172+
for (iaddr, acc) in self.accesses[offset]:
173+
lines.append(" ".rjust(10) + iaddr + ": " + str(acc))
174+
lines.append("")
145175
return "\n".join(lines)

chb/invariants/FnStackAccess.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
if TYPE_CHECKING:
5252
from chb.bctypes.BCTyp import BCTyp
5353
from chb.invariants.FnVarDictionary import FnVarDictionary
54+
from chb.invariants.VMemoryOffset import VMemoryOffset
5455
from chb.invariants.XVariable import XVariable
5556
from chb.invariants.XXpr import XXpr
5657

@@ -173,8 +174,10 @@ def stackvar(self) -> "XVariable":
173174
return self.xd.variable(self.args[0])
174175

175176
@property
176-
def offset(self) -> int:
177-
return self.args[1]
177+
def offset(self) -> "VMemoryOffset":
178+
"""Returns offset relative to the stack slot it is part of."""
179+
180+
return self.vd.memory_offset(self.args[1])
178181

179182
@property
180183
def size(self) -> Optional[int]:
@@ -224,8 +227,10 @@ def stackvar(self) -> "XVariable":
224227
return self.xd.variable(self.args[0])
225228

226229
@property
227-
def offset(self) -> int:
228-
return self.args[1]
230+
def offset(self) -> "VMemoryOffset":
231+
"""Returns offset relative to the stack slot it is part of."""
232+
233+
return self.vd.memory_offset(self.args[1])
229234

230235
@property
231236
def size(self) -> Optional[int]:

chb/invariants/FnVarDictionary.py

Lines changed: 15 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 Henny Sipma
9-
# Copyright (c) 2021-2023 Aarno Labs LLC
9+
# Copyright (c) 2021-2025 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
@@ -35,6 +35,7 @@
3535
from chb.invariants.FnDictionaryRecord import varregistry
3636
from chb.invariants.FnStackAccess import FnStackAccess
3737
from chb.invariants.FnXprDictionary import FnXprDictionary
38+
from chb.invariants.SideeffectArgumentLocation import SideeffectArgumentLocation
3839
from chb.invariants.VAssemblyVariable import VAssemblyVariable
3940
from chb.invariants.VConstantValueVariable import VConstantValueVariable
4041
from chb.invariants.VMemoryBase import VMemoryBase
@@ -63,6 +64,8 @@ def __init__(
6364
self._xd: Optional[FnXprDictionary] = None
6465
self.memory_base_table = IT.IndexedTable('memory-base-table')
6566
self.memory_offset_table = IT.IndexedTable('memory-offset-table')
67+
self.sideeffect_argument_location_table = IT.IndexedTable(
68+
"sideeffect-argument-location-table")
6669
self.assembly_variable_denotation_table = IT.IndexedTable(
6770
"assembly-variable-denotation-table")
6871
self.constant_value_variable_table = IT.IndexedTable(
@@ -71,6 +74,7 @@ def __init__(
7174
self.tables: List[IT.IndexedTable] = [
7275
self.memory_base_table,
7376
self.memory_offset_table,
77+
self.sideeffect_argument_location_table,
7478
self.assembly_variable_denotation_table,
7579
self.constant_value_variable_table,
7680
self.stack_access_table
@@ -139,6 +143,16 @@ def memory_offset(self, ix: int) -> VMemoryOffset:
139143
else:
140144
raise UF.CHBError("Illegal memory offset index value: " + str(ix))
141145

146+
def sideeffect_argument_location(
147+
self, ix: int) -> SideeffectArgumentLocation:
148+
if ix > 0:
149+
return varregistry.mk_instance(
150+
self,
151+
self.sideeffect_argument_location_table.retrieve(ix),
152+
SideeffectArgumentLocation)
153+
else:
154+
raise UF.CHBError("Illegal sideeffect argument location: " + str(ix))
155+
142156
def assembly_variable_denotation(self, ix: int) -> VAssemblyVariable:
143157
if ix > 0:
144158
return varregistry.mk_instance(
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# ------------------------------------------------------------------------------
2+
# CodeHawk Binary Analyzer
3+
# Author: Henny Sipma
4+
# ------------------------------------------------------------------------------
5+
# The MIT License (MIT)
6+
#
7+
# Copyright (c) 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+
"""
28+
29+
Based on sideeffect_argument_location in bCHLibTypes:
30+
31+
type sideeffect_argument_location_t =
32+
| SEGlobal of doubleword_int (* address of global variable passed *)
33+
| SEStack of numerical_int (* stack offset (negative for local stack) passed *)
34+
| SEDescr of string (* unidentified address *)
35+
"""
36+
37+
from typing import TYPE_CHECKING
38+
39+
from chb.invariants.FnDictionaryRecord import FnVarDictionaryRecord, varregistry
40+
41+
import chb.util.fileutil as UF
42+
from chb.util.IndexedTable import IndexedTableValue
43+
44+
if TYPE_CHECKING:
45+
from chb.bctypes.BCTyp import BCTyp
46+
from chb.invariants.FnVarDictionary import FnVarDictionary
47+
48+
49+
class SideeffectArgumentLocation(FnVarDictionaryRecord):
50+
51+
def __init__(
52+
self,
53+
vd: "FnVarDictionary",
54+
ixval: IndexedTableValue) -> None:
55+
FnVarDictionaryRecord.__init__(self, vd, ixval)
56+
57+
@property
58+
def is_global_location(self) -> bool:
59+
return False
60+
61+
@property
62+
def is_stack_location(self) -> bool:
63+
return False
64+
65+
@property
66+
def is_unknown_location(self) -> bool:
67+
return False
68+
69+
def __str__(self) -> str:
70+
return "sideeffect-argument-location:" + self.tags[0]
71+
72+
73+
@varregistry.register_tag("g", SideeffectArgumentLocation)
74+
class SideeffectArgumentGlobalLocation(SideeffectArgumentLocation):
75+
76+
def __init__(
77+
self,
78+
vd: "FnVarDictionary",
79+
ixval: IndexedTableValue) -> None:
80+
SideeffectArgumentLocation.__init__(self, vd, ixval)
81+
82+
@property
83+
def is_global_location(self) -> bool:
84+
return True
85+
86+
@property
87+
def gaddr(self) -> str:
88+
return self.tags[1]
89+
90+
def __str__(self) -> str:
91+
return "seglobal:" + self.gaddr
92+
93+
94+
@varregistry.register_tag("s", SideeffectArgumentLocation)
95+
class SideeffectArgumentStackLocation(SideeffectArgumentLocation):
96+
97+
def __init__(
98+
self,
99+
vd: "FnVarDictionary",
100+
ixval: IndexedTableValue) -> None:
101+
SideeffectArgumentLocation.__init__(self, vd, ixval)
102+
103+
@property
104+
def is_stack_location(self) -> bool:
105+
return True
106+
107+
@property
108+
def offset(self) -> int:
109+
return int(self.tags[1])
110+
111+
def __str__(self) -> str:
112+
return "sestack:" + str(self.offset)
113+
114+
115+
@varregistry.register_tag("d", SideeffectArgumentLocation)
116+
class SideeffectArgumentUnknownLocation(SideeffectArgumentLocation):
117+
118+
def __init__(
119+
self,
120+
vd: "FnVarDictionary",
121+
ixval: IndexedTableValue) -> None:
122+
SideeffectArgumentLocation.__init__(self, vd, ixval)
123+
124+
@property
125+
def is_unknown_location(self) -> bool:
126+
return True
127+
128+
@property
129+
def description(self) -> str:
130+
return self.tags[1]
131+
132+
def __str__(self) -> str:
133+
return "sedescr:" + str(self.description)

0 commit comments

Comments
 (0)