strilight 0.2.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.
- strilight/__init__.py +174 -0
- strilight/engine/__init__.py +16 -0
- strilight/engine/abstract_state.py +106 -0
- strilight/engine/domains/__init__.py +18 -0
- strilight/engine/domains/interval.py +619 -0
- strilight/engine/vsa/__init__.py +67 -0
- strilight/engine/vsa/models.py +2130 -0
- strilight/engine/vsa/smt_translator.py +608 -0
- strilight/frontend/__init__.py +26 -0
- strilight/frontend/base.py +24 -0
- strilight/frontend/c/__init__.py +15 -0
- strilight/frontend/c/contract.py +90 -0
- strilight/frontend/c/fusion.py +171 -0
- strilight/frontend/c/lifter.py +806 -0
- strilight/frontend/codegen.py +744 -0
- strilight/frontend/orbit_matcher.py +203 -0
- strilight/frontend/python/__init__.py +9 -0
- strilight/frontend/python/lifter.py +511 -0
- strilight/frontend/resolver.py +261 -0
- strilight/frontend/source_lifter.py +241 -0
- strilight/py.typed +1 -0
- strilight-0.2.0.dist-info/METADATA +208 -0
- strilight-0.2.0.dist-info/RECORD +26 -0
- strilight-0.2.0.dist-info/WHEEL +5 -0
- strilight-0.2.0.dist-info/licenses/LICENSE +654 -0
- strilight-0.2.0.dist-info/top_level.txt +1 -0
strilight/__init__.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strilight: High-Performance O(1) SMT Loop Lifting & Strided Interval Domain
|
|
3
|
+
============================================================================
|
|
4
|
+
A lightweight, high-performance abstract interpretation and symbolic loop-lifting library for x86_64 binaries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List, Union, Optional, Any
|
|
10
|
+
|
|
11
|
+
__version__ = "0.2.0"
|
|
12
|
+
|
|
13
|
+
# Module-level logger with default NullHandler (zero unwanted stdout noise when imported)
|
|
14
|
+
logger = logging.getLogger("strilight")
|
|
15
|
+
logger.addHandler(logging.NullHandler())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def set_log_level(level: Union[int, str]):
|
|
19
|
+
"""
|
|
20
|
+
Sets the logging level for the strilight root logger.
|
|
21
|
+
Example: sl.set_log_level(logging.DEBUG) or sl.set_log_level("INFO")
|
|
22
|
+
"""
|
|
23
|
+
if isinstance(level, str):
|
|
24
|
+
level = getattr(logging, level.upper(), logging.INFO)
|
|
25
|
+
logger.setLevel(level)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def enable_logging(level: Union[int, str] = logging.INFO, stream=None):
|
|
29
|
+
"""
|
|
30
|
+
Enables console logging for strilight with a clean, standard formatter.
|
|
31
|
+
"""
|
|
32
|
+
if stream is None:
|
|
33
|
+
stream = sys.stderr
|
|
34
|
+
set_log_level(level)
|
|
35
|
+
|
|
36
|
+
# Avoid adding multiple StreamHandlers
|
|
37
|
+
if not any(isinstance(h, logging.StreamHandler) and not isinstance(h, logging.NullHandler) for h in logger.handlers):
|
|
38
|
+
handler = logging.StreamHandler(stream)
|
|
39
|
+
formatter = logging.Formatter("[%(levelname)s] [%(name)s] %(message)s")
|
|
40
|
+
handler.setFormatter(formatter)
|
|
41
|
+
logger.addHandler(handler)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Core abstractions
|
|
45
|
+
from strilight.engine.vsa import LoopSummary, LoopInvariantContract
|
|
46
|
+
from strilight.engine.domains import Interval, StridedInterval, DisjointIntervalSet
|
|
47
|
+
from strilight.frontend import SourceLifter, CodeGenerator, accelerate, accelerate_c_source
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def __getattr__(name: str):
|
|
51
|
+
"""
|
|
52
|
+
Lazy load optional extension and architecture modules on demand (PEP 562).
|
|
53
|
+
Keeps core mathematical engine purely isolated from binary dependencies upon import.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
if name in ("LoopEvaluator", "X86LoopEvaluator"):
|
|
57
|
+
from strilight.arch.x86.evaluator import LoopEvaluator
|
|
58
|
+
return LoopEvaluator
|
|
59
|
+
if name in ("SymbolicInductionAnalyzer", "X86SymbolicInductionAnalyzer"):
|
|
60
|
+
from strilight.arch.x86.symbolic import SymbolicInductionAnalyzer
|
|
61
|
+
return SymbolicInductionAnalyzer
|
|
62
|
+
if name in ("Instruction", "LoopBlock", "TraceCompressor"):
|
|
63
|
+
import strilight.arch as a
|
|
64
|
+
return getattr(a, name)
|
|
65
|
+
if name in ("ConditionExtractor", "StaticFlagTracker"):
|
|
66
|
+
import strilight.arch.x86 as x86
|
|
67
|
+
return getattr(x86, name)
|
|
68
|
+
if name in (
|
|
69
|
+
"Tracker",
|
|
70
|
+
"TraceRecord",
|
|
71
|
+
"BackwardSliceTracker",
|
|
72
|
+
"ForwardSliceTracker",
|
|
73
|
+
"Descendant",
|
|
74
|
+
"Ancestor",
|
|
75
|
+
):
|
|
76
|
+
import strilight.extensions.tracker as t
|
|
77
|
+
return getattr(t, name)
|
|
78
|
+
if name == "BackwardTracker":
|
|
79
|
+
import strilight.extensions.tracker as t
|
|
80
|
+
return getattr(t, "BackwardSliceTracker")
|
|
81
|
+
if name == "Z3Translator":
|
|
82
|
+
from strilight.extensions.translator import Z3Translator
|
|
83
|
+
return Z3Translator
|
|
84
|
+
if name in ("SymbolicStackEngine", "StackByteCell"):
|
|
85
|
+
import strilight.extensions.stack_engine as s
|
|
86
|
+
return getattr(s, name)
|
|
87
|
+
if name == "AnalyzerCore":
|
|
88
|
+
from strilight.extensions.core import AnalyzerCore
|
|
89
|
+
return AnalyzerCore
|
|
90
|
+
if name == "setup_hooks":
|
|
91
|
+
from strilight.extensions.hooks import setup_hooks
|
|
92
|
+
return setup_hooks
|
|
93
|
+
if name == "AngrBridge":
|
|
94
|
+
from strilight.extensions.angr_bridge import AngrBridge
|
|
95
|
+
return AngrBridge
|
|
96
|
+
except ImportError as e:
|
|
97
|
+
raise AttributeError(f"Optional module {name!r} could not be loaded: {e}")
|
|
98
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# =============================================================================
|
|
102
|
+
# High-Level Facade API (Instant Developer Experience)
|
|
103
|
+
# =============================================================================
|
|
104
|
+
|
|
105
|
+
def disassemble(code_bytes: bytes, base_address: int = 0x1000, bit_mode: int = 64) -> List[Any]:
|
|
106
|
+
"""
|
|
107
|
+
Disassembles raw machine code bytes into standard Instruction objects.
|
|
108
|
+
"""
|
|
109
|
+
from strilight.arch.instruction import Instruction
|
|
110
|
+
return Instruction.disassemble_bytes(code_bytes, base_address=base_address, bit_mode=bit_mode)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def compress(trace: List[Any], min_iterations: int = 3) -> List[Any]:
|
|
114
|
+
"""
|
|
115
|
+
Compresses repeated instruction execution traces into LoopBlock hierarchies.
|
|
116
|
+
"""
|
|
117
|
+
from strilight.arch.loop_compressor import TraceCompressor
|
|
118
|
+
return TraceCompressor.compress_trace(trace, min_iterations=min_iterations)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def evaluate(block_or_trace: Any, k_passes: int = 100, iterations: int = 1000) -> LoopSummary:
|
|
122
|
+
"""
|
|
123
|
+
Evaluates abstract strided intervals and generates the closed-form loop invariant contract.
|
|
124
|
+
"""
|
|
125
|
+
from strilight.arch.loop_compressor import LoopBlock
|
|
126
|
+
from strilight.arch.x86.evaluator import LoopEvaluator
|
|
127
|
+
if isinstance(block_or_trace, list):
|
|
128
|
+
block_or_trace = LoopBlock(body=block_or_trace, iterations=iterations)
|
|
129
|
+
evaluator = LoopEvaluator(k_passes=k_passes)
|
|
130
|
+
return evaluator.evaluate(block_or_trace)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def analyze(code_bytes: bytes, iterations: int = 1000, base_address: int = 0x1000, bit_mode: int = 64, k_passes: int = 100) -> LoopSummary:
|
|
134
|
+
"""
|
|
135
|
+
One-line end-to-end loop analysis:
|
|
136
|
+
Disassembles machine code bytes, wraps into a LoopBlock, and extracts closed-form deltas & invariant contracts.
|
|
137
|
+
"""
|
|
138
|
+
from strilight.arch.loop_compressor import LoopBlock
|
|
139
|
+
instructions = disassemble(code_bytes, base_address=base_address, bit_mode=bit_mode)
|
|
140
|
+
block = LoopBlock(body=instructions, iterations=iterations)
|
|
141
|
+
return evaluate(block, k_passes=k_passes)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
__all__ = [
|
|
145
|
+
# High-Level Facade Functions & Decorators
|
|
146
|
+
"disassemble",
|
|
147
|
+
"compress",
|
|
148
|
+
"evaluate",
|
|
149
|
+
"analyze",
|
|
150
|
+
"accelerate",
|
|
151
|
+
"accelerate_c_source",
|
|
152
|
+
|
|
153
|
+
# Logging Configuration
|
|
154
|
+
"logger",
|
|
155
|
+
"set_log_level",
|
|
156
|
+
"enable_logging",
|
|
157
|
+
|
|
158
|
+
# Core Classes
|
|
159
|
+
"Instruction",
|
|
160
|
+
"LoopBlock",
|
|
161
|
+
"TraceCompressor",
|
|
162
|
+
"LoopEvaluator",
|
|
163
|
+
"LoopSummary",
|
|
164
|
+
"LoopInvariantContract",
|
|
165
|
+
"ConditionExtractor",
|
|
166
|
+
"StaticFlagTracker",
|
|
167
|
+
"Interval",
|
|
168
|
+
"StridedInterval",
|
|
169
|
+
"DisjointIntervalSet",
|
|
170
|
+
"SymbolicStackEngine",
|
|
171
|
+
"StackByteCell",
|
|
172
|
+
"SourceLifter",
|
|
173
|
+
"CodeGenerator",
|
|
174
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strilight Engine Subpackage (strilight.engine)
|
|
3
|
+
==============================================
|
|
4
|
+
Pure mathematical foundation: abstract interpretation domains, abstract state,
|
|
5
|
+
recurrence equation models, and SMT closed-form translation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from strilight.engine.abstract_state import AbstractState
|
|
9
|
+
import strilight.engine.domains as domains
|
|
10
|
+
import strilight.engine.vsa as vsa
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AbstractState",
|
|
14
|
+
"domains",
|
|
15
|
+
"vsa",
|
|
16
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Dict, List, Tuple, Union
|
|
2
|
+
from strilight.engine.domains import Interval, StridedInterval, DisjointIntervalSet
|
|
3
|
+
|
|
4
|
+
class StridedMemoryMap:
|
|
5
|
+
"""
|
|
6
|
+
Abstract memory mapping using Strided Intervals for fast, safe loop execution.
|
|
7
|
+
Implements 'Safe Approximation' and 'Bézout Modulo Congruence Non-Aliasing'
|
|
8
|
+
(Notion Sections 3 & 8) to maintain O(1) mathematical speed and zero false aliasing.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self):
|
|
11
|
+
# List of memory transactions: (address_interval, size_in_bytes, value_dset)
|
|
12
|
+
self.writes: List[Tuple[Union[Interval, StridedInterval], int, DisjointIntervalSet]] = []
|
|
13
|
+
|
|
14
|
+
def write(self, addr: Union[Interval, StridedInterval], size: int, value: DisjointIntervalSet):
|
|
15
|
+
"""Records an abstract write transaction."""
|
|
16
|
+
self.writes.append((addr, size, value))
|
|
17
|
+
|
|
18
|
+
def read(self, addr: Union[Interval, StridedInterval], size: int) -> DisjointIntervalSet:
|
|
19
|
+
"""
|
|
20
|
+
Reads from the abstract memory.
|
|
21
|
+
Uses Bézout Modulo Congruence Non-Aliasing (Rule 8.b.1) and Must-Alias (Rule 8.b.2):
|
|
22
|
+
- If proven Definite Non-Alias via gcd(s1, s2) or disjoint bounds: skip safely.
|
|
23
|
+
- If read exactly matches a recent write (Must-Alias): returns the precise value.
|
|
24
|
+
- If complex partial overlap is detected: returns TOP (UNKNOWN).
|
|
25
|
+
"""
|
|
26
|
+
# Search backwards (most recent write first)
|
|
27
|
+
for w_addr, w_size, w_value in reversed(self.writes):
|
|
28
|
+
# 1. Modulo Congruence Non-Alias Test (Bézout GCD)
|
|
29
|
+
if hasattr(addr, 'is_disjoint_modulo') and hasattr(w_addr, 'is_disjoint_modulo'):
|
|
30
|
+
if addr.is_disjoint_modulo(w_addr):
|
|
31
|
+
continue # Definite Non-Alias: No interference!
|
|
32
|
+
|
|
33
|
+
# 2. Must-Alias Test
|
|
34
|
+
if hasattr(addr, 'is_must_alias') and hasattr(w_addr, 'is_must_alias'):
|
|
35
|
+
if addr.is_must_alias(w_addr) and size == w_size:
|
|
36
|
+
return w_value
|
|
37
|
+
|
|
38
|
+
# 3. Fallback Intersection Check for Interval objects
|
|
39
|
+
if hasattr(addr, 'intersect') and hasattr(w_addr, 'intersect'):
|
|
40
|
+
intersected = addr.intersect(w_addr)
|
|
41
|
+
if intersected.min_val <= intersected.max_val:
|
|
42
|
+
# Physical overlap detected
|
|
43
|
+
if (addr.min_val == w_addr.min_val and
|
|
44
|
+
addr.max_val == w_addr.max_val and
|
|
45
|
+
size == w_size and
|
|
46
|
+
getattr(addr, 'stride', 1) == getattr(w_addr, 'stride', 1) and
|
|
47
|
+
getattr(addr, 'stride_offset', 0) == getattr(w_addr, 'stride_offset', 0)):
|
|
48
|
+
return w_value
|
|
49
|
+
else:
|
|
50
|
+
# Complex partial overlap -> TOP (Safe Approximation)
|
|
51
|
+
dset = DisjointIntervalSet(k_limit=8)
|
|
52
|
+
dset.add(Interval(0, (1 << (size * 8)) - 1, size * 8))
|
|
53
|
+
return dset
|
|
54
|
+
else:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
# Not found in writes, return TOP (Symbolic/Unknown initial memory)
|
|
58
|
+
dset = DisjointIntervalSet(k_limit=8)
|
|
59
|
+
dset.add(Interval(0, (1 << (size * 8)) - 1, size * 8))
|
|
60
|
+
return dset
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AbstractState:
|
|
64
|
+
"""
|
|
65
|
+
Represents the full hardware state in the Abstract Domain for the Loop Evaluator.
|
|
66
|
+
"""
|
|
67
|
+
def __init__(self):
|
|
68
|
+
# Registers are stored as DisjointIntervalSets for Surgical Precision + K-Limit
|
|
69
|
+
self.registers: Dict[str, DisjointIntervalSet] = {}
|
|
70
|
+
|
|
71
|
+
# Strided Memory to track loop array access
|
|
72
|
+
self.memory = StridedMemoryMap()
|
|
73
|
+
|
|
74
|
+
# Flags are stored as Interval (3-valued logic: 0, 1, or Unknown/TOP)
|
|
75
|
+
# 1-bit width: [0, 0] means False, [1, 1] means True, [0, 1] means Unknown
|
|
76
|
+
self.flags: Dict[str, Interval] = {}
|
|
77
|
+
|
|
78
|
+
def get_register(self, reg_name: str, bit_width: int = 64) -> DisjointIntervalSet:
|
|
79
|
+
if reg_name not in self.registers:
|
|
80
|
+
dset = DisjointIntervalSet(k_limit=8)
|
|
81
|
+
# Default to TOP (Unknown)
|
|
82
|
+
dset.add(Interval(0, (1 << bit_width) - 1, bit_width))
|
|
83
|
+
self.registers[reg_name] = dset
|
|
84
|
+
return self.registers[reg_name]
|
|
85
|
+
|
|
86
|
+
def set_register(self, reg_name: str, value: DisjointIntervalSet):
|
|
87
|
+
self.registers[reg_name] = value
|
|
88
|
+
|
|
89
|
+
# Generic mathematical variable aliases
|
|
90
|
+
get_variable = get_register
|
|
91
|
+
set_variable = set_register
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def variables(self) -> Dict[str, DisjointIntervalSet]:
|
|
95
|
+
"""Generic alias for abstract variables/registers."""
|
|
96
|
+
return self.registers
|
|
97
|
+
|
|
98
|
+
def get_flag(self, flag_name: str) -> Interval:
|
|
99
|
+
if flag_name not in self.flags:
|
|
100
|
+
# TOP for 1 bit is [0, 1] (Unknown)
|
|
101
|
+
self.flags[flag_name] = Interval(0, 1, 1)
|
|
102
|
+
return self.flags[flag_name]
|
|
103
|
+
|
|
104
|
+
def set_flag(self, flag_name: str, value: Interval):
|
|
105
|
+
assert value.bit_width == 1, f"Flags must be 1-bit Intervals, got {value.bit_width}-bit"
|
|
106
|
+
self.flags[flag_name] = value
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strilight Mathematical Abstract Domains (strilight.engine.domains)
|
|
3
|
+
==================================================================
|
|
4
|
+
Modular ring arithmetic intervals, Strided Intervals, Bézout GCD congruence,
|
|
5
|
+
dual-mask reduced products, and disjoint set management for VSA.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from strilight.engine.domains.interval import (
|
|
9
|
+
Interval,
|
|
10
|
+
StridedInterval,
|
|
11
|
+
DisjointIntervalSet,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Interval",
|
|
16
|
+
"StridedInterval",
|
|
17
|
+
"DisjointIntervalSet",
|
|
18
|
+
]
|