scientific-method-engine 0.6.0__tar.gz → 0.7.0__tar.gz
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-0.6.0 → scientific_method_engine-0.7.0}/PKG-INFO +1 -1
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/pyproject.toml +1 -1
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/pcode.py +5 -2
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/pcode_backend.py +90 -8
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_oracle.py +70 -3
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/.gitignore +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/LICENSE +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/README.md +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/__init__.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/__main__.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/cli.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ClearNoReturnFunctions.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/CreateFunctions.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ExportBoundedFlow.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ExportFunctionFingerprints.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ExportFunctionInventory.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/MergeFallThroughFragment.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/RecoverCitedFunctions.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/RepairReturningCallers.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportCallArguments.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportCallPaths.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportCallSitesWithScalars.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportCallsToRange.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportConstantFirstArgumentCalls.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportDataBytes.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportDecompileMatches.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportDecompileWindow.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportFilePatternInMemory.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportFirstArgumentCallSummary.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportFunctionScalarConstants.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportFunctionSummary.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportInstructionContext.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportInstructionWindow.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportMemoryBlockForFileOffset.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportMemoryBlocks.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportRandomnessCandidates.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportReferences.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportScalarConstants.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportStringReferences.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/ghidra/ReportSymbolReferences.java +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/__init__.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/dispatch.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/effect_order.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/handwritten.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/image.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/machine.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/pe.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/reports.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/result_flow.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/semantics.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/trace.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/src/scientific_method_engine/x86/values.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/differential.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/oracle.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_differential.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_dispatch.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_effect_order.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_nested_frame_request.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_pe.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_table_continuations.py +0 -0
- {scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_x86.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: scientific-method-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Bounded instruction-derived x86 evidence reports for segmented MZ/FBOV and PE32/i386 code.
|
|
5
5
|
Project-URL: Source, https://github.com/kibertoad/refurbished-dinosaurs-toolkit/tree/main/packages/scientific-method-engine
|
|
6
6
|
Author: kibertoad
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scientific-method-engine"
|
|
7
7
|
# The release workflow writes the published version from the package's release tag.
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.7.0"
|
|
9
9
|
description = "Bounded instruction-derived x86 evidence reports for segmented MZ/FBOV and PE32/i386 code."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.12"
|
|
@@ -300,8 +300,11 @@ def evaluate(code, args, bits, site):
|
|
|
300
300
|
# Identities that keep flag expressions small; constants fold in values.op.
|
|
301
301
|
mask = (1 << bits) - 1
|
|
302
302
|
for x, y in ((a, b), (b, a)):
|
|
303
|
-
if x.number == 0:
|
|
304
|
-
return Value(bits, ("constant", 0), origin)
|
|
303
|
+
if x.number == 0 and name == "and":
|
|
304
|
+
return Value(bits, ("constant", 0), origin)
|
|
305
|
+
if x.number == 0 and boolean(y):
|
|
306
|
+
# Flag selections OR a zero arm in; a value operand keeps its OR, as reports write it.
|
|
307
|
+
return Value(bits, y.term, origin)
|
|
305
308
|
if name == "and" and (x.number == mask or (x.number == 1 and boolean(y))):
|
|
306
309
|
return Value(bits, y.term, origin)
|
|
307
310
|
if name == "or" and (x.number == mask):
|
|
@@ -77,7 +77,7 @@ def bit(value, site):
|
|
|
77
77
|
# SLESS(x, 0) is the sign bit of x; a SLEIGH shift writes it as SLESS(x << k, 0).
|
|
78
78
|
if term[0] == "sless" and term[2] == ("constant", 0):
|
|
79
79
|
inner, shift = term[1], 0
|
|
80
|
-
if inner[0] == "shl" and inner[2][0] == "constant":
|
|
80
|
+
if inner[0] == "shl" and inner[2][0] == "constant" and inner not in LEAVES:
|
|
81
81
|
inner, shift = inner[1], inner[2][1]
|
|
82
82
|
width = width_of(inner, value)
|
|
83
83
|
if width is not None and shift < width:
|
|
@@ -90,7 +90,7 @@ def bit(value, site):
|
|
|
90
90
|
inner, mask = term[1][1], term[1][2]
|
|
91
91
|
if mask[0] == "constant" and mask[1] and mask[1] & (mask[1] - 1) == 0:
|
|
92
92
|
position = mask[1].bit_length() - 1
|
|
93
|
-
if inner[0] in ("shr", "sar") and inner[2][0] == "constant":
|
|
93
|
+
if inner[0] in ("shr", "sar") and inner[2][0] == "constant" and inner not in LEAVES:
|
|
94
94
|
position += inner[2][1]
|
|
95
95
|
inner = inner[1]
|
|
96
96
|
width = width_of(inner, value)
|
|
@@ -121,6 +121,9 @@ def width_of(term, value):
|
|
|
121
121
|
|
|
122
122
|
# Widths of the terms the current instruction read or computed; set per frame.
|
|
123
123
|
WIDTHS = {}
|
|
124
|
+
# The current instruction's operand values. Field decomposition stops at them, because reports
|
|
125
|
+
# treat each operand as one value even when an earlier instruction built it from fields.
|
|
126
|
+
LEAVES = set()
|
|
124
127
|
|
|
125
128
|
|
|
126
129
|
def fields(term, width):
|
|
@@ -130,6 +133,8 @@ def fields(term, width):
|
|
|
130
133
|
(right when negative). Rotates and shifts of joined values take this form in p-code.
|
|
131
134
|
"""
|
|
132
135
|
head = term[0]
|
|
136
|
+
if term in LEAVES:
|
|
137
|
+
return [(term, width, 0)]
|
|
133
138
|
top = top_bit(term)
|
|
134
139
|
if top is not None:
|
|
135
140
|
return [top]
|
|
@@ -252,6 +257,7 @@ class Frame:
|
|
|
252
257
|
|
|
253
258
|
def __init__(self, state, ins, image, widths=None, roles=None, present=None):
|
|
254
259
|
self.state, self.ins, self.image = state, ins, image
|
|
260
|
+
# present(value, frame) may rewrite a written value into the equal term reports use.
|
|
255
261
|
self.present = present
|
|
256
262
|
self.widths = widths or {}
|
|
257
263
|
self.roles = roles or {}
|
|
@@ -325,6 +331,7 @@ class Frame:
|
|
|
325
331
|
addressing_register=register)
|
|
326
332
|
self.loaded[index] = self.cache[index]
|
|
327
333
|
WIDTHS.setdefault(self.cache[index].term, self.cache[index].bits)
|
|
334
|
+
LEAVES.add(self.cache[index].term)
|
|
328
335
|
return extract(self.cache[index], d * 8, size * 8)
|
|
329
336
|
location = evidence if d == 0 else op("add", evidence, const(d, evidence.bits), state.at)
|
|
330
337
|
state.access(segment, location, size, value, addressing_register=register)
|
|
@@ -345,9 +352,12 @@ class Frame:
|
|
|
345
352
|
|
|
346
353
|
def execute(self):
|
|
347
354
|
WIDTHS.clear()
|
|
355
|
+
LEAVES.clear()
|
|
348
356
|
for index, value in self.before.items():
|
|
349
357
|
WIDTHS[value.term] = value.bits
|
|
350
|
-
|
|
358
|
+
LEAVES.add(value.term)
|
|
359
|
+
present = (lambda value: self.present(value, self)) if self.present else None
|
|
360
|
+
self.run = Run(self.state, self.ops, self.memory, self.constant, present=present)
|
|
351
361
|
self.run.execute()
|
|
352
362
|
for value in [*self.loaded.values(), *self.run.temps.values(), *self.run.registers.values()]:
|
|
353
363
|
if isinstance(value, Value): # Real-mode temporaries may hold a segmented Address.
|
|
@@ -442,7 +452,13 @@ class Pypcode:
|
|
|
442
452
|
# A handwritten instruction produced some of these flags; its predicate decides.
|
|
443
453
|
return answer, info
|
|
444
454
|
condition = Run(state, ops, None, flags=flags).execute(stop_at_branch=True)
|
|
445
|
-
|
|
455
|
+
if condition.number is None:
|
|
456
|
+
return None, info
|
|
457
|
+
if answer is None:
|
|
458
|
+
# The handwritten record says why it could not decide; p-code flags decided it.
|
|
459
|
+
info = {key: value for key, value in info.items() if key != "reason"}
|
|
460
|
+
info["decidedBy"] = "p-code flags"
|
|
461
|
+
return bool(condition.number), info
|
|
446
462
|
|
|
447
463
|
def string_iteration(self, state, ins, operation, width, source_segment, delta_step):
|
|
448
464
|
if operation not in self.mnemonics:
|
|
@@ -605,9 +621,25 @@ def compare(state, ins, image):
|
|
|
605
621
|
state.event("compare", operation=m, left=a.report(), right=b.report())
|
|
606
622
|
|
|
607
623
|
|
|
624
|
+
def unfolded(name, value, f, site):
|
|
625
|
+
"""A logic result p-code folded to one operand, written as the operation reports use.
|
|
626
|
+
|
|
627
|
+
``x | 0``, ``x ^ 0`` and ``x & ~0`` equal ``x``; the interpreter folds them, and reports
|
|
628
|
+
keep the instruction's operation in the result's expression.
|
|
629
|
+
"""
|
|
630
|
+
if name not in ("and", "or", "xor") or value.number is not None:
|
|
631
|
+
return value
|
|
632
|
+
a = f.value(0)
|
|
633
|
+
b = resize(f.value(1), a.bits)
|
|
634
|
+
neutral = (1 << a.bits) - 1 if name == "and" else 0
|
|
635
|
+
if value.bits == a.bits and (b.number == neutral and value.term == a.term or a.number == neutral and value.term == b.term):
|
|
636
|
+
return Value(value.bits, op(name, a, b, site).term, value.sources)
|
|
637
|
+
return value
|
|
638
|
+
|
|
639
|
+
|
|
608
640
|
def arithmetic(state, ins, image):
|
|
609
641
|
m = ins.mnemonic
|
|
610
|
-
f = run_plain(state, ins, image)
|
|
642
|
+
f = run_plain(state, ins, image, present=lambda v, frame: unfolded(m, v, frame, state.at))
|
|
611
643
|
a = f.value(0)
|
|
612
644
|
b = resize(f.value(1), a.bits)
|
|
613
645
|
result = f.result(0)
|
|
@@ -705,7 +737,7 @@ def rotate(state, ins, image):
|
|
|
705
737
|
state.forget_flags(keep_carry=True)
|
|
706
738
|
return
|
|
707
739
|
# Reports write a rotate as its shifted copies, from the leftmost copy for ROL and RCL.
|
|
708
|
-
f = run_plain(state, ins, image, present=lambda v: arranged(v, m in ("rol", "rcl"), state.at))
|
|
740
|
+
f = run_plain(state, ins, image, present=lambda v, _: arranged(v, m in ("rol", "rcl"), state.at))
|
|
709
741
|
a = f.value(0)
|
|
710
742
|
value = f.result(0)
|
|
711
743
|
carry = bit(f.run.flags["CF"], state.at)
|
|
@@ -733,10 +765,33 @@ def temporary(f, code):
|
|
|
733
765
|
return f.run.temps[o.output[1]]
|
|
734
766
|
|
|
735
767
|
|
|
768
|
+
def low_product(value, site):
|
|
769
|
+
"""The low half of a product of two extended values, as the product at the operands' width.
|
|
770
|
+
|
|
771
|
+
SLEIGH writes a two- or three-operand IMUL as the double-width product of the sign-extended
|
|
772
|
+
operands, truncated; the low half does not depend on the extension, and reports write it as
|
|
773
|
+
the operand-width product. An operand that already holds a sign extension (after CBW or MOVSX)
|
|
774
|
+
reaches the product as one extension of the narrower value, which is extended back to the
|
|
775
|
+
operand's width here.
|
|
776
|
+
"""
|
|
777
|
+
term = value.term
|
|
778
|
+
if term[0] != "extract" or term[2] != 0 or term[1][0] != "mul":
|
|
779
|
+
return value
|
|
780
|
+
factors = []
|
|
781
|
+
for factor in term[1][1:]:
|
|
782
|
+
if factor[0] in ("signExtend", "zeroExtend") and factor[2] <= value.bits:
|
|
783
|
+
factors.append(resize(Value(factor[2], factor[1]), value.bits, signed=factor[0] == "signExtend"))
|
|
784
|
+
elif factor[0] == "constant":
|
|
785
|
+
factors.append(const(factor[1], value.bits))
|
|
786
|
+
else:
|
|
787
|
+
return value
|
|
788
|
+
return Value(value.bits, op("mul", *factors, site).term, value.sources)
|
|
789
|
+
|
|
790
|
+
|
|
736
791
|
def multiply(state, ins, image):
|
|
737
792
|
m = ins.mnemonic
|
|
738
793
|
if m == "imul" and len(ins.operands) in (2, 3):
|
|
739
|
-
f = run_plain(state, ins, image)
|
|
794
|
+
f = run_plain(state, ins, image, present=lambda v, _: low_product(v, state.at))
|
|
740
795
|
left = resize(f.value(0 if len(ins.operands) == 2 else 1), ins.operands[0].size * 8)
|
|
741
796
|
right = resize(f.value(1 if len(ins.operands) == 2 else 2), left.bits, signed=True)
|
|
742
797
|
result = f.result(0)
|
|
@@ -786,10 +841,37 @@ def divide(state, ins, image):
|
|
|
786
841
|
quotient=quotient.report(), remainder=remainder.report(), fault=fault)
|
|
787
842
|
|
|
788
843
|
|
|
844
|
+
def through_extension(value, source, form):
|
|
845
|
+
"""``value`` as ``form(source)`` when p-code wrote ``form`` of the value ``source`` sign-extends.
|
|
846
|
+
|
|
847
|
+
When the source register holds a sign extension (after CBW, CWDE or MOVSX), p-code reads
|
|
848
|
+
through it to the narrower value it extended, because ``pcode.evaluate`` folds an extension
|
|
849
|
+
of an extension. Both name the same bits; reports name them in the register the instruction
|
|
850
|
+
reads.
|
|
851
|
+
"""
|
|
852
|
+
if value.number is not None or source.term[0] != "signExtend":
|
|
853
|
+
return value
|
|
854
|
+
inner = Value(source.term[2], source.term[1])
|
|
855
|
+
if value.term == form(inner).term:
|
|
856
|
+
return Value(value.bits, form(source).term, value.sources)
|
|
857
|
+
return value
|
|
858
|
+
|
|
859
|
+
|
|
789
860
|
def conversion(state, ins, image):
|
|
790
861
|
m = ins.mnemonic
|
|
791
862
|
before = {name: state.reg(name) for name in ("al", "ax", "eax")}
|
|
792
|
-
|
|
863
|
+
|
|
864
|
+
def present(value, _):
|
|
865
|
+
if value.bits not in (16, 32):
|
|
866
|
+
return value
|
|
867
|
+
if m in ("cwd", "cdq"):
|
|
868
|
+
# The high half is the sign bit of AX or EAX.
|
|
869
|
+
return through_extension(value, before["ax" if value.bits == 16 else "eax"],
|
|
870
|
+
lambda v: resize(extract(v, v.bits - 1, 1), value.bits, signed=True))
|
|
871
|
+
# CBW and CWDE write the sign extension of AL or AX.
|
|
872
|
+
return through_extension(value, before["al" if value.bits == 16 else "ax"],
|
|
873
|
+
lambda v: resize(v, value.bits, signed=True))
|
|
874
|
+
f = run_plain(state, ins, image, present=present)
|
|
793
875
|
(destination, value), = f.run.registers.items()
|
|
794
876
|
wide = value.bits == 32
|
|
795
877
|
if m in ("cbw", "cwde"):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Unicorn oracle cases per instruction group (ADR 0003). Synthetic machine code only."""
|
|
2
2
|
import unittest
|
|
3
3
|
|
|
4
|
-
from oracle import check
|
|
4
|
+
from oracle import STACK, check, configuration, run_report
|
|
5
5
|
|
|
6
6
|
DATA = {"ds": 0x3000, "es": 0x4000}
|
|
7
7
|
SAME = {"ds": 0x4000, "es": 0x4000}
|
|
@@ -70,8 +70,31 @@ class ArithmeticAndLogic(unittest.TestCase):
|
|
|
70
70
|
|
|
71
71
|
def test_counted_loop_exits_on_decrement_flags(self):
|
|
72
72
|
# inc ax; dec cx; jnz back: the handwritten backend forgets DEC's flags and splits.
|
|
73
|
-
check(self, "b90300 b80000 40 49 75fc c3", resolved=("ax", "cx"),
|
|
74
|
-
|
|
73
|
+
result = check(self, "b90300 b80000 40 49 75fc c3", resolved=("ax", "cx"),
|
|
74
|
+
extended="DEC flags from p-code resolve the loop exit")
|
|
75
|
+
branches = [e for e in result["paths"][0]["events"] if e["kind"] == "branch"]
|
|
76
|
+
self.assertEqual([e["taken"] for e in branches], [True, True, False])
|
|
77
|
+
# A branch p-code decided does not keep the handwritten backend's reason for not deciding it.
|
|
78
|
+
for e in branches:
|
|
79
|
+
self.assertNotIn("reason", e)
|
|
80
|
+
self.assertEqual((e["decidedBy"], e["flagProducer"]), ("p-code flags", 7))
|
|
81
|
+
|
|
82
|
+
def test_undecided_branch_keeps_the_reason(self):
|
|
83
|
+
# inc ax; jnz: AX is unknown, so neither backend decides the branch and both arms run.
|
|
84
|
+
data = bytes.fromhex("40 7501 90 c3".replace(" ", ""))
|
|
85
|
+
result = run_report(data, configuration(data, dict(STACK)), "trace")
|
|
86
|
+
branches = [e for path in result["paths"] for e in path["events"] if e["kind"] == "branch"]
|
|
87
|
+
self.assertEqual(sorted(e["taken"] for e in branches), [False, True])
|
|
88
|
+
for e in branches:
|
|
89
|
+
self.assertEqual(e["reason"], "flag producer unresolved")
|
|
90
|
+
self.assertNotIn("decidedBy", e)
|
|
91
|
+
|
|
92
|
+
def test_neutral_operands_keep_the_operation(self):
|
|
93
|
+
# BX is unknown to the engine, so each result stays an expression over it.
|
|
94
|
+
result = check(self, "b80100 80cf00 83f300 81e3ffff 09c3 c3", resolved=("ax",))
|
|
95
|
+
bx = result["paths"][0]["registers"]["bx"]["expression"]
|
|
96
|
+
# or(and(xor(..., 0), 0xffff), ax): neither the XOR with 0 nor the AND with ~0 folds away.
|
|
97
|
+
self.assertEqual((bx[0], bx[1][0], bx[1][1][0]), ("or", "and", "xor"))
|
|
75
98
|
|
|
76
99
|
def test_byte_forms_and_memory(self):
|
|
77
100
|
check(self, "bb1000 c707ff00 8007 01 fe07 8b07 b4ff 00e0 c3", registers=DATA, resolved=("ax",))
|
|
@@ -93,6 +116,23 @@ class ShiftsAndRotates(unittest.TestCase):
|
|
|
93
116
|
def test_rotates_through_carry_and_full_counts(self):
|
|
94
117
|
check(self, "b83412 f9 c1d00c f8 c1d805 c1c010 c1c808 83d000 c3", resolved=("ax",))
|
|
95
118
|
|
|
119
|
+
def test_chained_double_word_shifts(self):
|
|
120
|
+
# DX:AX shifted right twice through CF; the engine leaves both unknown, so each RCR's
|
|
121
|
+
# result and carry stay expressions over the previous one.
|
|
122
|
+
result = check(self, "b90100 d1ea d1d8 d1ea d1d8 c3", resolved=("cx",))
|
|
123
|
+
ax = result["paths"][0]["registers"]["ax"]["expression"]
|
|
124
|
+
self.assertEqual(ax[0], "or")
|
|
125
|
+
# The second RCR shifts out bit 0 of the first RCR's result, not bit 1 of the initial AX.
|
|
126
|
+
rotates = [e for e in result["paths"][0]["events"] if e.get("operation") == "rcr"]
|
|
127
|
+
self.assertEqual(rotates[1]["carryOut"]["expression"][1][0], "or")
|
|
128
|
+
|
|
129
|
+
def test_double_word_shift_from_a_zero_high_half(self):
|
|
130
|
+
# DX starts at zero; RCL carries AX's unknown top bits into it. The second RCL shifts out
|
|
131
|
+
# DX's bit 15, which is still zero; ADC moves that carry into BX for Unicorn to check.
|
|
132
|
+
result = check(self, "b90100 31d2 d1e0 d1d2 d1e0 d1d2 bb0000 11db c3", resolved=("cx", "bx"),
|
|
133
|
+
extended="a rotate's carry out folded from the operand's known bits")
|
|
134
|
+
self.assertEqual(result["paths"][0]["registers"]["dx"]["expression"][0], "or")
|
|
135
|
+
|
|
96
136
|
def test_memory_operands(self):
|
|
97
137
|
# RCL by one on memory: Capstone reports its implicit count with a size of 0.
|
|
98
138
|
check(self, "bb1000 c7070180 d107 c12f04 f9 d117 8b07 c7070180 f8 d017 8b07 c3", registers=DATA, resolved=("ax",))
|
|
@@ -102,6 +142,20 @@ class MultiplyAndDivide(unittest.TestCase):
|
|
|
102
142
|
def test_products(self):
|
|
103
143
|
check(self, "b80300 bb0500 f7e3 b0f0 b304 f6eb 6bc3fd 0fafc3 c3", resolved=("ax", "dx"))
|
|
104
144
|
|
|
145
|
+
def test_low_products_of_unknown_operands(self):
|
|
146
|
+
# The engine leaves CX unknown; the low products keep the operand-width form.
|
|
147
|
+
result = check(self, "b80300 0fafc8 6bd1fd 69d90500 c3")
|
|
148
|
+
registers = result["paths"][0]["registers"]
|
|
149
|
+
self.assertEqual(registers["cx"]["expression"][0], "mul")
|
|
150
|
+
self.assertEqual(registers["dx"]["expression"][0], "mul")
|
|
151
|
+
|
|
152
|
+
def test_low_products_of_extended_operands(self):
|
|
153
|
+
# AX holds the sign extension of an unknown byte; the products name AX, not the byte.
|
|
154
|
+
result = check(self, "a00000 98 0fafc8 6bd0fd b80100 c3", registers=DATA, resolved=("ax",))
|
|
155
|
+
registers = result["paths"][0]["registers"]
|
|
156
|
+
self.assertEqual(registers["cx"]["expression"][0], "mul")
|
|
157
|
+
self.assertEqual(registers["dx"]["expression"][0], "mul")
|
|
158
|
+
|
|
105
159
|
def test_quotients(self):
|
|
106
160
|
check(self, "ba0000 b86400 bb0700 f7f3 89c1 b8f6ff 99 f7fb c3", resolved=("ax", "cx", "dx"))
|
|
107
161
|
|
|
@@ -114,6 +168,19 @@ class Conversions(unittest.TestCase):
|
|
|
114
168
|
def test_sign_extensions(self):
|
|
115
169
|
check(self, "b080 98 89c3 99 b8ff7f 6698 6699 c3", resolved=("ax", "bx", "dx", "eax", "edx"))
|
|
116
170
|
|
|
171
|
+
def test_sign_fill_of_an_extended_byte(self):
|
|
172
|
+
# The byte at DS:0 is unknown to the engine; CWD names the sign bit of AX.
|
|
173
|
+
result = check(self, "a00000 98 99 b80100 c3", registers=DATA, resolved=("ax",))
|
|
174
|
+
dx = result["paths"][0]["registers"]["dx"]["expression"]
|
|
175
|
+
self.assertEqual(tuple(dx[1][2:4]), (15, 1))
|
|
176
|
+
|
|
177
|
+
def test_sign_extension_of_an_extended_byte(self):
|
|
178
|
+
# The byte at DS:0 is unknown to the engine; CWDE extends AX and CDQ names the sign bit of EAX.
|
|
179
|
+
result = check(self, "a00000 98 6698 6699 b80100 c3", registers=DATA, resolved=("ax",))
|
|
180
|
+
cwde = next(e for e in result["paths"][0]["events"] if e["kind"] == "conversion" and e["destinationBits"] == 32)
|
|
181
|
+
self.assertEqual(cwde["result"]["expression"][1][2:4], (8, 16))
|
|
182
|
+
self.assertEqual(tuple(result["paths"][0]["registers"]["edx"]["expression"][1][2:4]), (31, 1))
|
|
183
|
+
|
|
117
184
|
|
|
118
185
|
class FlagsAndDirection(unittest.TestCase):
|
|
119
186
|
def test_direction_controls_string_steps(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_differential.py
RENAMED
|
File without changes
|
|
File without changes
|
{scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_effect_order.py
RENAMED
|
File without changes
|
{scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_nested_frame_request.py
RENAMED
|
File without changes
|
|
File without changes
|
{scientific_method_engine-0.6.0 → scientific_method_engine-0.7.0}/tests/test_table_continuations.py
RENAMED
|
File without changes
|
|
File without changes
|