Firstly, do NOT directly use types.CodeType
and use pyobject.Code
instead, as constructing types.CodeType
is too complex and not compatible across multiple Python versions.
The pyobject library, which can be installed via pip install pyobject
, provides a high-level wrapper for code objects.
After testing it on Python 3.11, the output is:
E:\Git-repositories\Github-publish\pyc-zipper>py311
Python 3.11.8 (tags/v3.11.8:db85d51, Feb 6 2024, 22:03:32) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import random
>>> from pyobject import Code
>>> def f(a):return a+1
...
>>> c=Code(f.__code__)
>>> new_co_code = bytes([random.randint(0, 255) for _ in range(24)])
>>> new_c=c.copy()
>>> new_c.co_code=new_co_code # pyobject.Code is mutable
>>> new_code=new_c.to_code()
>>> new_code.co_code == new_co_code
False
>>> c.co_code
b'\x97\x00|\x00d\x01z\x00\x00\x00S\x00'
>>> new_code.co_code
b'\x90ik^\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
As I tried to disassembly it:
>>> c.dis()
1 0 EXTENDED_ARG 105
Traceback (most recent call last):
...
IndexError: tuple index out of range
>>> new_co_code=bytearray(new_co_code) # convert to bytearray
>>> new_co_code[1]=new_co_code[3]=0 # modify bytes at 1 and 3 to 0 to observe them clearly
>>> new_co_code=bytes(new_co_code) # convert it back to bytes
>>> new_co_code
b'"\x00k\x00d0Ef\x8f\xec\xf9\xc8x\x8adR=\xeb\xe7\xe7\xc8\x8cT\x90'
>>> c.co_code=new_co_code
>>> c.to_code().co_code
b'\x90\x00k\x00\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
>>> c.dis()
1 0 EXTENDED_ARG 0
2 COMPARE_OP 0 (<)
...
So I guess that Python 3.11 automatically adds EXTENDED_ARG
opcode that extends the original one-byte argument when some irregular bytecodes are detected.
>>> new_co_code=bytearray(new_co_code)
>>> new_co_code[0]=ord('d')
>>> new_co_code=bytes(new_co_code)
>>> c.co_code=new_co_code
>>> c.to_code().co_code
b'd\x00k\x00\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
>>> c.dis()
1 0 LOAD_CONST 0 (None)
2 COMPARE_OP 0 (<)
8 PRECALL 236
12 COPY 138
Traceback (most recent call last):
...
IndexError: tuple index out of range
The first opcode is set to LOAD_CONST
, and the EXTENDED_ARG
is not added automatically.
Furthermore:
>>> new_co_code=bytearray(new_co_code)
>>> new_co_code[0]=0x90 # directly set to EXTENDED_ARG
>>> new_co_code=bytes(new_co_code)
>>> c.co_code=new_co_code
>>> c.to_code().co_code
b'\x90\x00k\x00\x00\x00\x00\x00\xa6\xec\x00\x00x\x8adR=\xeb\x00\xe7\x00\x8cT\x90'
>>> c.dis()
1 0 EXTENDED_ARG 0
2 COMPARE_OP 0 (<)
8 PRECALL 236
...
It indicates that some other opcodes, including COMPARE_OP
will be modified automaticlly.