Update: Aus verschiedenen Gründen habe ich angefangen, einen Cross-Python-Assembler zu schreiben. Siehe https://github.com/rocky/python-xasm Es ist immer noch in sehr frühen Beta.
Soweit ich weiß, gibt es keine derzeit gepflegten Python Assembler. PEAK's Bytecode Disassembler wurde für Python 2.6 entwickelt und später modifiziert, um frühes Python 2.7 zu unterstützen.
Es ist ziemlich cool von der documentation. Aber es beruht auf anderen PEAK-Bibliotheken, die problematisch sein könnten.
Ich werde das ganze Beispiel durchgehen, um Ihnen ein Gefühl dafür zu geben, was Sie tun müssten. Es ist nicht schön, aber dann solltest du das erwarten.
Grundsätzlich müssen Sie nach dem Ändern des Bytecodes ein neues Objekt types.CodeType
erstellen. Sie benötigen eine neue, weil viele der Objekte im Codetyp aus gutem Grund nicht geändert werden können. Zum Beispiel kann der Interpreter einige dieser Objektwerte zwischengespeichert haben.
Nach dem Erstellen von Code können Sie dies in Funktionen verwenden, die einen Codetyp verwenden, der in exec
oder eval
verwendet werden kann.
Oder Sie können dies in eine Bytecode-Datei schreiben. Leider hat sich das Code-Format zwischen Python 2 und Python 3 geändert. Und nebenbei noch die Optimierung und Bytecodes. Tatsächlich sind sie in Python 3.6 Wort Codes nicht Bytecodes.
So, hier ist das, was würden Sie für Ihr Beispiel zu tun haben:
a = """
def fact():
a = 8
a = 0
return a
"""
c = compile(a, '<string>', 'exec')
fn_code = c.co_consts[0] # Pick up the function code from the main code
from dis import dis
dis(fn_code)
print("=" * 30)
x = fn_code.co_code[6:16] # modify bytecode
import types
opt_fn_code = types.CodeType(fn_code.co_argcount,
# c.co_kwonlyargcount, Add this in Python3
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
x, # fn_code.co_code: this you changed
fn_code.co_consts,
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab, # In general, You should adjust this
fn_code.co_freevars,
fn_code.co_cellvars)
dis(opt_fn_code)
print("=" * 30)
print("Result is", eval(opt_fn_code))
# Now let's change the value of what's returned
co_consts = list(opt_fn_code.co_consts)
co_consts[-1] = 10
opt_fn_code = types.CodeType(fn_code.co_argcount,
# c.co_kwonlyargcount, Add this in Python3
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
x, # fn_code.co_code: this you changed
tuple(co_consts), # this is now changed too
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab, # In general, You should adjust this
fn_code.co_freevars,
fn_code.co_cellvars)
dis(opt_fn_code)
print("=" * 30)
print("Result is now", eval(opt_fn_code))
Als ich das hier lief ist, was ich habe:
3 0 LOAD_CONST 1 (8)
3 STORE_FAST 0 (a)
4 6 LOAD_CONST 2 (0)
9 STORE_FAST 0 (a)
5 12 LOAD_FAST 0 (a)
15 RETURN_VALUE
==============================
3 0 LOAD_CONST 2 (0)
3 STORE_FAST 0 (a)
4 6 LOAD_FAST 0 (a)
9 RETURN_VALUE
==============================
('Result is', 0)
3 0 LOAD_CONST 2 (10)
3 STORE_FAST 0 (a)
4 6 LOAD_FAST 0 (a)
9 RETURN_VALUE
==============================
('Result is now', 10)
Beachten Sie, dass die Zeilennummern haben nicht geändert, obwohl ich im Code ein paar Zeilen entfernt habe. Das liegt daran, dass ich fn_code.co_lnotab
nicht aktualisiert habe.
Wenn Sie jetzt eine Python-Bytecode-Datei von diesem schreiben möchten. Hier ist, was Sie tun würden:
co_consts = list(c.co_consts)
co_consts[0] = opt_fn_code
c1 = types.CodeType(c.co_argcount,
# c.co_kwonlyargcount, Add this in Python3
c.co_nlocals,
c.co_stacksize,
c.co_flags,
c.co_code,
tuple(co_consts),
c.co_names,
c.co_varnames,
c.co_filename,
c.co_name,
c.co_firstlineno,
c.co_lnotab, # In general, You should adjust this
c.co_freevars,
c.co_cellvars)
from struct import pack
with open('/tmp/testing.pyc', 'w') as fp:
fp.write(pack('Hcc', 62211, '\r', '\n')) # Python 2.7 magic number
import time
fp.write(pack('I', int(time.time())))
# In Python 3 you need to write out the size mod 2**32 here
import marshal
fp.write(marshal.dumps(c1))
Zur Vereinfachung der vorformulierten Bytecode über das Schreiben, ich habe eine Routine zu xdiswrite_python_file() genannt hinzugefügt.
nun die Ergebnisse zu überprüfen:
$ uncompyle6 /tmp/testing.pyc
# uncompyle6 version 2.9.2
# Python bytecode 2.7 (62211)
# Disassembled from: Python 2.7.12 (default, Jul 26 2016, 22:53:31)
# [GCC 5.4.0 20160609]
# Embedded file name: <string>
# Compiled at: 2016-10-18 05:52:13
def fact():
a = 0
# okay decompiling /tmp/testing.pyc
$ pydisasm /tmp/testing.pyc
# pydisasm version 3.1.0
# Python bytecode 2.7 (62211) disassembled from Python 2.7
# Timestamp in code: 2016-10-18 05:52:13
# Method Name: <module>
# Filename: <string>
# Argument count: 0
# Number of locals: 0
# Stack size: 1
# Flags: 0x00000040 (NOFREE)
# Constants:
# 0: <code object fact at 0x7f815843e4b0, file "<string>", line 2>
# 1: None
# Names:
# 0: fact
2 0 LOAD_CONST 0 (<code object fact at 0x7f815843e4b0, file "<string>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (fact)
9 LOAD_CONST 1 (None)
12 RETURN_VALUE
# Method Name: fact
# Filename: <string>
# Argument count: 0
# Number of locals: 1
# Stack size: 1
# Flags: 0x00000043 (NOFREE | NEWLOCALS | OPTIMIZED)
# Constants:
# 0: None
# 1: 8
# 2: 10
# Local variables:
# 0: a
3 0 LOAD_CONST 2 (10)
3 STORE_FAST 0 (a)
4 6 LOAD_CONST 0 (None)
9 RETURN_VALUE
$
Ein alternativer Ansatz für die Optimierung ist bei der Abstract Syntax Tree Ebene (AST) zu optimieren. Ich bin mir jedoch nicht sicher, ob Sie das auch direkt ausführen können. Einige Modulroutinen scheinen darauf hinzudeuten, dass Sie das können. Oder wie Sie eine Bytecode-Datei von einem AST generieren würden. Ich nehme an, Sie schreiben das als Python-Quelle zurück.
Ich bin neugierig, warum Sie das tun würden, was ist Ihr Anwendungsfall? – shuttle87
Python-Bytecode wird als Implementierungsdetail betrachtet und kann von Version zu Version und Interpreter zu Interpreter wechseln. Es gibt keine Dokumentation außer der Interpreter-Quelle. Bist du sicher, dass du das machen willst? – Antimony
@ shuttle87, Ich schreibe eine Spiel-Engine mit Skripts, die ich in Python schreiben möchte, aber ich möchte einige Optimierungen auf den Skripten Bytecode ausführen, die im Klartext im Voraus nicht bekannt sein werden (obwohl die grundlegende Struktur wird bekannt sein, da sie alle eine gemeinsame Basisklasse teilen). Ich habe alle anderen Komponenten funktioniert, nur diese Konvertierung von Bytecode in eine nutzbare Funktion ist die letzte Hürde. –