قررت أن الوقت قد حان لوجود unpacker pyarmor الصحيح. جميع تلك الموجودة حاليًا عامة إما عفا عليها الزمن ، لا تعمل على الإطلاق أو فقط إعطاء مخرجات جزئية. أخطط لجعل هذا واحد يدعم أحدث إصدار من Pyarmor.
يرجى تشغيل المستودع إذا وجدت أنه مفيد. سأقدر ذلك حقًا.
هناك 3 طرق مختلفة لتفريغ Pyarmor ، في مجلد الطرق في هذا المستودع ، ستجد جميع الملفات اللازمة لكل طريقة. ستجد أدناه كتابة مفصلة حول كيفية بدء تشغيل المنتج النهائي. آمل أن يفهم المزيد من الناس فعليًا كيف يعمل بهذه الطريقة بدلاً من مجرد استخدام الأداة.
هذه قائمة بجميع المشكلات المعروفة/الميزات المفقودة. ليس لدي ما يكفي من الوقت لإصلاحهم بنفسي ، لذا فأنا أعتمد بشدة على المساهمين.
مشاكل:
الميزات المفقودة:
هام: استخدم نفس إصدار Python في كل مكان ، انظر إلى ما يتم تجميع البرنامج الذي تفريغه. إذا لم تكن ستواجه مشكلات.
method_1.pyrun.pydumps ، يمكنك العثور على ملف .pyc الذي تم تفريغه بالكامل. ملاحظة: لا تستخدم جهاز Unpacker الثابت لأي شيء أدناه الإصدار 3.9.7 ، تمت إضافة سجل تدقيق marshal.loads فقط في 3.9.7. أي مساهم مرحب بهم لإضافة الدعم
python3 bypass.py filename.pyc (استبدل filename.pyc باسم الملف الفعلي ، من الواضح))dumps ، يمكنك العثور على ملف .pyc الذي تم تفريغه بالكامل.المساهمات مهمة حقا. ليس لدي وقت كاف لإصلاح جميع المشكلات المذكورة أعلاه. يرجى المساهمة إذا استطعت.
التبرعات مرحب بها أيضًا:
BTC - 37RQ1XEB5Q8SCMMKKK3MVMD4RBE5FV7EMMH
ETH - 0x28152666867856FA48B3924C185D7E1FB36F3B9A
LTC - MFHDLRDZAQYGZXUVXQFM4RWVGBMRZMDZAO
هذه هي الكتابة التي طال انتظارها حول العملية الكاملة التي مررت بها إلى Deobfuscate أو بالأحرى تفريغ Pyarmor ، سأخوض كل الأبحاث التي أجريتها ، وفي النهاية أعطيت 3 طرق لتفريغ Pyarmor ، فهي كلها فريدة من نوعها وقابلة للتطبيق في حالات مختلفة. أريد أن أذكر أنني لم أكن أعرف الكثير عن Python Internals ، لذا استغرق الأمر وقتًا أطول بالنسبة لي أكثر من الأشخاص الآخرين الذين لديهم خبرة أكبر في بيثون الداخلية.
لدى Pyarmor وثائق واسعة للغاية حول كيفية قيامهم بكل شيء ، أوصيك بقراءة ذلك بالكامل. Pyarmor يحلق بشكل أساسي من خلال كل كائن رمز ويشفره. هناك رأس ثابت وتذييل رغم ذلك. هذا يعتمد على ما إذا تم تمكين "وضع التفاف" ، فهو افتراضيًا.
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
من مستندات Pyarmor
في الرأس ، توجد مكالمة إلى وظيفة __armor_enter__ ، والتي ستقوم بفك تشفير كائن الكود في الذاكرة. بعد الانتهاء من كائن الكود ، سيتم استدعاء وظيفة __armor_exit__ والتي ستعيد إعادة شطب كائن الكود مرة أخرى ، لذا لا تترك كائنات التعليمات البرمجية المقطوعة في الذاكرة.
عندما نقوم بتجميع نص Pyarmor ، يمكننا أن نرى هناك ملف نقطة الدخول ، ومجلد Pytransform. يحتوي هذا المجلد على DLL وملف __init__.py .
dist
│ test.py
└───pytransform
| _pytransform.dll
| __init__.py
لا يتعين على ملف __init__.py القيام بالكثير مع فك تشفير كائنات الكود. يتم استخدامه في الغالب حتى نتمكن من استيراد الوحدة النمطية. يقوم ببعض الشيكات مثل نظام التشغيل الذي تستخدمه ، إذا كنت ترغب في قراءته ، فهو مفتوح المصدر حتى تتمكن من فتحه مثل البرنامج النصي العادي للبيثون.
أهم شيء يفعله هو تحميل _pytransform.dll وفضح وظائفها إلى كرات المترجم المترجم Python. في جميع البرامج النصية ، يمكننا أن نرى أنه من pytransform ، فإنه يستورد pyarmor_runtime.
from pytransform import pyarmor_runtime
pyarmor_runtime ()
__pyarmor__ ( __name__ , __file__ , b' x50 x59 x41 x5...' ) ستنشئ هذه الوظيفة جميع الوظائف اللازمة لتشغيل البرامج النصية Pyarmor ، مثل وظيفة __armor_enter__ و __armor_exit__ .
كان المورد الأول الذي وجدته هذا الموضوع في Forum Turts4you ، هنا كتب المستخدمون extremecoders بعض المنشورات حول كيفية تفريغ الملف المحمي Pyarmor. قام بتحرير رمز مصدر Cpython لتفريغ المارشال من كل كائن رمز يتم تنفيذه.
على الرغم من أن هذه الطريقة رائعة لفضح جميع الثوابت ، إلا أنها أقل مثالية إذا كنت ترغب في الحصول على رمز Bytecode ، وهذا بسبب:
__armor_enter__ ، والتي هي في بداية كائن الكود. نظرًا لأن وظيفة __armor_enter__ تفكّرها في الذاكرة ، فلن يتم إلقاؤها بواسطة cpython. هناك بعض الأشخاص الذين جربوا إلقاء كائنات الكود المقطوعة من الذاكرة عن طريق حقن كود بيثون.
في هذا الفيديو ، يوضح شخص ما كيف يفصل كل الوظائف التي تم فك تشفيرها في الذاكرة.
ومع ذلك ، لم يكتشف بعد كيفية تفريغ الوحدة الرئيسية ، وظائف فقط. لحسن الحظ ، نشر رمزه الذي اعتاد عليه حقن كود بيثون. في مستودع GitHub ، يمكننا أن نرى أنه ينشئ DLL يطلق عليه وظيفة تم تصديرها من Python DLL لتنفيذ رمز Python بسيط. قام حاليًا بإضافة دعم فقط للعثور على Python DLLs للإصدارات من 3.7 إلى 3.9 ولكن يمكنك بسهولة إضافة المزيد من الإصدارات عن طريق تعديل المصدر وإعادة تجميعه. لقد صنعها بحيث تنفذ الرمز الموجود في Code.py ، وبهذه الطريقة ، من السهل تعديل رمز Python دون الحاجة إلى إعادة بناء المشروع في كل مرة.
في المستودع ، يتضمن ملف Python الذي سيتخلص من جميع أسماء الوظيفة إلى ملف بعنوانه المقابل في الذاكرة ، إذا لم يكن هناك ذاكرة ، فهذا يعني أنه لم يتم استدعاؤه بعد ، لذا لم يتم فك تشفيره بعد.
# 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 () من مستودع Call-042PE
في الكود ، يمكنك أن ترى أنه أضاف تعليقًا يقول إن المشكلة التي يواجهها هي أنه لا يستطيع الوصول إلى كائن رمز الوحدة النمطية الرئيسية.
بعد الكثير من googling ، تعثرت ، غير قادر على العثور على أي شيء حول كيفية الحصول على كائن رمز التشغيل الحالي. في وقت لاحق في مشروع غير ذي صلة ، رأيت استدعاء وظيفة إلى sys._getframe() . لقد أجريت بعض الأبحاث حول ما يفعله ، يحصل على إطار التشغيل الحالي.
يمكنك إعطاء عدد صحيح كوسيطة ستسير في مكدس المكالمات والحصول على الإطار في فهرس معين.
sys . _getframe ( 1 ) # get the caller's frameالآن السبب في أن هذا مهم هو أن الإطار في Python هو في الأساس مجرد كائن رمز ولكن مع مزيد من المعلومات حول حالته في الذاكرة. للحصول على كائن التعليمات البرمجية من إطار ، يمكننا استخدام سمة .f_code ، وستكون على دراية بهذا إذا قمت بإنشاء إصدار مخصص Cpython الذي يتفريغ كائنات التعليمات البرمجية التي يتم تنفيذها كما نحصل أيضًا على كائن الرمز من إطار هناك.
...
1443 tstate -> frame = frame ;
1444 co = frame -> f_code ;
... من نسخة Cpython المخصصة الخاصة بي
حتى الآن ، اكتشفنا كيفية الحصول على كائن رمز التشغيل الحالي ، يمكننا ببساطة السير في مكدس الاتصال حتى نجد الوحدة الرئيسية ، والتي سيتم فك تشفيرها.
لقد اكتشفنا الآن الفكرة الرئيسية حول كيفية تفريغ Pyarmor. سأعرض الآن 3 طرق لتفريغ أنني وجدت شخصيا مفيدة في مواقف مختلفة.
أول واحد يتطلب منك ضخ كود بيثون ، لذلك عليك تشغيل البرنامج النصي Pyarmor. عندما نقوم بتفريغ كائن الكود الرئيسي كما أوضحت أعلاه ، فإن المشكلة الرئيسية هي أن بعض الوظائف ستظل مشفرة ، وبالتالي فإن الطريقة الأولى تستدعي وظيفة وقت تشغيل Pyarmor بحيث يتم تحميل جميع الوظائف اللازمة لفك تشفير كائنات الكود ، مثل __armor_enter__ و __armor_exit__ .
هذا يبدو وكأنه شيء بسيط للغاية ولكن Pyarmor فكر في هذا ، لقد نفذوا وضع تقييد. يمكنك تحديد ذلك عند تجميع البرنامج النصي Pyarmor ، بشكل افتراضي ، وضع تقييد هو 1.
لم أختبر كل وضع تقييد ولكنه يعمل مع الوضع الافتراضي.
عندما نحاول تشغيل هذا الرمز في Reply ، ستحصل على الخطأ التالي:
> >> from pytransform import pyarmor_runtime
> >> pyarmor_runtime ()
Check bootstrap restrict mode failed هذا يمنعنا من القدرة على استخدام __armor_enter__ و __armor_exit__ .
لذلك كانت الخطوة التالية التي اتخذتها هي الاتصال extremecoders على Tuts4you. لقد ساعدني من خلال الإشارة إلى أنه يمكنني تصحيح _pytransform.dll . أود أيضًا أن أشكره على إعطائي الحل في القيام بذلك فقط في بيثون.
إذا فتحنا _pytransform.dll في مصحح أخطاء أصلي ، فقد اخترت X64DBG ، فسنبحث عن جميع الأوتار في الوحدة النمطية الحالية.
إذا قمنا بتصفية هذا الآن عن طريق البحث عن "bootstrap" ، فسوف نحصل على ما يلي.
عندما نشاهد التفكيك في نتيجة البحث الأولى ، ترى أن هناك مرجعًا لـ _errno يشير إلى أنه قد يكون هناك بعض الأخطاء التي يتم رفعها ، على بعد بضعة أسطر أدناه يمكننا رؤية الخطأ الذي نحصل عليه في Python.
عندما نلقي فقط كل شيء من نقطة القفزة التي تقفز فوق الكود الذي يؤدي إلى الخطأ إلى العائد ، لا توجد طريقة لإثارة الخطأ.
الآن إذا حفظنا هذا واستبدلنا _pytransform.dll فسترى أنه عندما نحاول نفس الرمز مرة أخرى ، فلن يحدث الخطأ ولدينا الوصول إلى وظائف __armor_enter__ و __armor_exit__ .
> >> from pytransform import pyarmor_runtime
> >> pyarmor_runtime ()
> >> __armor_enter__
< built - in function __armor_enter__ >
> >> __armor_exit__
< built - in function __armor_exit__ > الآن هذا متعب تمامًا إذا كان علينا القيام بذلك لكل نص Pyarmor الذي نريد تفريغه ، لذلك قامت extremecoders بعمل نص نصي على العناوين المحددة في الذاكرة في Python.
# 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" ) إذا وضعنا هذا الرمز A في ملف يسمى restrict_bypass.py ، فيمكننا استخدامه مثل ما يلي ، باستخدام _pytransform.dll الأصلي
> >> 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__ > تبدأ الطريقة الثانية من نفس الطريقة الأولى ، حيث حقن البرنامج النصي الذي يحصل على كائن رمز التشغيل الحالي.
الآن فقط الفرق هو أننا لن نتخلص منه فقط ، سنقوم "بإصلاحه". أعني بذلك إزالة Pyarmor منه تمامًا حتى نحصل على كائن الكود الأصلي.
نظرًا لأن Pyarmor لديه خيارات متعددة عندما قررت أن أضيف دعمًا لجميع الخيارات الشائعة.
عندما يكتشف البرنامج النصي ، فإن __armor_enter__ بداخله سيعدله بحيث يتم إرجاع كائن الكود مباشرة بعد استدعاء __armor_enter__ .
يوجد رمز opcode POP_TOP بعد استدعاء الوظيفة ، ويتم استخدام هذا بحيث تتم إزالة قيمة الإرجاع للوظيفة من المكدس ، بل نستبدلها فقط برمز opcode RETURN_VALUE حتى نتمكن من الحصول على قيمة إرجاع دالة __armor_enter__ ، وبالتالي لدينا كائن رمز فك التشفير في الذاكرة دون تشغيل النمو الأصلي بالفعل. انظر المثال أدناه
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 ) نظرًا لأن Pyarmor يقوم بتحرير كائن الرمز في الذاكرة ، ستبقى التغييرات حتى بعد خروجنا من كائن الرمز.
الآن يمكننا استدعاء (exec) كائن الكود. لدينا الآن إمكانية الوصول إلى كائن الكود المقطوع. كل ما تبقى الآن هو إزالة تعديلات Pyarmor إلى كائن الكود ، كونه رأس التفاف وتذييل.
بعد ذلك تم تنظيفه ، يتعين علينا إزالة __armor_enter__ و __armor_exit__ من co_names .
نكرر هذا بشكل متكرر لجميع كائنات الكود.
سيكون الإخراج كائن الكود الأصلي. سيكون مثل Pyarmor لم يتم تطبيقه أبدًا.
ولهذا السبب ، يمكننا استخدام جميع أدواتنا المفضلة ، على سبيل المثال DEFOMPYLE3 للحصول على رمز المصدر الأصلي.
الطريقة الثالثة تعمل على إصلاح المشكلة الأخيرة مع الطريقة رقم 2.
في الطريقة رقم 2 ، لا يزال يتعين علينا تشغيل البرنامج بالفعل وحقنه.
يمكن أن تكون هذه مشكلة لأن:
تحاول الطريقة الثالثة تفريغ Pyarmor بشكل ثابت ، والتي أعني بها دون تشغيل أي شيء من البرنامج المثير.
هناك بعض الطرق التي يمكنك من خلالها تفريغها بشكل ثابت ، لكن الطريقة التي سأشرحها تبدو أسهل في التنفيذ دون الحاجة إلى استخدام أدوات و/أو لغات أخرى.
سنستخدم سجلات التدقيق ، وتم تنفيذ سجلات التدقيق في بيثون لأسباب أمنية. ومن المفارقات الآن أننا سنستغل سجلات التدقيق لإزالة الأمان.
سجلات التدقيق بشكل أساسي تسجيل وظائف cpython الداخلية. بما في ذلك exec و marshal.loads ، وكلاهما يمكننا استخدامه للحصول على كائن الكود الرئيسي دون الحاجة إلى حقن/تشغيل الرمز. يمكن العثور على قائمة كاملة من سجلات التدقيق هنا
أضافت Cpython شيئًا أنيقًا يسمى خطافات التدقيق ، في كل مرة يتم فيها تشغيل سجل التدقيق ، سيقوم بإجراء رد اتصال على الخطاف الذي قمنا بتثبيته. سيكون الخطاف ببساطة وظيفة تأخذ 2 وسيطات ، event ، arg .
مثال على خطاف التدقيق:
import sys
def hook ( event , arg ):
print ( event , arg )
sys . addaudithook ( hook ) الطريقة الوحيدة لحفظ كائنات الكود على القرص هي تنظيمه. هذا يعني أن على Pyarmor تشفير كائنات التعليمات البرمجية المليئة بالألوان ، لذلك من الطبيعي أن يتمكنوا من فك تشفيرها عندما يرغبون في الوصول إليها في Python.
إنهم ، مثل معظم الأشخاص الآخرين ، يستخدمون مارشالر المدمج. تسمى الحزمة marshal وهي حزمة مدمجة ، مكتوبة في C. إنها واحدة من الحزم التي لديها سجلات التدقيق ، لذلك عندما يطلق عليها Pyarmor ، يمكننا رؤية الحجج.
سيظل كائن الكود يحتوي على رمز bytecode المشفر ، لكننا تمكنا بالفعل من تجاوز "الطبقة" الأولى ، يمكننا إعادة استخدام طريقتنا رقم 2 بشكل أساسي من هذه المرحلة لأنه يتعين على كائنات التعليمات البرمجية المشفرة أيضًا. الفرق الوحيد الآن هو أنه سيتم تشفير كل كائن رمز بدلاً من الكائنات التي كانت عادة ما يتم تشغيلها بالفعل ، مثل كائن الكود الرئيسي.
لأنه في الطريقة رقم 2 ، حقن الكود لدينا بالفعل الوصول إلى جميع وظائف Pyarmor مثل __armor_enter__ و __armor_exit__ . نظرًا لأننا نحاول تفريغها بشكل ثابت ، ليس لدينا هذا الرفاهية.
كما ذكرت أعلاه ، يقيد Pyarmor أوضاع ، لقد أوضحت بالفعل كيفية تجاوز وضع تقييد bootstrap لأن ذلك يتم تشغيله فقط عندما ندير وظيفة pyarmor_runtime() .
الآن نحتاج إلى تشغيل الملف المحبب بالكامل ، والذي يتضمن مكالمة __pyarmor__ . هذه الوظيفة تؤدي إلى وضع تقييد آخر ، لذلك يتعين علينا تجاوز ذلك. أولاً ، كنت أفكر في أننا نستخدم طريقة مماثلة عن طريق تصحيحها أصليًا.
ساعد أحد الأصدقاء في ذلك ، هذه هي الخطوات التي يمكنك القيام بها لتكرارها. ضع في اعتبارك أنني وجدت طريقة أفضل وأسهل. يتحقق Pyarmor مما إذا كانت سلسلة Pyarmor موجودة في عنوان ذاكرة محدد في __main__ . نحن بحاجة إلى تصحيح هذا الشيك. انظر الصورة أدناه
الآن الطريقة الأفضل التي وجدتها هي أن وضع تقييد Pyarmor لا يتحقق مما إذا كان الملف الرئيسي يتم تشغيله مباشرة بواسطة Python أو إذا تم الاحتجاج به ، حتى نتمكن من القيام بذلك ببساطة:
exec ( open ( filename )) بالطبع بعد أن قمنا بتثبيت خطاف التدقيق.
كانت المشكلة التي واجهتها هي أن خطاف التدقيق dumps تم تشغيله على marshal.loads . هذا أمر خطير لأنه إذا كان لا يزال هناك مجلد dumps خلفه من قبل أن يؤدي فقط إلى تنفيذ البرنامج النصي المحمي دون إيقافه. علينا أن نجد طريقة أفضل للقيام بذلك.
تحرير : اكتشفت مؤخرًا أنني نسيت الجزء الذي نحتاج فيه إلى تعديل القفزات المطلقة. سيغطي هذا الجزء ذلك.
عندما تحتاج إلى القيام بذلك في كل من الطريقة رقم 2 والطريقة رقم 3. عندما نقوم بإزالة التذييل ، لن يكون هناك صناديق مع الفهارس. عندما نقوم بإزالة الرأس ، فإنه سيؤدي إلى تحول الفهارس بحجم الرأس ، لذلك نحتاج إلى حلقة على جميع القفزات المطلقة وطرح حجم الرأس. هذا الجزء سهل إلى حد ما.
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 من الطريقة رقم 3
نقوم بتدوير من خلال رمز bytecode ونتحقق مما إذا كان رمز OPCODE هو الرمز OPCODE JUMP_ABSOLUTE . إذا كان الأمر كذلك ، فسنحسب الوسيطة (مع مراعاة EXTENDED_ARG ). ثم نأخذ try_start وهو حجم الرأس (إنه في الواقع فهرس آخر رمز opcode من الرأس ، ولهذا السبب نضيف 2) ونطرحه من وسيطة الرمز opcode JUMP_ABSOLUTE .
كان الجزء الأصعب في تنفيذ هذا هو العناية بأدوات opcodes EXTENDED_ARG التي يحتمل أن نضيفها عندما تتجاوز الحجة الحد الأقصى لحجم البايت 1 (255). نحن نتعامل مع ذلك في 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 من الطريقة رقم 3
لكتابة هذا الرمز ، كان علي أولاً أن أفهم كيف يعمل EXTENDED_ARG بالضبط.
ساعدت هذه المقالة كثيرًا لفهم هذا الرمز البسيط.
تعليمات في Python هي 2 بايت في الإصدارات الأكثر حداثة (3.6+). يتم استخدام بايت واحد في الرمز opcode وبايت واحد للوسيطة. عندما نحتاج إلى تجاوز بايت واحد ، نستخدم EXTENDED_ARG . يعمل بشكل أساسي مثل هذا:
arg = 300 # Let's say this is the size of our argumentنحن نعلم أن الحد الأقصى المسموح به هو 255 ، لذلك نحن بحاجة إلى استخدام Extended_arg ، قد تعتقد أنه سيكون مثل هذا:
extended_arg = 255
arg = 45هذا ما افترضته لأول مرة ولكن بعد النظر إلى الكود الذي ولدت فيه Python ، لاحظت أنه كان مثل هذا:
extended_arg = 1
arg = 44 كنت مرتبكًا جدًا لماذا كان الأمر هكذا لأنني لم أر أي علاقة بين ما كنت أتوقعه والواقع. أوضحت المقالة المرتبطة أعلاه كل شيء.
يعالج Python extender_arg مثل ما يلي:
extended_arg = extended_arg * 256بعد رؤية هذا كل شيء كان واضحًا لأنه يعني ذلك
extended_arg = 1 * 256
arg = 44
print ( extended_arg + arg ) سوف يخرج 300 .
لقد قمت بتطبيق هذا المنطق على الوظيفة بحيث يرجع قائمة من رموز opcodes extended_arg اللازمة وقيمة الوسيطة الجديدة (التي ستكون تحت أو تساوي 255).
ثم فقط أدخل extended_arg في الفهرس الصحيح.