Skip to content

Instantly share code, notes, and snippets.

@t-wy
Last active October 6, 2022 02:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t-wy/d89e8c430eba9d624168cdf1fa465e53 to your computer and use it in GitHub Desktop.
Save t-wy/d89e8c430eba9d624168cdf1fa465e53 to your computer and use it in GitHub Desktop.
GoogleCTF Write-up - Mixed

Challenge Name - MIXED

URL - https://capturetheflag.withgoogle.com/challenges/rev-mixed

Description - A company I interview for gave me a test to solve. I'd decompile it and get the answer that way, but they seem to use some custom Python version... At least I know it's based on commit 64113a4ba801126028505c50a7383f3e9df29573.

Attachments:

  • patch (See A1: patch)
  • x.pyc (See A2: x.pyc.b64)

Compiling Python

Based on the description, we can look for commit 64113a4ba801126028505c50a7383f3e9df29573 from the repository for cpython.

We can clone the repository up to the specified commit by using the git commands:

git clone https://github.com/python/cpython.git
git reset --hard 64113a4ba801126028505c50a7383f3e9df29573

There is a patch file attached to this challenge, we can see that opcodes are shuffled in some way in opcode.py, and some options are specified in ceval.c.

As we are dealing with the compilation, we can focus on applying the change in ceval.c first.

We can compile Python (which is 3.11) in different ways depending on the operating system and the environment. For example, if we are using Windows 11, we can use PCbuild/build.bat to compile.

The compilation result is located in PCbuild/amd64, from where we can see python.exe, pythonw.exe, etc.

Understanding the opcode shuffling

Refer to patch again, the shuffling procedure is done by the following lines:

perm = list(range(90))
perm2 = list(range(200-90))

import sys
if "generate_opcode_h" in sys.argv[0]:
  random = __import__("random")
  random.shuffle(perm)
  random.shuffle(perm2)

This means opcodes in range [1, 90) and [90, 200) are shuffled independently to build the mapping table, then operations will get their opcodes according to the corresponding indices in the appropriate table.

To prevent the original opcodes from distracting our attention when reversing, we change all the opcodes in opcode.py to 200 (which is not used), and define 200 single-byte operations named !0!, !1!, ..., !199!:

+ for i in range(200):
+     def_op('!{}!'.format(i), i)

- def_op('CACHE', 0)
+ def_op('CACHE', 200)
- def_op('POP_TOP', 1)
+ def_op('POP_TOP', 200)
- def_op('PUSH_NULL', 2)
+ def_op('PUSH_NULL', 200)
...
- jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 176)
+ jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 200)

Viewing the assembly

Now we can try to parse the contents of x.pyc.

Same as recent Python Versions, Python 3.11 pyc files use 16 bytes as the header, followed by the serialized code object.

Here is the code to recursive print out the information about x.pyc:

import marshal, types, dis
with open('x.pyc', 'rb') as f:
    f.read(16)
    code = marshal.loads(f.read())

def show_code(code, indent=''):
    print('%scode' % indent)
    indent += '   '
    try:
        dis.show_code(code)
        dis.dis(code, adaptive=True)
    except:
        pass
    for const in code.co_consts:
        if isinstance(const, types.CodeType):
            try:
                show_code(const, indent+'   ')
            except:
                pass
    
show_code(code)

About adaptive=True in dis.dis(code, adaptive=True)

Actually, we did not add adaptive=True at first. However, we can see weird opcodes like the following:

              0 !97!                              0

  1           2 !96!                              0
              4 !96!                              1
              6 !171!                             0
              8 !0!

  2          10 !0!
             12 !0!
             14 !0!
             16 !151!                             1

  3          18 !96!                              0
             20 !96!                              1
             22 !171!                             2
             24 !0!

and basically !0! opcodes appear at weird places. This is because if the opcodes are not used in the original Python, they are converted to 0 when deserializing. This also affects the offsets of the opcodes and leads to parsing failures.

(In the above case, the opcode after 171 is actually 150, which is not used in Python 3.11.)

How to make sure that this assumption is correct?

We can notice that dis is printing the serialized data based on co.co_code. We can compare the co_code with the actual x.pyc in Hex editor to find out that some bytes are different.

Luckily, the Code object still stores the untampered raw bytes in co._co_code_adaptive, and we can also ask dis to use that by setting adaptive=True.

After adding adaptive=True, it returns something like the following:

Name:              <module>
Filename:          /usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        2
Flags:             0x0
Constants:
   0: 0
   1: None
   2: <code object ks at 0x0000015AEF049E90, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 5>
   3: <code object cry at 0x0000015AEF098DF0, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 11>
   4: <code object fail at 0x0000015AEF056560, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 18>
   5: <code object game1 at 0x0000015AEEE6BDC0, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 23>
   6: <code object game2 at 0x0000015AEF29F040, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 68>
   7: <code object game3 at 0x0000015AEF2B42E0, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 96>
   8: <code object main at 0x0000015AEF2B4640, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 123>
Names:
   0: sys
   1: random
   2: time
   3: ks
   4: cry
   5: fail
   6: game1
   7: game2
   8: game3
   9: main
              0 !97!                              0

  1           2 !96!                              0
              4 !96!                              1
              6 !171!                             0
              8 !150!                             0

  2          10 !96!                              0
             12 !96!                              1
             14 !171!                             1
             16 !150!                             1

  3          18 !96!                              0
             20 !96!                              1
             22 !171!                             2
             24 !150!                             2

  5          26 !96!                              2
             28 !99!                              0
             30 !150!                             3
 ...

However, we may sometimes see that some lines are incomplete if opcodes are invalid. Therefore, we can use a try-except block to wrap around the _get_instructions_bytes function in dis.py to make sure that every Instruction is printed:

  cache_counter = _inline_cache_entries[deop]
+ try:
      if arg is not None:
          ...
          _, argrepr = _nb_ops[arg]
+ except:
+     pass
  yield Instruction(_all_opname[op], op,

How to test

We keep another unmodified version of 3.11 to observe the result of different codes after serialization, using the same recursive code stated above.

For example, we can create a file test.py like this:

import random
def a(x=4):
    print(x)
a(5)

Launch python from the same directly, and import test as a module:

import test

Then we should be able to find the corresponding pyc file in __pycache__/test.cpython-311.pyc.

We can modify the path x.pyc in the testing source code to __pycache__/test.cpython-311.pyc, and run the script in the unmodified Python 3.11 to check the corresponding opcodes.

Recovering the opcodes

We can deduce some opcodes from very basic observations:

  • Most functions start with RESUME opcode, except generator functions (with GENERATOR in Flags), which starts by RETURN_GENERATOR, POP_TOP, then RESUME.
  • The last opcode is most likely to be RETURN_VALUE.

By comparing back to our outputs to x.pyc, we can confirm that RESUME is 97, RETURN_GENERATOR is 3, POP_TOP is 1, and RETURN_VALUE is 49.

def_op('RESUME', 97)
def_op('RETURN_GENERATOR', 3)
def_op('POP_TOP', 1)
def_op('RETURN_VALUE', 49)

For the return value, if there are only very few opcodes in the same line, we can assume that it is either returning None (no explicit return), a constant, or the value of a variable. This corresponds to LOAD_CONST, and LOAD_FAST. Also, we can follow the consti / var_num to deduce the correct mapping as it should not be referencing an index exceeding the length of such values. Another observation is that import statements go like LOAD_CONSTLOAD_CONSTIMPORT_NAMESTORE_FAST.

  1           2 !96!                              0
              4 !96!                              1
              6 !171!                             0
              8 !150!                             0

  2          10 !96!                              0
             12 !96!                              1
             14 !171!                             1
             16 !150!                             1

  3          18 !96!                              0
             20 !96!                              1
             22 !171!                             2
             24 !150!                             2

 94         438 !98!                              1
            440 RETURN_VALUE
119         682 !98!                              4
            684 RETURN_VALUE
 66         750 !98!                              6
            752 !149!                             2
            754 POP_TOP
            756 RETURN_VALUE

Name:              game1
Number of locals:  14
 64         702 !98!                              3
            704 !98!                              4
            706 !130!                             2
            708 !96!                             13

We now know that (96, 96, 171, 150) is very likely to be (LOAD_CONST, LOAD_CONST, IMPORT_NAME, STORE_NAME), while 98 should be LOAD_FAST instead.

def_op('LOAD_CONST', 96)       # Index in const list
hasconst.append(96)
name_op('IMPORT_NAME', 171)     # Index in name list
name_op('STORE_NAME', 150)       # Index in name list
def_op('LOAD_FAST', 98)        # Local variable number
haslocal.append(98)

Since we have recovered LOAD_CONST and LOAD_NAME, all variables can be immediately shown in dis output. This helps a lot in later reversing.

              0 RESUME                            0

  1           2 LOAD_CONST                        0 (0)
              4 LOAD_CONST                        1 (None)
              6 IMPORT_NAME                       0 (sys)
              8 STORE_NAME                        0

  2          10 LOAD_CONST                        0 (0)
             12 LOAD_CONST                        1 (None)
             14 IMPORT_NAME                       1 (random)
             16 STORE_NAME                        1

  3          18 LOAD_CONST                        0 (0)
             20 LOAD_CONST                        1 (None)
             22 IMPORT_NAME                       2 (time)
             24 STORE_NAME                        2

  5          26 LOAD_CONST                        2 (<code object ks at 0x000002945AC9A790, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 5>)
             28 !99!                              0
             30 STORE_NAME                        3

 11          32 LOAD_CONST                        3 (<code object cry at 0x000002945ACE8DF0, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 11>)
             34 !99!                              0
             36 STORE_NAME                        4

 18          38 LOAD_CONST                        4 (<code object fail at 0x000002945ACA6560, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 18>)
             40 !99!                              0
             42 STORE_NAME                        5

 23          44 LOAD_CONST                        5 (<code object game1 at 0x000002945AF59520, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 23>)
             46 !99!                              0
             48 STORE_NAME                        6

 68          50 LOAD_CONST                        6 (<code object game2 at 0x000002945AA27D80, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 68>)
             52 !99!                              0
             54 STORE_NAME                        7

 96          56 LOAD_CONST                        7 (<code object game3 at 0x000002945AF5CE30, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 96>)
             58 !99!                              0
             60 STORE_NAME                        8

123          62 LOAD_CONST                        8 (<code object main at 0x000002945AF82C30, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 123>)
             64 !99!                              0
             66 STORE_NAME                        9
             68 LOAD_CONST                        1 (None)
             70 RETURN_VALUE

We can immediately reverse the first three lines, also we are certain that 99 is MAKE_FUNCTION as it loads a code object and stores it in a variable.

  1 import sys
  2 import random
  3 import time
  5 def ks(seed):
 11 def cry(s, seed):
 18 def fail(s):
 23 def game1():
 68 def game2():
 96 def game3():
123 def main():
def_op('MAKE_FUNCTION', 99)    # Flags

We can also make reasonable guesses on what's happening in the code:

 79          68 LOAD_CONST                        4 ('What is the %s of %d and %d?')
 ...
 81         104 LOAD_FAST                         3 (x)
            106 LOAD_CONST                        5 ('sum')
            108 !104!                             2
            110 !32!
            112 !32!
            114 !186!                             6
            116 LOAD_FAST                         4 (a)
            118 LOAD_FAST                         5 (b)
            120 !198!                             0
            122 !32!
            124 !141!                             6
            126 !94!                             64

 82         128 LOAD_FAST                         3 (x)
            130 LOAD_CONST                        6 ('difference')
            132 !104!                             2
            134 !32!
            136 !32!
            138 !186!                             6
            140 LOAD_FAST                         4 (a)
            142 LOAD_FAST                         5 (b)
            144 !198!                            10
            146 !32!
            148 !141!                             6
            150 !94!                             52

 83         152 LOAD_FAST                         3 (x)
            154 LOAD_CONST                        7 ('product')
            156 !104!                             2
            158 !32!
            160 !32!
            162 !186!                             6
            164 LOAD_FAST                         4 (a)
            166 LOAD_FAST                         5 (b)
            168 !198!                             5
            170 !32!
            172 !141!                             6
            174 !94!                             40

 84         176 LOAD_FAST                         3 (x)
            178 LOAD_CONST                        8 ('ratio')
            180 !104!                             2
            182 !32!
            184 !32!
            186 !186!                             6
            188 LOAD_FAST                         4 (a)
            190 LOAD_FAST                         5 (b)
            192 !198!                             2
            194 !32!
            196 !141!                             6
            198 !94!                             28

 85         200 LOAD_FAST                         3 (x)
            202 LOAD_CONST                        9 ('remainder from division')
            204 !104!                             2
            206 !32!
            208 !32!
            210 !186!                             6
            212 LOAD_FAST                         4 (a)
            214 LOAD_FAST                         5 (b)
            216 !198!                             6
            218 !32!
            220 !141!                             6
            222 !94!                             16

Notice that lines 81 to 85 are almost identical other than the string constants it loads (e.g. sum, difference, ...) and the argument to !198!.

As it's asking about the result of some mathematical operations, we can deduce that 198 is actually BINARY_OP.

We also cross-checked this with some testing codes using +, -, *, // and %, and confirmed that their ops are 0, 10, 5, 2, 6 respectively, which perfectly matches the above values.

def_op('BINARY_OP', 198, 1)

Since BINARY_OP is a major part of the code, the logic becomes far clearer after recovering this opcode.

Also, we can completely recover function w (a nested function in game1) now:

 24           0 RESUME                            0

 25           2 LOAD_FAST                         0 (m)
              4 LOAD_FAST                         1 (i)
              6 LOAD_CONST                        1 (10)
              8 BINARY_OP                         5 (*)
             12 LOAD_FAST                         2 (j)
             14 BINARY_OP                         0 (+)
             18 BINARY_OP                         9 (>>)
             22 LOAD_CONST                        2 (1)
             24 BINARY_OP                         1 (&)
             28 RETURN_VALUE
 24     def w(m, i, j):
 25         return m >> (i * 10 + j) & 1

Also, we can recover STORE_FAST as we recognize some += and -= operations:

 60         624 LOAD_FAST                         2 (fuel)
            626 LOAD_CONST                       27 (1)
            628 BINARY_OP                        23 (-=)
            632 !141!                             2
 60 fuel -= 1
def_op('STORE_FAST', 141)       # Local variable number
haslocal.append(141)

Also, some data structures can be recognized easily:

 55         514 LOAD_CONST                       21 ((0, -1))
            516 LOAD_CONST                       22 ((0, 1))
            518 LOAD_CONST                       23 ((-1, 0))
            520 LOAD_CONST                       24 ((1, 0))
            522 LOAD_CONST                       25 (('w', 's', 'a', 'd'))
            524 !177!                             4
            526 LOAD_FAST                        11 (c)
            528 !82!
            530 !32!
            532 !32!
            534 !32!
            536 !32!
            538 !143!                             2
            540 !32!
            542 STORE_FAST                       12 (dx)
            544 STORE_FAST                       13 (dy)

 56         546 LOAD_FAST                         3 (x)
            548 LOAD_FAST                        12 (dx)
            550 BINARY_OP                        13 (+=)
            554 STORE_FAST                        3 (x)

 57         556 LOAD_FAST                         4 (y)
            558 LOAD_FAST                        13 (dy)
            560 BINARY_OP                        13 (+=)
            564 STORE_FAST                        4 (y)
 55             dx, dy = {"w": (0, -1), "s": (0, 1), "a": (-1, 0), "d": (1, 0)}[c]
 56             x += dx
 57             y += dy

and we can keep updating the recovered code to find out the corresponding operations.

def_op('BUILD_CONST_KEY_MAP', 177)
def_op('BINARY_SUBSCR', 82, 4)
def_op('UNPACK_SEQUENCE', 143, 1)   # Number of tuple items

What is !32!? Looks like some dummy operation that is by default hidden in dis when its opcode was 0.

Also, it appears most frequently in the pyc file which supposes to be 0.

Disassembly of <code object fail at 0x000002834A066560, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 18>:
 18           0 RESUME                            0

 19           2 !50!
              4 !185!                             0
              6 !32!
              8 !32!
             10 !32!
             12 !32!
             14 !32!
             16 LOAD_FAST                         0 (s)
             18 !191!                             1
             20 !32!
             22 !142!                             1
             24 !32!
             26 !32!
             28 !32!
             30 !32!
             32 POP_TOP

 20          34 !50!
             36 !185!                             0
             38 !32!
             40 !32!
             42 !32!
             44 !32!
             46 !32!
             48 LOAD_CONST                        1 ('Thanks for playing!')
             50 !191!                             1
             52 !32!
             54 !142!                             1
             56 !32!
             58 !32!
             60 !32!
             62 !32!
             64 POP_TOP

 21          66 !50!
             68 !185!                             2
             70 !32!
             72 !32!
             74 !32!
             76 !32!
             78 !32!
             80 !188!                             2
             82 !32!
             84 !32!
             86 !32!
             88 !32!
             90 LOAD_CONST                        2 (0)
             92 !191!                             1
             94 !32!
             96 !142!                             1
             98 !32!
            100 !32!
            102 !32!
            104 !32!
            106 POP_TOP
            108 LOAD_CONST                        0 (None)
            110 RETURN_VALUE

This is also not hard to guess as a failing function:

 18 def fail(s):
 19     print(s)
 20     print('Thanks for playing!')
 21     sys.exit(0)
name_op('LOAD_GLOBAL', 185, 5)     # Index in name list
name_op('LOAD_ATTR', 188, 4)       # Index in name list
def_op('PRECALL', 191, 1)
def_op('CALL', 142, 4)

What is !50!? Looks like it corresponds to the NULL in operations like LOAD_GLOBAL (NULL + print).

Now we can also reverse the main function:

Name:              main
Filename:          /usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  1
Stack size:        7
Flags:             OPTIMIZED, NEWLOCALS
Constants:
   0: None
   1: 'Pass 3 tests to prove your worth!'
   2: 'seed:'
   3: ':'
   4: "You can drive to work, know some maths and can type fast. You're hired!"
   5: 'Your sign-on bonus:'
   6: b'\xa0?n\xa5\x7f)\x1f6Jvh\x95\xcc!\x1e\x95\x996a\x11\xf6OV\x88\xc1\x9f\xde\xb50\x9d\xae\x14\xde\x18YHI\xd8\xd5\x90\x8a\x181l\xb0\x16^O;]'
Names:
   0: print
   1: game1
   2: game2
   3: game3
   4: cry
   5: decode
Variable names:
   0: seed
123           0 RESUME                            0

124           2 !50!
              4 LOAD_GLOBAL                       0 (print)
             16 LOAD_CONST                        1 ('Pass 3 tests to prove your worth!')
             18 PRECALL                           1
             22 CALL                              1
             32 POP_TOP

125          34 LOAD_CONST                        2 ('seed:')
             36 STORE_FAST                        0 (seed)

127          38 LOAD_FAST                         0 (seed)
             40 !50!
             42 LOAD_GLOBAL                       2 (game1)
             54 PRECALL                           0
             58 CALL                              0
             68 LOAD_CONST                        3 (':')
             70 BINARY_OP                         0 (+)
             74 BINARY_OP                        13 (+=)
             78 STORE_FAST                        0 (seed)

128          80 !50!
             82 LOAD_GLOBAL                       0 (print)
             94 LOAD_FAST                         0 (seed)
             96 PRECALL                           1
            100 CALL                              1
            110 POP_TOP

130         112 LOAD_FAST                         0 (seed)
            114 !50!
            116 LOAD_GLOBAL                       4 (game2)
            128 PRECALL                           0
            132 CALL                              0
            142 LOAD_CONST                        3 (':')
            144 BINARY_OP                         0 (+)
            148 BINARY_OP                        13 (+=)
            152 STORE_FAST                        0 (seed)

131         154 !50!
            156 LOAD_GLOBAL                       0 (print)
            168 LOAD_FAST                         0 (seed)
            170 PRECALL                           1
            174 CALL                              1
            184 POP_TOP

133         186 LOAD_FAST                         0 (seed)
            188 !50!
            190 LOAD_GLOBAL                       6 (game3)
            202 PRECALL                           0
            206 CALL                              0
            216 BINARY_OP                        13 (+=)
            220 STORE_FAST                        0 (seed)

134         222 !50!
            224 LOAD_GLOBAL                       0 (print)
            236 LOAD_FAST                         0 (seed)
            238 PRECALL                           1
            242 CALL                              1
            252 POP_TOP

135         254 !50!
            256 LOAD_GLOBAL                       0 (print)
            268 PRECALL                           0
            272 CALL                              0
            282 POP_TOP

136         284 !50!
            286 LOAD_GLOBAL                       0 (print)
            298 LOAD_CONST                        4 ("You can drive to work, know some maths and can type fast. You're hired!")
            300 PRECALL                           1
            304 CALL                              1
            314 POP_TOP

137         316 !50!
            318 LOAD_GLOBAL                       0 (print)
            330 LOAD_CONST                        5 ('Your sign-on bonus:')
            332 !50!
            334 LOAD_GLOBAL                       8 (cry)
            346 LOAD_CONST                        6 (b'\xa0?n\xa5\x7f)\x1f6Jvh\x95\xcc!\x1e\x95\x996a\x11\xf6OV\x88\xc1\x9f\xde\xb50\x9d\xae\x14\xde\x18YHI\xd8\xd5\x90\x8a\x181l\xb0\x16^O;]')
            348 LOAD_FAST                         0 (seed)
            350 PRECALL                           2
            354 CALL                              2
            364 !146!                             5
            366 !32!
            368 !32!
            370 !32!
            372 !32!
            374 !32!
            376 !32!
            378 !32!
            380 !32!
            382 !32!
            384 !32!
            386 PRECALL                           0
            390 CALL                              0
            400 PRECALL                           2
            404 CALL                              2
            414 POP_TOP
            416 LOAD_CONST                        0 (None)
            418 RETURN_VALUE
123 def main():
124     print('Pass 3 tests to prove your worth!')
125     seed = 'seed:'
126 
127     seed += game1() + ":"
128     print(seed)
129 
130     seed += game2() + ":"
131     print(seed)
132 
133     seed += game3()
134     print(seed)
135     print()
136     print("You can drive to work, know some maths and can type fast. You're hired!")
137     print('Your sign-on bonus:', cry(b'\xa0?n\xa5\x7f)\x1f6Jvh\x95\xcc!\x1e\x95\x996a\x11\xf6OV\x88\xc1\x9f\xde\xb50\x9d\xae\x14\xde\x18YHI\xd8\xd5\x90\x8a\x181l\xb0\x16^O;]', seed).decode())

Also, the ks generator can be reversed:

Name:              ks
Filename:          /usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py
Argument count:    1
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  1
Stack size:        4
Flags:             OPTIMIZED, NEWLOCALS, GENERATOR
Constants:
   0: None
   1: True
   2: 0
   3: 255
   4: 13
   5: 17
   6: 256
Names:
   0: random
   1: seed
   2: randint
Variable names:
   0: seed
  5           0 RETURN_GENERATOR
              2 POP_TOP
              4 RESUME                            0

  6           6 !50!
              8 LOAD_GLOBAL                       0 (random)
             20 LOAD_ATTR                         1 (seed)
             30 LOAD_FAST                         0 (seed)
             32 PRECALL                           1
             36 CALL                              1
             46 POP_TOP

  7          48 !27!

  8          50 !50!
             52 LOAD_GLOBAL                       0 (random)
             64 LOAD_ATTR                         2 (randint)
             74 LOAD_CONST                        2 (0)
             76 LOAD_CONST                        3 (255)
             78 PRECALL                           2
             82 CALL                              2
             92 LOAD_CONST                        4 (13)
             94 BINARY_OP                         5 (*)
             98 LOAD_CONST                        5 (17)
            100 BINARY_OP                         0 (+)
            104 LOAD_CONST                        6 (256)
            106 BINARY_OP                         6 (%)
            110 !69!
            112 RESUME                            1
            114 POP_TOP

  7         116 !123!                            34
  5 def ks(seed):
  6     random.seed(seed)
  7     while True:
  8         yield (random.randint(0, 255) * 13 + 17) % 256

, where 27 corresponds to NOP which is the placeholder for the while-loop. 123 at the end is then JUMP_BACKWARD that jumps back to the place just after line 7.

As it is a generator function, 69 should correspond to the YIELD_VALUE operation.

def_op('NOP', 27)
def_op('YIELD_VALUE', 69)
jrel_op('JUMP_BACKWARD', 200)    # Number of words to skip (backwards)

The cry function which is used to decrypt the flag at the end is also quite trivial to reverse now:

Disassembly of <code object cry at 0x0000019A62658DF0, file "/usr/local/google/home/xxxxxxxxx/yyyy/zzzzzzzzzzzzzzzz/xxxxxxxxxxx.py", line 11>:
 11           0 RESUME                            0

 12           2 !116!                             0
              4 STORE_FAST                        2 (r)

 13           6 !50!
              8 LOAD_GLOBAL                       0 (zip)
             20 !50!
             22 LOAD_GLOBAL                       2 (ks)
             34 LOAD_FAST                         1 (seed)
             36 PRECALL                           1
             40 CALL                              1
             50 LOAD_FAST                         0 (s)
             52 PRECALL                           2
             56 CALL                              2
             66 !31!
        >>   68 !182!                            29
             70 UNPACK_SEQUENCE                   2
             74 STORE_FAST                        3 (x)
             76 STORE_FAST                        4 (y)

 14          78 LOAD_FAST                         2 (r)
             80 !146!                             2
             82 !32!
             84 !32!
             86 !32!
             88 !32!
             90 !32!
             92 !32!
             94 !32!
             96 !32!
             98 !32!
            100 !32!
            102 LOAD_FAST                         3 (x)
            104 LOAD_FAST                         4 (y)
            106 BINARY_OP                        12 (^)
            110 PRECALL                           1
            114 CALL                              1
            124 POP_TOP
            126 JUMP_BACKWARD                    30 (to 68)

 16         128 !50!
            130 LOAD_GLOBAL                       6 (bytes)
            142 LOAD_FAST                         2 (r)
            144 PRECALL                           1
            148 CALL                              1
            158 RETURN_VALUE
 11 def cry(s, seed):
 12     r = []
 13     for x, y in zip(ks(seed), s):
 14         r.append(x ^ y)
 15 
 16     return bytes(r)
def_op('BUILD_LIST', 116)       # Number of list items
def_op('GET_ITER', 31)
jrel_op('FOR_ITER', 182)
name_op('LOAD_METHOD', 146, 10)
 62         646 LOAD_FAST                         5 (stops)
            648 LOAD_METHOD                       6 (remove)
            670 LOAD_FAST                         3 (x)
            672 LOAD_FAST                         4 (y)
            674 !130!                             2
            676 PRECALL                           1
            680 CALL                              1
            690 POP_TOP            
 62 stops.remove((x, y))
def_op('BUILD_TUPLE', 130)      # Number of tuple items

From where we recovered BINARY_OP, we can also realize an if-else block is used to determine which operation to be used depending on the value of x.

 81         104 LOAD_FAST                         3 (x)
            106 LOAD_CONST                        5 ('sum')
            108 !104!                             2
            110 !32!
            112 !32!
            114 !186!                             6
            116 LOAD_FAST                         4 (a)
            118 LOAD_FAST                         5 (b)
            120 BINARY_OP                         0 (+)
            124 STORE_FAST                        6 (r)
            126 !94!                             64

 82         128 LOAD_FAST                         3 (x)
            130 LOAD_CONST                        6 ('difference')
            132 !104!                             2
            134 !32!
            136 !32!
            138 !186!                             6
            140 LOAD_FAST                         4 (a)
            142 LOAD_FAST                         5 (b)
            144 BINARY_OP                        10 (-)
            148 STORE_FAST                        6 (r)
            150 !94!                             52

 83         152 LOAD_FAST                         3 (x)
            154 LOAD_CONST                        7 ('product')
            156 !104!                             2
            158 !32!
            160 !32!
            162 !186!                             6
            164 LOAD_FAST                         4 (a)
            166 LOAD_FAST                         5 (b)
            168 BINARY_OP                         5 (*)
            172 STORE_FAST                        6 (r)
            174 !94!                             40

 84         176 LOAD_FAST                         3 (x)
            178 LOAD_CONST                        8 ('ratio')
            180 !104!                             2
            182 !32!
            184 !32!
            186 !186!                             6
            188 LOAD_FAST                         4 (a)
            190 LOAD_FAST                         5 (b)
            192 BINARY_OP                         2 (//)
            196 STORE_FAST                        6 (r)
            198 !94!                             28

 85         200 LOAD_FAST                         3 (x)
            202 LOAD_CONST                        9 ('remainder from division')
            204 !104!                             2
            206 !32!
            208 !32!
            210 !186!                             6
            212 LOAD_FAST                         4 (a)
            214 LOAD_FAST                         5 (b)
            216 BINARY_OP                         6 (%)
            220 STORE_FAST                        6 (r)
            222 !94!                             16
 81         if x == 'sum': r = a + b
 82         elif x == 'difference': r = a - b
 83         elif x == 'product': r = a * b
 84         elif x == 'ratio': r = a // b
 85         elif x == 'remainder from division': r = a % b
def_op('COMPARE_OP', 104, 2)       # Comparison operator
hascompare.append(104)
jrel_op('POP_JUMP_FORWARD_IF_FALSE', 186)
jrel_op('JUMP_FORWARD', 94)    # Number of words to skip
 70          34 BUILD_LIST                        0
             36 LOAD_CONST                        2 ((('sum', 12, 5), ('difference', 45, 14), ('product', 8, 9), ('ratio', 18, 6), ('remainder from division', 23, 7)))
             38 !93!                              1
             40 STORE_FAST                        0 (qs)
 70  qs = [
         ('sum', 12, 5),
         ('difference', 45, 14),
         ('product', 8, 9),
         ('ratio', 18, 6),
         ('remainder from division', 23, 7)
     ]
def_op('LIST_EXTEND', 200)

And for this:

 43     >>  276 LOAD_FAST                         9 (j)
            278 LOAD_FAST                         7 (i)
            280 BUILD_TUPLE                       2
            282 LOAD_FAST                         5 (stops)
            284 !117!                             0
            286 POP_JUMP_FORWARD_IF_FALSE         6 (to 300)

since all previous elif branches are checking for equality, this line, according to the game logic, should be checking whether (j, i) is in stops.

elif (j, i) in stops:
def_op('CONTAINS_OP', 117)

SWAP is used when returning a local variable value:

 66         750 LOAD_FAST                         6 (log)
            752 !149!                             2
            754 POP_TOP
            756 RETURN_VALUE
def_op('SWAP', 149)

Below is a far jump, which jumps by 92 + 256 = 348 instead of just 92:

 64     >>  758 JUMP_BACKWARD                   172 (to 416)

 32     >>  760 !114!                             1
            762 JUMP_BACKWARD                    92 (to 580)
def_op('EXTENDED_ARG', 114)
EXTENDED_ARG = 114
 64     >>  758 JUMP_BACKWARD                   172 (to 416)

 32     >>  760 EXTENDED_ARG                      1
            762 JUMP_BACKWARD                   348 (to 68)

The most difficult part is this:

109         252 !50!
            254 LOAD_GLOBAL                       0 (print)
            266 LOAD_CONST                        7 ('\x1b[32m')
            268 LOAD_CONST                        8 (' ')
            270 LOAD_METHOD                       4 (join)
            292 LOAD_FAST                         2 (words)
            294 LOAD_CONST                        0 (None)
            296 LOAD_FAST                         3 (it)
            298 !136!                             2
            300 BINARY_SUBSCR
            310 PRECALL                           1
            314 CALL                              1
            324 !137!                             1
            326 LOAD_CONST                        9 ('\x1b[39m ')
            328 LOAD_FAST                         2 (words)
            330 LOAD_FAST                         3 (it)
            332 BINARY_SUBSCR
            342 !137!                             1
            344 !138!                             4
            346 PRECALL                           1
            350 CALL                              1
            360 POP_TOP

As it calls a function with 4 parameters before calling the print function. We figured it out to be f-string actually.

 109 print(f"\x1b[32m{' '.join(words[:it])!s}\x1b[39m {words[it]!s}")
def_op('BUILD_SLICE', 136)
def_op('FORMAT_VALUE', 137)
def_op('BUILD_STRING', 138)

From here, all opcodes that appear in the source code have already been recovered. Therefore, it is now straightforward to rewrite everything back to readable Python codes (See rev.py below)

Unless you recovered the random seed used, it is not likely that you can recover the remaining opcodes (in /tmp/opcode_map).

As it is actually a game that generates a key by the actions during the game to recover the flag, we can actually calculate the flag directly instead of playing the game.

The modified file opcode.py with known opcodes is attached below.

Also, we have modified dis.py to show more bytes to facilitate the debugging, especially to recover those opcodes like LOAD_GLOBAL occupying many bytes, which prints out like this:

 24           0  97 | 6100620062016001c605 | RESUME                            0

 25           2  98 | 620062016001c6052000 | LOAD_FAST                         0 (m)
              4  98 | 62016001c60520006202 | LOAD_FAST                         1 (i)
              6  96 | 6001c60520006202c600 | LOAD_CONST                        1 (10)
              8 198 | c60520006202c6002000 | BINARY_OP                         5 (*)
             12  98 | 6202c6002000c6092000 | LOAD_FAST                         2 (j)
             14 198 | c6002000c60920006002 | BINARY_OP                         0 (+)
             18 198 | c60920006002c6012000 | BINARY_OP                         9 (>>)
             22  96 | 6002c60120003100---- | LOAD_CONST                        2 (1)
             24 198 | c60120003100-------- | BINARY_OP                         1 (&)
             28  49 | 3100---------------- | RETURN_VALUE

Game 1: "drive to work"

Pass 3 tests to prove your worth!
Fuel: 8
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱
🧱🚓🧱  🧱        🧱
🧱  🧱      🧱  🧱🧱
🧱  🧱⛽️🧱🧱🧱  🧱🧱
🧱      🧱⛽️🧱    🧱
🧱🧱🧱🧱🧱  🧱🧱  🧱
🧱                🧱
🧱  🧱🧱🧱🧱🧱🧱🧱🧱
🧱              🏁🧱
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱

Even the code block does not show it probably (emoji and double space have different widths.)

To improve the readability, here we replace double spaces with another emoji :

Pass 3 tests to prove your worth!
Fuel: 8
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱
🧱🚓🧱⬛🧱⬛⬛⬛⬛🧱
🧱⬛🧱⬛⬛⬛🧱⬛🧱🧱
🧱⬛🧱⛽️🧱🧱🧱⬛🧱🧱
🧱⬛⬛⬛🧱⛽️🧱⬛⬛🧱
🧱🧱🧱🧱🧱⬛🧱🧱⬛🧱
🧱⬛⬛⬛⬛⬛⬛⬛⬛🧱
🧱⬛🧱🧱🧱🧱🧱🧱🧱🧱
🧱⬛⬛⬛⬛⬛⬛⬛🏁🧱
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱

The answer here is sssddwwddwddsssdssaaawwssaaaassddddddd, where wasd refers to ↑←↓→.

Pass 3 tests to prove your worth!
Fuel: 8
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱
🧱⬇️🧱⬛🧱➡️➡️⬇️⬛🧱
🧱⬇️🧱➡️➡️⬆️🧱⬇️🧱🧱
🧱⬇️🧱⬆️🧱🧱🧱⬇️🧱🧱
🧱➡️➡️⬆️🧱⛽️🧱➡️⬇️🧱
🧱🧱🧱🧱🧱⬆️🧱🧱⬇️🧱
🧱⬛⬛⬛⬛⬆️⬅️⬅️⬅️🧱
🧱⬛🧱🧱🧱🧱🧱🧱🧱🧱
🧱⬛⬛⬛⬛⬛⬛⬛🏁🧱
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱

🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱
🧱⬛🧱⬛🧱⬛⬛⬛⬛🧱
🧱⬛🧱⬛⬛⬛🧱⬛🧱🧱
🧱⬛🧱⬛🧱🧱🧱⬛🧱🧱
🧱⬛⬛⬛🧱⬇️🧱⬛⬛🧱
🧱🧱🧱🧱🧱⬇️🧱🧱⬛🧱
🧱⬇️⬅️⬅️⬅️⬅️⬛⬛⬛🧱
🧱⬇️🧱🧱🧱🧱🧱🧱🧱🧱
🧱➡️➡️➡️➡️➡️➡️➡️🏁🧱
🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱

Game 2: "know some maths"

Math quiz time!
What is the sum of 12 and 5?
17
Correct!
What is the difference of 45 and 14?
31
Correct!
What is the product of 8 and 9?
72
Correct!
What is the ratio of 18 and 6?
3
Correct!
What is the remainder from division of 23 and 7?
2
Correct!

This is quite straightforward, and the appended seed is _17_31_72_3_2_.

Game 3: "can type fast"

Speed typing game.
20.00 seconds left.
Text: Because
Because
18.45 seconds left.
Text: Because of
of
17.32 seconds left.
Text: Because of its
its
16.23 seconds left.
Text: Because of its performance
performance
13.95 seconds left.
Text: Because of its performance advantage,

The typing speed requirement is too strict that you can just calculate the seed, or modify the time limit to solve this game.

The seed here is _BECAUSE_OF_ITS_PERFORMANCE_ADVANTAGE,_TODAY_MANY_LANGUAGE_IMPLEMENTATIONS_EXECUTE_A_PROGRAM_IN_TWO_PHASES,_FIRST_COMPILING_THE_SOURCE_CODE_INTO_BYTECODE,_AND_THEN_PASSING_THE_BYTECODE_TO_THE_VIRTUAL_MACHINE._

Getting the flag

This means the final seed is seed:sssddwwddwddsssdssaaawwssaaaassddddddd:_17_31_72_3_2_:_BECAUSE_OF_ITS_PERFORMANCE_ADVANTAGE,_TODAY_MANY_LANGUAGE_IMPLEMENTATIONS_EXECUTE_A_PROGRAM_IN_TWO_PHASES,_FIRST_COMPILING_THE_SOURCE_CODE_INTO_BYTECODE,_AND_THEN_PASSING_THE_BYTECODE_TO_THE_VIRTUAL_MACHINE._

Use this seed to decrypt the flag, which is CTF{4t_l3ast_1t_w4s_n0t_4n_x86_opc0d3_p3rmut4tion}.

import sys
import random
import time
def ks(seed):
random.seed(seed)
while True:
yield (random.randint(0, 255) * 13 + 17) % 256
def cry(s, seed): # line 11
r = []
for x, y in zip(ks(seed), s):
r.append(x ^ y)
return bytes(r)
def fail(s): # line 18
print(s)
print('Thanks for playing!')
sys.exit(0)
def game1(): # line 23
def w(m, i, j): # line 24
return m >> (i * 10 + j) & 1
m = 1267034045110727999721745963007
fuel = 8
x, y = 1, 1
stops = set([(5, 4), (3, 3)])
log = ""
while True:
print("Fuel:", fuel)
for i in range(10):
s = ''
for j in range(10):
if w(m, i, j):
s += '🧱'
elif (j, i) == (x, y):
s += '🚓'
elif (j, i) == (8, 8):
s += '🏁'
elif (j, i) in stops:
s += '⛽️'
else:
s += ' '
print(s)
inp = input().strip()
for c in inp:
log += c
if c not in 'wasd':
fail('Nope!')
if fuel == 0:
fail('Empty...')
dx, dy = {"w": (0, -1), "s": (0, 1), "a": (-1, 0), "d": (1, 0)}[c]
x += dx
y += dy
if w(m, y, x):
fail('Crash!')
fuel -= 1
if (x, y) in stops:
stops.remove((x, y))
fuel += 15
elif (x, y) == (8, 8):
print('Nice!')
return log
def game2(): # line 68
print('Math quiz time!')
qs = [
('sum', 12, 5),
('difference', 45, 14),
('product', 8, 9),
('ratio', 18, 6),
('remainder from division', 23, 7)
]
log = "_"
for q in qs:
print('What is the %s of %d and %d?' % q)
x, a, b = q
if x == 'sum': r = a + b
elif x == 'difference': r = a - b
elif x == 'product': r = a * b
elif x == 'ratio': r = a // b
elif x == 'remainder from division': r = a % b
else:
fail('What?')
inp = int(input())
if inp == r:
print('Correct!')
log += str(inp) + '_'
else:
fail('Wrong!')
return log
def game3(): # line 96
print('Speed typing game.')
t = time.time()
text = """
Text: Because of its performance advantage, today many language implementations
execute a program in two phases, first compiling the source code into bytecode,
and then passing the bytecode to the virtual machine.
"""
words = text.split()
it = 1
log = '_'
while it != len(words):
print('%0.2f seconds left.' % (20 - (time.time() - t)))
print(f"\x1b[32m{' '.join(words[:it])!s}\x1b[39m {words[it]!s}")
inp = input()
if time.time() > t + 20:
fail('Too slow!')
if inp == words[it]:
log += words[it].upper() + '_'
it += 1
else:
fail('You made a mistake!')
else:
print('Nice!')
return log
def main(): # line 123
print('Pass 3 tests to prove your worth!')
seed = 'seed:'
seed += game1() + ":"
print(seed)
seed += game2() + ":"
print(seed)
seed += game3()
print(seed)
print()
print("You can drive to work, know some maths and can type fast. You're hired!")
print('Your sign-on bonus:', cry(b'\xa0?n\xa5\x7f)\x1f6Jvh\x95\xcc!\x1e\x95\x996a\x11\xf6OV\x88\xc1\x9f\xde\xb50\x9d\xae\x14\xde\x18YHI\xd8\xd5\x90\x8a\x181l\xb0\x16^O;]', seed).decode())
# print(cry(b'\xa0?n\xa5\x7f)\x1f6Jvh\x95\xcc!\x1e\x95\x996a\x11\xf6OV\x88\xc1\x9f\xde\xb50\x9d\xae\x14\xde\x18YHI\xd8\xd5\x90\x8a\x181l\xb0\x16^O;]', 'seed:sssddwwddwddsssdssaaawwssaaaassddddddd:_17_31_72_3_2_:_BECAUSE_OF_ITS_PERFORMANCE_ADVANTAGE,_TODAY_MANY_LANGUAGE_IMPLEMENTATIONS_EXECUTE_A_PROGRAM_IN_TWO_PHASES,_FIRST_COMPILING_THE_SOURCE_CODE_INTO_BYTECODE,_AND_THEN_PASSING_THE_BYTECODE_TO_THE_VIRTUAL_MACHINE._').decode())
# CTF{4t_l3ast_1t_w4s_n0t_4n_x86_opc0d3_p3rmut4tion}
"""
opcode module - potentially shared between dis and other modules which
operate on bytecodes (e.g. peephole optimizers).
"""
__all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs",
"haslocal", "hascompare", "hasfree", "opname", "opmap",
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"]
# It's a chicken-and-egg I'm afraid:
# We're imported before _opcode's made.
# With exception unheeded
# (stack_effect is not needed)
# Both our chickens and eggs are allayed.
# --Larry Hastings, 2013/11/23
try:
from _opcode import stack_effect
__all__.append('stack_effect')
except ImportError:
pass
cmp_op = ('<', '<=', '==', '!=', '>', '>=')
hasconst = []
hasname = []
hasjrel = []
hasjabs = []
haslocal = []
hascompare = []
hasfree = []
hasnargs = [] # unused
opmap = {}
opname = ['<%r>' % (op,) for op in range(256)]
_inline_cache_entries = [0] * 256
def def_op(name, op, entries=0):
opname[op] = name
opmap[name] = op
_inline_cache_entries[op] = entries
def name_op(name, op, entries=0):
def_op(name, op, entries)
hasname.append(op)
def jrel_op(name, op, entries=0):
def_op(name, op, entries)
hasjrel.append(op)
def jabs_op(name, op, entries=0):
def_op(name, op, entries)
hasjabs.append(op)
# Instruction opcodes for compiled code
# Blank lines correspond to available opcodes
for i in range(200):
def_op('!{}!'.format(i), i)
def_op('CACHE', 200)
def_op('POP_TOP', 1)
def_op('PUSH_NULL', 200)
def_op('NOP', 27)
def_op('UNARY_POSITIVE', 200)
def_op('UNARY_NEGATIVE', 200)
def_op('UNARY_NOT', 200)
def_op('UNARY_INVERT', 200)
def_op('BINARY_SUBSCR', 82, 4)
def_op('GET_LEN', 200)
def_op('MATCH_MAPPING', 200)
def_op('MATCH_SEQUENCE', 200)
def_op('MATCH_KEYS', 200)
def_op('PUSH_EXC_INFO', 200)
def_op('CHECK_EXC_MATCH', 200)
def_op('CHECK_EG_MATCH', 200)
def_op('WITH_EXCEPT_START', 200)
def_op('GET_AITER', 200)
def_op('GET_ANEXT', 200)
def_op('BEFORE_ASYNC_WITH', 200)
def_op('BEFORE_WITH', 200)
def_op('END_ASYNC_FOR', 200)
def_op('STORE_SUBSCR', 200, 1)
def_op('DELETE_SUBSCR', 200)
def_op('GET_ITER', 31)
def_op('GET_YIELD_FROM_ITER', 200)
def_op('PRINT_EXPR', 200)
def_op('LOAD_BUILD_CLASS', 200)
def_op('LOAD_ASSERTION_ERROR', 200)
def_op('RETURN_GENERATOR', 3)
def_op('LIST_TO_TUPLE', 200)
def_op('RETURN_VALUE', 49)
def_op('IMPORT_STAR', 200)
def_op('SETUP_ANNOTATIONS', 200)
def_op('YIELD_VALUE', 69)
def_op('ASYNC_GEN_WRAP', 200)
def_op('PREP_RERAISE_STAR', 200)
def_op('POP_EXCEPT', 200)
HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
name_op('STORE_NAME', 150) # Index in name list
name_op('DELETE_NAME', 200) # ""IMPORT_NAME
def_op('UNPACK_SEQUENCE', 143, 1) # Number of tuple items
jrel_op('FOR_ITER', 182)
def_op('UNPACK_EX', 200)
name_op('STORE_ATTR', 200, 4) # Index in name list
name_op('DELETE_ATTR', 200) # ""
name_op('STORE_GLOBAL', 200) # ""
name_op('DELETE_GLOBAL', 200) # ""
def_op('SWAP', 149)
def_op('LOAD_CONST', 96) # Index in const list
hasconst.append(96)
name_op('LOAD_NAME', 200) # Index in name list
def_op('BUILD_TUPLE', 130) # Number of tuple items
def_op('BUILD_LIST', 116) # Number of list items
def_op('BUILD_SET', 200) # Number of set items
def_op('BUILD_MAP', 200) # Number of dict entries
name_op('LOAD_ATTR', 188, 4) # Index in name list
def_op('COMPARE_OP', 104, 2) # Comparison operator
hascompare.append(104)
name_op('IMPORT_NAME', 171) # Index in name list
name_op('IMPORT_FROM', 200) # Index in name list
jrel_op('JUMP_FORWARD', 94) # Number of words to skip
jrel_op('JUMP_IF_FALSE_OR_POP', 200) # Number of words to skip
jrel_op('JUMP_IF_TRUE_OR_POP', 200) # ""
jrel_op('POP_JUMP_FORWARD_IF_FALSE', 186)
jrel_op('POP_JUMP_FORWARD_IF_TRUE', 200)
name_op('LOAD_GLOBAL', 185, 5) # Index in name list
def_op('IS_OP', 200)
def_op('CONTAINS_OP', 117)
def_op('RERAISE', 200)
def_op('COPY', 200)
def_op('BINARY_OP', 198, 1)
jrel_op('SEND', 200) # Number of bytes to skip
def_op('LOAD_FAST', 98) # Local variable number
haslocal.append(98)
def_op('STORE_FAST', 141) # Local variable number
haslocal.append(141)
def_op('DELETE_FAST', 200) # Local variable number
haslocal.append(200)
jrel_op('POP_JUMP_FORWARD_IF_NOT_NONE', 200)
jrel_op('POP_JUMP_FORWARD_IF_NONE', 200)
def_op('RAISE_VARARGS', 200) # Number of raise arguments (1, 2, or 3)
def_op('GET_AWAITABLE', 200)
def_op('MAKE_FUNCTION', 99) # Flags
def_op('BUILD_SLICE', 136) # Number of items
jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 200) # Number of words to skip (backwards)
def_op('MAKE_CELL', 200)
hasfree.append(200)
def_op('LOAD_CLOSURE', 200)
hasfree.append(200)
def_op('LOAD_DEREF', 200)
hasfree.append(200)
def_op('STORE_DEREF', 200)
hasfree.append(200)
def_op('DELETE_DEREF', 200)
hasfree.append(200)
jrel_op('JUMP_BACKWARD', 123) # Number of words to skip (backwards)
def_op('CALL_FUNCTION_EX', 200) # Flags
def_op('EXTENDED_ARG', 114)
EXTENDED_ARG = 114
def_op('LIST_APPEND', 200)
def_op('SET_ADD', 200)
def_op('MAP_ADD', 200)
def_op('LOAD_CLASSDEREF', 200)
hasfree.append(200)
def_op('COPY_FREE_VARS', 200)
def_op('RESUME', 97)
def_op('MATCH_CLASS', 200)
def_op('FORMAT_VALUE', 137)
def_op('BUILD_CONST_KEY_MAP', 177)
def_op('BUILD_STRING', 138)
name_op('LOAD_METHOD', 146, 10)
def_op('LIST_EXTEND', 93)
def_op('SET_UPDATE', 200)
def_op('DICT_MERGE', 200)
def_op('DICT_UPDATE', 200)
def_op('PRECALL', 191, 1)
def_op('CALL', 142, 4)
def_op('KW_NAMES', 200)
hasconst.append(200)
jrel_op('POP_JUMP_BACKWARD_IF_NOT_NONE', 200)
jrel_op('POP_JUMP_BACKWARD_IF_NONE', 200)
jrel_op('POP_JUMP_BACKWARD_IF_FALSE', 200)
jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 175)
del def_op, name_op, jrel_op, jabs_op
_nb_ops = [
("NB_ADD", "+"),
("NB_AND", "&"),
("NB_FLOOR_DIVIDE", "//"),
("NB_LSHIFT", "<<"),
("NB_MATRIX_MULTIPLY", "@"),
("NB_MULTIPLY", "*"),
("NB_REMAINDER", "%"),
("NB_OR", "|"),
("NB_POWER", "**"),
("NB_RSHIFT", ">>"),
("NB_SUBTRACT", "-"),
("NB_TRUE_DIVIDE", "/"),
("NB_XOR", "^"),
("NB_INPLACE_ADD", "+="),
("NB_INPLACE_AND", "&="),
("NB_INPLACE_FLOOR_DIVIDE", "//="),
("NB_INPLACE_LSHIFT", "<<="),
("NB_INPLACE_MATRIX_MULTIPLY", "@="),
("NB_INPLACE_MULTIPLY", "*="),
("NB_INPLACE_REMAINDER", "%="),
("NB_INPLACE_OR", "|="),
("NB_INPLACE_POWER", "**="),
("NB_INPLACE_RSHIFT", ">>="),
("NB_INPLACE_SUBTRACT", "-="),
("NB_INPLACE_TRUE_DIVIDE", "/="),
("NB_INPLACE_XOR", "^="),
]
_specializations = {
"BINARY_OP": [
"BINARY_OP_ADAPTIVE",
"BINARY_OP_ADD_FLOAT",
"BINARY_OP_ADD_INT",
"BINARY_OP_ADD_UNICODE",
"BINARY_OP_INPLACE_ADD_UNICODE",
"BINARY_OP_MULTIPLY_FLOAT",
"BINARY_OP_MULTIPLY_INT",
"BINARY_OP_SUBTRACT_FLOAT",
"BINARY_OP_SUBTRACT_INT",
],
"BINARY_SUBSCR": [
"BINARY_SUBSCR_ADAPTIVE",
"BINARY_SUBSCR_DICT",
"BINARY_SUBSCR_GETITEM",
"BINARY_SUBSCR_LIST_INT",
"BINARY_SUBSCR_TUPLE_INT",
],
"CALL": [
"CALL_ADAPTIVE",
"CALL_PY_EXACT_ARGS",
"CALL_PY_WITH_DEFAULTS",
],
"COMPARE_OP": [
"COMPARE_OP_ADAPTIVE",
"COMPARE_OP_FLOAT_JUMP",
"COMPARE_OP_INT_JUMP",
"COMPARE_OP_STR_JUMP",
],
"EXTENDED_ARG": [
"EXTENDED_ARG_QUICK",
],
"JUMP_BACKWARD": [
"JUMP_BACKWARD_QUICK",
],
"LOAD_ATTR": [
"LOAD_ATTR_ADAPTIVE",
"LOAD_ATTR_INSTANCE_VALUE",
"LOAD_ATTR_MODULE",
"LOAD_ATTR_SLOT",
"LOAD_ATTR_WITH_HINT",
],
"LOAD_CONST": [
"LOAD_CONST__LOAD_FAST",
],
"LOAD_FAST": [
"LOAD_FAST__LOAD_CONST",
"LOAD_FAST__LOAD_FAST",
],
"LOAD_GLOBAL": [
"LOAD_GLOBAL_ADAPTIVE",
"LOAD_GLOBAL_BUILTIN",
"LOAD_GLOBAL_MODULE",
],
"LOAD_METHOD": [
"LOAD_METHOD_ADAPTIVE",
"LOAD_METHOD_CLASS",
"LOAD_METHOD_MODULE",
"LOAD_METHOD_NO_DICT",
"LOAD_METHOD_WITH_DICT",
"LOAD_METHOD_WITH_VALUES",
],
"PRECALL": [
"PRECALL_ADAPTIVE",
"PRECALL_BOUND_METHOD",
"PRECALL_BUILTIN_CLASS",
"PRECALL_BUILTIN_FAST_WITH_KEYWORDS",
"PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS",
"PRECALL_NO_KW_BUILTIN_FAST",
"PRECALL_NO_KW_BUILTIN_O",
"PRECALL_NO_KW_ISINSTANCE",
"PRECALL_NO_KW_LEN",
"PRECALL_NO_KW_LIST_APPEND",
"PRECALL_NO_KW_METHOD_DESCRIPTOR_FAST",
"PRECALL_NO_KW_METHOD_DESCRIPTOR_NOARGS",
"PRECALL_NO_KW_METHOD_DESCRIPTOR_O",
"PRECALL_NO_KW_STR_1",
"PRECALL_NO_KW_TUPLE_1",
"PRECALL_NO_KW_TYPE_1",
"PRECALL_PYFUNC",
],
"RESUME": [
"RESUME_QUICK",
],
"STORE_ATTR": [
"STORE_ATTR_ADAPTIVE",
"STORE_ATTR_INSTANCE_VALUE",
"STORE_ATTR_SLOT",
"STORE_ATTR_WITH_HINT",
],
"STORE_FAST": [
"STORE_FAST__LOAD_FAST",
"STORE_FAST__STORE_FAST",
],
"STORE_SUBSCR": [
"STORE_SUBSCR_ADAPTIVE",
"STORE_SUBSCR_DICT",
"STORE_SUBSCR_LIST_INT",
],
"UNPACK_SEQUENCE": [
"UNPACK_SEQUENCE_ADAPTIVE",
"UNPACK_SEQUENCE_LIST",
"UNPACK_SEQUENCE_TUPLE",
"UNPACK_SEQUENCE_TWO_TUPLE",
],
}
_specialized_instructions = [
opcode for family in _specializations.values() for opcode in family
]
_specialization_stats = [
"success",
"failure",
"hit",
"deferred",
"miss",
"deopt",
]
"""Disassembler of Python byte code into mnemonics."""
import sys
import types
import collections
import io
from opcode import *
from opcode import __all__ as _opcodes_all
from opcode import _nb_ops, _inline_cache_entries, _specializations, _specialized_instructions
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
"findlinestarts", "findlabels", "show_code",
"get_instructions", "Instruction", "Bytecode"] + _opcodes_all
del _opcodes_all
_have_code = (types.MethodType, types.FunctionType, types.CodeType,
classmethod, staticmethod, type)
FORMAT_VALUE = opmap['FORMAT_VALUE']
FORMAT_VALUE_CONVERTERS = (
(None, ''),
(str, 'str'),
(repr, 'repr'),
(ascii, 'ascii'),
)
MAKE_FUNCTION = opmap['MAKE_FUNCTION']
MAKE_FUNCTION_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure')
LOAD_CONST = opmap['LOAD_CONST']
LOAD_GLOBAL = opmap['LOAD_GLOBAL']
BINARY_OP = opmap['BINARY_OP']
JUMP_BACKWARD = opmap['JUMP_BACKWARD']
CACHE = opmap["CACHE"]
_all_opname = list(opname)
_all_opmap = dict(opmap)
_empty_slot = [slot for slot, name in enumerate(_all_opname) if name.startswith("<")]
for spec_op, specialized in zip(_empty_slot, _specialized_instructions):
# fill opname and opmap
_all_opname[spec_op] = specialized
_all_opmap[specialized] = spec_op
deoptmap = {
specialized: base for base, family in _specializations.items() for specialized in family
}
def _try_compile(source, name):
"""Attempts to compile the given source, first as an expression and
then as a statement if the first approach fails.
Utility function to accept strings in functions that otherwise
expect code objects
"""
try:
c = compile(source, name, 'eval')
except SyntaxError:
c = compile(source, name, 'exec')
return c
def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False):
"""Disassemble classes, methods, functions, and other compiled objects.
With no argument, disassemble the last traceback.
Compiled objects currently include generator objects, async generator
objects, and coroutine objects, all of which store their code object
in a special attribute.
"""
if x is None:
distb(file=file, show_caches=show_caches, adaptive=adaptive)
return
# Extract functions from methods.
if hasattr(x, '__func__'):
x = x.__func__
# Extract compiled code objects from...
if hasattr(x, '__code__'): # ...a function, or
x = x.__code__
elif hasattr(x, 'gi_code'): #...a generator object, or
x = x.gi_code
elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or
x = x.ag_code
elif hasattr(x, 'cr_code'): #...a coroutine.
x = x.cr_code
# Perform the disassembly.
if hasattr(x, '__dict__'): # Class or module
items = sorted(x.__dict__.items())
for name, x1 in items:
if isinstance(x1, _have_code):
print("Disassembly of %s:" % name, file=file)
try:
dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
except TypeError as msg:
print("Sorry:", msg, file=file)
print(file=file)
elif hasattr(x, 'co_code'): # Code object
_disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
_disassemble_bytes(x, file=file, show_caches=show_caches)
elif isinstance(x, str): # Source code
_disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
else:
raise TypeError("don't know how to disassemble %s objects" %
type(x).__name__)
def distb(tb=None, *, file=None, show_caches=False, adaptive=False):
"""Disassemble a traceback (default: last traceback)."""
if tb is None:
try:
tb = sys.last_traceback
except AttributeError:
raise RuntimeError("no last traceback to disassemble") from None
while tb.tb_next: tb = tb.tb_next
disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive)
# The inspect module interrogates this dictionary to build its
# list of CO_* constants. It is also used by pretty_flags to
# turn the co_flags field into a human readable list.
COMPILER_FLAG_NAMES = {
1: "OPTIMIZED",
2: "NEWLOCALS",
4: "VARARGS",
8: "VARKEYWORDS",
16: "NESTED",
32: "GENERATOR",
64: "NOFREE",
128: "COROUTINE",
256: "ITERABLE_COROUTINE",
512: "ASYNC_GENERATOR",
}
def pretty_flags(flags):
"""Return pretty representation of code flags."""
names = []
for i in range(32):
flag = 1<<i
if flags & flag:
names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
flags ^= flag
if not flags:
break
else:
names.append(hex(flags))
return ", ".join(names)
class _Unknown:
def __repr__(self):
return "<unknown>"
# Sentinel to represent values that cannot be calculated
UNKNOWN = _Unknown()
def _get_code_object(x):
"""Helper to handle methods, compiled or raw code objects, and strings."""
# Extract functions from methods.
if hasattr(x, '__func__'):
x = x.__func__
# Extract compiled code objects from...
if hasattr(x, '__code__'): # ...a function, or
x = x.__code__
elif hasattr(x, 'gi_code'): #...a generator object, or
x = x.gi_code
elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or
x = x.ag_code
elif hasattr(x, 'cr_code'): #...a coroutine.
x = x.cr_code
# Handle source code.
if isinstance(x, str):
x = _try_compile(x, "<disassembly>")
# By now, if we don't have a code object, we can't disassemble x.
if hasattr(x, 'co_code'):
return x
raise TypeError("don't know how to disassemble %s objects" %
type(x).__name__)
def _deoptop(op):
name = _all_opname[op]
return _all_opmap[deoptmap[name]] if name in deoptmap else op
def _get_code_array(co, adaptive):
return co._co_code_adaptive if adaptive else co.co_code
def code_info(x):
"""Formatted details of methods, functions, or code."""
return _format_code_info(_get_code_object(x))
def _format_code_info(co):
lines = []
lines.append("Name: %s" % co.co_name)
lines.append("Filename: %s" % co.co_filename)
lines.append("Argument count: %s" % co.co_argcount)
lines.append("Positional-only arguments: %s" % co.co_posonlyargcount)
lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
lines.append("Number of locals: %s" % co.co_nlocals)
lines.append("Stack size: %s" % co.co_stacksize)
lines.append("Flags: %s" % pretty_flags(co.co_flags))
if co.co_consts:
lines.append("Constants:")
for i_c in enumerate(co.co_consts):
lines.append("%4d: %r" % i_c)
if co.co_names:
lines.append("Names:")
for i_n in enumerate(co.co_names):
lines.append("%4d: %s" % i_n)
if co.co_varnames:
lines.append("Variable names:")
for i_n in enumerate(co.co_varnames):
lines.append("%4d: %s" % i_n)
if co.co_freevars:
lines.append("Free variables:")
for i_n in enumerate(co.co_freevars):
lines.append("%4d: %s" % i_n)
if co.co_cellvars:
lines.append("Cell variables:")
for i_n in enumerate(co.co_cellvars):
lines.append("%4d: %s" % i_n)
return "\n".join(lines)
def show_code(co, *, file=None):
"""Print details of methods, functions, or code to *file*.
If *file* is not provided, the output is printed on stdout.
"""
print(code_info(co), file=file)
Positions = collections.namedtuple(
'Positions',
[
'lineno',
'end_lineno',
'col_offset',
'end_col_offset',
],
defaults=[None] * 4
)
_Instruction = collections.namedtuple(
"_Instruction",
[
'opname',
'opcode',
'arg',
'argval',
'argrepr',
'offset',
'starts_line',
'is_jump_target',
'positions',
'code'
],
defaults=[None]
)
_Instruction.opname.__doc__ = "Human readable name for operation"
_Instruction.opcode.__doc__ = "Numeric code for operation"
_Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None"
_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg"
_Instruction.argrepr.__doc__ = "Human readable description of operation argument"
_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence"
_Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None"
_Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False"
_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction"
_ExceptionTableEntry = collections.namedtuple("_ExceptionTableEntry",
"start end target depth lasti")
_OPNAME_WIDTH = max(map(len, opmap))
_OPARG_WIDTH = 5
class Instruction(_Instruction):
"""Details for a bytecode operation
Defined fields:
opname - human readable name for operation
opcode - numeric code for operation
arg - numeric argument to operation (if any), otherwise None
argval - resolved arg value (if known), otherwise same as arg
argrepr - human readable description of operation argument
offset - start index of operation within bytecode sequence
starts_line - line started by this opcode (if any), otherwise None
is_jump_target - True if other code jumps to here, otherwise False
positions - Optional dis.Positions object holding the span of source code
covered by this instruction
"""
def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
"""Format instruction details for inclusion in disassembly output
*lineno_width* sets the width of the line number field (0 omits it)
*mark_as_current* inserts a '-->' marker arrow as part of the line
*offset_width* sets the width of the instruction offset field
"""
fields = []
# Column: Source code line number
if lineno_width:
if self.starts_line is not None:
lineno_fmt = "%%%dd" % lineno_width
fields.append(lineno_fmt % self.starts_line)
else:
fields.append(' ' * lineno_width)
# Column: Current instruction indicator
if mark_as_current:
fields.append('-->')
else:
fields.append(' ')
# Column: Jump target marker
if self.is_jump_target:
fields.append('>>')
else:
fields.append(' ')
# Column: Instruction offset from start of code sequence
fields.append(repr(self.offset).rjust(offset_width))
# Column: Opcode name
fields.append("{:>3} | {} | {}".format(self.opcode, self.code[self.offset:self.offset+10].hex().ljust(20, "-"), self.opname.ljust(_OPNAME_WIDTH)))
# Column: Opcode argument
if self.arg is not None:
fields.append(repr(self.arg).rjust(_OPARG_WIDTH))
# Column: Opcode argument details
if self.argrepr:
fields.append('(' + self.argrepr + ')')
return ' '.join(fields).rstrip()
def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False):
"""Iterator for the opcodes in methods, functions or code
Generates a series of Instruction named tuples giving the details of
each operations in the supplied code.
If *first_line* is not None, it indicates the line number that should
be reported for the first source line in the disassembled code.
Otherwise, the source line information (if any) is taken directly from
the disassembled code object.
"""
co = _get_code_object(x)
linestarts = dict(findlinestarts(co))
if first_line is not None:
line_offset = first_line - co.co_firstlineno
else:
line_offset = 0
return _get_instructions_bytes(_get_code_array(co, adaptive),
co._varname_from_oparg,
co.co_names, co.co_consts,
linestarts, line_offset,
co_positions=co.co_positions(),
show_caches=show_caches)
def _get_const_value(op, arg, co_consts):
"""Helper to get the value of the const in a hasconst op.
Returns the dereferenced constant if this is possible.
Otherwise (if it is a LOAD_CONST and co_consts is not
provided) returns the dis.UNKNOWN sentinel.
"""
assert op in hasconst
argval = UNKNOWN
if op == LOAD_CONST:
if co_consts is not None:
argval = co_consts[arg]
return argval
def _get_const_info(op, arg, co_consts):
"""Helper to get optional details about const references
Returns the dereferenced constant and its repr if the value
can be calculated.
Otherwise returns the sentinel value dis.UNKNOWN for the value
and an empty string for its repr.
"""
argval = _get_const_value(op, arg, co_consts)
argrepr = repr(argval) if argval is not UNKNOWN else ''
return argval, argrepr
def _get_name_info(name_index, get_name, **extrainfo):
"""Helper to get optional details about named references
Returns the dereferenced name as both value and repr if the name
list is defined.
Otherwise returns the sentinel value dis.UNKNOWN for the value
and an empty string for its repr.
"""
if get_name is not None:
argval = get_name(name_index, **extrainfo)
return argval, argval
else:
return UNKNOWN, ''
def parse_varint(iterator):
b = next(iterator)
val = b & 63
while b&64:
val <<= 6
b = next(iterator)
val |= b&63
return val
def parse_exception_table(code):
iterator = iter(code.co_exceptiontable)
entries = []
try:
while True:
start = parse_varint(iterator)*2
length = parse_varint(iterator)*2
end = start + length
target = parse_varint(iterator)*2
dl = parse_varint(iterator)
depth = dl >> 1
lasti = bool(dl&1)
entries.append(_ExceptionTableEntry(start, end, target, depth, lasti))
except StopIteration:
return entries
def _is_backward_jump(op):
return 'JUMP_BACKWARD' in opname[op]
def _get_instructions_bytes(code, varname_from_oparg=None,
names=None, co_consts=None,
linestarts=None, line_offset=0,
exception_entries=(), co_positions=None,
show_caches=False):
"""Iterate over the instructions in a bytecode string.
Generates a sequence of Instruction namedtuples giving the details of each
opcode. Additional information about the code's runtime environment
(e.g. variable names, co_consts) can be specified using optional
arguments.
"""
co_positions = co_positions or iter(())
get_name = None if names is None else names.__getitem__
labels = set(findlabels(code))
for start, end, target, _, _ in exception_entries:
for i in range(start, end):
labels.add(target)
starts_line = None
cache_counter = 0
for offset, op, arg in _unpack_opargs(code):
if cache_counter > 0:
if show_caches:
yield Instruction("CACHE", 0, None, None, '',
offset, None, False, None, None)
cache_counter -= 1
continue
if linestarts is not None:
starts_line = linestarts.get(offset, None)
if starts_line is not None:
starts_line += line_offset
is_jump_target = offset in labels
argval = None
argrepr = ''
positions = Positions(*next(co_positions, ()))
deop = _deoptop(op)
cache_counter = _inline_cache_entries[deop]
try:
if arg is not None:
# Set argval to the dereferenced value of the argument when
# available, and argrepr to the string representation of argval.
# _disassemble_bytes needs the string repr of the
# raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
argval = arg
if deop in hasconst:
argval, argrepr = _get_const_info(deop, arg, co_consts)
elif deop in hasname:
if deop == LOAD_GLOBAL:
argval, argrepr = _get_name_info(arg//2, get_name)
if (arg & 1) and argrepr:
argrepr = "NULL + " + argrepr
else:
argval, argrepr = _get_name_info(arg, get_name)
elif deop in hasjabs:
argval = arg*2
argrepr = "to " + repr(argval)
elif deop in hasjrel:
signed_arg = -arg if _is_backward_jump(deop) else arg
argval = offset + 2 + signed_arg*2
argrepr = "to " + repr(argval)
elif deop in haslocal or deop in hasfree:
argval, argrepr = _get_name_info(arg, varname_from_oparg)
elif deop in hascompare:
argval = cmp_op[arg]
argrepr = argval
elif deop == FORMAT_VALUE:
argval, argrepr = FORMAT_VALUE_CONVERTERS[arg & 0x3]
argval = (argval, bool(arg & 0x4))
if argval[1]:
if argrepr:
argrepr += ', '
argrepr += 'with format'
elif deop == MAKE_FUNCTION:
argrepr = ', '.join(s for i, s in enumerate(MAKE_FUNCTION_FLAGS)
if arg & (1<<i))
elif deop == BINARY_OP:
_, argrepr = _nb_ops[arg]
except:
pass
yield Instruction(_all_opname[op], op,
arg, argval, argrepr,
offset, starts_line, is_jump_target, positions, code)
def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
"""Disassemble a code object."""
linestarts = dict(findlinestarts(co))
exception_entries = parse_exception_table(co)
_disassemble_bytes(_get_code_array(co, adaptive),
lasti, co._varname_from_oparg,
co.co_names, co.co_consts, linestarts, file=file,
exception_entries=exception_entries,
co_positions=co.co_positions(), show_caches=show_caches)
def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False):
disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive)
if depth is None or depth > 0:
if depth is not None:
depth = depth - 1
for x in co.co_consts:
if hasattr(x, 'co_code'):
print(file=file)
print("Disassembly of %r:" % (x,), file=file)
_disassemble_recursive(
x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive
)
def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
names=None, co_consts=None, linestarts=None,
*, file=None, line_offset=0, exception_entries=(),
co_positions=None, show_caches=False):
# Omit the line number column entirely if we have no line number info
show_lineno = bool(linestarts)
if show_lineno:
maxlineno = max(linestarts.values()) + line_offset
if maxlineno >= 1000:
lineno_width = len(str(maxlineno))
else:
lineno_width = 3
else:
lineno_width = 0
maxoffset = len(code) - 2
if maxoffset >= 10000:
offset_width = len(str(maxoffset))
else:
offset_width = 4
for instr in _get_instructions_bytes(code, varname_from_oparg, names,
co_consts, linestarts,
line_offset=line_offset,
exception_entries=exception_entries,
co_positions=co_positions,
show_caches=show_caches):
new_source_line = (show_lineno and
instr.starts_line is not None and
instr.offset > 0)
if new_source_line:
print(file=file)
is_current_instr = instr.offset == lasti
print(instr._disassemble(lineno_width, is_current_instr, offset_width),
file=file)
if exception_entries:
print("ExceptionTable:", file=file)
for entry in exception_entries:
lasti = " lasti" if entry.lasti else ""
end = entry.end-2
print(f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}", file=file)
def _disassemble_str(source, **kwargs):
"""Compile the source string, then disassemble the code object."""
_disassemble_recursive(_try_compile(source, '<dis>'), **kwargs)
disco = disassemble # XXX For backwards compatibility
# Rely on C `int` being 32 bits for oparg
_INT_BITS = 32
# Value for c int when it overflows
_INT_OVERFLOW = 2 ** (_INT_BITS - 1)
def _unpack_opargs(code):
extended_arg = 0
for i in range(0, len(code), 2):
op = code[i]
if _deoptop(op) >= HAVE_ARGUMENT:
arg = code[i+1] | extended_arg
extended_arg = (arg << 8) if op == EXTENDED_ARG else 0
# The oparg is stored as a signed integer
# If the value exceeds its upper limit, it will overflow and wrap
# to a negative integer
if extended_arg >= _INT_OVERFLOW:
extended_arg -= 2 * _INT_OVERFLOW
else:
arg = None
extended_arg = 0
yield (i, op, arg)
def findlabels(code):
"""Detect all offsets in a byte code which are jump targets.
Return the list of offsets.
"""
labels = []
for offset, op, arg in _unpack_opargs(code):
if arg is not None:
if op in hasjrel:
if _is_backward_jump(op):
arg = -arg
label = offset + 2 + arg*2
elif op in hasjabs:
label = arg*2
else:
continue
if label not in labels:
labels.append(label)
return labels
def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source.
Generate pairs (offset, lineno)
"""
lastline = None
for start, end, line in code.co_lines():
if line is not None and line != lastline:
lastline = line
yield start, line
return
def _find_imports(co):
"""Find import statements in the code
Generate triplets (name, level, fromlist) where
name is the imported module and level, fromlist are
the corresponding args to __import__.
"""
IMPORT_NAME = opmap['IMPORT_NAME']
LOAD_CONST = opmap['LOAD_CONST']
consts = co.co_consts
names = co.co_names
opargs = [(op, arg) for _, op, arg in _unpack_opargs(co.co_code)
if op != EXTENDED_ARG]
for i, (op, oparg) in enumerate(opargs):
if op == IMPORT_NAME and i >= 2:
from_op = opargs[i-1]
level_op = opargs[i-2]
if (from_op[0] in hasconst and level_op[0] in hasconst):
level = _get_const_value(level_op[0], level_op[1], consts)
fromlist = _get_const_value(from_op[0], from_op[1], consts)
yield (names[oparg], level, fromlist)
def _find_store_names(co):
"""Find names of variables which are written in the code
Generate sequence of strings
"""
STORE_OPS = {
opmap['STORE_NAME'],
opmap['STORE_GLOBAL']
}
names = co.co_names
for _, op, arg in _unpack_opargs(co.co_code):
if op in STORE_OPS:
yield names[arg]
class Bytecode:
"""The bytecode operations of a piece of code
Instantiate this with a function, method, other compiled object, string of
code, or a code object (as returned by compile()).
Iterating over this yields the bytecode operations as Instruction instances.
"""
def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False):
self.codeobj = co = _get_code_object(x)
if first_line is None:
self.first_line = co.co_firstlineno
self._line_offset = 0
else:
self.first_line = first_line
self._line_offset = first_line - co.co_firstlineno
self._linestarts = dict(findlinestarts(co))
self._original_object = x
self.current_offset = current_offset
self.exception_entries = parse_exception_table(co)
self.show_caches = show_caches
self.adaptive = adaptive
def __iter__(self):
co = self.codeobj
return _get_instructions_bytes(_get_code_array(co, self.adaptive),
co._varname_from_oparg,
co.co_names, co.co_consts,
self._linestarts,
line_offset=self._line_offset,
exception_entries=self.exception_entries,
co_positions=co.co_positions(),
show_caches=self.show_caches)
def __repr__(self):
return "{}({!r})".format(self.__class__.__name__,
self._original_object)
@classmethod
def from_traceback(cls, tb, *, show_caches=False, adaptive=False):
""" Construct a Bytecode from the given traceback """
while tb.tb_next:
tb = tb.tb_next
return cls(
tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive
)
def info(self):
"""Return formatted information about the code object."""
return _format_code_info(self.codeobj)
def dis(self):
"""Return a formatted view of the bytecode operations."""
co = self.codeobj
if self.current_offset is not None:
offset = self.current_offset
else:
offset = -1
with io.StringIO() as output:
_disassemble_bytes(_get_code_array(co, self.adaptive),
varname_from_oparg=co._varname_from_oparg,
names=co.co_names, co_consts=co.co_consts,
linestarts=self._linestarts,
line_offset=self._line_offset,
file=output,
lasti=offset,
exception_entries=self.exception_entries,
co_positions=co.co_positions(),
show_caches=self.show_caches)
return output.getvalue()
def _test():
"""Simple test program to disassemble a file."""
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-')
args = parser.parse_args()
with args.infile as infile:
source = infile.read()
code = compile(source, args.infile.name, "exec")
dis(code)
if __name__ == "__main__":
_test()
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
diff --git a/Lib/opcode.py b/Lib/opcode.py
index 6c3862707c..40b6fdd2e0 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -57,150 +57,164 @@ def jabs_op(name, op, entries=0):
# Instruction opcodes for compiled code
# Blank lines correspond to available opcodes
-def_op('CACHE', 0)
-def_op('POP_TOP', 1)
-def_op('PUSH_NULL', 2)
-
-def_op('NOP', 9)
-def_op('UNARY_POSITIVE', 10)
-def_op('UNARY_NEGATIVE', 11)
-def_op('UNARY_NOT', 12)
-
-def_op('UNARY_INVERT', 15)
-
-def_op('BINARY_SUBSCR', 25, 4)
-
-def_op('GET_LEN', 30)
-def_op('MATCH_MAPPING', 31)
-def_op('MATCH_SEQUENCE', 32)
-def_op('MATCH_KEYS', 33)
-
-def_op('PUSH_EXC_INFO', 35)
-def_op('CHECK_EXC_MATCH', 36)
-def_op('CHECK_EG_MATCH', 37)
-
-def_op('WITH_EXCEPT_START', 49)
-def_op('GET_AITER', 50)
-def_op('GET_ANEXT', 51)
-def_op('BEFORE_ASYNC_WITH', 52)
-def_op('BEFORE_WITH', 53)
-def_op('END_ASYNC_FOR', 54)
-
-def_op('STORE_SUBSCR', 60, 1)
-def_op('DELETE_SUBSCR', 61)
-
-def_op('GET_ITER', 68)
-def_op('GET_YIELD_FROM_ITER', 69)
-def_op('PRINT_EXPR', 70)
-def_op('LOAD_BUILD_CLASS', 71)
-
-def_op('LOAD_ASSERTION_ERROR', 74)
-def_op('RETURN_GENERATOR', 75)
-
-def_op('LIST_TO_TUPLE', 82)
-def_op('RETURN_VALUE', 83)
-def_op('IMPORT_STAR', 84)
-def_op('SETUP_ANNOTATIONS', 85)
-def_op('YIELD_VALUE', 86)
-def_op('ASYNC_GEN_WRAP', 87)
-def_op('PREP_RERAISE_STAR', 88)
-def_op('POP_EXCEPT', 89)
+perm = list(range(90))
+perm2 = list(range(200-90))
+
+import sys
+if "generate_opcode_h" in sys.argv[0]:
+ random = __import__("random")
+ random.shuffle(perm)
+ random.shuffle(perm2)
+
+def_op('CACHE', perm[0])
+def_op('POP_TOP', perm[1])
+def_op('PUSH_NULL', perm[2])
+
+def_op('NOP', perm[9])
+def_op('UNARY_POSITIVE', perm[10])
+def_op('UNARY_NEGATIVE', perm[11])
+def_op('UNARY_NOT', perm[12])
+
+def_op('UNARY_INVERT', perm[15])
+
+def_op('BINARY_SUBSCR', perm[25], 4)
+
+def_op('GET_LEN', perm[30])
+def_op('MATCH_MAPPING', perm[31])
+def_op('MATCH_SEQUENCE', perm[32])
+def_op('MATCH_KEYS', perm[33])
+
+def_op('PUSH_EXC_INFO', perm[35])
+def_op('CHECK_EXC_MATCH', perm[36])
+def_op('CHECK_EG_MATCH', perm[37])
+
+def_op('WITH_EXCEPT_START', perm[49])
+def_op('GET_AITER', perm[50])
+def_op('GET_ANEXT', perm[51])
+def_op('BEFORE_ASYNC_WITH', perm[52])
+def_op('BEFORE_WITH', perm[53])
+def_op('END_ASYNC_FOR', perm[54])
+
+def_op('STORE_SUBSCR', perm[60], 1)
+def_op('DELETE_SUBSCR', perm[61])
+
+def_op('GET_ITER', perm[68])
+def_op('GET_YIELD_FROM_ITER', perm[69])
+def_op('PRINT_EXPR', perm[70])
+def_op('LOAD_BUILD_CLASS', perm[71])
+
+def_op('LOAD_ASSERTION_ERROR', perm[74])
+def_op('RETURN_GENERATOR', perm[75])
+
+def_op('LIST_TO_TUPLE', perm[82])
+def_op('RETURN_VALUE', perm[83])
+def_op('IMPORT_STAR', perm[84])
+def_op('SETUP_ANNOTATIONS', perm[85])
+def_op('YIELD_VALUE', perm[86])
+def_op('ASYNC_GEN_WRAP', perm[87])
+def_op('PREP_RERAISE_STAR', perm[88])
+def_op('POP_EXCEPT', perm[89])
HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
-name_op('STORE_NAME', 90) # Index in name list
-name_op('DELETE_NAME', 91) # ""
-def_op('UNPACK_SEQUENCE', 92, 1) # Number of tuple items
-jrel_op('FOR_ITER', 93)
-def_op('UNPACK_EX', 94)
-name_op('STORE_ATTR', 95, 4) # Index in name list
-name_op('DELETE_ATTR', 96) # ""
-name_op('STORE_GLOBAL', 97) # ""
-name_op('DELETE_GLOBAL', 98) # ""
-def_op('SWAP', 99)
-def_op('LOAD_CONST', 100) # Index in const list
-hasconst.append(100)
-name_op('LOAD_NAME', 101) # Index in name list
-def_op('BUILD_TUPLE', 102) # Number of tuple items
-def_op('BUILD_LIST', 103) # Number of list items
-def_op('BUILD_SET', 104) # Number of set items
-def_op('BUILD_MAP', 105) # Number of dict entries
-name_op('LOAD_ATTR', 106, 4) # Index in name list
-def_op('COMPARE_OP', 107, 2) # Comparison operator
-hascompare.append(107)
-name_op('IMPORT_NAME', 108) # Index in name list
-name_op('IMPORT_FROM', 109) # Index in name list
-jrel_op('JUMP_FORWARD', 110) # Number of words to skip
-jrel_op('JUMP_IF_FALSE_OR_POP', 111) # Number of words to skip
-jrel_op('JUMP_IF_TRUE_OR_POP', 112) # ""
-jrel_op('POP_JUMP_FORWARD_IF_FALSE', 114)
-jrel_op('POP_JUMP_FORWARD_IF_TRUE', 115)
-name_op('LOAD_GLOBAL', 116, 5) # Index in name list
-def_op('IS_OP', 117)
-def_op('CONTAINS_OP', 118)
-def_op('RERAISE', 119)
-def_op('COPY', 120)
-def_op('BINARY_OP', 122, 1)
-jrel_op('SEND', 123) # Number of bytes to skip
-def_op('LOAD_FAST', 124) # Local variable number
-haslocal.append(124)
-def_op('STORE_FAST', 125) # Local variable number
-haslocal.append(125)
-def_op('DELETE_FAST', 126) # Local variable number
-haslocal.append(126)
-jrel_op('POP_JUMP_FORWARD_IF_NOT_NONE', 128)
-jrel_op('POP_JUMP_FORWARD_IF_NONE', 129)
-def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3)
-def_op('GET_AWAITABLE', 131)
-def_op('MAKE_FUNCTION', 132) # Flags
-def_op('BUILD_SLICE', 133) # Number of items
-jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 134) # Number of words to skip (backwards)
-def_op('MAKE_CELL', 135)
-hasfree.append(135)
-def_op('LOAD_CLOSURE', 136)
-hasfree.append(136)
-def_op('LOAD_DEREF', 137)
-hasfree.append(137)
-def_op('STORE_DEREF', 138)
-hasfree.append(138)
-def_op('DELETE_DEREF', 139)
-hasfree.append(139)
-jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)
-
-def_op('CALL_FUNCTION_EX', 142) # Flags
-
-def_op('EXTENDED_ARG', 144)
-EXTENDED_ARG = 144
-def_op('LIST_APPEND', 145)
-def_op('SET_ADD', 146)
-def_op('MAP_ADD', 147)
-def_op('LOAD_CLASSDEREF', 148)
-hasfree.append(148)
-def_op('COPY_FREE_VARS', 149)
-
-def_op('RESUME', 151)
-def_op('MATCH_CLASS', 152)
-
-def_op('FORMAT_VALUE', 155)
-def_op('BUILD_CONST_KEY_MAP', 156)
-def_op('BUILD_STRING', 157)
-
-name_op('LOAD_METHOD', 160, 10)
-
-def_op('LIST_EXTEND', 162)
-def_op('SET_UPDATE', 163)
-def_op('DICT_MERGE', 164)
-def_op('DICT_UPDATE', 165)
-def_op('PRECALL', 166, 1)
-
-def_op('CALL', 171, 4)
-def_op('KW_NAMES', 172)
-hasconst.append(172)
-
-jrel_op('POP_JUMP_BACKWARD_IF_NOT_NONE', 173)
-jrel_op('POP_JUMP_BACKWARD_IF_NONE', 174)
-jrel_op('POP_JUMP_BACKWARD_IF_FALSE', 175)
-jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 176)
+name_op('STORE_NAME', 90 + perm2[90-90]) # Index in name list
+name_op('DELETE_NAME', 90 + perm2[91-90]) # ""
+def_op('UNPACK_SEQUENCE', 90 + perm2[92-90], 1) # Number of tuple items
+jrel_op('FOR_ITER', 90 + perm2[93-90])
+def_op('UNPACK_EX', 90 + perm2[94-90])
+name_op('STORE_ATTR', 90 + perm2[95-90], 4) # Index in name list
+name_op('DELETE_ATTR', 90 + perm2[96-90]) # ""
+name_op('STORE_GLOBAL', 90 + perm2[97-90]) # ""
+name_op('DELETE_GLOBAL', 90 + perm2[98-90]) # ""
+def_op('SWAP', 90 + perm2[99-90])
+def_op('LOAD_CONST', 90 + perm2[100-90]) # Index in const list
+hasconst.append(90 + perm2[100-90])
+name_op('LOAD_NAME', 90 + perm2[101-90]) # Index in name list
+def_op('BUILD_TUPLE', 90 + perm2[102-90]) # Number of tuple items
+def_op('BUILD_LIST', 90 + perm2[103-90]) # Number of list items
+def_op('BUILD_SET', 90 + perm2[104-90]) # Number of set items
+def_op('BUILD_MAP', 90 + perm2[105-90]) # Number of dict entries
+name_op('LOAD_ATTR', 90 + perm2[106-90], 4) # Index in name list
+def_op('COMPARE_OP', 90 + perm2[107-90], 2) # Comparison operator
+hascompare.append(90 + perm2[107-90])
+name_op('IMPORT_NAME', 90 + perm2[108-90]) # Index in name list
+name_op('IMPORT_FROM', 90 + perm2[109-90]) # Index in name list
+jrel_op('JUMP_FORWARD', 90 + perm2[110-90]) # Number of words to skip
+jrel_op('JUMP_IF_FALSE_OR_POP', 90 + perm2[111-90]) # Number of words to skip
+jrel_op('JUMP_IF_TRUE_OR_POP', 90 + perm2[112-90]) # ""
+jrel_op('POP_JUMP_FORWARD_IF_FALSE', 90 + perm2[114-90])
+jrel_op('POP_JUMP_FORWARD_IF_TRUE', 90 + perm2[115-90])
+name_op('LOAD_GLOBAL', 90 + perm2[116-90], 5) # Index in name list
+def_op('IS_OP', 90 + perm2[117-90])
+def_op('CONTAINS_OP', 90 + perm2[118-90])
+def_op('RERAISE', 90 + perm2[119-90])
+def_op('COPY', 90 + perm2[120-90])
+def_op('BINARY_OP', 90 + perm2[122-90], 1)
+jrel_op('SEND', 90 + perm2[123-90]) # Number of bytes to skip
+def_op('LOAD_FAST', 90 + perm2[124-90]) # Local variable number
+haslocal.append(90 + perm2[124-90])
+def_op('STORE_FAST', 90 + perm2[125-90]) # Local variable number
+haslocal.append(90 + perm2[125-90])
+def_op('DELETE_FAST', 90 + perm2[126-90]) # Local variable number
+haslocal.append(90 + perm2[126-90])
+jrel_op('POP_JUMP_FORWARD_IF_NOT_NONE', 90 + perm2[128-90])
+jrel_op('POP_JUMP_FORWARD_IF_NONE', 90 + perm2[129-90])
+def_op('RAISE_VARARGS', 90 + perm2[130-90]) # Number of raise arguments (1, 2, or 3)
+def_op('GET_AWAITABLE', 90 + perm2[131-90])
+def_op('MAKE_FUNCTION', 90 + perm2[132-90]) # Flags
+def_op('BUILD_SLICE', 90 + perm2[133-90]) # Number of items
+jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 90 + perm2[134-90]) # Number of words to skip (backwards)
+def_op('MAKE_CELL', 90 + perm2[135-90])
+hasfree.append(90 + perm2[135-90])
+def_op('LOAD_CLOSURE', 90 + perm2[136-90])
+hasfree.append(90 + perm2[136-90])
+def_op('LOAD_DEREF', 90 + perm2[137-90])
+hasfree.append(90 + perm2[137-90])
+def_op('STORE_DEREF', 90 + perm2[138-90])
+hasfree.append(90 + perm2[138-90])
+def_op('DELETE_DEREF', 90 + perm2[139-90])
+hasfree.append(90 + perm2[139-90])
+jrel_op('JUMP_BACKWARD', 90 + perm2[140-90]) # Number of words to skip (backwards)
+
+def_op('CALL_FUNCTION_EX', 90 + perm2[142-90]) # Flags
+
+def_op('EXTENDED_ARG', 90 + perm2[144-90])
+EXTENDED_ARG = 90 + perm2[144-90]
+def_op('LIST_APPEND', 90 + perm2[145-90])
+def_op('SET_ADD', 90 + perm2[146-90])
+def_op('MAP_ADD', 90 + perm2[147-90])
+def_op('LOAD_CLASSDEREF', 90 + perm2[148-90])
+hasfree.append(90 + perm2[148-90])
+def_op('COPY_FREE_VARS', 90 + perm2[149-90])
+
+def_op('RESUME', 90 + perm2[151-90])
+def_op('MATCH_CLASS', 90 + perm2[152-90])
+
+def_op('FORMAT_VALUE', 90 + perm2[155-90])
+def_op('BUILD_CONST_KEY_MAP', 90 + perm2[156-90])
+def_op('BUILD_STRING', 90 + perm2[157-90])
+
+name_op('LOAD_METHOD', 90 + perm2[160-90], 10)
+
+def_op('LIST_EXTEND', 90 + perm2[162-90])
+def_op('SET_UPDATE', 90 + perm2[163-90])
+def_op('DICT_MERGE', 90 + perm2[164-90])
+def_op('DICT_UPDATE', 90 + perm2[165-90])
+def_op('PRECALL', 90 + perm2[166-90], 1)
+
+def_op('CALL', 90 + perm2[171-90], 4)
+def_op('KW_NAMES', 90 + perm2[172-90])
+hasconst.append(90 + perm2[172-90])
+
+jrel_op('POP_JUMP_BACKWARD_IF_NOT_NONE', 90 + perm2[173-90])
+jrel_op('POP_JUMP_BACKWARD_IF_NONE', 90 + perm2[174-90])
+jrel_op('POP_JUMP_BACKWARD_IF_FALSE', 90 + perm2[175-90])
+jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 90 + perm2[176-90])
+
+
+with open("/tmp/opcode_map", "w") as f:
+ for x, y in enumerate(opname):
+ f.write("%s %s\n" % (x, y))
del def_op, name_op, jrel_op, jabs_op
diff --git a/Python/ceval.c b/Python/ceval.c
index 1d2c6432d0..e275158d9d 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -1344,6 +1344,8 @@ eval_frame_handle_pending(PyThreadState *tstate)
#define OR_DTRACE_LINE
#endif
+#define USE_COMPUTED_GOTOS 0
+
#ifdef HAVE_COMPUTED_GOTOS
#ifndef USE_COMPUTED_GOTOS
#define USE_COMPUTED_GOTOS 1
@@ -1705,6 +1707,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
#if USE_COMPUTED_GOTOS
/* Import the static jump table */
#include "opcode_targets.h"
+ asdasdasd, break the compilation...
#endif
#ifdef Py_STATS
pg0NCgAAAABw4G9igwwAAOMAAAAAAAAAAAAAAAACAAAAAAAAAHNIAAAAYQBgAGABqwCWAGAAYAGr
AZYBYABgAasClgJgAmMAlgNgA2MAlgRgBGMAlgVgBWMAlgZgBmMAlgdgB2MAlghgCGMAlglgATEA
KQnpAAAAAE5jAQAAAAAAAAAAAAAABAAAACMAAABzdgAAAAMAAQBhADIAuQAgACAAIAAgACAAvAEg
ACAAIAAgAGIAvwEgAI4BIAAgACAAIAABABsAMgC5ACAAIAAgACAAIAC8AiAAIAAgACAAYAJgA78C
IACOAiAAIAAgACAAYATGBSAAYAXGACAAYAbGBiAARQBhAQEAeyIpB05UcgEAAADp/wAAAOkNAAAA
6REAAADpAAEAACkD2gZyYW5kb23aBHNlZWRaB3JhbmRpbnSpAXIHAAAAcwEAAAAg+kUvdXNyL2xv
Y2FsL2dvb2dsZS9ob21lL3h4eHh4eHh4eC95eXl5L3p6enp6enp6enp6enp6enoveHh4eHh4eHh4
eHgucHnaAmtzcgoAAAAFAAAAc0kAAADoAOgAgADYAg2FJoQriGTRAhPUAhPQAhPYCAzYCxmNNow+
mCGYU9ELIdQLIaBC0QsmqBLRCyuoc9EKMtAEMtAEMtAEMvADAAkN8wAAAABjAgAAAAAAAAAAAAAA
BQAAAAMAAABzoAAAAGEAdACNAjIAuQAgACAAIAAgACAAMgC5AiAAIAAgACAAIABiAb8BIACOASAA
IAAgACAAYgC/AiAAjgIgACAAIAAgAB8Ath2PAiAAjQONBGICkgIgACAAIAAgACAAIAAgACAAIAAg
AGIDYgTGDCAAvwEgAI4BIAAgACAAIAABAHseMgC5BiAAIAAgACAAIABiAr8BIACOASAAIAAgACAA
MQApAU4pBNoDemlwcgoAAADaBmFwcGVuZNoFYnl0ZXMpBdoBc3IHAAAA2gFy2gF42gF5cwUAAAAg
ICAgIHIJAAAA2gNjcnlyEwAAAAsAAABzUAAAAIAA2AYIgCHYDhGNY5AilSKQVJEolCiYQdEOHtQO
HvAAAQMU8AABAxSBZIBhiBHYBAWHT4JPiFGQEYlVgU+ET4BPgE/gCQ6NFYhxiRiMGIAvcgsAAABj
AQAAAAAAAAAAAAAAAwAAAAMAAABzcAAAAGEAMgC5ACAAIAAgACAAIABiAL8BIACOASAAIAAgACAA
AQAyALkAIAAgACAAIAAgAGABvwEgAI4BIAAgACAAIAABADIAuQIgACAAIAAgACAAvAIgACAAIAAg
AGACvwEgAI4BIAAgACAAIAABAGAAMQApA056E1RoYW5rcyBmb3IgcGxheWluZyFyAQAAACkD2gVw
cmludNoDc3lz2gRleGl0KQFyDwAAAHMBAAAAIHIJAAAA2gRmYWlschcAAAASAAAAczMAAACAANgC
B4UliAGBKIQogCjYAgeFJdAIHdECHtQCHtACHtgCCoUjhCiIMYErhCuAK4ArgCtyCwAAAGMAAAAA
AAAAAAAAAAAHAAAAAwAAAHP8AgAAYQBgAWMAjQBgAo0BYAONAmAEjwIgAI0DjQQyALkAIAAgACAA
IAAgAGAFYAZ0Ar8BIACOASAAIAAgACAAjQVgB40GGwAyALkCIAAgACAAIAAgAGAJYgK/AiAAjgIg
ACAAIAAgAAEAMgC5BCAAIAAgACAAIABgCr8BIACOASAAIAAgACAAHwC2aY0HYAeNCDIAuQQgACAA
IAAgACAAYAq/ASAAjgEgACAAIAAgAB8AtkSNCTIAYgBiAWIHYgm/AyAAjgMgACAAIAAgALoGYghg
C8YNIACNCHsVYgliB4ICYgNiBIICaAIgACAAugZiCGAMxg0gAI0IeyViCWIHggJgDWgCIAAgALoG
YghgDsYNIACNCHszYgliB4ICYgV1ALoGYghgD8YNIACNCHs/YghgEMYNIACNCHtFMgC5AiAAIAAg
ACAAIABiCL8BIACOASAAIAAgACAAAQB7ajIAuQYgACAAIAAgACAAvwAgAI4AIAAgACAAIACSBCAA
IAAgACAAIAAgACAAIAAgACAAvwAgAI4AIAAgACAAIACNCmIKHwC2q40LYgZiC8YNIACNBmILYBF1
AboQMgC5CiAAIAAgACAAIABgEr8BIACOASAAIAAgACAAAQBiAmATaAIgACAAuhAyALkKIAAgACAA
IAAgAGAUvwEgAI4BIAAgACAAIAABAGAVYBZgF2AYYBmxBGILUgAgACAAIAAgAI8CIACNDI0NYgNi
DMYNIACNA2IEYg3GDSAAjQQyAGIAYgFiBGIDvwMgAI4DIAAgACAAIAC6EDIAuQogACAAIAAgACAA
YBq/ASAAjgEgACAAIAAgAAEAYgJgG8YXIACNAmIDYgSCAmIFdQC6HGIFkgYgACAAIAAgACAAIAAg
ACAAIAAgAGIDYgSCAr8BIACOASAAIAAgACAAAQBiAmAcxg0gAI0CYgNiBIICYA1oAiAAIAC6FDIA
uQIgACAAIAAgACAAYB2/ASAAjgEgACAAIAAgAAEAYgaVAgEAMQB7rHIBe1wpHk5jAwAAAAAAAAAA
AAAAAwAAABMAAABzHgAAAGEAYgBiAWABxgUgAGICxgAgAMYJIABgAsYBIAAxACkDTukKAAAA6QEA
AACpACkD2gFt2gFp2gFqcwMAAAAgICByCQAAANoBd3oQZ2FtZTEuPGxvY2Fscz4udxgAAABzGQAA
AIAA2AwNkCGQQpEkmBGRKIlPmHHRCyDQBCByCwAAAGwHAAAA/1ewaHVH8lsBdj9A/wPpCAAAACkC
chkAAAByGQAAACkC6QUAAADpBAAAACkC6QMAAAByIgAAANoAVHoFRnVlbDpyGAAAAHUEAAAA8J+n
sXUEAAAA8J+akykCch8AAAByHwAAAHUEAAAA8J+PgXUGAAAA4pu977iPegIgIFoEd2FzZHoFTm9w
ZSFyAQAAAHoIRW1wdHkuLi4pAnIBAAAA6f////8pAnIBAAAAchkAAAApAnIkAAAAcgEAAAApAnIZ
AAAAcgEAAAApBHIeAAAAcg8AAADaAWHaAWR6BkNyYXNoIXIZAAAA6Q8AAAD6BU5pY2UhKQfaA3Nl
dHIUAAAA2gVyYW5nZdoFaW5wdXTaBXN0cmlwchcAAADaBnJlbW92ZSkOch4AAAByGwAAAFoEZnVl
bHIRAAAAchIAAABaBXN0b3Bz2gNsb2dyHAAAAHIPAAAAch0AAADaA2lucNoBY1oCZHhaAmR5cw4A
AAAgICAgICAgICAgICAgIHIJAAAA2gVnYW1lMXIxAAAAFwAAAHMwAgAAgADwAgEDIfAAAQMh8AAB
AyHwBgAHJoAh2AkKgCTYCQ2BJIAhgFHYCg2NI4h2kHbQDh7RCh/UCh+AJdgICoAj2AgM2AQJhUWI
J5A00QQY1AQY0AQY2A0SjVWQMolZjFnwAA0FD/AADQUPiAHYCgyAYdgPFI11kFKJeYx58AAKBxTw
AAoHFIgh2AsMiDGIUZABkDGJOow68AAJCRTYCguIdokriCGIIdgOD5ARiFaYAZgxkHbSDR3wAAcJ
FNgKC4h2iSuIIYgh2A4PkBGIVpB20g0d8AAFCRTYCguIdokriCGIIdgOD5ARiFaQdYhf8AADCRTY
CguIeIktiCGIIeAKC4h0iSmIIYgh2AYLhWWIQYFohGiAaIBo2AoPjSWJJ4wnjy+KL4kvjC+AQ9gN
EPAAEQUT8AARBROIAdgGCYhRgWiAY9gJCpAmiB/wAAEHFtgIDI0EiFeJDYwNiA3YCQ2QEYoZ8AAB
BxnYCAyNBIha0QgY1AgY0AgY2BUcoDawB7hm0A9F0A9FwGHUD0iBZoBiiCLYBgeIMoFngGHYBgeI
MoFngGHYCQqIEYgxiGGQEYkajBrwAAEHF9gIDI0EiFiJDowOiA7YBgqIYYFpgGTYCguIUYgWkDWI
H/AAAgcT2AgN1wgc0ggckGGYEZBW0Qgc1Agc0Agc2AgMkAKJCogE2AoLiFGIFpA20gkZ8AACBxPY
CA2NBYhniQ6MDogO2A8SiAqICogK8AUCBxPxQQEACQ1yCwAAAGMAAAAAAAAAAAAAAAAFAAAAAwAA
AHO6AQAAYQAyALkAIAAgACAAIAAgAGABvwEgAI4BIAAgACAAIAABAHQAYAJdAY0AYAONAWIAHwC2
wY0CMgC5ACAAIAAgACAAIABgBGICxgYgAL8BIACOASAAIAAgACAAAQBiAo8DIACNA40EjQViA2AF
aAIgACAAugZiBGIFxgAgAI0GXkBiA2AGaAIgACAAugZiBGIFxgogAI0GXjRiA2AHaAIgACAAugZi
BGIFxgUgAI0GXihiA2AIaAIgACAAugZiBGIFxgIgAI0GXhxiA2AJaAIgACAAugZiBGIFxgYgAI0G
XhAyALkCIAAgACAAIAAgAGAKvwEgAI4BIAAgACAAIAABADIAuQQgACAAIAAgACAAMgC5BiAAIAAg
ACAAIAC/ACAAjgAgACAAIAAgAL8BIACOASAAIAAgACAAjQdiB2IGaAIgACAAuicyALkAIAAgACAA
IAAgAGALvwEgAI4BIAAgACAAIAABAGIBMgC5CCAAIAAgACAAIABiB78BIACOASAAIAAgACAAYAPG
ACAAxg0gAI0Be7EyALkCIAAgACAAIAAgAGAMvwEgAI4BIAAgACAAIAABAHvCYgExACkNTnoPTWF0
aCBxdWl6IHRpbWUhKQUpA9oDc3Vt6QwAAAByIAAAACkD2gpkaWZmZXJlbmNl6S0AAADpDgAAACkD
2gdwcm9kdWN0ch8AAADpCQAAACkD2gVyYXRpb+kSAAAA6QYAAAApA/oXcmVtYWluZGVyIGZyb20g
ZGl2aXNpb27pFwAAAOkHAAAA2gFfehxXaGF0IGlzIHRoZSAlcyBvZiAlZCBhbmQgJWQ/cjIAAABy
NAAAAHI3AAAAcjkAAAByPAAAAHoFV2hhdD96CENvcnJlY3QhegZXcm9uZyEpBXIUAAAAchcAAADa
A2ludHIrAAAA2gNzdHIpCFoCcXNyLgAAANoBcXIRAAAAciUAAADaAWJyEAAAAHIvAAAAcwgAAAAg
ICAgICAgIHIJAAAA2gVnYW1lMnJEAAAARAAAAHM0AQAAgADYAgeFJdAIGdECGtQCGtACGvACBggG
8AAGCAbwAAYIBoAi8A4ACQyAI9gLDfAADwMV8AAPAxWAYdgECYVF0AooqDHRCizRBC3UBC3QBC3Y
Dg+BR4BBgHGIIdgHCIhFgnrwAAYFFJBxmDGRdZAxkDHYCQqIbNIJGvAABQUUoAGgQaEFmEGYQdgJ
Cohpih7wAAQFFJhRoBGZVZgRmBHYCQqIZ4oc8AADBRSYMaABmTaQcZBx2AkK0A4n0gkn8AACBRSo
UbARqVWoEagR4AYKhWSIN4FthG2AbdgKDY0jiGWNZYlnjGeJLIwsgEPYBwqIYYJ48AAEBRXYBguF
ZYhK0QYX1AYX0AYX2AYJiFONU5ATiViMWJgDiV7RBhuAY4Bj4AYKhWSIOIFuhG6AboBu2AkMgCpy
CwAAAGMAAAAAAAAAAAAAAAAIAAAAAwAAAHOuAgAAYQAyALkAIAAgACAAIAAgAGABvwEgAI4BIAAg
ACAAIAABADIAuQIgACAAIAAgACAAvAEgACAAIAAgAL8AIACOACAAIAAgACAAjQBgAo0BYgGSAiAA
IAAgACAAIAAgACAAIAAgACAAvwAgAI4AIAAgACAAIACNAmADjQNgBI0EYgMyALkGIAAgACAAIAAg
AGICvwEgAI4BIAAgACAAIABoAyAAIAC68jIAuQAgACAAIAAgACAAYAVgBjIAuQIgACAAIAAgACAA
vAEgACAAIAAgAL8AIACOACAAIAAgACAAYgDGCiAAxgogAMYGIAC/ASAAjgEgACAAIAAgAAEAMgC5
ACAAIAAgACAAIABgB2AIkgQgACAAIAAgACAAIAAgACAAIAAgAGICYABiA4gCUgAgACAAIAAgAL8B
IACOASAAIAAgACAAiQFgCWICYgNSACAAIAAgACAAiQGKBL8BIACOASAAIAAgACAAAQAyALkKIAAg
ACAAIAAgAL8AIACOACAAIAAgACAAjQUyALkCIAAgACAAIAAgALwBIAAgACAAIAC/ACAAjgAgACAA
IAAgAGIAYAbGACAAaAQgACAAuhAyALkMIAAgACAAIAAgAGAKvwEgAI4BIAAgACAAIAABAGIFYgJi
A1IAIAAgACAAIABoAiAAIAC6JmIEYgJiA1IAIAAgACAAIACSByAAIAAgACAAIAAgACAAIAAgACAA
vwAgAI4AIAAgACAAIABgBMYAIADGDSAAjQRiA2ADxg0gAI0DXhAyALkMIAAgACAAIAAgAGALvwEg
AI4BIAAgACAAIAABAGIDMgC5BiAAIAAgACAAIABiAr8BIACOASAAIAAgACAAaAMgACAAr/IyALkA
IAAgACAAIAAgAGAMvwEgAI4BIAAgACAAIAABAGIEMQApDU56ElNwZWVkIHR5cGluZyBnYW1lLnrf
CiAgVGV4dDogQmVjYXVzZSBvZiBpdHMgcGVyZm9ybWFuY2UgYWR2YW50YWdlLCB0b2RheSBtYW55
IGxhbmd1YWdlIGltcGxlbWVudGF0aW9ucwogIGV4ZWN1dGUgYSBwcm9ncmFtIGluIHR3byBwaGFz
ZXMsIGZpcnN0IGNvbXBpbGluZyB0aGUgc291cmNlIGNvZGUgaW50byBieXRlY29kZSwKICBhbmQg
dGhlbiBwYXNzaW5nIHRoZSBieXRlY29kZSB0byB0aGUgdmlydHVhbCBtYWNoaW5lLgogIHIZAAAA
cj8AAAB6EyUwLjJmIHNlY29uZHMgbGVmdC7pFAAAAHoFG1szMm36ASB6BhtbMzltIHoJVG9vIHNs
b3chehNZb3UgbWFkZSBhIG1pc3Rha2UhcigAAAApCHIUAAAA2gR0aW1l2gVzcGxpdNoDbGVu2gRq
b2lucisAAAByFwAAANoFdXBwZXIpBtoBdNoEdGV4dFoFd29yZHPaAml0ci4AAAByLwAAAHMGAAAA
ICAgICAgcgkAAADaBWdhbWUzck8AAABgAAAAc2ABAACAANgCB4Ul0Agc0QId1AId0AId2AYPhWSE
aYFrhGuAIfACBAoGgCTwCgALD48siiyJLIwsgCXYBwiAItgIC4Aj2AgKiGONY5AliWqMatIIGPAA
CgMi2AQJhUXQCh+gMqgZrRSsGakbrBuwcakf0SM50Qo60QQ71AQ70AQ72AQJhUWARaBT1yU50iU5
qGWwQ7BSsEOsatElOdQlOdAlOdAlObg1wBK8Obg50ApF0QRG1ARG0ARG2AoPjSWJJ4wngEPYBxCF
dIR5gXuEe5BRmBKRVtIHG/AAAQUY2AYKhWSIO9EGF9QGF9AGF9gHCohlkEKMadIHF/AABAUi2AYJ
iFWQMoxZ1w0e0g0e0Q0e1A0eoBPRDSTRBiSAY9gGCIhBgWeAYoBi4AYKhWTQCyDRBiHUBiHQBiHw
FQAJC4hjjWOQJYlqjGrSCBjwAAoDIvAWAAMIhSWIB4EuhC6ALtgJDIAqcgsAAABjAAAAAAAAAAAA
AAAABwAAAAMAAABzpAEAAGEAMgC5ACAAIAAgACAAIABgAb8BIACOASAAIAAgACAAAQBgAo0AYgAy
ALkCIAAgACAAIAAgAL8AIACOACAAIAAgACAAYAPGACAAxg0gAI0AMgC5ACAAIAAgACAAIABiAL8B
IACOASAAIAAgACAAAQBiADIAuQQgACAAIAAgACAAvwAgAI4AIAAgACAAIABgA8YAIADGDSAAjQAy
ALkAIAAgACAAIAAgAGIAvwEgAI4BIAAgACAAIAABAGIAMgC5BiAAIAAgACAAIAC/ACAAjgAgACAA
IAAgAMYNIACNADIAuQAgACAAIAAgACAAYgC/ASAAjgEgACAAIAAgAAEAMgC5ACAAIAAgACAAIAC/
ACAAjgAgACAAIAAgAAEAMgC5ACAAIAAgACAAIABgBL8BIACOASAAIAAgACAAAQAyALkAIAAgACAA
IAAgAGAFMgC5CCAAIAAgACAAIABgBmIAvwIgAI4CIAAgACAAIACSBSAAIAAgACAAIAAgACAAIAAg
ACAAvwAgAI4AIAAgACAAIAC/AiAAjgIgACAAIAAgAAEAYAAxACkHTnohUGFzcyAzIHRlc3RzIHRv
IHByb3ZlIHlvdXIgd29ydGghegVzZWVkOvoBOnpHWW91IGNhbiBkcml2ZSB0byB3b3JrLCBrbm93
IHNvbWUgbWF0aHMgYW5kIGNhbiB0eXBlIGZhc3QuIFlvdSdyZSBoaXJlZCF6E1lvdXIgc2lnbi1v
biBib251czpzMgAAAKA/bqV/KR82SnZolcwhHpWZNmER9k9WiMGf3rUwna4U3hhZSEnY1ZCKGDFs
sBZeTztdKQZyFAAAAHIxAAAAckQAAAByTwAAAHITAAAA2gZkZWNvZGVyCAAAAHMBAAAAIHIJAAAA
2gRtYWluclIAAAB7AAAAc+0AAACAANgCB4Ul0Agr0QIs1AIs0AIs2AkQgCTgAgaIJY0liSeMJ5BD
iS3RAheAJNgCB4UliASBK4QrgCvgAgaIJY0liSeMJ5BDiS3RAheAJNgCB4UliASBK4QrgCvgAgaI
JY0liSeMJ4EvgCTYAgeFJYgEgSuEK4Ar2AIHhSWBJ4QngCfYAgeFJdAIUdECUtQCUtACUtgCB4Ul
0AgdmHOdc/AAACRtAvAAAG8CcwLxAAAgdAL0AAAgdAL3AAAgfQLyAAAgfQLxAAAgfQL0AAAgfQLx
AAADfgL0AAADfgLwAAADfgLwAAADfgLwAAADfgJyCwAAACkKchUAAAByBgAAAHJHAAAAcgoAAABy
EwAAAHIXAAAAcjEAAAByRAAAAHJPAAAAclIAAAByGgAAAHILAAAAcgkAAAD6CDxtb2R1bGU+clMA
AAABAAAAc5QAAAD4gAqACoAKgArYAA2ADYANgA3YAAuAC4ALgAvwBAMBM/AAAwEz8AADATPwDAUB
EvAABQES8AAFARLwDgMBDvAAAwEO8AADAQ7wCisBE/AAKwET8AArARPwWgEaAQ3wABoBDfAAGgEN
8DgXAQ3wABcBDfAAFwEN8DYOAX4C8AAOAX4C8AAOAX4C8AAOAX4C8AAOAX4CcgsAAAAK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment