xasm 1.2.0__py310-none-any.whl → 1.2.1__py310-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.
- xasm/assemble.py +307 -114
- xasm/pyc_convert.py +36 -32
- xasm/version.py +1 -1
- xasm/write_pyc.py +10 -6
- xasm/xasm_cli.py +21 -9
- {xasm-1.2.0.dist-info → xasm-1.2.1.dist-info}/METADATA +54 -27
- xasm-1.2.1.dist-info/RECORD +13 -0
- {xasm-1.2.0.dist-info → xasm-1.2.1.dist-info}/WHEEL +1 -1
- xasm-1.2.1.dist-info/entry_points.txt +3 -0
- xasm-1.2.0.dist-info/RECORD +0 -13
- xasm-1.2.0.dist-info/entry_points.txt +0 -5
- {xasm-1.2.0.dist-info → xasm-1.2.1.dist-info/licenses}/LICENSE.gpl2 +0 -0
- {xasm-1.2.0.dist-info → xasm-1.2.1.dist-info}/top_level.txt +0 -0
- {xasm-1.2.0.dist-info → xasm-1.2.1.dist-info}/zip-safe +0 -0
xasm/assemble.py
CHANGED
@@ -1,27 +1,35 @@
|
|
1
1
|
#!/usr/bin/env python
|
2
|
-
import ast
|
3
|
-
|
2
|
+
import ast
|
3
|
+
import re
|
4
|
+
from typing import Any, List, Optional
|
5
|
+
|
6
|
+
import xdis
|
4
7
|
from xdis import get_opcode, load_module
|
8
|
+
from xdis.opcodes.base import cmp_op
|
5
9
|
from xdis.version_info import PYTHON_VERSION_TRIPLE, version_str_to_tuple
|
6
10
|
|
7
11
|
# import xdis.bytecode as Mbytecode
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
13
|
+
|
14
|
+
class Instruction: # (Mbytecode.Instruction):
|
15
|
+
line_no: Optional[int]
|
16
|
+
opname: str
|
17
|
+
arg: Optional[int]
|
18
|
+
|
19
|
+
def __repr__(self) -> str:
|
12
20
|
if self.line_no:
|
13
21
|
s = "%4d: " % self.line_no
|
14
22
|
else:
|
15
23
|
s = " " * 6
|
16
|
-
s += "
|
24
|
+
s += f"{self.opname:15}"
|
17
25
|
if self.arg is not None:
|
18
|
-
s += "\t
|
26
|
+
s += f"\t{self.arg}"
|
19
27
|
return s
|
20
28
|
|
21
29
|
pass
|
22
30
|
|
23
31
|
|
24
|
-
def is_int(s):
|
32
|
+
def is_int(s: Any) -> bool:
|
25
33
|
try:
|
26
34
|
int(s)
|
27
35
|
return True
|
@@ -29,11 +37,11 @@ def is_int(s):
|
|
29
37
|
return False
|
30
38
|
|
31
39
|
|
32
|
-
def
|
40
|
+
def match_lineno(s: str):
|
33
41
|
return re.match(r"^\d+:", s)
|
34
42
|
|
35
43
|
|
36
|
-
def get_opname_operand(opc, fields):
|
44
|
+
def get_opname_operand(opc, fields: List[str]):
|
37
45
|
assert len(fields) > 0
|
38
46
|
opname = fields[0]
|
39
47
|
if opc.opmap[opname] < opc.HAVE_ARGUMENT:
|
@@ -42,9 +50,9 @@ def get_opname_operand(opc, fields):
|
|
42
50
|
if is_int(fields[1]):
|
43
51
|
operand = int(fields[1])
|
44
52
|
else:
|
45
|
-
operand =
|
53
|
+
operand = " ".join(fields[1:])
|
46
54
|
if operand.startswith("(to "):
|
47
|
-
int_val = operand[len("(to "):]
|
55
|
+
int_val = operand[len("(to ") :]
|
48
56
|
# In xasm format this shouldn't appear
|
49
57
|
if is_int(int_val):
|
50
58
|
operand = int(int_val)
|
@@ -54,12 +62,12 @@ def get_opname_operand(opc, fields):
|
|
54
62
|
return opname, None
|
55
63
|
|
56
64
|
|
57
|
-
class Assembler
|
58
|
-
def __init__(self, python_version, is_pypy):
|
65
|
+
class Assembler:
|
66
|
+
def __init__(self, python_version, is_pypy) -> None:
|
59
67
|
self.opc = get_opcode(python_version, is_pypy)
|
60
68
|
self.code_list = []
|
61
69
|
self.codes = [] # FIXME use a better name
|
62
|
-
self.status = "unfinished"
|
70
|
+
self.status: str = "unfinished"
|
63
71
|
self.size = 0 # Size of source code. Only relevant in version 3 and above
|
64
72
|
self.python_version = python_version
|
65
73
|
self.timestamp = None
|
@@ -68,8 +76,7 @@ class Assembler(object):
|
|
68
76
|
self.code = None
|
69
77
|
self.siphash = None
|
70
78
|
|
71
|
-
def code_init(self, python_version=None):
|
72
|
-
|
79
|
+
def code_init(self, python_version=None) -> None:
|
73
80
|
if self.python_version is None and python_version:
|
74
81
|
self.python_version = python_version
|
75
82
|
|
@@ -95,50 +102,70 @@ class Assembler(object):
|
|
95
102
|
|
96
103
|
self.code.instructions = []
|
97
104
|
|
98
|
-
def update_lists(self, co, label, backpatch):
|
105
|
+
def update_lists(self, co, label, backpatch) -> None:
|
99
106
|
self.code_list.append(co)
|
100
107
|
self.codes.append(self.code)
|
101
108
|
self.label.append(label)
|
102
109
|
self.backpatch.append(backpatch)
|
103
110
|
|
104
|
-
def print_instructions(self):
|
111
|
+
def print_instructions(self) -> None:
|
105
112
|
for inst in self.code.instructions:
|
106
113
|
if inst.line_no:
|
107
114
|
print()
|
108
115
|
print(inst)
|
109
116
|
|
110
|
-
def
|
111
|
-
|
117
|
+
def warn(self, mess: str) -> None:
|
118
|
+
"""
|
119
|
+
Print an error message and record that we warned, unless we have already errored.
|
120
|
+
"""
|
121
|
+
print("Warning: ", mess)
|
122
|
+
if self.status != "errored":
|
123
|
+
self.status = "warning"
|
124
|
+
|
125
|
+
def err(self, mess: str) -> None:
|
126
|
+
"""
|
127
|
+
Print an error message and record that we errored.
|
128
|
+
"""
|
129
|
+
print("Error: ", mess)
|
112
130
|
self.status = "errored"
|
113
131
|
|
114
132
|
|
115
|
-
def asm_file(path):
|
133
|
+
def asm_file(path) -> Optional[Assembler]:
|
116
134
|
offset = 0
|
117
135
|
methods = {}
|
118
136
|
method_name = None
|
119
137
|
asm = None
|
120
138
|
backpatch_inst = set([])
|
121
139
|
label = {}
|
122
|
-
|
140
|
+
python_bytecode_version = None
|
123
141
|
lines = open(path).readlines()
|
124
142
|
i = 0
|
125
143
|
bytecode_seen = False
|
126
144
|
while i < len(lines):
|
127
145
|
line = lines[i]
|
128
146
|
i += 1
|
147
|
+
if line.startswith("##"):
|
148
|
+
# comment line
|
149
|
+
continue
|
129
150
|
if line.startswith(".READ"):
|
130
151
|
match = re.match("^.READ (.+)$", line)
|
131
152
|
if match:
|
132
153
|
input_pyc = match.group(1)
|
133
154
|
print(f"Reading {input_pyc}")
|
134
|
-
(
|
135
|
-
|
136
|
-
|
137
|
-
|
155
|
+
(
|
156
|
+
version,
|
157
|
+
timestamp,
|
158
|
+
magic_int,
|
159
|
+
co,
|
160
|
+
is_pypy,
|
161
|
+
source_size,
|
162
|
+
sip_hash,
|
163
|
+
) = load_module(input_pyc)
|
164
|
+
if python_bytecode_version and python_bytecode_version != version:
|
138
165
|
TypeError(
|
139
|
-
f"We previously saw Python version {
|
166
|
+
f"We previously saw Python version {python_bytecode_version} but we just loaded {version}.\n"
|
140
167
|
)
|
141
|
-
|
168
|
+
python_bytecode_version = version
|
142
169
|
# FIXME: extract all code options below the top-level and asm.code_list
|
143
170
|
|
144
171
|
elif line.startswith("#"):
|
@@ -151,15 +178,18 @@ def asm_file(path):
|
|
151
178
|
is_pypy = False
|
152
179
|
pypy_str = ""
|
153
180
|
|
154
|
-
|
181
|
+
python_bytecode_version = (
|
155
182
|
line[len("# Python bytecode " + pypy_str) :].strip().split()[0]
|
156
183
|
)
|
157
184
|
|
158
|
-
python_version_pair = version_str_to_tuple(
|
185
|
+
python_version_pair = version_str_to_tuple(
|
186
|
+
python_bytecode_version, length=2
|
187
|
+
)
|
159
188
|
asm = Assembler(python_version_pair, is_pypy)
|
160
189
|
if python_version_pair >= (3, 10):
|
161
190
|
TypeError(
|
162
|
-
f"Creating Python version {
|
191
|
+
f"Creating Python version {python_bytecode_version} not supported yet. "
|
192
|
+
"Feel free to fix and put in a PR.\n"
|
163
193
|
)
|
164
194
|
asm.code_init(python_version_pair)
|
165
195
|
bytecode_seen = True
|
@@ -170,15 +200,25 @@ def asm_file(path):
|
|
170
200
|
asm.timestamp = int(time_str)
|
171
201
|
elif line.startswith("# Method Name: "):
|
172
202
|
if method_name:
|
173
|
-
co = create_code(asm, label, backpatch_inst)
|
203
|
+
co, is_valid = create_code(asm, label, backpatch_inst)
|
204
|
+
if not is_valid:
|
205
|
+
return
|
174
206
|
asm.update_lists(co, label, backpatch_inst)
|
175
207
|
label = {}
|
176
208
|
backpatch_inst = set([])
|
177
209
|
methods[method_name] = co
|
178
210
|
offset = 0
|
179
|
-
|
211
|
+
if python_bytecode_version is None:
|
212
|
+
raise TypeError(
|
213
|
+
f'Line {i}: "Python bytecode" not seen before "Method Name:"; please set this.'
|
214
|
+
)
|
215
|
+
python_version_pair = version_str_to_tuple(
|
216
|
+
python_bytecode_version, length=2
|
217
|
+
)
|
180
218
|
asm.code_init(python_version_pair)
|
181
|
-
asm.code.co_name = line[
|
219
|
+
asm.code.co_qual_name = asm.code.co_name = line[
|
220
|
+
len("# Method Name: ") :
|
221
|
+
].strip()
|
182
222
|
method_name = asm.code.co_name
|
183
223
|
elif line.startswith("# SipHash: "):
|
184
224
|
siphash = line[len("# ShipHash: ") :].strip().split()[0]
|
@@ -227,9 +267,8 @@ def asm_file(path):
|
|
227
267
|
if match:
|
228
268
|
index = int(match.group(1))
|
229
269
|
assert index == count, (
|
230
|
-
"Constant index {
|
231
|
-
"doesn't match expected constant index {
|
232
|
-
% (index, i, count)
|
270
|
+
f"Constant index {index} found on line {i} "
|
271
|
+
f"doesn't match expected constant index {count}."
|
233
272
|
)
|
234
273
|
expr = match.group(2)
|
235
274
|
match = re.match(
|
@@ -239,7 +278,7 @@ def asm_file(path):
|
|
239
278
|
name = match.group(1)
|
240
279
|
m2 = re.match("^<(.+)>$", name)
|
241
280
|
if m2:
|
242
|
-
name = "
|
281
|
+
name = f"{m2.group(1)}_{match.group(2)}"
|
243
282
|
if name in methods:
|
244
283
|
asm.code.co_consts.append(methods[name])
|
245
284
|
else:
|
@@ -248,7 +287,7 @@ def asm_file(path):
|
|
248
287
|
)
|
249
288
|
bogus_name = f"**bogus {name}**"
|
250
289
|
print(f"\t appending {bogus_name} to list of constants")
|
251
|
-
asm.code.co_consts.append()
|
290
|
+
asm.code.co_consts.append(bogus_name)
|
252
291
|
else:
|
253
292
|
asm.code.co_consts.append(ast.literal_eval(expr))
|
254
293
|
count += 1
|
@@ -276,46 +315,73 @@ def asm_file(path):
|
|
276
315
|
if not line.strip():
|
277
316
|
continue
|
278
317
|
|
279
|
-
match = re.match(r"^(
|
318
|
+
match = re.match(r"^(\S+):$", line)
|
280
319
|
if match:
|
281
|
-
|
282
|
-
|
320
|
+
label_value = match.group(1)
|
321
|
+
# All-numeric labels, i.e. line numbers, are handled below
|
322
|
+
if not re.match(r"^(\d+)$", label_value):
|
323
|
+
label[label_value] = offset
|
324
|
+
continue
|
325
|
+
|
326
|
+
line_no = None
|
327
|
+
|
328
|
+
match = re.match(r"^\s*(\d+):\s*", line)
|
329
|
+
|
330
|
+
# Sanity checking: make sure we have seen
|
331
|
+
# proper header lines
|
332
|
+
if i == 1:
|
333
|
+
assert bytecode_seen, (
|
334
|
+
f"Improper beginning:\n{line}"
|
335
|
+
"\nLine should begin with '#' "
|
336
|
+
"and contain header bytecode header information."
|
337
|
+
)
|
338
|
+
assert bytecode_seen, (
|
339
|
+
f"Error translating line {i}: "
|
340
|
+
"a line before this should include: \n"
|
341
|
+
"# Python bytecode <version>"
|
342
|
+
)
|
283
343
|
|
284
|
-
match = re.match(r"^\s*([\d]+):\s*$", line)
|
285
344
|
if match:
|
286
345
|
line_no = int(match.group(1))
|
287
|
-
|
288
|
-
|
346
|
+
linetable_field = (
|
347
|
+
"co_lnotab" if python_version_pair < (3, 10) else "co_linetable"
|
348
|
+
)
|
349
|
+
assert asm is not None
|
350
|
+
linetable = getattr(asm.code, linetable_field)
|
351
|
+
linetable[offset] = line_no
|
289
352
|
|
290
353
|
# Opcode section
|
291
|
-
assert (
|
292
|
-
bytecode_seen
|
293
|
-
), "File needs to start out with: # Python bytecode <version>"
|
294
354
|
fields = line.strip().split()
|
295
|
-
line_no = None
|
296
355
|
num_fields = len(fields)
|
297
356
|
|
298
|
-
if num_fields
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
if
|
303
|
-
line_no = int(fields[0][:-1])
|
304
|
-
opname, operand = get_opname_operand(asm.opc, fields[2:])
|
305
|
-
elif is_lineno(fields[0]):
|
306
|
-
line_no = int(fields[0][:-1])
|
307
|
-
fields = fields[1:]
|
357
|
+
if num_fields == 1 and line_no is not None:
|
358
|
+
continue
|
359
|
+
|
360
|
+
try:
|
361
|
+
if num_fields > 1:
|
308
362
|
if fields[0] == ">>":
|
309
363
|
fields = fields[1:]
|
310
|
-
|
364
|
+
num_fields -= 1
|
365
|
+
if match_lineno(fields[0]) and is_int(fields[1]):
|
366
|
+
line_no = int(fields[0][:-1])
|
367
|
+
opname, operand = get_opname_operand(asm.opc, fields[2:])
|
368
|
+
elif match_lineno(fields[0]):
|
369
|
+
line_no = int(fields[0][:-1])
|
370
|
+
fields = fields[1:]
|
371
|
+
if fields[0] == ">>":
|
311
372
|
fields = fields[1:]
|
312
|
-
|
313
|
-
|
314
|
-
|
373
|
+
if is_int(fields[0]):
|
374
|
+
fields = fields[1:]
|
375
|
+
opname, operand = get_opname_operand(asm.opc, fields)
|
376
|
+
elif is_int(fields[0]):
|
377
|
+
opname, operand = get_opname_operand(asm.opc, fields[1:])
|
378
|
+
else:
|
379
|
+
opname, operand = get_opname_operand(asm.opc, fields)
|
315
380
|
else:
|
316
|
-
opname,
|
317
|
-
|
318
|
-
|
381
|
+
opname, _ = get_opname_operand(asm.opc, fields)
|
382
|
+
except Exception as e:
|
383
|
+
print(f"Line {i}: {e}")
|
384
|
+
raise
|
319
385
|
|
320
386
|
if opname in asm.opc.opname:
|
321
387
|
inst = Instruction()
|
@@ -332,19 +398,22 @@ def asm_file(path):
|
|
332
398
|
backpatch_inst.add(inst)
|
333
399
|
offset += xdis.op_size(inst.opcode, asm.opc)
|
334
400
|
else:
|
335
|
-
raise RuntimeError("Illegal opname
|
401
|
+
raise RuntimeError(f"Illegal opname {opname} in:\n{line}")
|
336
402
|
pass
|
337
403
|
pass
|
338
|
-
|
339
|
-
if asm:
|
340
|
-
|
404
|
+
|
405
|
+
if asm is not None:
|
406
|
+
# print(linetable)
|
407
|
+
|
408
|
+
co, is_valid = create_code(asm, label, backpatch_inst)
|
341
409
|
asm.update_lists(co, label, backpatch_inst)
|
342
|
-
|
343
|
-
|
410
|
+
asm.code_list.reverse()
|
411
|
+
asm.status = "finished"
|
412
|
+
|
344
413
|
return asm
|
345
414
|
|
346
415
|
|
347
|
-
def member(fields, match_value):
|
416
|
+
def member(fields, match_value) -> int:
|
348
417
|
for i, v in enumerate(fields):
|
349
418
|
if v == match_value and type(v) == type(match_value):
|
350
419
|
return i
|
@@ -352,7 +421,7 @@ def member(fields, match_value):
|
|
352
421
|
return -1
|
353
422
|
|
354
423
|
|
355
|
-
def update_code_field(field_name, value, inst, opc):
|
424
|
+
def update_code_field(field_name: str, value, inst, opc) -> None:
|
356
425
|
field_values = getattr(opc, field_name)
|
357
426
|
# Can't use "in" because True == 1 and False == 0
|
358
427
|
# if value in l:
|
@@ -364,7 +433,7 @@ def update_code_field(field_name, value, inst, opc):
|
|
364
433
|
field_values.append(value)
|
365
434
|
|
366
435
|
|
367
|
-
def update_code_tuple_field(field_name, code, lines, i):
|
436
|
+
def update_code_tuple_field(field_name: str, code, lines: List[str], i: int):
|
368
437
|
count = 0
|
369
438
|
while i < len(lines):
|
370
439
|
line = lines[i]
|
@@ -372,7 +441,9 @@ def update_code_tuple_field(field_name, code, lines, i):
|
|
372
441
|
match = re.match(r"^#\s+(\d+): (.+)$", line)
|
373
442
|
if match:
|
374
443
|
index = int(match.group(1))
|
375
|
-
assert
|
444
|
+
assert (
|
445
|
+
index == count
|
446
|
+
), f'In field" "{field_name}", line {i}, number {index} is expected to have value {count}.'
|
376
447
|
field_values = getattr(code, field_name)
|
377
448
|
field_values.append(match.group(2))
|
378
449
|
count += 1
|
@@ -380,17 +451,26 @@ def update_code_tuple_field(field_name, code, lines, i):
|
|
380
451
|
i -= 1
|
381
452
|
break
|
382
453
|
pass
|
383
|
-
pass
|
384
454
|
return i
|
385
455
|
|
386
456
|
|
387
|
-
def err(msg, inst, i):
|
457
|
+
def err(msg: str, inst, i: int):
|
388
458
|
msg += ". Instruction %d:\n%s" % (i, inst)
|
389
459
|
raise RuntimeError(msg)
|
390
460
|
|
391
461
|
|
392
|
-
def
|
462
|
+
def warn(mess: str) -> None:
|
463
|
+
"""
|
464
|
+
Print an error message and record that we warned.
|
465
|
+
"""
|
466
|
+
print("Warning: ", mess)
|
467
|
+
|
393
468
|
|
469
|
+
def decode_lineno_tab_old(lnotab, first_lineno: int) -> dict:
|
470
|
+
"""
|
471
|
+
Uncompresses line number table for Python versions before
|
472
|
+
3.10
|
473
|
+
"""
|
394
474
|
line_number, line_number_diff = first_lineno, 0
|
395
475
|
offset, offset_diff = 0, 0
|
396
476
|
uncompressed_lnotab = {}
|
@@ -409,28 +489,138 @@ def decode_lineno_tab(lnotab, first_lineno):
|
|
409
489
|
continue
|
410
490
|
line_number += line_number_diff
|
411
491
|
offset += offset_diff
|
412
|
-
line_number_diff, offset_diff = 0, 0
|
413
492
|
uncompressed_lnotab[offset] = line_number
|
414
493
|
|
415
494
|
return uncompressed_lnotab
|
416
495
|
|
417
496
|
|
418
|
-
def
|
497
|
+
def is_code_ok(asm: Assembler) -> bool:
|
498
|
+
"""
|
499
|
+
Performs some sanity checks on code.
|
500
|
+
"""
|
501
|
+
|
502
|
+
is_valid: bool = True
|
503
|
+
|
504
|
+
code = asm.code
|
505
|
+
last_instruction = code.instructions[-1]
|
506
|
+
last_offset = last_instruction.offset
|
507
|
+
if last_instruction.opname not in ("RETURN_VALUE", "RERAISE", "RAISE_VARARGS"):
|
508
|
+
warn(
|
509
|
+
f"Last instruction of at offset {last_offset} of {code.co_name}"
|
510
|
+
f' should be "RETURN_VALUE", is "{last_instruction.opname}"'
|
511
|
+
)
|
512
|
+
is_valid = False
|
513
|
+
|
514
|
+
cells_free_len = len(code.co_freevars) + len(code.co_cellvars)
|
515
|
+
consts_len = len(code.co_consts)
|
516
|
+
names_len = len(code.co_names)
|
517
|
+
varnames_len = len(code.co_varnames)
|
518
|
+
|
519
|
+
for i, inst in enumerate(code.instructions):
|
520
|
+
if xdis.op_has_argument(inst.opcode, asm.opc):
|
521
|
+
if is_int(inst.arg):
|
522
|
+
if inst.opcode == asm.opc.EXTENDED_ARG:
|
523
|
+
continue
|
524
|
+
operand = inst.arg
|
525
|
+
if inst.opcode in asm.opc.CONST_OPS:
|
526
|
+
# FIXME: DRY operand check
|
527
|
+
if operand >= consts_len:
|
528
|
+
print(inst)
|
529
|
+
warn(
|
530
|
+
f"Constant operand index {operand} at offset {inst.offset} of {code.co_name} "
|
531
|
+
f"is too large; it should be less than {consts_len}."
|
532
|
+
)
|
533
|
+
is_valid = False
|
534
|
+
elif inst.opcode in asm.opc.LOCAL_OPS:
|
535
|
+
if operand >= varnames_len:
|
536
|
+
print(inst)
|
537
|
+
warn(
|
538
|
+
f"Variable operand index {operand} at offset {inst.offset} of {code.co_name} "
|
539
|
+
f"is too large; it should be less than {varnames_len}."
|
540
|
+
)
|
541
|
+
is_valid = False
|
542
|
+
elif inst.opcode in asm.opc.NAME_OPS:
|
543
|
+
if operand >= names_len:
|
544
|
+
print(inst)
|
545
|
+
warn(
|
546
|
+
f"Name operand index {operand} at offset {inst.offset} of {code.co_name} "
|
547
|
+
f"is too large; it should be less than {names_len}."
|
548
|
+
)
|
549
|
+
is_valid = False
|
550
|
+
elif inst.opcode in asm.opc.FREE_OPS:
|
551
|
+
# FIXME: is this right?
|
552
|
+
if operand >= cells_free_len:
|
553
|
+
print(inst)
|
554
|
+
warn(
|
555
|
+
f"Free operand index {operand} at offset {inst.offset} of {code.co_name} "
|
556
|
+
f"is too large; it should be less than {cells_free_len}."
|
557
|
+
)
|
558
|
+
is_valid = False
|
559
|
+
|
560
|
+
return is_valid
|
561
|
+
|
562
|
+
|
563
|
+
def append_operand(
|
564
|
+
bytecode: list, arg_value, extended_arg_shift, arg_max_value, extended_arg_op
|
565
|
+
) -> None:
|
566
|
+
"""
|
567
|
+
Write instruction operand adding EXTENDED_ARG instructions
|
568
|
+
when necessary.
|
569
|
+
"""
|
570
|
+
arg_shifts = []
|
571
|
+
shift_value = 1
|
572
|
+
|
573
|
+
while arg_value > arg_max_value:
|
574
|
+
shift_value <<= extended_arg_shift
|
575
|
+
ext_arg_value, arg_value = divmod(arg_value, shift_value)
|
576
|
+
arg_shifts.append(ext_arg_value)
|
577
|
+
|
578
|
+
while arg_shifts:
|
579
|
+
bytecode.append(extended_arg_op)
|
580
|
+
ext_arg_value = arg_shifts.pop()
|
581
|
+
bytecode.append(ext_arg_value)
|
582
|
+
|
583
|
+
bytecode.append(arg_value)
|
584
|
+
|
585
|
+
|
586
|
+
def create_code(asm: Assembler, label, backpatch) -> tuple:
|
587
|
+
"""
|
588
|
+
Turn ``asm`` assembler text into a code object and
|
589
|
+
return that.
|
590
|
+
"""
|
419
591
|
# print('label: ', asm.label)
|
420
592
|
# print('backpatch: ', asm.backpatch_inst)
|
421
593
|
|
422
|
-
|
594
|
+
bytecode = []
|
423
595
|
# print(asm.code.instructions)
|
424
596
|
|
425
597
|
offset = 0
|
426
|
-
extended_value = 0
|
427
598
|
offset2label = {label[j]: j for j in label}
|
599
|
+
is_valid = True
|
428
600
|
|
429
601
|
for i, inst in enumerate(asm.code.instructions):
|
430
|
-
|
602
|
+
# Strip out extended arg instructions.
|
603
|
+
# Operands in the input can be arbitary numbers.
|
604
|
+
# In this loop we will figure out whether
|
605
|
+
# or not to add EXTENDED_ARG
|
606
|
+
if inst.opcode == asm.opc.EXTENDED_ARG:
|
607
|
+
print(
|
608
|
+
f"Line {i}: superflous EXTENDED_ARG instruction removed;"
|
609
|
+
" this code decides when they are needed."
|
610
|
+
)
|
611
|
+
continue
|
612
|
+
|
613
|
+
bytecode.append(inst.opcode)
|
431
614
|
if offset in offset2label:
|
432
615
|
if is_int(offset2label[offset]):
|
433
616
|
inst.line_no = int(offset2label[offset])
|
617
|
+
if (
|
618
|
+
inst.line_no in asm.code.co_lnotab.values()
|
619
|
+
and asm.python_version < (3, 10)
|
620
|
+
):
|
621
|
+
print(
|
622
|
+
f"Line {i}: this is not the first we encounter source-code line {inst.line_no}."
|
623
|
+
)
|
434
624
|
asm.code.co_lnotab[offset] = inst.line_no
|
435
625
|
|
436
626
|
inst.offset = offset
|
@@ -447,17 +637,13 @@ def create_code(asm, label, backpatch):
|
|
447
637
|
inst.arg = label[target] - offset
|
448
638
|
else:
|
449
639
|
inst.arg = label[target]
|
450
|
-
|
640
|
+
if asm.opc.version_tuple >= (3, 10):
|
641
|
+
inst.arg >>= 1
|
451
642
|
pass
|
452
643
|
except KeyError:
|
453
644
|
err(f"Label {target} not found.\nI know about {backpatch}", inst, i)
|
645
|
+
is_valid = False
|
454
646
|
elif is_int(inst.arg):
|
455
|
-
if inst.opcode == asm.opc.EXTENDED_ARG:
|
456
|
-
extended_value += inst.arg
|
457
|
-
if asm.opc.version >= 3.6:
|
458
|
-
extended_value <<= 8
|
459
|
-
else:
|
460
|
-
extended_value <<= 16
|
461
647
|
pass
|
462
648
|
elif inst.arg.startswith("(") and inst.arg.endswith(")"):
|
463
649
|
operand = inst.arg[1:-1]
|
@@ -465,11 +651,13 @@ def create_code(asm, label, backpatch):
|
|
465
651
|
if operand in cmp_op:
|
466
652
|
inst.arg = cmp_op.index(operand)
|
467
653
|
else:
|
468
|
-
err("Can't handle compare operand
|
654
|
+
err(f"Can't handle compare operand {inst.arg}", inst, i)
|
655
|
+
is_valid = False
|
656
|
+
break
|
469
657
|
|
470
658
|
pass
|
471
659
|
elif inst.opcode in asm.opc.CONST_OPS:
|
472
|
-
if not operand.startswith("<Code"):
|
660
|
+
if not (operand.startswith("<Code") or operand.startswith("<code")):
|
473
661
|
operand = ast.literal_eval(operand)
|
474
662
|
update_code_field("co_consts", operand, inst, asm.code)
|
475
663
|
elif inst.opcode in asm.opc.LOCAL_OPS:
|
@@ -483,47 +671,52 @@ def create_code(asm, label, backpatch):
|
|
483
671
|
update_code_field("co_freevars", operand, inst, asm.code)
|
484
672
|
else:
|
485
673
|
# from trepan.api import debug; debug()
|
486
|
-
err("Can't handle operand
|
674
|
+
err(f"Can't handle operand {inst.arg}", inst, i)
|
675
|
+
is_valid = False
|
676
|
+
break
|
487
677
|
else:
|
488
678
|
# from trepan.api import debug; debug()
|
489
679
|
err(
|
490
|
-
"Don't understand operand
|
680
|
+
f"Don't understand operand {inst.arg} expecting int or (..)",
|
491
681
|
inst,
|
492
682
|
i,
|
493
683
|
)
|
494
684
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
else:
|
504
|
-
if inst.opcode == asm.opc.EXTENDED_ARG:
|
505
|
-
bcode.append(inst.arg)
|
506
|
-
else:
|
507
|
-
bcode.append(inst.arg - extended_value)
|
508
|
-
extended_value = 0
|
685
|
+
append_operand(
|
686
|
+
bytecode,
|
687
|
+
inst.arg,
|
688
|
+
asm.opc.EXTENDED_ARG_SHIFT,
|
689
|
+
asm.opc.ARG_MAX_VALUE,
|
690
|
+
asm.opc.EXTENDED_ARG,
|
691
|
+
)
|
692
|
+
|
509
693
|
elif asm.opc.version_tuple >= (3, 6):
|
510
|
-
|
694
|
+
# instructions with no operand, or one-byte instructions, are padded
|
695
|
+
# to two bytes in 3.6 and later.
|
696
|
+
bytecode.append(0)
|
697
|
+
|
698
|
+
if not is_valid:
|
699
|
+
return None, False
|
511
700
|
|
512
701
|
if asm.opc.version_tuple >= (3, 0):
|
513
702
|
co_code = bytearray()
|
514
|
-
for j in
|
703
|
+
for j in bytecode:
|
515
704
|
co_code.append(j % 255)
|
516
705
|
asm.code.co_code = bytes(co_code)
|
517
706
|
else:
|
518
|
-
asm.code.co_code = "".join([chr(j) for j in
|
707
|
+
asm.code.co_code = "".join([chr(j) for j in bytecode])
|
708
|
+
|
709
|
+
# FIXME: get
|
710
|
+
is_code_ok(asm)
|
519
711
|
|
520
712
|
# Stamp might be added here
|
521
713
|
if asm.python_version[:2] == PYTHON_VERSION_TRIPLE[:2]:
|
522
714
|
code = asm.code.to_native()
|
523
715
|
else:
|
524
716
|
code = asm.code.freeze()
|
717
|
+
|
525
718
|
# asm.print_instructions()
|
526
719
|
|
527
720
|
# print (*args)
|
528
721
|
# co = self.Code(*args)
|
529
|
-
return code
|
722
|
+
return code, is_valid
|
xasm/pyc_convert.py
CHANGED
@@ -2,28 +2,29 @@
|
|
2
2
|
"""Convert Python Bytecode from one version to another for
|
3
3
|
some limited set of Python bytecode versions
|
4
4
|
"""
|
5
|
-
from xdis import disassemble_file, load_module, magic2int, write_bytecode_file
|
6
|
-
from xasm.write_pyc import write_pycfile
|
7
|
-
import xdis
|
8
|
-
from xdis.opcodes import opcode_33, opcode_27
|
9
|
-
from tempfile import NamedTemporaryFile
|
10
|
-
import os.path as osp
|
11
5
|
import os
|
6
|
+
import os.path as osp
|
12
7
|
from copy import copy
|
8
|
+
from tempfile import NamedTemporaryFile
|
9
|
+
|
10
|
+
import click
|
11
|
+
import xdis
|
12
|
+
from xdis import disassemble_file, load_module, magic2int, write_bytecode_file
|
13
|
+
from xdis.magics import magics
|
14
|
+
from xdis.opcodes import opcode_27, opcode_33
|
15
|
+
|
13
16
|
from xasm.assemble import (
|
14
|
-
asm_file,
|
15
17
|
Assembler,
|
16
|
-
create_code,
|
17
18
|
Instruction,
|
18
|
-
|
19
|
+
asm_file,
|
20
|
+
create_code,
|
21
|
+
decode_lineno_tab_old,
|
19
22
|
)
|
20
|
-
from xdis.magics import magics
|
21
|
-
import click
|
22
|
-
|
23
23
|
from xasm.version import __version__
|
24
|
+
from xasm.write_pyc import write_pycfile
|
24
25
|
|
25
26
|
|
26
|
-
def add_credit(asm, src_version, dest_version):
|
27
|
+
def add_credit(asm, src_version, dest_version) -> None:
|
27
28
|
stamp = "Converted from Python %s to %s by %s version %s" % (
|
28
29
|
src_version,
|
29
30
|
dest_version,
|
@@ -34,20 +35,20 @@ def add_credit(asm, src_version, dest_version):
|
|
34
35
|
return
|
35
36
|
|
36
37
|
|
37
|
-
def copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version):
|
38
|
+
def copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version) -> None:
|
38
39
|
"""Bytecodes are the same except the magic number, so just change
|
39
40
|
that"""
|
40
41
|
(version, timestamp, magic_int, co, is_pypy, source_size) = load_module(input_pyc)
|
41
42
|
assert version == float(
|
42
43
|
src_version
|
43
|
-
), "Need Python
|
44
|
+
), f"Need Python {src_version} bytecode; got bytecode for version {version}"
|
44
45
|
magic_int = magic2int(magics.magics[dest_version])
|
45
46
|
write_bytecode_file(output_pyc, co, magic_int)
|
46
|
-
print("Wrote
|
47
|
+
print(f"Wrote {output_pyc}")
|
47
48
|
return
|
48
49
|
|
49
50
|
|
50
|
-
def xlate26_27(inst):
|
51
|
+
def xlate26_27(inst) -> None:
|
51
52
|
"""Between 2.6 and 2.7 opcode values changed
|
52
53
|
Adjust for the differences by using the opcode name
|
53
54
|
"""
|
@@ -145,9 +146,10 @@ def transform_33_32(inst, new_inst, i, n, offset, instructions, new_asm):
|
|
145
146
|
return 0
|
146
147
|
|
147
148
|
|
148
|
-
def transform_asm(
|
149
|
-
|
150
|
-
|
149
|
+
def transform_asm(
|
150
|
+
asm: Assembler | None, conversion_type, src_version, dest_version
|
151
|
+
) -> Assembler:
|
152
|
+
new_asm = Assembler(dest_version, is_pypy=False)
|
151
153
|
for field in "code size".split():
|
152
154
|
setattr(new_asm, field, copy(getattr(asm, field)))
|
153
155
|
|
@@ -158,13 +160,15 @@ def transform_asm(asm, conversion_type, src_version, dest_version):
|
|
158
160
|
elif conversion_type == "33-32":
|
159
161
|
transform_fn = transform_33_32
|
160
162
|
else:
|
161
|
-
raise RuntimeError("Don't know how to
|
163
|
+
raise RuntimeError(f"Don't know how to convert {conversion_type} ")
|
162
164
|
for j, code in enumerate(asm.code_list):
|
163
165
|
offset2label = {v: k for k, v in asm.label[j].items()}
|
164
166
|
new_asm.backpatch.append(copy(asm.backpatch[j]))
|
165
167
|
new_asm.label.append(copy(asm.label[j]))
|
166
168
|
new_asm.codes.append(copy(code))
|
167
|
-
new_asm.code.co_lnotab =
|
169
|
+
new_asm.code.co_lnotab = decode_lineno_tab_old(
|
170
|
+
code.co_lnotab, code.co_firstlineno
|
171
|
+
)
|
168
172
|
instructions = asm.codes[j].instructions
|
169
173
|
new_asm.code.instructions = []
|
170
174
|
i, offset, n = 0, 0, len(instructions)
|
@@ -182,14 +186,14 @@ def transform_asm(asm, conversion_type, src_version, dest_version):
|
|
182
186
|
i += 1
|
183
187
|
pass
|
184
188
|
|
185
|
-
co = create_code(new_asm, new_asm.label[-1], new_asm.backpatch[-1])
|
189
|
+
co, is_valid = create_code(new_asm, new_asm.label[-1], new_asm.backpatch[-1])
|
186
190
|
new_asm.code_list.append(co)
|
187
191
|
new_asm.code_list.reverse()
|
188
|
-
new_asm.
|
192
|
+
new_asm.status = "finished" if is_valid else "invalid"
|
189
193
|
return new_asm
|
190
194
|
|
191
195
|
|
192
|
-
|
196
|
+
UPWARD_COMPATIBLE = tuple("20-21 21-22 23-24 24-23".split())
|
193
197
|
|
194
198
|
|
195
199
|
@click.command()
|
@@ -212,15 +216,15 @@ UPWARD_COMPATABLE = tuple("20-21 21-22 23-24 24-23".split())
|
|
212
216
|
help="specify conversion from/to bytecode",
|
213
217
|
default="26-27",
|
214
218
|
)
|
215
|
-
@click.argument("input_pyc", type=click.Path(
|
219
|
+
@click.argument("input_pyc", type=click.Path(writable=True), nargs=1)
|
216
220
|
@click.argument(
|
217
|
-
"output_pyc", type=click.Path(
|
221
|
+
"output_pyc", type=click.Path(writable=True), required=False, nargs=1, default=None
|
218
222
|
)
|
219
|
-
def main(conversion_type, input_pyc, output_pyc):
|
223
|
+
def main(conversion_type, input_pyc, output_pyc) -> None:
|
220
224
|
"""Convert Python bytecode from one version to another.
|
221
225
|
|
222
226
|
INPUT_PYC contains the input bytecode path name
|
223
|
-
OUTPUT_PYC
|
227
|
+
OUTPUT_PYC contains the output bytecode path name if supplied
|
224
228
|
The --conversion type option specifies what conversion to do.
|
225
229
|
|
226
230
|
Note: there are a very limited set of conversions currently supported.
|
@@ -232,9 +236,9 @@ def main(conversion_type, input_pyc, output_pyc):
|
|
232
236
|
src_version = conversion_to_version(conversion_type, is_dest=False)
|
233
237
|
dest_version = conversion_to_version(conversion_type, is_dest=True)
|
234
238
|
if output_pyc is None:
|
235
|
-
output_pyc = "
|
239
|
+
output_pyc = f"{shortname}-{dest_version}.pyc"
|
236
240
|
|
237
|
-
if conversion_type in
|
241
|
+
if conversion_type in UPWARD_COMPATIBLE:
|
238
242
|
copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version)
|
239
243
|
return
|
240
244
|
temp_asm = NamedTemporaryFile("w", suffix=".pyasm", prefix=shortname, delete=False)
|
@@ -244,7 +248,7 @@ def main(conversion_type, input_pyc, output_pyc):
|
|
244
248
|
temp_asm.close()
|
245
249
|
assert version == float(
|
246
250
|
src_version
|
247
|
-
), "Need Python
|
251
|
+
), f"Need Python {src_version} bytecode; got bytecode for version {version}"
|
248
252
|
asm = asm_file(temp_asm.name)
|
249
253
|
new_asm = transform_asm(asm, conversion_type, src_version, dest_version)
|
250
254
|
os.unlink(temp_asm.name)
|
xasm/version.py
CHANGED
xasm/write_pyc.py
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
import time
|
2
|
+
from struct import pack
|
3
|
+
|
1
4
|
import xdis
|
2
5
|
from xdis import magic2int
|
3
|
-
from xdis.marsh import dumps
|
4
6
|
from xdis.magics import magics
|
5
|
-
from
|
7
|
+
from xdis.marsh import dumps
|
6
8
|
from xdis.version_info import PYTHON3, version_tuple_to_str
|
7
|
-
import time
|
8
9
|
|
9
10
|
|
10
|
-
def write_pycfile(fp, code_list, timestamp=None, version_triple=xdis.PYTHON_VERSION_TRIPLE):
|
11
|
+
def write_pycfile(fp, code_list, timestamp=None, version_triple=xdis.PYTHON_VERSION_TRIPLE) -> int:
|
11
12
|
|
13
|
+
rc = 0
|
12
14
|
version_str = version_tuple_to_str(version_triple, end=2)
|
13
15
|
magic_bytes = magics[version_str]
|
14
16
|
magic_int = magic2int(magic_bytes)
|
@@ -40,5 +42,7 @@ def write_pycfile(fp, code_list, timestamp=None, version_triple=xdis.PYTHON_VERS
|
|
40
42
|
pass
|
41
43
|
|
42
44
|
fp.write(co_obj)
|
43
|
-
except:
|
44
|
-
|
45
|
+
except Exception as e:
|
46
|
+
print(f"error dumping {co}: {e}; ignoring")
|
47
|
+
rc = 1
|
48
|
+
return rc
|
xasm/xasm_cli.py
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
#!/usr/bin/env python
|
2
|
-
import
|
3
|
-
|
4
|
-
from
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
import click
|
5
7
|
import xdis
|
6
8
|
from xdis.version_info import version_tuple_to_str
|
7
9
|
|
10
|
+
from xasm.assemble import asm_file
|
11
|
+
from xasm.write_pyc import write_pycfile
|
12
|
+
|
8
13
|
|
9
14
|
@click.command()
|
10
15
|
@click.option("--pyc-file", default=None)
|
11
16
|
@click.argument("asm-path", type=click.Path(exists=True, readable=True), required=True)
|
12
|
-
def main(pyc_file, asm_path):
|
17
|
+
def main(pyc_file: List[str], asm_path):
|
13
18
|
"""
|
14
19
|
Create Python bytecode from a Python assembly file.
|
15
20
|
|
@@ -23,12 +28,15 @@ def main(pyc_file, asm_path):
|
|
23
28
|
for how to write a Python assembler file.
|
24
29
|
"""
|
25
30
|
if os.stat(asm_path).st_size == 0:
|
26
|
-
print("Size of assembly file
|
31
|
+
print(f"Size of assembly file {asm_path} is zero")
|
27
32
|
sys.exit(1)
|
28
33
|
asm = asm_file(asm_path)
|
29
34
|
|
30
|
-
if not pyc_file
|
31
|
-
|
35
|
+
if not pyc_file:
|
36
|
+
if asm_path.endswith(".pyasm"):
|
37
|
+
pyc_file = asm_path[: -len(".pyasm")] + ".pyc"
|
38
|
+
elif not pyc_file and asm_path.endswith(".xasm"):
|
39
|
+
pyc_file = asm_path[: -len(".xasm")] + ".pyc"
|
32
40
|
|
33
41
|
if xdis.PYTHON3:
|
34
42
|
file_mode = "wb"
|
@@ -36,13 +44,17 @@ def main(pyc_file, asm_path):
|
|
36
44
|
file_mode = "w"
|
37
45
|
|
38
46
|
with open(pyc_file, file_mode) as fp:
|
39
|
-
write_pycfile(fp, asm.code_list, asm.timestamp, asm.python_version)
|
47
|
+
rc = write_pycfile(fp, asm.code_list, asm.timestamp, asm.python_version)
|
40
48
|
size = fp.tell()
|
41
49
|
print(
|
42
50
|
f"""Wrote Python {version_tuple_to_str(asm.python_version)} bytecode file "{pyc_file}"; {size} bytes."""
|
43
51
|
)
|
44
52
|
if size <= 16:
|
45
|
-
print("Warning: bytecode file is too small to be
|
53
|
+
print("Warning: bytecode file is too small to be usable.")
|
54
|
+
rc = 2
|
55
|
+
if rc != 0:
|
56
|
+
print(f"Exiting with return code {rc}")
|
57
|
+
sys.exit(rc)
|
46
58
|
|
47
59
|
|
48
60
|
if __name__ == "__main__":
|
@@ -1,12 +1,11 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: xasm
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.1
|
4
4
|
Summary: Python cross-version byte-code assembler
|
5
5
|
Home-page: https://github.com/rocky/python-xasm/
|
6
6
|
Author: Rocky Bernstein
|
7
7
|
Author-email: rb@dustyfeet.com
|
8
8
|
License: GPL-2.0
|
9
|
-
Platform: UNKNOWN
|
10
9
|
Classifier: Development Status :: 4 - Beta
|
11
10
|
Classifier: Intended Audience :: Developers
|
12
11
|
Classifier: Operating System :: OS Independent
|
@@ -19,48 +18,63 @@ Classifier: Programming Language :: Python :: 3.7
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.8
|
20
19
|
Classifier: Programming Language :: Python :: 3.9
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
22
23
|
Classifier: Topic :: Software Development :: Debuggers
|
23
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
24
25
|
Description-Content-Type: text/x-rst
|
25
26
|
License-File: LICENSE.gpl2
|
27
|
+
Requires-Dist: xdis>=6.1.0
|
28
|
+
Dynamic: author
|
29
|
+
Dynamic: author-email
|
30
|
+
Dynamic: classifier
|
31
|
+
Dynamic: description
|
32
|
+
Dynamic: description-content-type
|
33
|
+
Dynamic: home-page
|
34
|
+
Dynamic: license
|
35
|
+
Dynamic: license-file
|
36
|
+
Dynamic: requires-dist
|
37
|
+
Dynamic: summary
|
26
38
|
|
27
39
|
|Pypi Installs| |Latest Version| |Supported Python Versions|
|
28
40
|
|
29
41
|
xasm
|
30
42
|
====
|
31
43
|
|
32
|
-
*NOTE: this is in beta
|
44
|
+
*NOTE: this is in beta.*
|
33
45
|
|
34
|
-
A
|
46
|
+
A cross-version Python bytecode assembler
|
35
47
|
|
36
48
|
|
37
49
|
Introduction
|
38
50
|
------------
|
39
51
|
|
40
|
-
The Python
|
52
|
+
The Python ``xasm`` module has routines for assembly, and has a command to
|
41
53
|
assemble bytecode for several different versions of Python.
|
42
54
|
|
43
55
|
Here are some potential uses:
|
44
56
|
|
45
|
-
* Make small
|
57
|
+
* Make small changes to existing Python bytecode when you don’t have source
|
46
58
|
* Craft custom and efficient bytecode
|
47
59
|
* Write an instruction-level optimizing compiler
|
48
60
|
* Experiment with and learn about Python bytecode
|
49
|
-
* Foil uncompyle6_ so that
|
61
|
+
* Foil decompilers like uncompyle6_ so that they can’t disassemble bytecode (at least for now)
|
50
62
|
|
51
|
-
This
|
63
|
+
This support the same kinds of bytecode that xdis_ supports. This is
|
64
|
+
pretty much all released bytecode before Python 3.11. We tend to lag behind the
|
65
|
+
latest Python releases.
|
52
66
|
|
53
|
-
The code requires Python
|
67
|
+
The code requires Python 3.6 or later.
|
54
68
|
|
55
69
|
Assembly files
|
56
70
|
--------------
|
57
71
|
|
58
|
-
|
72
|
+
See how-to-use_ for more detail. Some general some principles:
|
59
73
|
|
60
|
-
* Preferred extension for Python assembly is
|
61
|
-
* assembly is designed to work with the output of
|
74
|
+
* Preferred extension for Python assembly is ``.pyasm``
|
75
|
+
* assembly is designed to work with the output of ``pydisasm -F xasm``
|
62
76
|
* Assembly file labels are at the beginning of the line
|
63
|
-
and end in a colon, e.g.
|
77
|
+
and end in a colon, e.g. ``END_IF:``
|
64
78
|
* instruction offsets in the assembly file are ignored and don't need
|
65
79
|
to be entered
|
66
80
|
* in those instructions that refer to offsets, if the if the
|
@@ -71,17 +85,30 @@ More detail will be filled in, but some principles:
|
|
71
85
|
Installation
|
72
86
|
------------
|
73
87
|
|
74
|
-
|
88
|
+
*If you are using Python 3.11 or later*, you can install from PyPI using the name ``xasm``::
|
75
89
|
|
76
|
-
|
77
|
-
|
78
|
-
pip install -e .
|
79
|
-
pip install -r requirements-dev.txt
|
90
|
+
pip install xasm
|
80
91
|
|
81
|
-
A GNU makefile is also provided so
|
92
|
+
A GNU makefile is also provided so ``make install`` (possibly as root or
|
82
93
|
sudo) will do the steps above.
|
83
94
|
|
84
95
|
|
96
|
+
*If you are using Python before 3.11*, do not install using PyPI, but instead install using a file in the `GitHub Releases section <https://github.com/rocky/python-xasm/releases>`_. Older Python used to use `easy_install <https://python101.pythonlibrary.org/chapter29_pip.html#using-easy-install>`_. But this is no longer supported in PyPi or newer Python versions. And vice versa, *poetry* nor *pip*, (the newer ways) are not supported on older Pythons.
|
97
|
+
|
98
|
+
If the Python version you are running xasm is between Python 3.6 through 3.11, use a tarball called xasm_36-*x.y.z*.tar.gz.
|
99
|
+
|
100
|
+
If the Python version you are running xasm is 3.11 or later, use a file called xasm-*x.y.z*.tar.gz.
|
101
|
+
|
102
|
+
Similarly, a tarball with or without the underscore *xx*, e.g., xasm_36-*x.y.z*.tar.gz. works only from Python 3.11 or greater.
|
103
|
+
|
104
|
+
Rationale for using Git Branches
|
105
|
+
++++++++++++++++++++++++++++++++
|
106
|
+
|
107
|
+
It is currently impossible (if not impractical) to have one Python source code of this complexity and with this many features that can run both Python 3.6 and Python 3.13+. The languages have drifted so much, and packaging is vastly different.
|
108
|
+
|
109
|
+
A GNU makefile is also provided so :code:`make install` (possibly as root or sudo) will do the steps above.
|
110
|
+
|
111
|
+
|
85
112
|
Testing
|
86
113
|
-------
|
87
114
|
|
@@ -161,14 +188,14 @@ Here is an assembly for the above:
|
|
161
188
|
RETURN_VALUE
|
162
189
|
|
163
190
|
|
164
|
-
The above can be created automatically from Python source code using the
|
165
|
-
command from
|
191
|
+
The above can be created automatically from Python source code using the ``pydisasm``
|
192
|
+
command from ``xdis``:
|
166
193
|
|
167
194
|
::
|
168
195
|
|
169
196
|
pydisasm --format xasm /tmp/five.pyc
|
170
197
|
|
171
|
-
In the example above though, I have
|
198
|
+
In the example above though, I have shortened and simplified the result.
|
172
199
|
|
173
200
|
|
174
201
|
Usage
|
@@ -181,7 +208,7 @@ To create a python bytecode file from an assemble file, run:
|
|
181
208
|
pyc-xasm [OPTIONS] ASM_PATH
|
182
209
|
|
183
210
|
|
184
|
-
For usage help, type
|
211
|
+
For usage help, type: ``pyc-xasm --help``.
|
185
212
|
|
186
213
|
|
187
214
|
To convert a python bytecode from one bytecode to another, run:
|
@@ -191,7 +218,7 @@ To convert a python bytecode from one bytecode to another, run:
|
|
191
218
|
pyc-convert [OPTIONS] INPUT_PYC [OUTPUT_PYC]
|
192
219
|
|
193
220
|
|
194
|
-
For usage help, type
|
221
|
+
For usage help, type: ``pyc-convert --help``.
|
195
222
|
|
196
223
|
|
197
224
|
See Also
|
@@ -204,11 +231,11 @@ See Also
|
|
204
231
|
|
205
232
|
|
206
233
|
.. _uncompyle6: https://github.com/rocky/python-uncompyle6
|
234
|
+
.. _how-to-use: https://github.com/rocky/python-xasm/blob/master/HOW-TO-USE.rst
|
235
|
+
.. _xdis: https://github.com/rocky/xdis
|
207
236
|
.. |Latest Version| image:: https://badge.fury.io/py/xasm.svg
|
208
237
|
:target: https://badge.fury.io/py/xasm
|
209
238
|
.. |Pypi Installs| image:: https://pepy.tech/badge/xasm
|
210
239
|
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/xasm.svg
|
211
240
|
.. _remake: http://bashdb.sf.net/remake
|
212
241
|
|
213
|
-
|
214
|
-
|
@@ -0,0 +1,13 @@
|
|
1
|
+
xasm/__init__.py,sha256=gz94OZ_Jc1WozfOYH1pjQeg7IgB1L9wv5fyytJ2XgZU,174
|
2
|
+
xasm/assemble.py,sha256=GIf2cfvHa8QFyq0w820-c__Wpj5DeOtKtgicg9XbEjU,26420
|
3
|
+
xasm/pyc_convert.py,sha256=s2UixbgUQZFHv9nmtvOV7TYohLBev2lcet0GIE9g6GM,9177
|
4
|
+
xasm/version.py,sha256=xEuuftKJb_Pe2D5lJpTv3HxDgx6Bd1-lH_6HBGWFguw,206
|
5
|
+
xasm/write_pyc.py,sha256=ZoqjupN9rEjJ5o49SZcX9rbEhaImnqsJKkNzLnT9j4c,1455
|
6
|
+
xasm/xasm_cli.py,sha256=4EYRZVejspzBvoNaXsWgGbN7qRU5p-MPrt05IYu1kGc,1789
|
7
|
+
xasm-1.2.1.dist-info/licenses/LICENSE.gpl2,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
|
8
|
+
xasm-1.2.1.dist-info/METADATA,sha256=ttOzLNtFPOn_c015VJhuv-E9sNCXsYGiojLM23FAZ8k,7429
|
9
|
+
xasm-1.2.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
10
|
+
xasm-1.2.1.dist-info/entry_points.txt,sha256=Ypclbf0sCcywW8uPxxHAQv2pUujiKHx5OfcAYbSks98,84
|
11
|
+
xasm-1.2.1.dist-info/top_level.txt,sha256=24UNOItB5o_EJoJ9nqhPZEC5GiSaYtHll3F6mJXzOkA,5
|
12
|
+
xasm-1.2.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
13
|
+
xasm-1.2.1.dist-info/RECORD,,
|
xasm-1.2.0.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
xasm/__init__.py,sha256=gz94OZ_Jc1WozfOYH1pjQeg7IgB1L9wv5fyytJ2XgZU,174
|
2
|
-
xasm/assemble.py,sha256=SMLDowOTUyX5WW1F1MxVgVW8PABo0Q2i_qIQwrIA2VI,19603
|
3
|
-
xasm/pyc_convert.py,sha256=onWRcmEhqpQi1Pf-lvPeun8qxPKmTWJEqLBGUW6aH28,9029
|
4
|
-
xasm/version.py,sha256=DbW6-QjmrYtEr06qLRCM9LzccIv-54mBuJWsQ6fclI0,206
|
5
|
-
xasm/write_pyc.py,sha256=LJKDKg8WGS9zzXIilY8keW3NJU2N2Kf_murjIVv1_IY,1357
|
6
|
-
xasm/xasm_cli.py,sha256=cw2TY6kYOU50xC-czeLEGMBnCuYOjaV3mMuX75QKKnI,1514
|
7
|
-
xasm-1.2.0.dist-info/LICENSE.gpl2,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
|
8
|
-
xasm-1.2.0.dist-info/METADATA,sha256=nGfVdYl1WaSq_dbbq3YlWwND39mzo_C5KuXOu5CHVfw,5571
|
9
|
-
xasm-1.2.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
|
10
|
-
xasm-1.2.0.dist-info/entry_points.txt,sha256=ddG5Ud4yX9DHkME_98f0fEHp4X-_6nipBUq0IADDBfg,119
|
11
|
-
xasm-1.2.0.dist-info/top_level.txt,sha256=24UNOItB5o_EJoJ9nqhPZEC5GiSaYtHll3F6mJXzOkA,5
|
12
|
-
xasm-1.2.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
13
|
-
xasm-1.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|