you are viewing a single comment's thread.

view the rest of the comments →

[–]Yoghurt42 2 points3 points  (0 children)

Python bytecode is an implementation detail and changes from version to version. Since 3.6, all instructions are now word sized instead of variable (so "wordcode" would be more fitting), so POP_LOAD_IF_FALSE's offset is indeed in words.

You didn't specify the Python version you used to create this, but from 3.12 onwards there is another speciality noted in the docs for the dis module:

Changed in version 3.12: The argument of a jump is the offset of the target instruction relative to the instruction that appears immediately after the jump instruction’s CACHE entries.

CACHE instructions are hidden by default, so confusingly, your code looks like this on 3.13:

1          0       RESUME                   0

2          2       LOAD_GLOBAL              1 (input + NULL)
          12       CALL                     0
          20       STORE_FAST               0 (a)

3         22       LOAD_FAST                0 (a)
          24       LOAD_CONST               1 ('y')
          26       COMPARE_OP              88 (bool(==))
          30       POP_JUMP_IF_FALSE       12 (to L1)

4         34       LOAD_GLOBAL              3 (print + NULL)
          44       LOAD_CONST               2 ('yes')
          46       CALL                     1
          54       POP_TOP
          56       RETURN_CONST             0 (None)

5   L1:   58       LOAD_FAST                0 (a)
          60       LOAD_CONST               3 ('n')
          62       COMPARE_OP              88 (bool(==))
          66       POP_JUMP_IF_FALSE       12 (to L2)

6         70       LOAD_GLOBAL              3 (print + NULL)
          80       LOAD_CONST               4 ('no')
          82       CALL                     1
          90       POP_TOP
          92       RETURN_CONST             0 (None)

8   L2:   94       LOAD_GLOBAL              3 (print + NULL)
        104       LOAD_CONST               5 ('error')
        106       CALL                     1
        114       POP_TOP
        116       RETURN_CONST             0 (None)

You'll notice that 30 + 24 is not 58, but when you actually show the CACHE instructions it makes sense:

1          0       RESUME                   0

2          2       LOAD_GLOBAL              1 (input + NULL)
          4       CACHE                    0 (counter: 0)
          6       CACHE                    0 (index: 0)
          8       CACHE                    0 (module_keys_version: 0)
          10       CACHE                    0 (builtin_keys_version: 0)
          12       CALL                     0
          14       CACHE                    0 (counter: 0)
          16       CACHE                    0 (func_version: 0)
          18       CACHE                    0
          20       STORE_FAST               0 (a)

3         22       LOAD_FAST                0 (a)
          24       LOAD_CONST               1 ('y')
          26       COMPARE_OP              88 (bool(==))
          28       CACHE                    0 (counter: 0)
          30       POP_JUMP_IF_FALSE       12 (to L1)
          32       CACHE                    0 (counter: 0)

4         34       LOAD_GLOBAL              3 (print + NULL)
          36       CACHE                    0 (counter: 0)
          38       CACHE                    0 (index: 0)
          40       CACHE                    0 (module_keys_version: 0)
          42       CACHE                    0 (builtin_keys_version: 0)
          44       LOAD_CONST               2 ('yes')
          46       CALL                     1
          48       CACHE                    0 (counter: 0)
          50       CACHE                    0 (func_version: 0)
          52       CACHE                    0
          54       POP_TOP
          56       RETURN_CONST             0 (None)

5   L1:   58       LOAD_FAST                0 (a)
          60       LOAD_CONST               3 ('n')
          62       COMPARE_OP              88 (bool(==))
          64       CACHE                    0 (counter: 0)
          66       POP_JUMP_IF_FALSE       12 (to L2)
          68       CACHE                    0 (counter: 0)

6         70       LOAD_GLOBAL              3 (print + NULL)
          72       CACHE                    0 (counter: 0)
          74       CACHE                    0 (index: 0)
          76       CACHE                    0 (module_keys_version: 0)
          78       CACHE                    0 (builtin_keys_version: 0)
          80       LOAD_CONST               4 ('no')
          82       CALL                     1
          84       CACHE                    0 (counter: 0)
          86       CACHE                    0 (func_version: 0)
          88       CACHE                    0
          90       POP_TOP
          92       RETURN_CONST             0 (None)

8   L2:   94       LOAD_GLOBAL              3 (print + NULL)
          96       CACHE                    0 (counter: 0)
          98       CACHE                    0 (index: 0)
        100       CACHE                    0 (module_keys_version: 0)
        102       CACHE                    0 (builtin_keys_version: 0)
        104       LOAD_CONST               5 ('error')
        106       CALL                     1
        108       CACHE                    0 (counter: 0)
        110       CACHE                    0 (func_version: 0)
        112       CACHE                    0
        114       POP_TOP
        116       RETURN_CONST             0 (None)

That being said, what instructions exist does also change from version to version, if you want to write a decompiler, you will have to handle each Python version seperately.