2015-10-26 7 views
6

Ich frage mich, wie Byte-Code zu ändern, dann kompilieren Sie den Code neu, damit ich es in Python als Funktion verwenden kann? Ich habe versucht worden:Ändern von Python Bytecode

a = """ 
def fact(): 
    a = 8 
    a = 0 
""" 
c = compile(a, '<string>', 'exec') 
w = c.co_consts[0].co_code 
dis(w) 

die dekompiliert zu:

 0 LOAD_CONST   1 (1) 
     3 STORE_FAST   1 (1) 
     6 LOAD_CONST   2 (2) 
     9 STORE_FAST   1 (1) 
    12 LOAD_CONST   0 (0) 
    15 RETURN_VALUE 

angenommen Ich möchte loswerden Linien 0 und 3, rufe ich:

x = c.co_consts[0].co_code[6:16] 
dis(x) 

die Ergebnisse:

 0 LOAD_CONST   2 (2) 
     3 STORE_FAST   1 (1) 
     6 LOAD_CONST   0 (0) 
     9 RETURN_VALUE 

mein Problem ist was zu tun mit x, wenn ich versuche, exec x Ich bekomme eine 'erwartete Zeichenfolge ohne nullbytes und ich bekomme das gleiche für exec w, versucht, x zu kompilieren Ergebnisse in: Compile() erwartete Zeichenfolge ohne Nullbytes.

Ich bin nicht sicher, was der beste Weg, um fortzufahren, außer vielleicht muss ich eine Art von Code-Objekt erstellen, aber ich bin mir nicht sicher, wie, aber ich nehme an, es muss möglich aka byteplay sein, Python Assembler et al

Ich benutze Python 2.7.10, aber ich würde es gerne zukünftige kompatibel (zB Python 3), wenn es möglich ist.

+0

Ich bin neugierig, warum Sie das tun würden, was ist Ihr Anwendungsfall? – shuttle87

+1

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

+0

@ 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. –

Antwort

6

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.