xasm 1.2.1__py311-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/.gitignore +2 -0
- 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/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 +2 -0
- xasm-1.2.1.dist-info/licenses/LICENSE.gpl2 +339 -0
- xasm-1.2.1.dist-info/top_level.txt +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:])
|
@@ -0,0 +1,233 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: xasm
|
3
|
+
Version: 1.2.1
|
4
|
+
Summary: Cross-version Python bytecode assembler
|
5
|
+
Author-email: Rocky Bernstein <rb@dustyfeet.com>
|
6
|
+
License-Expression: GPL-2.0
|
7
|
+
Project-URL: Homepage, https://github.com/rocky/python-xasm
|
8
|
+
Project-URL: Downloads, https://github.com/rocky/python-xasm/releases
|
9
|
+
Keywords: Python bytecode,bytecode,disassembler
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
11
|
+
Classifier: Intended Audience :: Developers
|
12
|
+
Classifier: Programming Language :: Python
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
20
|
+
Classifier: Topic :: Software Development :: Code Generators
|
21
|
+
Description-Content-Type: text/x-rst
|
22
|
+
License-File: LICENSE.gpl2
|
23
|
+
Requires-Dist: click
|
24
|
+
Requires-Dist: xdis<6.3.0,>=6.1.1
|
25
|
+
Provides-Extra: dev
|
26
|
+
Requires-Dist: pre-commit; extra == "dev"
|
27
|
+
Requires-Dist: black; extra == "dev"
|
28
|
+
Requires-Dist: isort; extra == "dev"
|
29
|
+
Requires-Dist: pytest; extra == "dev"
|
30
|
+
Dynamic: license-file
|
31
|
+
|
32
|
+
|Pypi Installs| |Latest Version| |Supported Python Versions|
|
33
|
+
|
34
|
+
xasm
|
35
|
+
====
|
36
|
+
|
37
|
+
*NOTE: this is in beta.*
|
38
|
+
|
39
|
+
A cross-version Python bytecode assembler
|
40
|
+
|
41
|
+
|
42
|
+
Introduction
|
43
|
+
------------
|
44
|
+
|
45
|
+
The Python ``xasm`` module has routines for assembly, and has a command to
|
46
|
+
assemble bytecode for several different versions of Python.
|
47
|
+
|
48
|
+
Here are some potential uses:
|
49
|
+
|
50
|
+
* Make small changes to existing Python bytecode when you don’t have source
|
51
|
+
* Craft custom and efficient bytecode
|
52
|
+
* Write an instruction-level optimizing compiler
|
53
|
+
* Experiment with and learn about Python bytecode
|
54
|
+
* Foil decompilers like uncompyle6_ so that they can’t disassemble bytecode (at least for now)
|
55
|
+
|
56
|
+
This support the same kinds of bytecode that xdis_ supports. This is
|
57
|
+
pretty much all released bytecode before Python 3.11. We tend to lag behind the
|
58
|
+
latest Python releases.
|
59
|
+
|
60
|
+
The code requires Python 3.6 or later.
|
61
|
+
|
62
|
+
Assembly files
|
63
|
+
--------------
|
64
|
+
|
65
|
+
See how-to-use_ for more detail. Some general some principles:
|
66
|
+
|
67
|
+
* Preferred extension for Python assembly is ``.pyasm``
|
68
|
+
* assembly is designed to work with the output of ``pydisasm -F xasm``
|
69
|
+
* Assembly file labels are at the beginning of the line
|
70
|
+
and end in a colon, e.g. ``END_IF:``
|
71
|
+
* instruction offsets in the assembly file are ignored and don't need
|
72
|
+
to be entered
|
73
|
+
* in those instructions that refer to offsets, if the if the
|
74
|
+
operand is an int, exactly that value will be used for the operand. Otherwise
|
75
|
+
we will look for labels and match up with that
|
76
|
+
|
77
|
+
|
78
|
+
Installation
|
79
|
+
------------
|
80
|
+
|
81
|
+
*If you are using Python 3.11 or later*, you can install from PyPI using the name ``xasm``::
|
82
|
+
|
83
|
+
pip install xasm
|
84
|
+
|
85
|
+
A GNU makefile is also provided so ``make install`` (possibly as root or
|
86
|
+
sudo) will do the steps above.
|
87
|
+
|
88
|
+
|
89
|
+
*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.
|
90
|
+
|
91
|
+
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.
|
92
|
+
|
93
|
+
If the Python version you are running xasm is 3.11 or later, use a file called xasm-*x.y.z*.tar.gz.
|
94
|
+
|
95
|
+
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.
|
96
|
+
|
97
|
+
Rationale for using Git Branches
|
98
|
+
++++++++++++++++++++++++++++++++
|
99
|
+
|
100
|
+
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.
|
101
|
+
|
102
|
+
A GNU makefile is also provided so :code:`make install` (possibly as root or sudo) will do the steps above.
|
103
|
+
|
104
|
+
|
105
|
+
Testing
|
106
|
+
-------
|
107
|
+
|
108
|
+
::
|
109
|
+
|
110
|
+
make check
|
111
|
+
|
112
|
+
A GNU makefile has been added to smooth over setting running the right
|
113
|
+
command, and running tests from fastest to slowest.
|
114
|
+
|
115
|
+
If you have remake_ installed, you can see the list of all tasks
|
116
|
+
including tests via :code:`remake --tasks`.
|
117
|
+
|
118
|
+
|
119
|
+
Example Assembly File
|
120
|
+
---------------------
|
121
|
+
|
122
|
+
For this Python source code:
|
123
|
+
|
124
|
+
::
|
125
|
+
|
126
|
+
def five():
|
127
|
+
return 5
|
128
|
+
|
129
|
+
print(five())
|
130
|
+
|
131
|
+
Here is an assembly for the above:
|
132
|
+
|
133
|
+
::
|
134
|
+
|
135
|
+
# Python bytecode 3.6 (3379)
|
136
|
+
|
137
|
+
# Method Name: five
|
138
|
+
# Filename: /tmp/five.pl
|
139
|
+
# Argument count: 0
|
140
|
+
# Kw-only arguments: 0
|
141
|
+
# Number of locals: 0
|
142
|
+
# Stack size: 1
|
143
|
+
# Flags: 0x00000043 (NOFREE | NEWLOCALS | OPTIMIZED)
|
144
|
+
# First Line: 1
|
145
|
+
# Constants:
|
146
|
+
# 0: None
|
147
|
+
# 1: 5
|
148
|
+
2:
|
149
|
+
LOAD_CONST (5)
|
150
|
+
RETURN_VALUE
|
151
|
+
|
152
|
+
|
153
|
+
# Method Name: <module>
|
154
|
+
# Filename: /tmp/five.pl
|
155
|
+
# Argument count: 0
|
156
|
+
# Kw-only arguments: 0
|
157
|
+
# Number of locals: 0
|
158
|
+
# Stack size: 2
|
159
|
+
# Flags: 0x00000040 (NOFREE)
|
160
|
+
# First Line: 1
|
161
|
+
# Constants:
|
162
|
+
# 0: <code object five at 0x0000>
|
163
|
+
# 1: 'five'
|
164
|
+
# 2: None
|
165
|
+
# Names:
|
166
|
+
# 0: five
|
167
|
+
# 1: print
|
168
|
+
1:
|
169
|
+
LOAD_CONST 0 (<code object five at 0x0000>)
|
170
|
+
LOAD_CONST ('five')
|
171
|
+
MAKE_FUNCTION 0
|
172
|
+
STORE_NAME (five)
|
173
|
+
|
174
|
+
3:
|
175
|
+
LOAD_NAME (print)
|
176
|
+
LOAD_NAME (five)
|
177
|
+
CALL_FUNCTION 0
|
178
|
+
CALL_FUNCTION 1
|
179
|
+
POP_TOP
|
180
|
+
LOAD_CONST (None)
|
181
|
+
RETURN_VALUE
|
182
|
+
|
183
|
+
|
184
|
+
The above can be created automatically from Python source code using the ``pydisasm``
|
185
|
+
command from ``xdis``:
|
186
|
+
|
187
|
+
::
|
188
|
+
|
189
|
+
pydisasm --format xasm /tmp/five.pyc
|
190
|
+
|
191
|
+
In the example above though, I have shortened and simplified the result.
|
192
|
+
|
193
|
+
|
194
|
+
Usage
|
195
|
+
-----
|
196
|
+
|
197
|
+
To create a python bytecode file from an assemble file, run:
|
198
|
+
|
199
|
+
::
|
200
|
+
|
201
|
+
pyc-xasm [OPTIONS] ASM_PATH
|
202
|
+
|
203
|
+
|
204
|
+
For usage help, type: ``pyc-xasm --help``.
|
205
|
+
|
206
|
+
|
207
|
+
To convert a python bytecode from one bytecode to another, run:
|
208
|
+
|
209
|
+
::
|
210
|
+
|
211
|
+
pyc-convert [OPTIONS] INPUT_PYC [OUTPUT_PYC]
|
212
|
+
|
213
|
+
|
214
|
+
For usage help, type: ``pyc-convert --help``.
|
215
|
+
|
216
|
+
|
217
|
+
See Also
|
218
|
+
--------
|
219
|
+
|
220
|
+
* https://github.com/rocky/python-xdis : Cross Python version disassemble
|
221
|
+
* https://github.com/rocky/x-python : Cross Python version interpreter
|
222
|
+
* https://github.com/rocky/python-xasm/blob/master/HOW-TO-USE.rst : How to write an assembler file
|
223
|
+
* https://rocky.github.io/pycon2018-light.co/ : Pycolumbia 2018 Lightning talk showing how to use the assembler
|
224
|
+
|
225
|
+
|
226
|
+
.. _uncompyle6: https://github.com/rocky/python-uncompyle6
|
227
|
+
.. _how-to-use: https://github.com/rocky/python-xasm/blob/master/HOW-TO-USE.rst
|
228
|
+
.. _xdis: https://github.com/rocky/xdis
|
229
|
+
.. |Latest Version| image:: https://badge.fury.io/py/xasm.svg
|
230
|
+
:target: https://badge.fury.io/py/xasm
|
231
|
+
.. |Pypi Installs| image:: https://pepy.tech/badge/xasm
|
232
|
+
.. |Supported Python Versions| image:: https://img.shields.io/pypi/pyversions/xasm.svg
|
233
|
+
.. _remake: http://bashdb.sf.net/remake
|
@@ -0,0 +1,13 @@
|
|
1
|
+
xasm/.gitignore,sha256=4M-0eEErWDaEphX2OA2Bsp72_sUTUkrs1LXr70HkItw,30
|
2
|
+
xasm/__init__.py,sha256=gz94OZ_Jc1WozfOYH1pjQeg7IgB1L9wv5fyytJ2XgZU,174
|
3
|
+
xasm/assemble.py,sha256=0l_0AlsQL2Pwp4ZAR8ucdC6CKnNjeZACc5pD6GORFis,26442
|
4
|
+
xasm/pyc_convert.py,sha256=s2UixbgUQZFHv9nmtvOV7TYohLBev2lcet0GIE9g6GM,9177
|
5
|
+
xasm/version.py,sha256=xEuuftKJb_Pe2D5lJpTv3HxDgx6Bd1-lH_6HBGWFguw,206
|
6
|
+
xasm/write_pyc.py,sha256=ZoqjupN9rEjJ5o49SZcX9rbEhaImnqsJKkNzLnT9j4c,1455
|
7
|
+
xasm/xasm_cli.py,sha256=4EYRZVejspzBvoNaXsWgGbN7qRU5p-MPrt05IYu1kGc,1789
|
8
|
+
xasm-1.2.1.dist-info/licenses/LICENSE.gpl2,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
|
9
|
+
xasm-1.2.1.dist-info/METADATA,sha256=8iv_uKXBGELwvcM1oLGQRciIftEK9gcG0cqM3Is_mfM,7334
|
10
|
+
xasm-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
xasm-1.2.1.dist-info/entry_points.txt,sha256=3NCzOsGwLI-kMOKN72-joKrVjz8DAjHdw7aZof31A2E,50
|
12
|
+
xasm-1.2.1.dist-info/top_level.txt,sha256=24UNOItB5o_EJoJ9nqhPZEC5GiSaYtHll3F6mJXzOkA,5
|
13
|
+
xasm-1.2.1.dist-info/RECORD,,
|