scientific-method-engine 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scientific_method_engine/__init__.py +5 -0
- scientific_method_engine/__main__.py +3 -0
- scientific_method_engine/cli.py +75 -0
- scientific_method_engine/ghidra/ClearNoReturnFunctions.java +22 -0
- scientific_method_engine/ghidra/CreateFunctions.java +27 -0
- scientific_method_engine/ghidra/ExportBoundedFlow.java +59 -0
- scientific_method_engine/ghidra/ExportFunctionFingerprints.java +135 -0
- scientific_method_engine/ghidra/ExportFunctionInventory.java +50 -0
- scientific_method_engine/ghidra/MergeFallThroughFragment.java +63 -0
- scientific_method_engine/ghidra/RecoverCitedFunctions.java +93 -0
- scientific_method_engine/ghidra/RepairReturningCallers.java +76 -0
- scientific_method_engine/ghidra/ReportCallArguments.java +65 -0
- scientific_method_engine/ghidra/ReportCallPaths.java +106 -0
- scientific_method_engine/ghidra/ReportCallSitesWithScalars.java +95 -0
- scientific_method_engine/ghidra/ReportCallsToRange.java +67 -0
- scientific_method_engine/ghidra/ReportConstantFirstArgumentCalls.java +55 -0
- scientific_method_engine/ghidra/ReportDataBytes.java +36 -0
- scientific_method_engine/ghidra/ReportDecompileMatches.java +71 -0
- scientific_method_engine/ghidra/ReportDecompileWindow.java +61 -0
- scientific_method_engine/ghidra/ReportFilePatternInMemory.java +102 -0
- scientific_method_engine/ghidra/ReportFirstArgumentCallSummary.java +63 -0
- scientific_method_engine/ghidra/ReportFunctionScalarConstants.java +56 -0
- scientific_method_engine/ghidra/ReportFunctionSummary.java +64 -0
- scientific_method_engine/ghidra/ReportInstructionContext.java +64 -0
- scientific_method_engine/ghidra/ReportInstructionWindow.java +36 -0
- scientific_method_engine/ghidra/ReportMemoryBlockForFileOffset.java +105 -0
- scientific_method_engine/ghidra/ReportMemoryBlocks.java +52 -0
- scientific_method_engine/ghidra/ReportRandomnessCandidates.java +74 -0
- scientific_method_engine/ghidra/ReportReferences.java +42 -0
- scientific_method_engine/ghidra/ReportScalarConstants.java +55 -0
- scientific_method_engine/ghidra/ReportStringReferences.java +102 -0
- scientific_method_engine/ghidra/ReportSymbolReferences.java +72 -0
- scientific_method_engine/x86/__init__.py +0 -0
- scientific_method_engine/x86/dispatch.py +51 -0
- scientific_method_engine/x86/image.py +172 -0
- scientific_method_engine/x86/machine.py +659 -0
- scientific_method_engine/x86/pe.py +96 -0
- scientific_method_engine/x86/reports.py +1259 -0
- scientific_method_engine/x86/trace.py +547 -0
- scientific_method_engine/x86/values.py +123 -0
- scientific_method_engine-0.1.0.dist-info/METADATA +110 -0
- scientific_method_engine-0.1.0.dist-info/RECORD +45 -0
- scientific_method_engine-0.1.0.dist-info/WHEEL +4 -0
- scientific_method_engine-0.1.0.dist-info/entry_points.txt +2 -0
- scientific_method_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"""Path-specific instruction effects. Unsupported semantics stop the path."""
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from capstone.x86 import X86_OP_REG, X86_OP_IMM, X86_OP_MEM
|
|
4
|
+
from .values import Value, const, unknown, op, extract, join, resize, sources, address_parts
|
|
5
|
+
|
|
6
|
+
REGISTERS = ("eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp", "cs", "ds", "es", "ss", "fs", "gs")
|
|
7
|
+
ALIASES = {}
|
|
8
|
+
for root in REGISTERS:
|
|
9
|
+
ALIASES[root] = (root, 0, 16 if len(root) == 2 else 32)
|
|
10
|
+
if root.startswith("e") and len(root) == 3:
|
|
11
|
+
ALIASES[root[1:]] = (root, 0, 16)
|
|
12
|
+
for name in ("a", "b", "c", "d"):
|
|
13
|
+
ALIASES[name + "l"] = ("e" + name + "x", 0, 8)
|
|
14
|
+
ALIASES[name + "h"] = ("e" + name + "x", 8, 8)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StopPath(Exception):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def segment_register(ins, mem):
|
|
22
|
+
"""Name the segment register an explicit memory operand uses (override or stack/data default)."""
|
|
23
|
+
if mem.segment:
|
|
24
|
+
return ins.reg_name(mem.segment)
|
|
25
|
+
base = ins.reg_name(mem.base) if mem.base else None
|
|
26
|
+
index = ins.reg_name(mem.index) if mem.index else None
|
|
27
|
+
return "ss" if base in ("bp", "sp", "ebp", "esp") or index == "bp" else "ds"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def alias(name):
|
|
31
|
+
# Registers outside the modeled set (control, debug, FPU, ...) stop the path instead of failing the report.
|
|
32
|
+
try:
|
|
33
|
+
return ALIASES[name]
|
|
34
|
+
except KeyError:
|
|
35
|
+
raise StopPath("Unsupported register: " + name) from None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class State:
|
|
39
|
+
def __init__(self, entry, image, config):
|
|
40
|
+
self.bits, self.flat, self.mask = image.bits, image.flat, image.mask
|
|
41
|
+
self.sp, self.bp = ("esp", "ebp") if self.flat else ("sp", "bp")
|
|
42
|
+
self.at = entry
|
|
43
|
+
self.regs = {r: unknown("initial:" + r, ALIASES[r][2]) for r in REGISTERS}
|
|
44
|
+
self.regs["esp"] = resize(unknown("entry:sp", self.bits), 32)
|
|
45
|
+
self.setreg(self.sp, unknown("entry:sp", self.bits), None)
|
|
46
|
+
self.segment_bases = {r: const(0, 32) if r in ("cs", "ds", "es", "ss") else unknown("initial-base:" + r, 32)
|
|
47
|
+
for r in ("cs", "ds", "es", "ss", "fs", "gs")}
|
|
48
|
+
self.setreg("cs", const(image.region(entry)["segment"], 16), None)
|
|
49
|
+
for r, n in config.get("registers", {}).items():
|
|
50
|
+
if r not in ALIASES or type(n) is not int or not 0 <= n < 1 << ALIASES[r][2]:
|
|
51
|
+
raise ValueError("Invalid initial register value")
|
|
52
|
+
self.setreg(r, const(n, ALIASES[r][2]), None)
|
|
53
|
+
self.memory = {}
|
|
54
|
+
# Keys grouped by (segment, base) so a write scans only groups that can alias it.
|
|
55
|
+
self.memory_groups = {}
|
|
56
|
+
self.memory_epoch = 0
|
|
57
|
+
self.events = []
|
|
58
|
+
self.guards = []
|
|
59
|
+
self.assumptions = {}
|
|
60
|
+
self.flags = None
|
|
61
|
+
# CF when an instruction sets it without leaving a comparable flag producer; None defers to flags.
|
|
62
|
+
self.carry = None
|
|
63
|
+
self.flag_serial = 0
|
|
64
|
+
self.flag_epoch = 0
|
|
65
|
+
self.direction_flag = unknown("initial:DF", 1)
|
|
66
|
+
self.interrupt_flag = unknown("initial:IF", 1)
|
|
67
|
+
self.saved_flags = {}
|
|
68
|
+
self.unknown_flag_site = None
|
|
69
|
+
self.frames = [{"entry": entry, "sp": self.reg(self.sp), "returnBytes": config.get("returnBytes", 4 if self.flat else 2)}]
|
|
70
|
+
self.steps = 0
|
|
71
|
+
self.visits = {}
|
|
72
|
+
self.path = []
|
|
73
|
+
self.conditional = []
|
|
74
|
+
flags = config.get("flags", {})
|
|
75
|
+
if not isinstance(flags, dict) or set(flags) - {"direction"}:
|
|
76
|
+
raise ValueError("Only an explicit starting direction flag is supported")
|
|
77
|
+
if "direction" in flags:
|
|
78
|
+
if type(flags["direction"]) is not int or flags["direction"] not in (0, 1):
|
|
79
|
+
raise ValueError("Starting direction flag must be zero or one")
|
|
80
|
+
self.direction_flag = const(flags["direction"], 1)
|
|
81
|
+
self.event("flag-assumption", flag="DF", value=self.direction_flag.report(),
|
|
82
|
+
evidence="explicit query starting hypothesis, not native state")
|
|
83
|
+
|
|
84
|
+
def forget_flags(self, keep_carry=False):
|
|
85
|
+
carry = self.carry_value() if keep_carry else None
|
|
86
|
+
self.flags = None
|
|
87
|
+
self.carry = carry
|
|
88
|
+
self.flag_serial += 1
|
|
89
|
+
self.flag_epoch = self.flag_serial
|
|
90
|
+
self.unknown_flag_site = self.at
|
|
91
|
+
|
|
92
|
+
def save_flags(self, bits):
|
|
93
|
+
word = unknown(f"saved-flags:{self.at}:{len(self.events)}", bits, self.at)
|
|
94
|
+
self.saved_flags[(bits, word.term)] = (self.flags, self.flag_epoch, self.unknown_flag_site,
|
|
95
|
+
self.direction_flag, self.interrupt_flag, self.carry)
|
|
96
|
+
self.push(word)
|
|
97
|
+
self.event("flags-save", width=bits // 8, value=word.report(),
|
|
98
|
+
direction=self.direction_flag.report(), interrupt=self.interrupt_flag.report())
|
|
99
|
+
|
|
100
|
+
def restore_flags(self, bits):
|
|
101
|
+
word = self.pop(bits // 8)
|
|
102
|
+
saved = self.saved_flags.get((bits, word.term))
|
|
103
|
+
if saved is None:
|
|
104
|
+
self.forget_flags()
|
|
105
|
+
self.carry = extract(word, 0, 1)
|
|
106
|
+
self.direction_flag = extract(word, 10, 1)
|
|
107
|
+
self.interrupt_flag = extract(word, 9, 1)
|
|
108
|
+
else:
|
|
109
|
+
self.flags, self.flag_epoch, self.unknown_flag_site, self.direction_flag, self.interrupt_flag, self.carry = saved
|
|
110
|
+
self.event("flags-restore", width=bits // 8, value=word.report(), intactLocalSnapshot=saved is not None,
|
|
111
|
+
direction=self.direction_flag.report(), interrupt=self.interrupt_flag.report())
|
|
112
|
+
|
|
113
|
+
def carry_value(self):
|
|
114
|
+
"""CF as a one-bit value: from the last comparable flag producer, an explicit carry, or unknown."""
|
|
115
|
+
if self.flags is not None:
|
|
116
|
+
answer, _ = predicate(self, "jb")
|
|
117
|
+
if answer is not None:
|
|
118
|
+
return const(int(answer), 1, self.flags[3])
|
|
119
|
+
# Name the carry by its producer's operands, so every reading of one comparison shares an assumption.
|
|
120
|
+
a, b, operation, site = self.flags
|
|
121
|
+
return unknown(f"carry:{site}:{(operation, a.term, b.term)!r}", 1, site)
|
|
122
|
+
if self.carry is not None:
|
|
123
|
+
return self.carry
|
|
124
|
+
return unknown(f"carry:unresolved:{self.flag_epoch}", 1, self.unknown_flag_site)
|
|
125
|
+
|
|
126
|
+
def set_flags(self, a, b, operation):
|
|
127
|
+
self.flags = (a, b, operation, self.at)
|
|
128
|
+
self.carry = None
|
|
129
|
+
|
|
130
|
+
def clear_memory(self):
|
|
131
|
+
self.memory.clear()
|
|
132
|
+
self.memory_groups.clear()
|
|
133
|
+
self.memory_epoch += 1
|
|
134
|
+
|
|
135
|
+
def reg(self, name):
|
|
136
|
+
root, low, bits = alias(name)
|
|
137
|
+
return extract(self.regs[root], low, bits)
|
|
138
|
+
|
|
139
|
+
def setreg(self, name, value, site):
|
|
140
|
+
root, low, bits = alias(name)
|
|
141
|
+
value = resize(value, bits)
|
|
142
|
+
value = Value(bits, value.term, sources(value, site=site))
|
|
143
|
+
if bits == ALIASES[root][2]:
|
|
144
|
+
self.regs[root] = value
|
|
145
|
+
else:
|
|
146
|
+
old = self.regs[root]
|
|
147
|
+
chunks = [extract(old, n, 8) for n in range(0, old.bits, 8)]
|
|
148
|
+
chunks[low // 8:(low + bits) // 8] = [extract(value, n, 8) for n in range(0, bits, 8)]
|
|
149
|
+
self.regs[root] = join(chunks)
|
|
150
|
+
|
|
151
|
+
def event(self, kind, **fields):
|
|
152
|
+
event = {"kind": kind, "site": self.at, "entry": self.frames[-1]["entry"],
|
|
153
|
+
"depth": len(self.frames) - 1, "order": len(self.events), **fields}
|
|
154
|
+
self.events.append(event)
|
|
155
|
+
return event
|
|
156
|
+
|
|
157
|
+
def segment(self, name):
|
|
158
|
+
# PE selectors are not real-mode paragraph bases. FS/GS bases remain unknown.
|
|
159
|
+
return self.segment_bases[name] if self.flat else self.reg(name)
|
|
160
|
+
|
|
161
|
+
def location(self, segment, offset):
|
|
162
|
+
# Segment aliases resolve only when both components are concrete.
|
|
163
|
+
if segment.number is not None and offset.number is not None:
|
|
164
|
+
return ("linear",), ("absolute",), segment.number * (1 if self.flat else 16) + offset.number
|
|
165
|
+
base, delta = address_parts(offset)
|
|
166
|
+
return segment.term, base, delta
|
|
167
|
+
|
|
168
|
+
def keys(self, segment, offset, width):
|
|
169
|
+
if offset.number is not None and offset.number + width > 1 << self.bits:
|
|
170
|
+
raise StopPath("Memory access crosses the address boundary")
|
|
171
|
+
seg, base, delta = self.location(segment, offset)
|
|
172
|
+
return seg, base, delta, [(seg, base, (delta + i) if seg == ("linear",) else (delta + i) % (1 << self.bits)) for i in range(width)]
|
|
173
|
+
|
|
174
|
+
def peek(self, segment, offset, width):
|
|
175
|
+
"""Inspect modeled memory without reporting an access the program never performed."""
|
|
176
|
+
_, _, _, keys = self.keys(segment, offset, width)
|
|
177
|
+
return join([self.memory.get(key, unknown(f"memory:{self.memory_epoch}:{key}", 8, self.at)) for key in keys])
|
|
178
|
+
|
|
179
|
+
def access(self, segment, offset, width, write=None, role=None, addressing_register=None):
|
|
180
|
+
seg, base, delta, keys = self.keys(segment, offset, width)
|
|
181
|
+
uncertain = []
|
|
182
|
+
if write is not None:
|
|
183
|
+
write = Value(write.bits, write.term, sources(write, site=self.at))
|
|
184
|
+
self.memory_epoch += 1
|
|
185
|
+
|
|
186
|
+
def domain(k):
|
|
187
|
+
if k[0] == ("linear",):
|
|
188
|
+
return k[2], k[2] + 1
|
|
189
|
+
if k[0][0] == "constant":
|
|
190
|
+
start = k[0][1] * (1 if self.flat else 16)
|
|
191
|
+
return start, start + (1 << self.bits)
|
|
192
|
+
return None
|
|
193
|
+
# A concrete write covers every byte it stores, not only its first byte.
|
|
194
|
+
written = (keys[0][2], keys[-1][2] + 1) if seg == ("linear",) else domain(keys[0])
|
|
195
|
+
for group, members in list(self.memory_groups.items()):
|
|
196
|
+
if group == (seg, base):
|
|
197
|
+
continue
|
|
198
|
+
for key in list(members):
|
|
199
|
+
# Different symbolic segments/bases may alias. Concrete linear locations do not.
|
|
200
|
+
a, b = domain(key), written
|
|
201
|
+
disjoint = a is not None and b is not None and (a[1] <= b[0] or b[1] <= a[0])
|
|
202
|
+
if not disjoint:
|
|
203
|
+
uncertain.append(key)
|
|
204
|
+
del self.memory[key]
|
|
205
|
+
members.discard(key)
|
|
206
|
+
if not members:
|
|
207
|
+
del self.memory_groups[group]
|
|
208
|
+
for i, key in enumerate(keys):
|
|
209
|
+
self.memory[key] = extract(write, i * 8, 8)
|
|
210
|
+
self.memory_groups.setdefault((seg, base), set()).update(keys)
|
|
211
|
+
value = write
|
|
212
|
+
missing = []
|
|
213
|
+
else:
|
|
214
|
+
missing = [i for i, key in enumerate(keys) if key not in self.memory]
|
|
215
|
+
value = join([self.memory.get(key, unknown(f"memory:{self.memory_epoch}:{key}", 8, self.at)) for key in keys])
|
|
216
|
+
relevant = []
|
|
217
|
+
for g in self.guards:
|
|
218
|
+
left = g.get("left", {}).get("expression")
|
|
219
|
+
right = g.get("right", {}).get("value")
|
|
220
|
+
if right == 0:
|
|
221
|
+
same = left == offset.term
|
|
222
|
+
relevant.append({"site": g["site"], "taken": g["taken"], "predicate": g["predicate"],
|
|
223
|
+
"samePointerValue": same,
|
|
224
|
+
"assessment": "same expression; inspect predicate polarity" if same else "checked value differs from this access"})
|
|
225
|
+
event = self.event("write" if write is not None else "read", segment=segment.report(), offset=offset.report(),
|
|
226
|
+
width=width, effectiveSegmentRegister=addressing_register, segmentInterpretation="base" if self.flat else "selector-paragraph", interval={"segment": seg, "base": base, "start": delta, "end": delta + width},
|
|
227
|
+
value=value.report(), missingByteProducers=missing,
|
|
228
|
+
byteProducers=[{"index": i, "producers": list(self.memory[key].sources) if key in self.memory else []} for i, key in enumerate(keys)],
|
|
229
|
+
guards=deepcopy(relevant), role=role,
|
|
230
|
+
uncertainAliasesInvalidated=len(uncertain))
|
|
231
|
+
f = self.frames[-1]
|
|
232
|
+
stack_base, stack_delta = address_parts(f["sp"])
|
|
233
|
+
mem_base, mem_delta = address_parts(offset)
|
|
234
|
+
relative = (mem_delta - stack_delta) % (1 << self.bits)
|
|
235
|
+
if write is None and segment.term == self.segment("ss").term and mem_base == stack_base and f["returnBytes"] <= relative < 1 << (self.bits - 1):
|
|
236
|
+
event["argument"] = {"offsetFromEntrySP": relative, "width": width, "returnFrameBytes": f["returnBytes"],
|
|
237
|
+
"pushProducers": list(value.sources), "grouping": role or "consumed width only"}
|
|
238
|
+
return value
|
|
239
|
+
|
|
240
|
+
def address(self, ins, operand):
|
|
241
|
+
mem = operand.mem
|
|
242
|
+
base = ins.reg_name(mem.base) if mem.base else None
|
|
243
|
+
index = ins.reg_name(mem.index) if mem.index else None
|
|
244
|
+
if ins.addr_size != self.bits // 8:
|
|
245
|
+
raise StopPath("Address-size override is outside the selected model")
|
|
246
|
+
offset = const(mem.disp, self.bits, self.at)
|
|
247
|
+
if base:
|
|
248
|
+
offset = op("add", self.reg(base), offset, self.at)
|
|
249
|
+
if index:
|
|
250
|
+
index_value = op("mul", self.reg(index), const(mem.scale, self.bits), self.at)
|
|
251
|
+
offset = op("add", offset, index_value, self.at)
|
|
252
|
+
register = segment_register(ins, mem)
|
|
253
|
+
return self.segment(register), offset, register
|
|
254
|
+
|
|
255
|
+
def get(self, ins, operand, image):
|
|
256
|
+
if operand.type == X86_OP_REG:
|
|
257
|
+
return self.reg(ins.reg_name(operand.reg))
|
|
258
|
+
if operand.type == X86_OP_IMM:
|
|
259
|
+
value = const(operand.imm, operand.size * 8, self.at)
|
|
260
|
+
fixup = image.fixups.get(self.at + ins.imm_offset)
|
|
261
|
+
if fixup and operand.size == 2:
|
|
262
|
+
value = const(fixup["segment"], 16, self.at)
|
|
263
|
+
self.event("relocated-immediate", provenance=fixup, value=value.report())
|
|
264
|
+
return value
|
|
265
|
+
if operand.type == X86_OP_MEM:
|
|
266
|
+
segment, offset, register = self.address(ins, operand)
|
|
267
|
+
return self.access(segment, offset, operand.size, addressing_register=register)
|
|
268
|
+
raise StopPath("Unsupported operand")
|
|
269
|
+
|
|
270
|
+
def put(self, ins, operand, value):
|
|
271
|
+
if operand.type == X86_OP_REG:
|
|
272
|
+
self.setreg(ins.reg_name(operand.reg), value, self.at)
|
|
273
|
+
elif operand.type == X86_OP_MEM:
|
|
274
|
+
segment, offset, register = self.address(ins, operand)
|
|
275
|
+
self.access(segment, offset, operand.size, resize(value, operand.size * 8), addressing_register=register)
|
|
276
|
+
else:
|
|
277
|
+
raise StopPath("Unsupported destination")
|
|
278
|
+
|
|
279
|
+
def push(self, value):
|
|
280
|
+
size = value.bits // 8
|
|
281
|
+
self.setreg(self.sp, op("sub", self.reg(self.sp), const(size, self.bits), self.at), self.at)
|
|
282
|
+
self.access(self.segment("ss"), self.reg(self.sp), size, value, role="push", addressing_register="ss")
|
|
283
|
+
|
|
284
|
+
def pop(self, size):
|
|
285
|
+
value = self.access(self.segment("ss"), self.reg(self.sp), size, role="pop", addressing_register="ss")
|
|
286
|
+
self.setreg(self.sp, op("add", self.reg(self.sp), const(size, self.bits), self.at), self.at)
|
|
287
|
+
return value
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
CARRY_BRANCHES = {"jb": True, "jc": True, "jnae": True, "jae": False, "jnb": False, "jnc": False}
|
|
291
|
+
# Branches taken when CF or OF is set; logic operations clear both whatever their operands.
|
|
292
|
+
CLEARED_BY_LOGIC = {**CARRY_BRANCHES, "jo": True, "jno": False}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def predicate(state, mnemonic):
|
|
296
|
+
flags = state.flags
|
|
297
|
+
if flags is None and state.carry is not None and mnemonic in CARRY_BRANCHES:
|
|
298
|
+
info = {"predicate": mnemonic, "flag": "CF", "carry": state.carry.report()}
|
|
299
|
+
if state.carry.number is None:
|
|
300
|
+
return None, {**info, "reason": "carry unresolved"}
|
|
301
|
+
return bool(state.carry.number) == CARRY_BRANCHES[mnemonic], info
|
|
302
|
+
if flags is None:
|
|
303
|
+
return None, {"predicate": mnemonic, "reason": "flag producer unresolved",
|
|
304
|
+
"flagProducer": state.unknown_flag_site, "flagGeneration": state.flag_epoch}
|
|
305
|
+
a, b, operation, site = flags
|
|
306
|
+
info = {"predicate": mnemonic, "flagProducer": site, "operation": operation,
|
|
307
|
+
"left": a.report(), "right": b.report()}
|
|
308
|
+
if a.number is not None and b.number is not None:
|
|
309
|
+
x, y = a.number, b.number
|
|
310
|
+
elif operation in ("cmp", "sub", "xor") and a.term == b.term:
|
|
311
|
+
# Any value compared with, subtracted from or XORed with itself yields zero.
|
|
312
|
+
x = y = 0
|
|
313
|
+
elif operation in ("test", "and", "or", "xor") and mnemonic in CLEARED_BY_LOGIC:
|
|
314
|
+
return not CLEARED_BY_LOGIC[mnemonic], info
|
|
315
|
+
else:
|
|
316
|
+
return None, info
|
|
317
|
+
bits = a.bits
|
|
318
|
+
if operation in ("cmp", "sub"):
|
|
319
|
+
raw = x - y
|
|
320
|
+
result = raw % (1 << bits)
|
|
321
|
+
cf = x < y
|
|
322
|
+
of = bool(((x ^ y) & (x ^ result)) & (1 << (bits - 1)))
|
|
323
|
+
elif operation == "add":
|
|
324
|
+
raw = x + y
|
|
325
|
+
result = raw % (1 << bits)
|
|
326
|
+
cf = raw >= 1 << bits
|
|
327
|
+
of = bool((~(x ^ y) & (x ^ result)) & (1 << (bits - 1)))
|
|
328
|
+
elif operation in ("test", "and", "or", "xor"):
|
|
329
|
+
result = {"test": x & y, "and": x & y, "or": x | y, "xor": x ^ y}[operation]
|
|
330
|
+
cf = of = False
|
|
331
|
+
else:
|
|
332
|
+
return None, info
|
|
333
|
+
zf, sf = result == 0, bool(result & (1 << (bits - 1)))
|
|
334
|
+
conditions = {"je": zf, "jz": zf, "jne": not zf, "jnz": not zf,
|
|
335
|
+
"jb": cf, "jc": cf, "jnae": cf, "jae": not cf, "jnb": not cf, "jnc": not cf,
|
|
336
|
+
"jbe": cf or zf, "jna": cf or zf, "ja": not cf and not zf, "jnbe": not cf and not zf,
|
|
337
|
+
"jl": sf != of, "jnge": sf != of, "jge": sf == of, "jnl": sf == of,
|
|
338
|
+
"jle": zf or sf != of, "jng": zf or sf != of, "jg": not zf and sf == of,
|
|
339
|
+
"jnle": not zf and sf == of, "js": sf, "jns": not sf, "jo": of, "jno": not of}
|
|
340
|
+
return conditions.get(mnemonic), info
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def ordinary(state, ins, image):
|
|
344
|
+
m, operands = ins.mnemonic, ins.operands
|
|
345
|
+
if m in ("cld", "std", "cli", "sti"):
|
|
346
|
+
value = const(1 if m in ("std", "sti") else 0, 1, state.at)
|
|
347
|
+
flag = "DF" if m in ("cld", "std") else "IF"
|
|
348
|
+
if flag == "DF": state.direction_flag = value
|
|
349
|
+
else: state.interrupt_flag = value
|
|
350
|
+
state.event("flag-write", flag=flag, value=value.report(),
|
|
351
|
+
interpretation="local flag effect only; interrupts and timing are not simulated")
|
|
352
|
+
return
|
|
353
|
+
if m in ("pushf", "pushfd", "popf", "popfd"):
|
|
354
|
+
bits = 32 if (0x66 in ins.prefix) != state.flat else 16
|
|
355
|
+
if m.startswith("push"): state.save_flags(bits)
|
|
356
|
+
else: state.restore_flags(bits)
|
|
357
|
+
return
|
|
358
|
+
if m == "nop":
|
|
359
|
+
return
|
|
360
|
+
if m in ("mov", "movzx", "movsx"):
|
|
361
|
+
if state.flat and operands[0].type == X86_OP_REG and ins.reg_name(operands[0].reg) in state.segment_bases:
|
|
362
|
+
raise StopPath("Segment selector assignment requires a descriptor model")
|
|
363
|
+
value = state.get(ins, operands[1], image)
|
|
364
|
+
state.put(ins, operands[0], resize(value, operands[0].size * 8, signed=m == "movsx"))
|
|
365
|
+
return
|
|
366
|
+
if m == "xchg":
|
|
367
|
+
values = [state.get(ins, operand, image) for operand in operands]
|
|
368
|
+
addresses = [state.address(ins, operand) if operand.type == X86_OP_MEM else None for operand in operands]
|
|
369
|
+
for index, operand in enumerate(operands):
|
|
370
|
+
value = values[1-index]
|
|
371
|
+
if addresses[index] is None:
|
|
372
|
+
state.put(ins, operand, value)
|
|
373
|
+
else:
|
|
374
|
+
segment, offset, register = addresses[index]
|
|
375
|
+
state.access(segment, offset, operand.size, resize(value, operand.size * 8), addressing_register=register)
|
|
376
|
+
return
|
|
377
|
+
if m == "imul" and len(operands) in (2, 3):
|
|
378
|
+
left, right = (state.get(ins, operand, image) for operand in (operands if len(operands) == 2 else operands[1:]))
|
|
379
|
+
left = resize(left, operands[0].size * 8)
|
|
380
|
+
right = resize(right, left.bits, signed=True)
|
|
381
|
+
result = op("mul", left, right, state.at)
|
|
382
|
+
state.put(ins, operands[0], result)
|
|
383
|
+
state.forget_flags() # CF/OF require the full signed product; other flags are undefined.
|
|
384
|
+
state.event("arithmetic", operation="imul", left=left.report(), right=right.report(),
|
|
385
|
+
result=result.report(), modulus=1 << left.bits, flags="unresolved signed-product overflow")
|
|
386
|
+
return
|
|
387
|
+
if m == "lea":
|
|
388
|
+
segment, offset, register = state.address(ins, operands[1])
|
|
389
|
+
state.put(ins, operands[0], offset)
|
|
390
|
+
state.event("address-formation", value=offset.report(), addressingSegment=segment.report(),
|
|
391
|
+
addressingSegmentRegister=register, destinationRegister=ins.reg_name(operands[0].reg),
|
|
392
|
+
note="LEA does not access memory; this addressing default does not bind a later dereference")
|
|
393
|
+
return
|
|
394
|
+
if m in ("lds", "les"):
|
|
395
|
+
if state.flat:
|
|
396
|
+
raise StopPath("Descriptor loads are outside the PE32 flat model")
|
|
397
|
+
segment, offset, register = state.address(ins, operands[1])
|
|
398
|
+
if operands[0].size != 2:
|
|
399
|
+
raise StopPath("Only 16:16 pointer loads are supported")
|
|
400
|
+
value = state.access(segment, offset, 4, role="far-pointer", addressing_register=register)
|
|
401
|
+
state.put(ins, operands[0], extract(value, 0, 16))
|
|
402
|
+
state.setreg("ds" if m == "lds" else "es", extract(value, 16, 16), state.at)
|
|
403
|
+
return
|
|
404
|
+
if m == "push":
|
|
405
|
+
if state.flat and operands[0].type == X86_OP_REG and ins.reg_name(operands[0].reg) in state.segment_bases:
|
|
406
|
+
raise StopPath("Segment stack operations require a descriptor model")
|
|
407
|
+
state.push(state.get(ins, operands[0], image))
|
|
408
|
+
return
|
|
409
|
+
if m == "pop":
|
|
410
|
+
if state.flat and operands[0].type == X86_OP_REG and ins.reg_name(operands[0].reg) in state.segment_bases:
|
|
411
|
+
raise StopPath("Segment selector assignment requires a descriptor model")
|
|
412
|
+
state.put(ins, operands[0], state.pop(operands[0].size))
|
|
413
|
+
return
|
|
414
|
+
if m == "leave":
|
|
415
|
+
if 0x66 in ins.prefix:
|
|
416
|
+
raise StopPath("Operand-size override on LEAVE is unsupported")
|
|
417
|
+
state.setreg(state.sp, state.reg(state.bp), state.at)
|
|
418
|
+
state.setreg(state.bp, state.pop(state.bits // 8), state.at)
|
|
419
|
+
return
|
|
420
|
+
if m in ("cmp", "test"):
|
|
421
|
+
a, b = (state.get(ins, o, image) for o in operands)
|
|
422
|
+
state.set_flags(a, resize(b, a.bits), m)
|
|
423
|
+
state.event("compare", operation=m, left=a.report(), right=b.report())
|
|
424
|
+
return
|
|
425
|
+
if m in ("add", "sub", "and", "or", "xor", "shl", "sal", "shr", "sar"):
|
|
426
|
+
a, b = (state.get(ins, o, image) for o in operands)
|
|
427
|
+
b = resize(b, a.bits)
|
|
428
|
+
result = op("shl" if m == "sal" else m, a, b, state.at)
|
|
429
|
+
state.put(ins, operands[0], result)
|
|
430
|
+
if m in ("add", "sub", "and", "or", "xor"):
|
|
431
|
+
state.set_flags(a, b, m)
|
|
432
|
+
else:
|
|
433
|
+
shift_carry(state, m, a, b)
|
|
434
|
+
state.event("arithmetic", operation=m, left=a.report(), right=b.report(), result=result.report(), modulus=1 << a.bits)
|
|
435
|
+
return
|
|
436
|
+
if m in ("inc", "dec"):
|
|
437
|
+
a = state.get(ins, operands[0], image)
|
|
438
|
+
result = op("add" if m == "inc" else "sub", a, const(1, a.bits), state.at)
|
|
439
|
+
state.put(ins, operands[0], result)
|
|
440
|
+
# Carry is preserved; the other flags are not modeled for INC/DEC.
|
|
441
|
+
state.forget_flags(keep_carry=True)
|
|
442
|
+
return
|
|
443
|
+
if m in ("cbw", "cwde"):
|
|
444
|
+
# Capstone 5 names these inconsistently in 16-bit mode. Use effective size.
|
|
445
|
+
# The prefix toggles the mode's default operand size (16-bit real mode, 32-bit flat).
|
|
446
|
+
wide = (0x66 in ins.prefix) != state.flat
|
|
447
|
+
source, destination = ("ax", "eax") if wide else ("al", "ax")
|
|
448
|
+
value = resize(state.reg(source), 32 if wide else 16, True)
|
|
449
|
+
state.setreg(destination, value, state.at)
|
|
450
|
+
state.event("conversion", sourceRegister=source, destinationRegister=destination,
|
|
451
|
+
effectiveOperandBits=32 if wide else 16, decoderMnemonic=m,
|
|
452
|
+
mnemonicWidthMismatch=m != ("cwde" if wide else "cbw"), result=value.report())
|
|
453
|
+
return
|
|
454
|
+
if m in ("cwd", "cdq"):
|
|
455
|
+
wide = (0x66 in ins.prefix) != state.flat
|
|
456
|
+
source, destination = ("eax", "edx") if wide else ("ax", "dx")
|
|
457
|
+
bits = 32 if wide else 16
|
|
458
|
+
value = resize(extract(state.reg(source), bits-1, 1), bits, signed=True)
|
|
459
|
+
state.setreg(destination, value, state.at)
|
|
460
|
+
state.event("conversion", sourceRegister=source, destinationRegister=destination,
|
|
461
|
+
effectiveOperandBits=bits, decoderMnemonic=m,
|
|
462
|
+
mnemonicWidthMismatch=m != ("cdq" if wide else "cwd"), result=value.report())
|
|
463
|
+
return
|
|
464
|
+
if m in ("clc", "stc", "cmc"):
|
|
465
|
+
if m == "cmc":
|
|
466
|
+
value = op("xor", state.carry_value(), const(1, 1), state.at)
|
|
467
|
+
else:
|
|
468
|
+
value = const(int(m == "stc"), 1, state.at)
|
|
469
|
+
state.forget_flags()
|
|
470
|
+
state.carry = value
|
|
471
|
+
state.event("flag-write", flag="CF", value=value.report(), interpretation="local carry effect")
|
|
472
|
+
return
|
|
473
|
+
if m in ("not", "neg"):
|
|
474
|
+
a = state.get(ins, operands[0], image)
|
|
475
|
+
if m == "not":
|
|
476
|
+
state.put(ins, operands[0], op("xor", a, const((1 << a.bits) - 1, a.bits), state.at))
|
|
477
|
+
return
|
|
478
|
+
result = op("sub", const(0, a.bits), a, state.at)
|
|
479
|
+
state.put(ins, operands[0], result)
|
|
480
|
+
state.set_flags(const(0, a.bits, state.at), a, "sub")
|
|
481
|
+
state.event("arithmetic", operation="neg", left=a.report(), result=result.report(), modulus=1 << a.bits)
|
|
482
|
+
return
|
|
483
|
+
if m in ("adc", "sbb"):
|
|
484
|
+
a, b = (state.get(ins, o, image) for o in operands)
|
|
485
|
+
b = resize(b, a.bits)
|
|
486
|
+
carry = state.carry_value()
|
|
487
|
+
name = "add" if m == "adc" else "sub"
|
|
488
|
+
result = op(name, op(name, a, b, state.at), resize(carry, a.bits), state.at)
|
|
489
|
+
state.put(ins, operands[0], result)
|
|
490
|
+
state.forget_flags()
|
|
491
|
+
if None not in (a.number, b.number, carry.number):
|
|
492
|
+
raw = a.number + b.number + carry.number if m == "adc" else a.number - b.number - carry.number
|
|
493
|
+
state.carry = const(int(raw < 0 or raw >= 1 << a.bits), 1, state.at)
|
|
494
|
+
else:
|
|
495
|
+
state.carry = unknown(f"carry:{state.at}:{state.flag_serial}", 1, state.at)
|
|
496
|
+
state.event("arithmetic", operation=m, left=a.report(), right=b.report(), carryIn=carry.report(),
|
|
497
|
+
result=result.report(), carryOut=state.carry.report(), modulus=1 << a.bits)
|
|
498
|
+
return
|
|
499
|
+
if m in ("rol", "ror", "rcl", "rcr"):
|
|
500
|
+
a = state.get(ins, operands[0], image)
|
|
501
|
+
count = state.get(ins, operands[1], image) if len(operands) > 1 else const(1, 8)
|
|
502
|
+
if count.number is None:
|
|
503
|
+
raise StopPath("rotate count unresolved")
|
|
504
|
+
masked = count.number & 31
|
|
505
|
+
if masked == 0:
|
|
506
|
+
return # The value and flags are unchanged.
|
|
507
|
+
bits = a.bits
|
|
508
|
+
through = m in ("rcl", "rcr")
|
|
509
|
+
n = masked % (bits + 1 if through else bits)
|
|
510
|
+
if through and n == 0:
|
|
511
|
+
# A full rotation through CF restores the value and CF; OF is undefined.
|
|
512
|
+
state.forget_flags(keep_carry=True)
|
|
513
|
+
return
|
|
514
|
+
carry_in = resize(state.carry_value(), bits)
|
|
515
|
+
# Each form is an OR of shifted copies (positive shifts left), built once so the
|
|
516
|
+
# expression does not repeat the operand for every bit rotated.
|
|
517
|
+
parts = {"rol": [(a, n), (a, n - bits)],
|
|
518
|
+
"ror": [(a, -n), (a, bits - n)],
|
|
519
|
+
"rcl": [(a, n), (carry_in, n - 1), (a, n - bits - 1)],
|
|
520
|
+
"rcr": [(a, -n), (carry_in, bits - n), (a, bits + 1 - n)]}[m]
|
|
521
|
+
value = None
|
|
522
|
+
for part, shift in parts:
|
|
523
|
+
if abs(shift) >= bits:
|
|
524
|
+
continue # Every bit leaves the operand; op() would mask the count instead.
|
|
525
|
+
if shift:
|
|
526
|
+
part = op("shl" if shift > 0 else "shr", part, const(abs(shift), bits), state.at)
|
|
527
|
+
value = part if value is None else op("or", value, part, state.at)
|
|
528
|
+
# CF is the last bit rotated out: the result's low bit for ROL/RCL and its high bit for ROR/RCR.
|
|
529
|
+
out = {"rol": (bits - n) % bits, "ror": (n - 1) % bits, "rcl": bits - n, "rcr": n - 1}[m]
|
|
530
|
+
carry = extract(a, out, 1)
|
|
531
|
+
state.put(ins, operands[0], value)
|
|
532
|
+
state.forget_flags()
|
|
533
|
+
state.carry = Value(1, carry.term, sources(carry, site=state.at))
|
|
534
|
+
state.event("arithmetic", operation=m, left=a.report(), count=n, result=value.report(),
|
|
535
|
+
carryOut=state.carry.report(), modulus=1 << bits)
|
|
536
|
+
return
|
|
537
|
+
if m in ("mul", "imul") and len(operands) == 1:
|
|
538
|
+
source = state.get(ins, operands[0], image)
|
|
539
|
+
bits = source.bits
|
|
540
|
+
low_reg, high_reg = {8: ("al", "ah"), 16: ("ax", "dx"), 32: ("eax", "edx")}[bits]
|
|
541
|
+
signed = m == "imul"
|
|
542
|
+
multiplicand = state.reg(low_reg)
|
|
543
|
+
product = op("mul", resize(multiplicand, 2 * bits, signed), resize(source, 2 * bits, signed), state.at)
|
|
544
|
+
low = extract(product, 0, bits)
|
|
545
|
+
if bits == 8:
|
|
546
|
+
state.setreg("ax", product, state.at)
|
|
547
|
+
else:
|
|
548
|
+
state.setreg(low_reg, low, state.at)
|
|
549
|
+
state.setreg(high_reg, extract(product, bits, bits), state.at)
|
|
550
|
+
state.forget_flags()
|
|
551
|
+
if product.number is None:
|
|
552
|
+
state.carry = unknown(f"carry:{state.at}:{state.flag_serial}", 1, state.at)
|
|
553
|
+
else:
|
|
554
|
+
# CF and OF say whether the high half carries information beyond the low half.
|
|
555
|
+
state.carry = const(int(resize(low, 2 * bits, signed).number != product.number), 1, state.at)
|
|
556
|
+
state.event("arithmetic", operation=m, left=multiplicand.report(), right=source.report(),
|
|
557
|
+
result=product.report(), resultBits=2 * bits, carryOut=state.carry.report())
|
|
558
|
+
return
|
|
559
|
+
if m in ("div", "idiv"):
|
|
560
|
+
divisor = state.get(ins, operands[0], image)
|
|
561
|
+
bits = divisor.bits
|
|
562
|
+
signed = m == "idiv"
|
|
563
|
+
if bits == 8:
|
|
564
|
+
dividend = state.reg("ax")
|
|
565
|
+
else:
|
|
566
|
+
dividend = join([state.reg({16: "ax", 32: "eax"}[bits]), state.reg({16: "dx", 32: "edx"}[bits])])
|
|
567
|
+
quotient_reg, remainder_reg = {8: ("al", "ah"), 16: ("ax", "dx"), 32: ("eax", "edx")}[bits]
|
|
568
|
+
fault = None
|
|
569
|
+
if divisor.number == 0:
|
|
570
|
+
raise StopPath("divide by zero raises interrupt 0; its handler is not modeled")
|
|
571
|
+
if None not in (dividend.number, divisor.number):
|
|
572
|
+
x, y = dividend.number, divisor.number
|
|
573
|
+
if signed:
|
|
574
|
+
x -= (x >> (2 * bits - 1)) << (2 * bits)
|
|
575
|
+
y -= (y >> (bits - 1)) << bits
|
|
576
|
+
q = abs(x) // abs(y) * (1 if (x < 0) == (y < 0) else -1)
|
|
577
|
+
r = x - q * y
|
|
578
|
+
if not ((-(1 << (bits - 1)) <= q < 1 << (bits - 1)) if signed else q < 1 << bits):
|
|
579
|
+
raise StopPath("divide overflow raises interrupt 0; its handler is not modeled")
|
|
580
|
+
quotient, remainder = const(q, bits, state.at), const(r, bits, state.at)
|
|
581
|
+
else:
|
|
582
|
+
wide = resize(divisor, 2 * bits, signed)
|
|
583
|
+
origin = sources(dividend, divisor, site=state.at)
|
|
584
|
+
quotient = extract(Value(2 * bits, ("sdiv" if signed else "udiv", dividend.term, wide.term), origin), 0, bits)
|
|
585
|
+
remainder = extract(Value(2 * bits, ("smod" if signed else "umod", dividend.term, wide.term), origin), 0, bits)
|
|
586
|
+
fault = "possible divide error (interrupt 0) unresolved; this path assumes none"
|
|
587
|
+
state.conditional.append({"site": state.at, "assumption": "no divide error"})
|
|
588
|
+
state.setreg(quotient_reg, quotient, state.at)
|
|
589
|
+
state.setreg(remainder_reg, remainder, state.at)
|
|
590
|
+
state.forget_flags()
|
|
591
|
+
state.event("arithmetic", operation=m, dividend=dividend.report(), divisor=divisor.report(),
|
|
592
|
+
quotient=quotient.report(), remainder=remainder.report(), fault=fault)
|
|
593
|
+
return
|
|
594
|
+
raise StopPath("Unsupported instruction semantics: " + m)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def shift_carry(state, m, a, count):
|
|
598
|
+
"""CF after SHL/SHR/SAR: the last bit shifted out, when the count is known."""
|
|
599
|
+
n = None if count.number is None else count.number & 31
|
|
600
|
+
if n == 0:
|
|
601
|
+
return # A zero count leaves every flag unchanged.
|
|
602
|
+
state.forget_flags()
|
|
603
|
+
if n is None or n > a.bits:
|
|
604
|
+
state.carry = unknown(f"carry:{state.at}:{state.flag_serial}", 1, state.at)
|
|
605
|
+
elif m in ("shl", "sal"):
|
|
606
|
+
state.carry = Value(1, extract(a, a.bits - n, 1).term, sources(a, site=state.at))
|
|
607
|
+
else:
|
|
608
|
+
state.carry = Value(1, extract(a, n - 1, 1).term, sources(a, site=state.at))
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def string_instruction(ins):
|
|
612
|
+
# Match the one-byte opcode, not the last encoded byte: SSE MOVSD (F2 0F 10 /r) can end in A5.
|
|
613
|
+
return ins.opcode[0] in (0xA4, 0xA5, 0xAA, 0xAB, 0xAC, 0xAD) and ins.opcode[1] == 0
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def string_count(state, ins):
|
|
617
|
+
return state.reg("ecx" if state.flat else "cx") if 0xF3 in ins.prefix else const(1, state.bits)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def check_string_form(state, ins):
|
|
621
|
+
if ins.addr_size != state.bits // 8:
|
|
622
|
+
raise StopPath("Address-size override on string operation is outside the selected model")
|
|
623
|
+
if 0xF2 in ins.prefix:
|
|
624
|
+
raise StopPath("REPNE string form is not supported")
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def string_effect(state, ins, count, remaining):
|
|
628
|
+
"""Apply a string form already accepted by check_string_form with its string_count."""
|
|
629
|
+
width = 1 if ins.opcode[0] % 2 == 0 else (4 if (0x66 in ins.prefix) != state.flat else 2)
|
|
630
|
+
operation = ins.mnemonic.split()[-1][:4]
|
|
631
|
+
state.event("string-operation", operation=operation, width=width, repetitions=count.report(),
|
|
632
|
+
direction=state.direction_flag.report(), repeat=0xF3 in ins.prefix,
|
|
633
|
+
interpretation="bounded memory effects only, not pixels or native input coverage")
|
|
634
|
+
if count.number is None:
|
|
635
|
+
raise StopPath("String repetition count unresolved; a bounded producer is required")
|
|
636
|
+
if count.number > remaining:
|
|
637
|
+
raise StopPath("String iteration budget exhausted; remaining effects unresolved")
|
|
638
|
+
if count.number == 0:
|
|
639
|
+
return 0
|
|
640
|
+
if state.direction_flag.number is None:
|
|
641
|
+
raise StopPath("Direction flag unresolved; conditional string paths required")
|
|
642
|
+
# MOVS/LODS decode their source as the second memory operand, carrying any segment override.
|
|
643
|
+
source_name = segment_register(ins, ins.operands[1].mem) if operation in ("movs", "lods") else None
|
|
644
|
+
si, di = ("esi", "edi") if state.flat else ("si", "di")
|
|
645
|
+
delta = -width if state.direction_flag.number else width
|
|
646
|
+
for _ in range(count.number):
|
|
647
|
+
if operation in ("movs", "lods"):
|
|
648
|
+
value = state.access(state.segment(source_name), state.reg(si), width, role="string-source", addressing_register=source_name)
|
|
649
|
+
state.setreg(si, op("add", state.reg(si), const(delta, state.bits), state.at), state.at)
|
|
650
|
+
else:
|
|
651
|
+
value = state.reg({1:"al",2:"ax",4:"eax"}[width])
|
|
652
|
+
if operation in ("movs", "stos"):
|
|
653
|
+
state.access(state.segment("es"), state.reg(di), width, value, role="string-destination", addressing_register="es")
|
|
654
|
+
state.setreg(di, op("add", state.reg(di), const(delta, state.bits), state.at), state.at)
|
|
655
|
+
else:
|
|
656
|
+
state.setreg({1:"al",2:"ax",4:"eax"}[width], value, state.at)
|
|
657
|
+
if 0xF3 in ins.prefix:
|
|
658
|
+
state.setreg("ecx" if state.flat else "cx", const(0, state.bits, state.at), state.at)
|
|
659
|
+
return count.number
|