xasm 1.2.1__py38-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/__init__.py +8 -0
- xasm/assemble.py +722 -0
- xasm/pyc_convert.py +259 -0
- xasm/version.py +8 -0
- xasm/write_pyc.py +48 -0
- xasm/xasm_cli.py +61 -0
- xasm-1.2.1.dist-info/LICENSE.gpl2 +339 -0
- xasm-1.2.1.dist-info/METADATA +233 -0
- xasm-1.2.1.dist-info/RECORD +13 -0
- xasm-1.2.1.dist-info/WHEEL +5 -0
- xasm-1.2.1.dist-info/entry_points.txt +5 -0
- xasm-1.2.1.dist-info/top_level.txt +1 -0
- xasm-1.2.1.dist-info/zip-safe +1 -0
xasm/pyc_convert.py
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
"""Convert Python Bytecode from one version to another for
|
3
|
+
some limited set of Python bytecode versions
|
4
|
+
"""
|
5
|
+
import os
|
6
|
+
import os.path as osp
|
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
|
+
|
16
|
+
from xasm.assemble import (
|
17
|
+
Assembler,
|
18
|
+
Instruction,
|
19
|
+
asm_file,
|
20
|
+
create_code,
|
21
|
+
decode_lineno_tab_old,
|
22
|
+
)
|
23
|
+
from xasm.version import __version__
|
24
|
+
from xasm.write_pyc import write_pycfile
|
25
|
+
|
26
|
+
|
27
|
+
def add_credit(asm, src_version, dest_version) -> None:
|
28
|
+
stamp = "Converted from Python %s to %s by %s version %s" % (
|
29
|
+
src_version,
|
30
|
+
dest_version,
|
31
|
+
"pyc-convert",
|
32
|
+
__version__,
|
33
|
+
)
|
34
|
+
asm.codes[-1].co_consts = list(asm.codes[-1].co_consts).append(stamp)
|
35
|
+
return
|
36
|
+
|
37
|
+
|
38
|
+
def copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version) -> None:
|
39
|
+
"""Bytecodes are the same except the magic number, so just change
|
40
|
+
that"""
|
41
|
+
(version, timestamp, magic_int, co, is_pypy, source_size) = load_module(input_pyc)
|
42
|
+
assert version == float(
|
43
|
+
src_version
|
44
|
+
), f"Need Python {src_version} bytecode; got bytecode for version {version}"
|
45
|
+
magic_int = magic2int(magics.magics[dest_version])
|
46
|
+
write_bytecode_file(output_pyc, co, magic_int)
|
47
|
+
print(f"Wrote {output_pyc}")
|
48
|
+
return
|
49
|
+
|
50
|
+
|
51
|
+
def xlate26_27(inst) -> None:
|
52
|
+
"""Between 2.6 and 2.7 opcode values changed
|
53
|
+
Adjust for the differences by using the opcode name
|
54
|
+
"""
|
55
|
+
inst.opcode = opcode_27.opmap[inst.opname]
|
56
|
+
|
57
|
+
|
58
|
+
def conversion_to_version(conversion_type, is_dest=False):
|
59
|
+
if is_dest:
|
60
|
+
return conversion_type[-2] + "." + conversion_type[-1]
|
61
|
+
else:
|
62
|
+
return conversion_type[0] + "." + conversion_type[1]
|
63
|
+
|
64
|
+
|
65
|
+
def transform_26_27(inst, new_inst, i, n, offset, instructions, new_asm):
|
66
|
+
"""Change JUMP_IF_FALSE and JUMP_IF_TRUE to
|
67
|
+
POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE"""
|
68
|
+
if inst.opname in ("JUMP_IF_FALSE", "JUMP_IF_TRUE"):
|
69
|
+
i += 1
|
70
|
+
assert i < n
|
71
|
+
assert instructions[i].opname == "POP_TOP"
|
72
|
+
new_inst.offset = offset
|
73
|
+
new_inst.opname = (
|
74
|
+
"POP_JUMP_IF_FALSE"
|
75
|
+
if inst.opname == "JUMP_IF_FALSE"
|
76
|
+
else "POP_JUMP_IF_TRUE"
|
77
|
+
)
|
78
|
+
new_asm.backpatch[-1].remove(inst)
|
79
|
+
new_inst.arg = "L%d" % (inst.offset + inst.arg + 3)
|
80
|
+
new_asm.backpatch[-1].add(new_inst)
|
81
|
+
else:
|
82
|
+
xlate26_27(new_inst)
|
83
|
+
return xdis.op_size(new_inst.opcode, opcode_27)
|
84
|
+
|
85
|
+
|
86
|
+
def transform_32_33(inst, new_inst, i, n, offset, instructions, new_asm):
|
87
|
+
"""MAKE_FUNCTION adds another const. probably MAKE_CLASS as well"""
|
88
|
+
add_size = xdis.op_size(new_inst.opcode, opcode_33)
|
89
|
+
if inst.opname in ("MAKE_FUNCTION", "MAKE_CLOSURE"):
|
90
|
+
# Previous instruction should be a load const which
|
91
|
+
# contains the name of the function to call
|
92
|
+
prev_inst = instructions[i - 1]
|
93
|
+
assert prev_inst.opname == "LOAD_CONST"
|
94
|
+
assert isinstance(prev_inst.arg, int)
|
95
|
+
|
96
|
+
# Add the function name as an additional LOAD_CONST
|
97
|
+
load_fn_const = Instruction()
|
98
|
+
load_fn_const.opname = "LOAD_CONST"
|
99
|
+
load_fn_const.opcode = opcode_33.opmap["LOAD_CONST"]
|
100
|
+
load_fn_const.line_no = None
|
101
|
+
prev_const = new_asm.code.co_consts[prev_inst.arg]
|
102
|
+
if hasattr(prev_const, "co_name"):
|
103
|
+
fn_name = new_asm.code.co_consts[prev_inst.arg].co_name
|
104
|
+
else:
|
105
|
+
fn_name = "what-is-up"
|
106
|
+
const_index = len(new_asm.code.co_consts)
|
107
|
+
new_asm.code.co_consts = list(new_asm.code.co_consts)
|
108
|
+
new_asm.code.co_consts.append(fn_name)
|
109
|
+
load_fn_const.arg = const_index
|
110
|
+
load_fn_const.offset = offset
|
111
|
+
load_fn_const.starts_line = False
|
112
|
+
load_fn_const.is_jump_target = False
|
113
|
+
new_asm.code.instructions.append(load_fn_const)
|
114
|
+
load_const_size = xdis.op_size(load_fn_const.opcode, opcode_33)
|
115
|
+
add_size += load_const_size
|
116
|
+
new_inst.offset = offset + add_size
|
117
|
+
pass
|
118
|
+
return add_size
|
119
|
+
|
120
|
+
|
121
|
+
def transform_33_32(inst, new_inst, i, n, offset, instructions, new_asm):
|
122
|
+
"""MAKE_FUNCTION, and MAKE_CLOSURE have an additional LOAD_CONST of a name
|
123
|
+
that are not in Python 3.2. Remove these.
|
124
|
+
"""
|
125
|
+
add_size = xdis.op_size(new_inst.opcode, opcode_33)
|
126
|
+
if inst.opname in ("MAKE_FUNCTION", "MAKE_CLOSURE"):
|
127
|
+
# Previous instruction should be a load const which
|
128
|
+
# contains the name of the function to call
|
129
|
+
prev_inst = instructions[i - 1]
|
130
|
+
assert prev_inst.opname == "LOAD_CONST"
|
131
|
+
assert isinstance(prev_inst.arg, int)
|
132
|
+
assert len(instructions) > 2
|
133
|
+
assert len(instructions) > 2
|
134
|
+
prev_inst2 = instructions[i - 2]
|
135
|
+
assert prev_inst2.opname == "LOAD_CONST"
|
136
|
+
assert isinstance(prev_inst2.arg, int)
|
137
|
+
|
138
|
+
# Remove the function name as an additional LOAD_CONST
|
139
|
+
prev2_const = new_asm.code.co_consts[prev_inst.arg]
|
140
|
+
assert hasattr(prev2_const, "co_name")
|
141
|
+
new_asm.code.instructions = new_asm.code.instructions[:-1]
|
142
|
+
load_const_size = xdis.op_size(prev_inst.opcode, opcode_33)
|
143
|
+
add_size -= load_const_size
|
144
|
+
new_inst.offset = offset - add_size
|
145
|
+
return -load_const_size
|
146
|
+
return 0
|
147
|
+
|
148
|
+
|
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)
|
153
|
+
for field in "code size".split():
|
154
|
+
setattr(new_asm, field, copy(getattr(asm, field)))
|
155
|
+
|
156
|
+
if conversion_type == "26-27":
|
157
|
+
transform_fn = transform_26_27
|
158
|
+
elif conversion_type == "32-33":
|
159
|
+
transform_fn = transform_32_33
|
160
|
+
elif conversion_type == "33-32":
|
161
|
+
transform_fn = transform_33_32
|
162
|
+
else:
|
163
|
+
raise RuntimeError(f"Don't know how to convert {conversion_type} ")
|
164
|
+
for j, code in enumerate(asm.code_list):
|
165
|
+
offset2label = {v: k for k, v in asm.label[j].items()}
|
166
|
+
new_asm.backpatch.append(copy(asm.backpatch[j]))
|
167
|
+
new_asm.label.append(copy(asm.label[j]))
|
168
|
+
new_asm.codes.append(copy(code))
|
169
|
+
new_asm.code.co_lnotab = decode_lineno_tab_old(
|
170
|
+
code.co_lnotab, code.co_firstlineno
|
171
|
+
)
|
172
|
+
instructions = asm.codes[j].instructions
|
173
|
+
new_asm.code.instructions = []
|
174
|
+
i, offset, n = 0, 0, len(instructions)
|
175
|
+
while i < n:
|
176
|
+
inst = instructions[i]
|
177
|
+
new_inst = copy(inst)
|
178
|
+
inst_size = transform_fn(
|
179
|
+
inst, new_inst, i, offset, n, instructions, new_asm
|
180
|
+
)
|
181
|
+
if inst.offset in offset2label:
|
182
|
+
new_asm.label[-1][offset2label[inst.offset]] = offset
|
183
|
+
pass
|
184
|
+
offset += inst_size
|
185
|
+
new_asm.code.instructions.append(new_inst)
|
186
|
+
i += 1
|
187
|
+
pass
|
188
|
+
|
189
|
+
co, is_valid = create_code(new_asm, new_asm.label[-1], new_asm.backpatch[-1])
|
190
|
+
new_asm.code_list.append(co)
|
191
|
+
new_asm.code_list.reverse()
|
192
|
+
new_asm.status = "finished" if is_valid else "invalid"
|
193
|
+
return new_asm
|
194
|
+
|
195
|
+
|
196
|
+
UPWARD_COMPATIBLE = tuple("20-21 21-22 23-24 24-23".split())
|
197
|
+
|
198
|
+
|
199
|
+
@click.command()
|
200
|
+
@click.option(
|
201
|
+
"--conversion-type",
|
202
|
+
"-t",
|
203
|
+
type=click.Choice(
|
204
|
+
[
|
205
|
+
"20-21",
|
206
|
+
"21-22",
|
207
|
+
"23-24",
|
208
|
+
"24-23",
|
209
|
+
"24-25",
|
210
|
+
"25-26",
|
211
|
+
"26-27",
|
212
|
+
"32-33",
|
213
|
+
"33-32",
|
214
|
+
]
|
215
|
+
),
|
216
|
+
help="specify conversion from/to bytecode",
|
217
|
+
default="26-27",
|
218
|
+
)
|
219
|
+
@click.argument("input_pyc", type=click.Path(writable=True), nargs=1)
|
220
|
+
@click.argument(
|
221
|
+
"output_pyc", type=click.Path(writable=True), required=False, nargs=1, default=None
|
222
|
+
)
|
223
|
+
def main(conversion_type, input_pyc, output_pyc) -> None:
|
224
|
+
"""Convert Python bytecode from one version to another.
|
225
|
+
|
226
|
+
INPUT_PYC contains the input bytecode path name
|
227
|
+
OUTPUT_PYC contains the output bytecode path name if supplied
|
228
|
+
The --conversion type option specifies what conversion to do.
|
229
|
+
|
230
|
+
Note: there are a very limited set of conversions currently supported.
|
231
|
+
Help out and write more!"""
|
232
|
+
|
233
|
+
shortname = osp.basename(input_pyc)
|
234
|
+
if shortname.endswith(".pyc"):
|
235
|
+
shortname = shortname[:-4]
|
236
|
+
src_version = conversion_to_version(conversion_type, is_dest=False)
|
237
|
+
dest_version = conversion_to_version(conversion_type, is_dest=True)
|
238
|
+
if output_pyc is None:
|
239
|
+
output_pyc = f"{shortname}-{dest_version}.pyc"
|
240
|
+
|
241
|
+
if conversion_type in UPWARD_COMPATIBLE:
|
242
|
+
copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version)
|
243
|
+
return
|
244
|
+
temp_asm = NamedTemporaryFile("w", suffix=".pyasm", prefix=shortname, delete=False)
|
245
|
+
(filename, co, version, timestamp, magic_int) = disassemble_file(
|
246
|
+
input_pyc, temp_asm, asm_format=True
|
247
|
+
)
|
248
|
+
temp_asm.close()
|
249
|
+
assert version == float(
|
250
|
+
src_version
|
251
|
+
), f"Need Python {src_version} bytecode; got bytecode for version {version}"
|
252
|
+
asm = asm_file(temp_asm.name)
|
253
|
+
new_asm = transform_asm(asm, conversion_type, src_version, dest_version)
|
254
|
+
os.unlink(temp_asm.name)
|
255
|
+
write_pycfile(output_pyc, new_asm)
|
256
|
+
|
257
|
+
|
258
|
+
if __name__ == "__main__":
|
259
|
+
main()
|
xasm/version.py
ADDED
xasm/write_pyc.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
import time
|
2
|
+
from struct import pack
|
3
|
+
|
4
|
+
import xdis
|
5
|
+
from xdis import magic2int
|
6
|
+
from xdis.magics import magics
|
7
|
+
from xdis.marsh import dumps
|
8
|
+
from xdis.version_info import PYTHON3, version_tuple_to_str
|
9
|
+
|
10
|
+
|
11
|
+
def write_pycfile(fp, code_list, timestamp=None, version_triple=xdis.PYTHON_VERSION_TRIPLE) -> int:
|
12
|
+
|
13
|
+
rc = 0
|
14
|
+
version_str = version_tuple_to_str(version_triple, end=2)
|
15
|
+
magic_bytes = magics[version_str]
|
16
|
+
magic_int = magic2int(magic_bytes)
|
17
|
+
fp.write(magic_bytes)
|
18
|
+
|
19
|
+
if timestamp is None:
|
20
|
+
timestamp = int(time.time())
|
21
|
+
write_source_size = version_triple >= (3, 3)
|
22
|
+
if version_triple >= (3, 7):
|
23
|
+
if magic_int == 3393:
|
24
|
+
fp.write(pack("I", timestamp))
|
25
|
+
fp.write(pack("I", 0))
|
26
|
+
else:
|
27
|
+
# PEP 552. https://www.python.org/dev/peps/pep-0552/
|
28
|
+
# 0 in the lowest-order bit means used old-style timestamps
|
29
|
+
fp.write(pack("<I", 0))
|
30
|
+
fp.write(pack("<I", timestamp))
|
31
|
+
else:
|
32
|
+
fp.write(pack("<I", timestamp))
|
33
|
+
|
34
|
+
if write_source_size:
|
35
|
+
fp.write(pack("<I", 0)) # size mod 2**32
|
36
|
+
|
37
|
+
for co in code_list:
|
38
|
+
try:
|
39
|
+
co_obj = dumps(co, python_version=version_triple)
|
40
|
+
if PYTHON3 and version_triple < (3, 0):
|
41
|
+
co_obj = str.encode(co_obj)
|
42
|
+
pass
|
43
|
+
|
44
|
+
fp.write(co_obj)
|
45
|
+
except Exception as e:
|
46
|
+
print(f"error dumping {co}: {e}; ignoring")
|
47
|
+
rc = 1
|
48
|
+
return rc
|
xasm/xasm_cli.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
import click
|
7
|
+
import xdis
|
8
|
+
from xdis.version_info import version_tuple_to_str
|
9
|
+
|
10
|
+
from xasm.assemble import asm_file
|
11
|
+
from xasm.write_pyc import write_pycfile
|
12
|
+
|
13
|
+
|
14
|
+
@click.command()
|
15
|
+
@click.option("--pyc-file", default=None)
|
16
|
+
@click.argument("asm-path", type=click.Path(exists=True, readable=True), required=True)
|
17
|
+
def main(pyc_file: List[str], asm_path):
|
18
|
+
"""
|
19
|
+
Create Python bytecode from a Python assembly file.
|
20
|
+
|
21
|
+
ASM_PATH gives the input Python assembly file. We suggest ending the
|
22
|
+
file in .pyc
|
23
|
+
|
24
|
+
If --pyc-file is given, that indicates the path to write the
|
25
|
+
Python bytecode. The path should end in '.pyc'.
|
26
|
+
|
27
|
+
See https://github.com/rocky/python-xasm/blob/master/HOW-TO-USE.rst
|
28
|
+
for how to write a Python assembler file.
|
29
|
+
"""
|
30
|
+
if os.stat(asm_path).st_size == 0:
|
31
|
+
print(f"Size of assembly file {asm_path} is zero")
|
32
|
+
sys.exit(1)
|
33
|
+
asm = asm_file(asm_path)
|
34
|
+
|
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"
|
40
|
+
|
41
|
+
if xdis.PYTHON3:
|
42
|
+
file_mode = "wb"
|
43
|
+
else:
|
44
|
+
file_mode = "w"
|
45
|
+
|
46
|
+
with open(pyc_file, file_mode) as fp:
|
47
|
+
rc = write_pycfile(fp, asm.code_list, asm.timestamp, asm.python_version)
|
48
|
+
size = fp.tell()
|
49
|
+
print(
|
50
|
+
f"""Wrote Python {version_tuple_to_str(asm.python_version)} bytecode file "{pyc_file}"; {size} bytes."""
|
51
|
+
)
|
52
|
+
if size <= 16:
|
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)
|
58
|
+
|
59
|
+
|
60
|
+
if __name__ == "__main__":
|
61
|
+
main(sys.argv[1:])
|