Ich entschied, dass es Zeit war, dass ein ordnungsgemäßer Pyarmor -Expacker veröffentlicht wurde. Alle, die derzeit öffentlich sind, sind entweder veraltet und arbeiten überhaupt nicht oder geben nur teilweise Ausgaben. Ich habe vor, diese zu der neuesten Version von Pyarmor zu unterstützen.
Bitte spielen Sie das Repository, wenn Sie es hilfreich fanden. Ich würde es wirklich schätzen.
In diesem Repository finden Sie 3 verschiedene Methoden zum Auspacken von Pyarmor. In dem Ordner Methoden finden Sie alle für jede Methode benötigten Dateien. Im Folgenden finden Sie eine detaillierte Erstellung darüber, wie ich bis zum Endprodukt angefangen habe. Ich hoffe, mehr Menschen verstehen tatsächlich, wie es auf diese Weise funktioniert, anstatt nur das Tool zu verwenden.
Dies ist eine Liste aller bekannten Probleme/fehlenden Funktionen. Ich habe nicht genug Zeit, um sie selbst zu reparieren, damit ich mich stark auf Mitwirkende verlasse.
Probleme:
Fehlende Funktionen:
WICHTIG: Verwenden Sie überall dieselbe Python -Version und schauen Sie sich an, mit welchem Programm Sie auspacken werden. Wenn Sie es nicht tun, werden Sie sich mit Problemen stellen.
method_1.py ausrun.py ausführendumps -Verzeichnis finden Sie die vollständig ausgepackte .pyc -Datei. HINWEIS: Verwenden Sie den statischen Expacker nicht für etwas unterversion 3.9.7, das Audit -Protokoll marshal.loads Wurde erst in und nach 3.9.7 hinzugefügt. Mitwirkenden sind herzlich eingeladen, Unterstützung hinzuzufügen
python3 bypass.py filename.pyc (ersetzen Sie filename.pyc mit dem tatsächlichen Dateinamen offensichtlich)dumps -Verzeichnis finden Sie die vollständig ausgepackte .pyc -Datei.Beiträge sind wirklich wichtig. Ich habe nicht genug Zeit, um alle oben aufgeführten Probleme zu beheben. Bitte beitragen Sie, wenn Sie können.
Spenden sind auch sehr willkommen:
BTC - 37RQ1XEB5Q8SCMMKK3MVMD4RBE5FV7EMMH
ETH - 0X28152666867856FA48B3924C185D7E1FB36F3B9A
LTC - MFHDLRDZAQYGZXUVXQFM4RWVGBMRZMDZAO
Dies ist das lang erwartete Aufschreiben über den vollständigen Prozess, den ich durch Deobfuscate oder eher Pyarmor ausgepackt habe. Ich möchte erwähnen, dass ich nicht viel über Python -Interna wusste, daher dauerte es viel länger als für andere Menschen mit mehr Erfahrung in Python -Interna.
Pyarmor hat eine sehr umfangreiche Dokumentation darüber, wie sie alles machen, ich würde Ihnen empfehlen, das vollständig zu lesen. Pyarmor schaltet im Wesentlichen jedes Codeobjekt durch und verschlüsselt es. Es gibt jedoch einen festen Header und eine Fußzeile. Dies hängt davon ab, ob der „Wrap -Modus“ aktiviert ist, er ist standardmäßig.
wrap header:
LOAD_GLOBALS N (__armor_enter__) N = length of co_consts
CALL_FUNCTION 0
POP_TOP
SETUP_FINALLY X (jump to wrap footer) X = size of original byte code
changed original byte code:
Increase oparg of each absolute jump instruction by the size of wrap header
Obfuscate original byte code
...
wrap footer:
LOAD_GLOBALS N + 1 (__armor_exit__)
CALL_FUNCTION 0
POP_TOP
END_FINALLY
Aus den Pyarmor -Dokumenten
Im Header gibt es einen Aufruf der Funktion __armor_enter__ , mit der das Codeobjekt im Speicher entschlüsselt wird. Nachdem das Codeobjekt beendet hat, wird die Funktion __armor_exit__ aufgerufen, die das Codeobjekt erneut neu entschlüsselt, sodass keine entschlüsselten Codeobjekte im Speicher zurückgelassen werden.
Wenn wir ein Pyarmor -Skript kompilieren, können wir sehen, dass es die Einstiegspunktdatei und einen Pytransform -Ordner gibt. Dieser Ordner enthält eine DLL und eine __init__.py -Datei.
dist
│ test.py
└───pytransform
| _pytransform.dll
| __init__.py
Die Datei __init__.py muss nicht viel mit dem Entschlüsseln der Codeobjekte tun. Es wird meistens verwendet, damit wir das Modul importieren können. Es macht einige Überprüfungen, wie das Betriebssystem, das Sie verwenden, wenn Sie es lesen möchten, es ist Open Source, sodass Sie es einfach wie ein normales Python -Skript öffnen können.
Das Wichtigste, was es tut, ist das Laden der _pytransform.dll und der Aufnahme ihrer Funktionen den Globalen des Python -Interpreters. In allen Skripten können wir sehen, dass es aus Pytransform Pyarmor_runtime importiert.
from pytransform import pyarmor_runtime
pyarmor_runtime ()
__pyarmor__ ( __name__ , __file__ , b' x50 x59 x41 x5...' ) Diese Funktion erstellt alle Funktionen, die erforderlich sind, um Pyarmor -Skripte wie die Funktion __armor_enter__ und __armor_exit__ auszuführen.
Die erste Ressource, die ich gefunden habe, war dieser Thread im Forum Tuts4you. Hier schrieb der Benutzer extremecoders ein paar Beiträge darüber, wie er die pyarmor -geschützte Datei entpackte. Er bearbeitete den CPython -Quellcode, um den Marschall jedes Codeobjekts zu entsorgen, das ausgeführt wird.
Diese Methode ist zwar großartig, um alle Konstanten aufzudecken, aber es ist weniger ideal, wenn Sie den Bytecode erhalten möchten. Dies liegt jedoch daran, dass:
__armor_enter__ aufgerufen wird, was zu Beginn des Codebobjekts steht. Da die Funktion __armor_enter__ sie im Speicher entschlüsselt, wird sie nicht von CPython abgeladen. Es gibt einige Leute, die mit dem Ableiten der entschlüsselten Codeobjekte aus dem Speicher experimentiert haben, indem sie Python -Code injiziert haben.
In diesem Video demonstriert jemand, wie er alle entschlüsselten Funktionen im Gedächtnis zerlegt.
Er hat jedoch noch nicht herausgefunden, wie das Hauptmodul abgelassen werden soll, nur Funktionen. Zum Glück veröffentlichte er seinen Code, mit dem er Python -Code injiziert hatte. Im Github -Repository können wir sehen, dass er eine DLL erstellt, in der er eine exportierte Funktion von der Python -DLL anruft, um einfachen Python -Code auszuführen. Derzeit hat er nur Unterstützung für das Finden der Python -DLLs für Versionen 3.7 bis 3.9 hinzugefügt. Sie können jedoch problemlos weitere Versionen hinzufügen, indem Sie die Quelle ändern und neu kompilieren. Er hat es so gemacht, dass es den in Code.py gefundenen Code ausgeführt hat. Auf diese Weise ist es einfach, den Python -Code zu bearbeiten, ohne das Projekt jedes Mal wieder aufbauen zu müssen.
Im Repository enthält er eine Python -Datei, die alle Namen der Funktion mit ihrer entsprechenden Adresse im Speicher in eine Datei abgelegt hat.
# Copyright holder: https://github.com/call-042PE
# License: GNU GPL v3.0 (https://github.com/call-042PE/PyInjector/blob/main/LICENSE)
import os , sys , inspect , re , dis , json , types
hexaPattern = re . compile ( r'b0x[0-9A-F]+b' )
def GetAllFunctions (): # get all function in a script
functionFile = open ( "dumpedMembers.txt" , "w+" )
members = inspect . getmembers ( sys . modules [ __name__ ]) # the code will take all the members in the __main__ module, the main problem is that it can't dump main code function
for member in members :
match = re . search ( hexaPattern , str ( member [ 1 ]))
if ( match ):
functionFile . write ( "{ " functionName " : " " + str ( member [ 0 ]) + " " , " functionAddr " : " " + match . group ( 0 ) + " " } n " )
else :
functionFile . write ( "{ " functionName " : " " + str ( member [ 0 ]) + " " , " functionAddr " :null} n " )
functionFile . close ()
GetAllFunctions () Aus dem Repository von Call-042PE
In dem Code können Sie sehen, dass er einen Kommentar hinzugefügt hat, in dem er das Problem hat, dass er nicht auf das Hauptmodulcodeobjekt zugreifen kann.
Nach viel Googeln war ich verblüfft und konnte nichts darüber finden, wie das Objekt des aktuellen laufenden Codes erhalten kann. Irgendwann später habe ich in einem nicht verwandten Projekt einen Funktionsaufruf bei sys._getframe() gesehen. Ich habe einige Nachforschungen darüber gemacht, was es tut, es bekommt den aktuellen Laufrahmen.
Sie können eine Ganzzahl als Argument geben, das den Anrufstapel hinaufgibt und den Rahmen in einem bestimmten Index erhält.
sys . _getframe ( 1 ) # get the caller's frameDer Grund, warum dies wichtig ist, ist wichtig, dass ein Rahmen in Python im Grunde nur ein Codebobjekt ist, aber mit weiteren Informationen über seinen Zustand im Speicher. Um das Codeobjekt aus einem Frame zu erhalten, können wir das Attribut .f_code verwenden. Sie sind auch damit vertraut, wenn Sie eine benutzerdefinierte CPython -Version erstellt haben, die die Codeobjekte, die ausgeführt werden, so ausgeführt werden, wie wir das Codeobjekt auch von einem Frame dort abrufen.
...
1443 tstate -> frame = frame ;
1444 co = frame -> f_code ;
... Aus meiner benutzerdefinierten CPython -Version
Jetzt haben wir herausgefunden, wie wir das aktuelle laufende Codeobjekt erhalten können. Wir können einfach den Anrufstapel hinaufgehen, bis wir das Hauptmodul finden, das entschlüsselt wird.
Jetzt haben wir ziemlich genau die Hauptidee herausgefunden, wie man Pyarmor auspacken kann. Ich werde jetzt 3 Methoden des Auspackens zeigen, die ich in verschiedenen Situationen persönlich als nützlich empfunden habe.
Der erste erfordert, dass Sie den Python -Code injizieren, sodass Sie das Pyarmor -Skript ausführen müssen. Wenn wir das Hauptcodeobjekt wie ich über das Hauptproblem entlassen haben, ist, dass einige Funktionen immer noch verschlüsselt werden, sodass die erste Methode die Pyarmor -Laufzeitfunktion aufruft, so dass alle Funktionen, die zum Entschlüsseln der Codeobjekte benötigt werden, wie __armor_enter__ und __armor_exit__ geladen werden.
Dies scheint eine ziemlich einfache Sache zu sein, aber Pyarmor hat daran gedacht, dass sie einen Einschränkungsmodus implementierten. Sie können dies beim Kompilieren eines Pyarmor -Skripts angeben. Standardmäßig beträgt der Grenzmodus 1.
Ich habe nicht jeden Einschränkungsmodus getestet, aber er funktioniert für die Standardeinstellung.
Wenn wir versuchen, diesen Code in unserer Reply auszuführen, erhalten Sie den folgenden Fehler:
> >> from pytransform import pyarmor_runtime
> >> pyarmor_runtime ()
Check bootstrap restrict mode failed Dies hindert uns daran, die __armor_enter__ und __armor_exit__ zu verwenden.
Der nächste Schritt, den ich unternahm, war, extremecoders auf Tuts4you zu kontaktieren. Er half mir, indem er erwähnte, dass ich die _pytransform.dll nativ flattern konnte. Ich möchte ihm auch dafür danken, dass er mir die Lösung gegeben hat, dies ausschließlich in Python zu tun.
Wenn wir die _pytransform.dll in einem nativen Debugger öffnen, habe ich X64DBG ausgewählt, wir werden nach allen Zeichenfolgen im aktuellen Modul suchen.
Wenn wir dies jetzt filtern, indem wir nach "Bootstrap" suchen, erhalten wir Folgendes.
Wenn wir die Demontage beim ersten Suchergebnis ansehen, sehen Sie, dass es eine Referenz von _errno gibt, die darauf hinweist, dass ein Fehler aufgenommen werden kann. Ein paar Zeilen, die den Fehler, den wir in Python erhalten, sehen können.
Wenn wir nur vom Punkt des Sprunges, der über den Code springt, der den Fehler auf die Rückgabe auslöst, nur nop nop, gibt es keine Möglichkeit, dass der Fehler aufgeworfen werden kann.
Wenn wir dies nun speichern und die _pytransform.dll ersetzen, werden Sie sehen, dass der Fehler, wenn wir denselben Code erneut versuchen, nicht auf die Funktionen __armor_enter__ und __armor_exit__ zugreifen kann.
> >> from pytransform import pyarmor_runtime
> >> pyarmor_runtime ()
> >> __armor_enter__
< built - in function __armor_enter__ >
> >> __armor_exit__
< built - in function __armor_exit__ > Dies ist ziemlich anstrengend, wenn wir dies für jedes Pyarmor -Skript tun müssen, das wir auspacken möchten, sodass extremecoders ein Skript erstellt haben, das die spezifischen Adressen im Speicher in Python noppiert.
# Credit to extremecoders (https://forum.tuts4you.com/profile/79240-extreme-coders/) for writing the script
# Credit to me for adding the comments explaining it
import ctypes
from ctypes . wintypes import *
VirtualProtect = ctypes . windll . kernel32 . VirtualProtect
VirtualProtect . argtypes = [ LPVOID , ctypes . c_size_t , DWORD , PDWORD ]
VirtualProtect . restype = BOOL
# Load the dll in memory, this is useful because once it's loaded in memory it won't need to get loaded again so all the changes we make will be kept, including the bootstrap bypass
h_pytransform = ctypes . cdll . LoadLibrary ( "pytransform \ _pytransform.dll" )
pytransform_base = h_pytransform . _handle # Get the memory address where the dll is loaded
print ( "[+] _pytransform.dll loaded at" , hex ( pytransform_base ))
# We got this offset like I showed above with x64dbg, it's the first address where we start the NOP
patch_offset = 0x70A18F80 - pytransform_base
num_nops = 0x70A18FD5 - 0x70A18F80 # Minus the end address, this is the size that the NOP will be. The result will be 0x55
oldprotect = DWORD ( 0 )
PAGE_EXECUTE_READWRITE = DWORD ( 0x40 )
print ( "[+] Setting memory permissions" )
VirtualProtect ( pytransform_base + patch_offset , num_nops , PAGE_EXECUTE_READWRITE , ctypes . byref ( oldprotect ))
print ( "[+] Patching bootstrap restrict mode" )
ctypes . memset ( pytransform_base + patch_offset , 0x90 , num_nops ) # 0x90 is NOP
print ( "[+] Restoring memory permission" )
VirtualProtect ( pytransform_base + patch_offset , num_nops , oldprotect , ctypes . byref ( oldprotect ))
print ( "[+] All done! Pyarmor bootstrap restrict mode disabled" ) Wenn wir diesen Code A in eine Datei namens restrict_bypass.py einfügen, können wir ihn wie folgt verwenden, indem wir die ursprüngliche _pytransform.dll verwenden
> >> import restrict_bypass
[ + ] _pytransform . dll loaded at 0x70a00000
[ + ] Setting memory permissions
[ + ] Patching bootstrap restrict mode
[ + ] Restoring memory permission
[ + ] All done ! Pyarmor bootstrap restrict mode disabled
>> > from pytransform import pyarmor_runtime
>> > pyarmor_runtime ()
>> > __armor_enter__
< built - in function __armor_enter__ >
> >> __armor_exit__
< built - in function __armor_exit__ > Die zweite Methode startet genauso wie die erste Methode. Wir injizieren das Skript, das das aktuelle laufende Codeobjekt erhält.
Erst jetzt ist der Unterschied, dass wir es nicht einfach abwerfen, wir werden es "reparieren". Damit meine ich, Pyarmor vollständig davon zu entfernen, damit wir das ursprüngliche Codeobjekt erhalten.
Da Pyarmor bei der Verschleierung mehrere Optionen hat, habe ich beschlossen, alle gemeinsamen Unterstützung zu unterstützen.
Wenn es ein Skript erkennt, hat es __armor_enter__ darin, es wird so geändert, dass das Codeobjekt direkt nach dem Aufrufen von __armor_enter__ zurückgibt.
Es gibt einen POP_TOP -Opcode, der dem Funktionsaufruf folgt. Dies wird verwendet, damit der Rückgabewert der Funktion aus dem Stapel entfernt wird. Wir ersetzen ihn nur durch den Opcode RETURN_VALUE , damit wir den Rückgabewert des __armor_enter__ -Funktions und das entschlüsseltes Code -Objekt in Speicher im Speicher haben, ohne das ursprüngliche ByteCode auszuführen. Siehe das Beispiel unten
1 0 JUMP_ABSOLUTE 18
2 NOP
4 NOP
>> 6 POP_BLOCK
3 8 < 53 >
10 NOP
12 NOP
14 NOP
7 16 JUMP_ABSOLUTE 82
>> 18 LOAD_GLOBAL 5 ( __armor_enter__ )
20 CALL_FUNCTION 0
22 POP_TOP # we change this to RETURN_VALUE
9 24 NOP
26 NOP
28 NOP
30 SETUP_FINALLY 50 ( to 82 ) Da Pyarmor das Codeobjekt im Speicher bearbeitet, bleiben die Änderungen auch, nachdem wir das Codungsobjekt beendet haben.
Jetzt können wir das Codeobjekt aufrufen (ausführend). Wir haben jetzt Zugriff auf das entschlüsselte Codeobjekt. Jetzt ist nur noch übrig, um die Pyarmor -Modifikationen an dem Codebobjekt zu entfernen, die die Wrap -Header und die Fußzeile sind.
Nachdem dies gereinigt wurde, müssen wir die __armor_enter__ und __armor_exit__ aus den co_names entfernen.
Wir wiederholen dies rekursiv für alle Codeobjekte.
Die Ausgabe ist das ursprüngliche Codeobjekt. Es wird so sein, als ob Pyarmor nie angewendet wurde.
Aus diesem Grund können wir alle unsere bevorzugten Tools verwenden, z. B. Decompyle3, um den ursprünglichen Quellcode zu erhalten.
Die dritte Methode behebt das letzte Problem mit der Methode Nr. 2.
In Methode Nr. 2 müssen wir das Programm noch ausführen und injizieren.
Dies kann ein Problem sein, weil:
Die dritte Methode versucht, Pyarmor statisch auszupacken, mit dem ich meine, ohne etwas von dem verschleierten Programm auszuführen.
Es gibt einige Möglichkeiten, wie Sie es statisch auspacken können, aber die Methode, die ich erklären werde, sieht am einfachsten aus, ohne andere Tools und/oder Sprachen verwenden zu müssen.
Wir werden Audit -Protokolle verwenden, Audit -Protokolle wurden aus Sicherheitsgründen in Python implementiert. Ironischerweise werden wir die Prüfungsprotokolle ausnutzen, um die Sicherheit zu beseitigen.
Audit -Protokolle protokollieren im Wesentlichen interne CPython -Funktionen. Einschließlich exec und marshal.loads , die beide verwenden können, um das Haupt -Verschleierte -Code -Objekt zu erhalten, ohne den Code injizieren/ausführen zu müssen. Eine vollständige Liste der Prüfprotokolle finden Sie hier
CPython fügte etwas ordentliches als Audit -Hooks hinzu. Jedes Mal, wenn ein Audit -Protokoll ausgelöst wird, wird ein Rückruf zum von uns installierten Haken durchgeführt. Der Haken wird einfach eine Funktion sein, die 2 Argumente nimmt, event , arg .
Beispiel eines Prüfungshakens:
import sys
def hook ( event , arg ):
print ( event , arg )
sys . addaudithook ( hook ) Der einzige Weg, um Codeobjekte auf der Festplatte zu speichern, besteht darin, es zu marktieren. Dies bedeutet, dass Pyarmor die marschallierten Codeobjekte verschlüsseln muss. Natürlich müssen sie sie natürlich entschlüsseln, wenn sie in Python darauf zugreifen möchten.
Sie verwenden, wie die meisten anderen Menschen, den eingebauten Marshaller. Das Paket heißt marshal und es ist ein integriertes Paket, das in C geschrieben wurde. Es ist eines der Pakete mit Audit-Protokollen. Wenn Pyarmor es also anruft, können wir die Argumente sehen.
Das Codeobjekt hat weiterhin Bytecode verschlüsselt, aber wir haben es bereits geschafft, die erste "Ebene" zu überwinden. Wir können unsere Methode Nr. 2 aus dieser Phase im Grunde wiederverwenden, da es sich auch mit verschlüsselten Codeobjekten befassen muss. Der einzige Unterschied ist jetzt, dass jedes Codeobjekt anstelle derjenigen verschlüsselt wird, die normalerweise bereits ausgeführt wurden, wie das Hauptcodeobjekt.
Weil wir in Methode Nr. 2 den Code injizieren, haben wir bereits Zugriff auf alle Pyarmor -Funktionen wie __armor_enter__ und __armor_exit__ . Da wir versuchen, es statisch auszupacken, haben wir diesen Luxus nicht.
Wie ich oben erwähnt habe, hat Pyarmor die Modi einschränkend, ich habe bereits gezeigt, wie der Bootstrap -Beschränkungsmodus umgehen kann, da dies nur ausgelöst wird, wenn wir die Funktion pyarmor_runtime() ausführen.
Jetzt müssen wir die gesamte verschleierte Datei ausführen, die den __pyarmor__ -Anruf enthält. Diese Funktion löst einen weiteren Einschränkungsmodus aus, sodass wir das umgehen müssen. Zuerst dachte ich, dass wir eine ähnliche Methode anwenden, indem wir sie nativ gepatscht haben.
Ein Freund hat dabei geholfen, dies sind die Schritte, die Sie ausführen können, um es zu wiederholen. Denken Sie daran, ich fand eine bessere und einfachere Methode. Pyarmor überprüft, ob die Pyarmor -Zeichenfolge in __main__ an einer bestimmten Speicheradresse vorhanden ist. Wir müssen diesen Scheck patchen. Siehe das Bild unten
Die bessere Methode, die ich fand, ist, dass der Einschränkungsmodus von Pyarmor nicht überprüft, ob die Hauptdatei direkt von Python ausgeführt wird oder ob er aufgerufen wurde, sodass wir dies einfach tun können:
exec ( open ( filename )) Natürlich, nachdem wir den Prüfungshaken installiert haben.
Das Problem, das ich hatte, war, dass der Prüfhaken auf marshal.loads ausgelöst wurde, aber offensichtlich musste ich das Codeobjekt selbst laden, aber das würde es einfach erneut auslösen, also fügte ich einen Scheck hinzu, um zu sehen, ob das dumps -Verzeichnis existiert. Dies ist gefährlich, denn wenn immer noch ein dumps übrig ist, bevor er nur dazu führen würde, dass das geschützte Skript ausgeführt wird, ohne es zu stoppen. Wir müssen einen besseren Weg finden, dies zu tun.
EDIT : Ich habe kürzlich festgestellt, dass ich den Teil vergessen habe, in dem wir die absoluten Sprünge bearbeiten müssen. Dieser Teil wird das abdecken.
Wenn dies sowohl in der Methode Nr. 2 als auch in Methode Nr. 3 tun muss. Wenn wir die Fußzeile entfernen, gibt es keine Collions mit den Indizes. Wenn wir den Header entfernen, werden die Indizes jedoch um die Größe des Headers verschoben, sodass wir alle absoluten Sprünge überschreiten und die Größe des Headers subtrahieren müssen. Dieser Teil ist ziemlich einfach.
for i in range ( 0 , len ( raw_code ), 2 ):
opcode = raw_code [ i ]
if opcode == JUMP_ABSOLUTE :
argument = calculate_arg ( raw_code , i )
new_arg = argument - ( try_start + 2 )
extended_args , new_arg = calculate_extended_args ( new_arg )
for extended_arg in extended_args :
raw_code . insert ( i , EXTENDED_ARG )
raw_code . insert ( i + 1 , extended_arg )
i += 2
raw_code [ i + 1 ] = new_arg Aus Methode Nr. 3
Wir schauen durch den Bytecode und prüfen, ob der Opcode der Opcode JUMP_ABSOLUTE ist. Wenn ja, werden wir das Argument berechnen (im EXTENDED_ARG behalten). Dann nehmen wir den try_start , der die Größe des Headers hat (es ist tatsächlich der Index des letzten Opcode aus dem Header, deshalb fügen wir 2 hinzu) und subtrahieren Sie ihn vom Argument des Opcode JUMP_ABSOLUTE ab.
Der schwierigste Teil bei der Implementierung war die Pflege der EXTENDED_ARG -Opcodes, die wir möglicherweise hinzufügen müssen, wenn das Argument über die maximale Größe von 1 Byte (255) übernimmt. Wir verarbeiten das in calculate_extended_args .
def calculate_extended_args ( arg : int ): # This function will calculate the necessary extended_args needed
extended_args = []
new_arg = arg
if arg > 255 :
extended_arg = arg >> 8
while True :
if extended_arg > 255 :
extended_arg -= 255
extended_args . append ( 255 )
else :
extended_args . append ( extended_arg )
break
new_arg = arg % 256
return extended_args , new_arg Aus Methode Nr. 3
Um diesen Code zu schreiben, musste ich zuerst verstehen, wie der EXTENDED_ARG genau funktioniert hat.
Dieser Artikel hat sehr geholfen, diesen Opcode zu verstehen.
Eine Anweisung in Python beträgt 2 Bytes in den neueren Versionen (3.6+). Ein Byte wird für den Opcode verwendet und ein Byte ist für das Argument. Wenn wir ein Byte überschreiten müssen, verwenden wir den EXTENDED_ARG . Es funktioniert im Grunde wie folgt:
arg = 300 # Let's say this is the size of our argumentWir wissen, dass der maximal erlaubte ist 255, also müssen wir Extended_arg verwenden. Sie würden denken, es wäre so:
extended_arg = 255
arg = 45Das habe ich zum ersten Mal angenommen, aber nachdem ich mir den Code angesehen habe, den Python generierte, bemerkte ich, dass es so war:
extended_arg = 1
arg = 44 Ich war sehr verwirrt, warum es so war, da ich keine Korrelation zwischen dem, was ich erwartet hatte, und der Realität sah. Der oben verlinkte Artikel erklärte alles.
Python behandelt die Extended_arg wie folgt:
extended_arg = extended_arg * 256Nachdem dies gesehen hatte, war alles klar, da es das bedeuten würde
extended_arg = 1 * 256
arg = 44
print ( extended_arg + arg ) Würde 300 ausgeben.
Ich habe diese Logik auf die Funktion angewendet, damit sie eine Liste der notwendigen Extended_ARG -Opcodes und des neuen Argumentwerts zurückgibt (der unter oder gleich 255 entspricht).
Ich füge dann einfach den Extended_arg in den richtigen Index ein.