mcasm 0.4.3__cp315-cp315-win_amd64.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.
- mcasm/__init__.py +4 -0
- mcasm/__main__.py +306 -0
- mcasm/_core/__init__.pyi +510 -0
- mcasm/_core/mc/__init__.pyi +1588 -0
- mcasm/_core.cp315-win_amd64.pyd +0 -0
- mcasm/py.typed +0 -0
- mcasm/version.py +1 -0
- mcasm-0.4.3.dist-info/METADATA +60 -0
- mcasm-0.4.3.dist-info/RECORD +12 -0
- mcasm-0.4.3.dist-info/WHEEL +5 -0
- mcasm-0.4.3.dist-info/licenses/LICENSE.txt +21 -0
- mcasm-0.4.3.dist-info/top_level.txt +2 -0
mcasm/__init__.py
ADDED
mcasm/__main__.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import inspect
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import mcasm
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import rich
|
|
11
|
+
import rich.tree
|
|
12
|
+
except ImportError:
|
|
13
|
+
print("Please install 'mcasm[cli]' to use this command.", file=sys.stderr)
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
# types.NoneType is too new to rely on, so just declare it here.
|
|
17
|
+
NoneType = type(None)
|
|
18
|
+
|
|
19
|
+
FilterFunc = Callable[[str, mcasm.ParserState], bool]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PrettyPrintingStreamer(mcasm.Streamer):
|
|
23
|
+
def __init__(self, filter_func: FilterFunc, verbose: bool):
|
|
24
|
+
self._current_tree: Optional[rich.tree.Tree] = None
|
|
25
|
+
self._argnames = self._extract_arg_names()
|
|
26
|
+
self._filter_func = filter_func
|
|
27
|
+
self._verbose = verbose
|
|
28
|
+
super().__init__()
|
|
29
|
+
|
|
30
|
+
def _parse_help_decl(self, method_help: str) -> Optional[List[str]]:
|
|
31
|
+
"""
|
|
32
|
+
Parses a pybind11-generated docstr to extract parameter names.
|
|
33
|
+
|
|
34
|
+
This is required because pybind11 methods are not compatible with
|
|
35
|
+
inspect.Signature.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
decl_re = re.compile(r"[a-zA-Z0-9_]+\((.*)\) -> .+")
|
|
39
|
+
arg_re = re.compile(r"([a-zA-Z0-9_]+): [a-zA-Z0-9_.]+")
|
|
40
|
+
|
|
41
|
+
for line in method_help.splitlines():
|
|
42
|
+
match = decl_re.match(line)
|
|
43
|
+
if match:
|
|
44
|
+
return [
|
|
45
|
+
arg_match.group(1)
|
|
46
|
+
for arg_match in arg_re.finditer(match.group(1))
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def _extract_arg_names(self) -> Dict[str, List[str]]:
|
|
52
|
+
"""
|
|
53
|
+
Generates a map from method name to their argument names.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
result: Dict[str, List[str]] = {}
|
|
57
|
+
|
|
58
|
+
for name in dir(self):
|
|
59
|
+
if name[0] == "_" or name == "unhandled_event":
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
attr = getattr(self, name)
|
|
63
|
+
if not inspect.ismethod(attr) or not attr.__doc__:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
arg_names = self._parse_help_decl(attr.__doc__)
|
|
67
|
+
if arg_names is not None:
|
|
68
|
+
if arg_names and arg_names[0] == "self":
|
|
69
|
+
del arg_names[0]
|
|
70
|
+
result[name] = arg_names
|
|
71
|
+
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
def _is_enum_like(self, value: Any) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Determines if a value looks like a pybind11 enum instance.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
hasattr(value, "__members__")
|
|
81
|
+
and hasattr(value, "value")
|
|
82
|
+
and hasattr(value, "name")
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _is_default_value(self, value: Any) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Is this value the default value for the type?
|
|
88
|
+
"""
|
|
89
|
+
if value is None:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
if isinstance(value, (int, bool, str, float, bytes, list, dict, set)):
|
|
93
|
+
return value == type(value)()
|
|
94
|
+
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def _add_value_node(
|
|
98
|
+
self,
|
|
99
|
+
parent: rich.tree.Tree,
|
|
100
|
+
name: str,
|
|
101
|
+
value: Any,
|
|
102
|
+
force_print: bool = False,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Adds a named value to the tree, potentially creating additional child
|
|
106
|
+
nodes.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
not self._verbose
|
|
111
|
+
and not force_print
|
|
112
|
+
and self._is_default_value(value)
|
|
113
|
+
):
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if isinstance(value, (int, bool, str, float, bytes, NoneType)):
|
|
117
|
+
parent.add(f"{name} = {value!r}")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
if self._is_enum_like(value):
|
|
121
|
+
parent.add(f"{name} = {type(value).__qualname__}.{value.name}")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if isinstance(value, list):
|
|
125
|
+
if not value:
|
|
126
|
+
parent.add(f"{name} = []")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
node = parent.add(f"{name} [i](list)[/i]")
|
|
130
|
+
for i, value in enumerate(value):
|
|
131
|
+
self._add_value_node(node, f"[{i}]", value, force_print=True)
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
if isinstance(value, dict):
|
|
135
|
+
if not value:
|
|
136
|
+
parent.add(name + " = {}")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
node = parent.add(f"{name} [i](dict)[/i]")
|
|
140
|
+
for key, value in value.items():
|
|
141
|
+
self._add_value_node(node, key, value, force_print=True)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
# Assume all other types should be printed as a class with attributes
|
|
145
|
+
node = parent.add(f"{name} [i]({type(value).__name__})[/i]")
|
|
146
|
+
for name in sorted(dir(type(value))):
|
|
147
|
+
if name[0] == "_":
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
type_attr = getattr(type(value), name)
|
|
151
|
+
if not isinstance(type_attr, property):
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
self._add_value_node(node, name, getattr(value, name))
|
|
155
|
+
|
|
156
|
+
def unhandled_event(self, name: str, base_impl, *args, **kwargs):
|
|
157
|
+
if not self._filter_func(name, args[0]):
|
|
158
|
+
return super().unhandled_event(name, base_impl, *args, **kwargs)
|
|
159
|
+
|
|
160
|
+
def make_node(value: str) -> rich.tree.Tree:
|
|
161
|
+
if self._current_tree:
|
|
162
|
+
return self._current_tree.add(value)
|
|
163
|
+
else:
|
|
164
|
+
self._current_tree = rich.tree.Tree(value)
|
|
165
|
+
return self._current_tree
|
|
166
|
+
|
|
167
|
+
root = make_node(f"⚡️ [b]{name}[/b]")
|
|
168
|
+
arg_names = self._argnames.get(name, [])
|
|
169
|
+
for i, (arg_name, arg) in enumerate(zip(arg_names, args)):
|
|
170
|
+
self._add_value_node(
|
|
171
|
+
root, arg_name or f"[{i}]", arg, force_print=True
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Call into super here because this may trigger additional events to
|
|
175
|
+
# fire and we want those to be nested in our tree.
|
|
176
|
+
result = super().unhandled_event(name, base_impl, *args, **kwargs)
|
|
177
|
+
|
|
178
|
+
if root == self._current_tree:
|
|
179
|
+
rich.print(root)
|
|
180
|
+
self._current_tree = None
|
|
181
|
+
print()
|
|
182
|
+
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class NameOnlyStreamer(mcasm.Streamer):
|
|
187
|
+
def __init__(self, filter_func: FilterFunc):
|
|
188
|
+
self._filter_func = filter_func
|
|
189
|
+
super().__init__()
|
|
190
|
+
|
|
191
|
+
def unhandled_event(self, name: str, base_impl, *args, **kwargs) -> Any:
|
|
192
|
+
if self._filter_func(name, args[0]):
|
|
193
|
+
print(name)
|
|
194
|
+
|
|
195
|
+
return super().unhandled_event(name, base_impl, *args, **kwargs)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def make_line_filter(start: int, end: Optional[int]) -> FilterFunc:
|
|
199
|
+
"""
|
|
200
|
+
Creates a filter function that matches line numbers.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def line_filter(event_name: str, state: mcasm.ParserState) -> bool:
|
|
204
|
+
if end is not None:
|
|
205
|
+
return start >= state.loc.lineno >= end
|
|
206
|
+
else:
|
|
207
|
+
return start == state.loc.lineno
|
|
208
|
+
|
|
209
|
+
return line_filter
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def make_event_filter(name: str) -> FilterFunc:
|
|
213
|
+
"""
|
|
214
|
+
Creates a filter function that matches event names.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
def event_filter(event_name: str, state: mcasm.ParserState) -> bool:
|
|
218
|
+
return event_name == name
|
|
219
|
+
|
|
220
|
+
return event_filter
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def filter_type(value: str) -> FilterFunc:
|
|
224
|
+
"""
|
|
225
|
+
An argparse type that accepts filters (event names or line numbers).
|
|
226
|
+
"""
|
|
227
|
+
lineno_re = re.compile("([0-9]+)(?:-([0-9]+))")
|
|
228
|
+
name_re = re.compile("([a-z][a-zA-Z0-9_]+)")
|
|
229
|
+
|
|
230
|
+
match = lineno_re.match(value)
|
|
231
|
+
if match:
|
|
232
|
+
start = int(match.group(1))
|
|
233
|
+
end = int(match.group(2)) if match.group(2) is not None else None
|
|
234
|
+
return make_line_filter(start, end)
|
|
235
|
+
|
|
236
|
+
match = name_re.match(value)
|
|
237
|
+
if match:
|
|
238
|
+
return make_event_filter(match.group(1))
|
|
239
|
+
|
|
240
|
+
raise argparse.ArgumentTypeError("unknown filter type")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main() -> None:
|
|
244
|
+
ap = argparse.ArgumentParser(
|
|
245
|
+
description="parse an assembly file and print out the mc-asm callbacks"
|
|
246
|
+
)
|
|
247
|
+
ap.add_argument(
|
|
248
|
+
"asm", type=argparse.FileType("r"), help="the assembly file to parse"
|
|
249
|
+
)
|
|
250
|
+
ap.add_argument(
|
|
251
|
+
"--target",
|
|
252
|
+
default=mcasm.Assembler.default_triple(),
|
|
253
|
+
help="the target triple to use (default: %(default)s)",
|
|
254
|
+
)
|
|
255
|
+
ap.add_argument(
|
|
256
|
+
"--syntax",
|
|
257
|
+
choices=["intel", "att"],
|
|
258
|
+
default="intel",
|
|
259
|
+
help="the syntax to use (default: %(default)s)",
|
|
260
|
+
)
|
|
261
|
+
ap.add_argument(
|
|
262
|
+
"--names-only",
|
|
263
|
+
action="store_true",
|
|
264
|
+
help="only print the callback names instead of the detailed output",
|
|
265
|
+
)
|
|
266
|
+
ap.add_argument(
|
|
267
|
+
"--filter",
|
|
268
|
+
action="append",
|
|
269
|
+
type=filter_type,
|
|
270
|
+
help="only print out callbacks with a given name or line number",
|
|
271
|
+
)
|
|
272
|
+
ap.add_argument(
|
|
273
|
+
"--verbose", action="store_true", help="print out all object values"
|
|
274
|
+
)
|
|
275
|
+
args = ap.parse_args()
|
|
276
|
+
|
|
277
|
+
if args.syntax == "intel":
|
|
278
|
+
syntax = mcasm.X86Syntax.INTEL
|
|
279
|
+
elif args.syntax == "att":
|
|
280
|
+
syntax = mcasm.X86Syntax.ATT
|
|
281
|
+
else:
|
|
282
|
+
assert False, "invalid syntax name"
|
|
283
|
+
|
|
284
|
+
with args.asm:
|
|
285
|
+
asm = args.asm.read()
|
|
286
|
+
|
|
287
|
+
assembler = mcasm.Assembler(args.target)
|
|
288
|
+
assembler.x86_syntax = syntax
|
|
289
|
+
|
|
290
|
+
def filter_func(event_name: str, state: mcasm.ParserState) -> bool:
|
|
291
|
+
if not args.filter:
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
return any(f(event_name, state) for f in args.filter)
|
|
295
|
+
|
|
296
|
+
if args.names_only:
|
|
297
|
+
streamer = NameOnlyStreamer(filter_func)
|
|
298
|
+
else:
|
|
299
|
+
streamer = PrettyPrintingStreamer(filter_func, args.verbose)
|
|
300
|
+
|
|
301
|
+
if not assembler.assemble(streamer, asm):
|
|
302
|
+
sys.exit(1)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
main()
|