2015-10-26 15 views
6

Zastanawiam się, jak zmodyfikować kod bajtowy, a następnie przekompilować ten kod, aby można go było używać w pythonie jako funkcji? Próbowałem:modyfikowanie python bytecode

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

który decompiles do:

 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 

zakładając Chcę pozbyć się linii 0 do 3, wzywam:

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

skutkiem czego jest:

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

moim problemem jest do zrobienia z x, jeśli spróbuję exec x otrzymam oczekiwany ciąg bez nullbytes i otrzymam to samo dla exec w, próbując skompilować x wyników w: oczekiwany łańcuch kompilacji() bez pustych bajtów.

Nie jestem pewien, jaki jest najlepszy sposób, aby kontynuować, z wyjątkiem być może potrzebuję stworzyć jakiś obiekt-kodu, ale nie jestem pewien jak, ale zakładam, że musi to być możliwe aka byteplay, python asemblers i inne

Używam Pythona 2.7.10, ale chciałbym, aby był zgodny w przyszłości (np python 3), jeśli jest to możliwe.

+0

Jestem ciekawy, dlaczego zrobiłbyś to, jaki jest twój przypadek użycia? – shuttle87

+1

Python bytecode jest uważany za szczegół implementacji i może zmieniać się z wersji na wersję i tłumacza na interpreter. Nie ma dokumentacji oprócz źródła tłumacza. Czy na pewno chcesz to zrobić? – Antimony

+0

@ shuttle87, piszę silnik gry ze skryptami, które mam nadzieję napisać w Pythonie, ale chcę uruchomić pewne optymalizacje kodu skryptu, który luźno mówiąc nie będzie znany z góry (chociaż podstawowa struktura będzie być znane, ponieważ wszystkie mają wspólną klasę podstawową). Mam wszystkie inne komponenty działające, właśnie ten konwertujący kod bajtowy do użytecznej funkcji jest ostatnią przeszkodą. –

Odpowiedz

6

Aktualizacja: Z różnych powodów zacząłem pisać asembler w wersji Cross-Python. Zobacz https://github.com/rocky/python-xasm Jest to nadal bardzo wczesna wersja beta.

O ile mi wiadomo, nie ma obecnie obecnie utrzymywanego asemblera Python. PEAK's Bytecode Disassembler został opracowany dla Pythona 2.6, a później zmodyfikowany w celu obsługi wczesnego Pythona 2.7.

To całkiem fajne z documentation. Ale opiera się na innych bibliotekach PEAK, które mogą być problematyczne.

Przeanalizuję cały przykład, aby poczuć, co musisz zrobić. Nie jest ładna, ale należy się tego spodziewać.

Zasadniczo po modyfikacji kodu bajtowego należy utworzyć nowy obiekt types.CodeType. Potrzebujesz nowego, ponieważ wiele obiektów w typie kodu, z dobrego powodu, nie możesz zmienić. Na przykład interpreter może mieć niektóre z tych wartości w pamięci podręcznej.

Po utworzeniu kodu można go użyć w funkcjach wykorzystujących kod, który może być używany w exec lub eval.

Lub możesz zapisać to do pliku kodu bajtowego. Niestety format kodu zmienił się pomiędzy Python 2 i Python 3. A tak na marginesie, tak samo jest z optymalizacją i bajtodami. W rzeczywistości, w Pythonie 3.6 będą one słowo kody nie bytecodes.

Więc tutaj jest to, co trzeba było zrobić dla przykładu:

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)) 

Kiedy wpadłem to tutaj jest to, co mam:

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) 

Zauważ, że nie mają numery linii zmieniono, mimo że usunąłem w kodzie kilka linii. To dlatego, że nie zaktualizowałem fn_code.co_lnotab.

Jeśli chcesz teraz zapisać z tego plik kodu bajtowego Python. Oto, co można zrobić:

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)) 

Aby uprościć pisanie kodu bajtowego boilerplate powyżej Dodałem rutynowych xdis zwanego write_python_file().

teraz, aby sprawdzić wyniki:

$ 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 
$ 

Alternatywnym podejściem do optymalizacji jest optymalizacja na poziomie Abstract Syntax Tree (AST). Jednak nie jestem pewien, czy możesz wykonać to bezpośrednio. Niektóre procedury modułów sugerują, że możesz. Lub w jaki sposób wygenerować plik kodu bajtowego z AST. Więc przypuszczam, że wypiszesz to jako źródło Pythona.