Skip to main content

Python cross-version byte-code disassembler and marshal routines

Project description

PyPI Installs Latest Version Supported Python Versions

Packaging Status

xdis

A Cross-Python bytecode disassembler, bytecode/wordcode, and magic-number manipulation library/package.

I gave a talk that mentions using this at BlackHat Asia 2024.

Introduction

The Python dis module allows you to disassemble bytecode from the same version of Python that you are running on. But what about bytecode from different versions?

That’s what this package is for. It can “marshal load” Python bytecodes from different versions of Python. The command-line routine pydisasm will show disassembly output using the most modern Python disassembly conventions in a variety of user-specified formats. Some of these formats, like extended and extended-format are the most advanced of any Python disassembler I know of because they can show an expression tree for operators. See the Disassembler Example below.

Also, if you need to modify and write bytecode, the routines here can be of help. There are routines to pack and unpack the read-only tuples in Python’s Code type. For interoperability between the changes over the years to Python CodeType, provide our own versions of the Code Type to allow interoperability, and we provide routines to reduce the tedium in writing a bytecode file.

This package also has an extensive knowledge of Python bytecode magic numbers, including PyPy and others, and how to translate from sys.sys_info major, minor, and release numbers to the corresponding magic value.

So if you want to write a cross-version assembler, bytecode-level analyzer or optimizer, this package may also be useful. In addition to the kinds of instruction categorization that dis offers, we have additional categories for things that would be useful in such a bytecode assembler, optimizer, or decompiler.

The programs here accept bytecodes from Python version 1.0 to 3.13. The code requires Python 2.4 or later and has been tested on Python running on many versions.

When installing, except for the most recent versions of Python, use the Python egg or wheel that matches that version, e.g., xdis-6.0.2-py3.3.egg, xdis-6.0.2-py33-none-any.whl. Of course, for versions that predate wheels, like Python 2.6, you will have to use eggs.

Installation

If you are using Python 3.11 or later, you can install from PyPI using the name xdis:

pip install xdis

For Python releases before 3.11, do not install using PyPI, but instead install using a file in the [GitHub Releases section](https://github.com/rocky/python-xdis/releases). Older Python used to use easy_install. But this is no longer supported in PyPi or newer Python versions. And vice versa, poetry nor pip, (the newer ways) are not supported on older Pythons.

If the Python version you are running xdis is between Python 2.4 through 2.7, use a tarball called xdis_24-x.y.z.tar.gz.

If the Python version you are running xdis is between Python 3.0 through 3.2, use a tarball called xdis_30-x.y.z.tar.gz.

If the Python version you are running xdis is between Python 3.3 through 3.5, use a tarball called xdis_33-x.y.z.tar.gz.

If the Python version you are running xdis is between Python 3.6 through 3.11, use a tarball called xdis_36-x.y.z.tar.gz.

If the Python version you are running xdis is 3.11 or later, use a file called xdis-x.y.z.tar.gz.

You can also try eggs or wheels that have the same version designation, e.g., xdis-x.y.z-py39-none-any.whl for a Python 3.9 installation. However, note that *the version without the designation means Python 3.11 or greater.

Similarly, a tarball with or without the underscore xx, e.g., xdis_36-x.y.z.tar.gz. works only from Python 3.11 or greater.

Rationale for using Git Branches

It is currently impossible (if not impractical) to have one Python source code of this complexity and with this many features that can run both Python 2.7 and Python 3.13+. The languages have drifted so much, and packaging is vastly different. In fact, the packaging practice for Python 3.11+ is incompatible with Python 2.7 (and before back to Python 2.4), which favored “easy_install”.

Installation from source text

To install from source code, make sure you have the right Git branch. See the Requirements section for the Git branch names.

After setting the right branch:

$ pip install -e .  # or pip install -e .[dev] to include testing package

A GNU makefile is also provided, so make install (possibly as root or sudo) will do the steps above.

Disassembler Example

The cross-version disassembler that is packaged here can produce assembly listings that are superior to those typically found in Python’s dis module. Here is an example:

pydisasm -S -F extended bytecode_3.8/pydisasm-example.pyc
# pydisasm version 6.1.1.dev0
# Python bytecode 3.8.0 (3413)
# Disassembled from Python 3.11.8 (main, Feb 14 2024, 04:47:01) [GCC 13.2.0]
# Timestamp in code: 1693155156 (2023-08-27 12:52:36)
# Source code size mod 2**32: 320 bytes
# Method Name:       <module>
# Filename:          simple_source/pydisasm-example.py
# Argument count:    0
# Position-only argument count: 0
# Keyword-only arguments: 0
# Number of locals:  0
# Stack size:        3
# Flags:             0x00000040 (NOFREE)
# First Line:        4
# Constants:
#    0: 0
#    1: None
#    2: ('version_info',)
#    3: 1
#    4: (2, 4)
#    5: 'Is small power of two'
# Names:
#    0: sys
#    1: version_info
#    2: print
#    3: version
#    4: len
#    5: major
#    6: power_of_two
             # import sys
  4:           0 LOAD_CONST           (0) ; TOS = 0
               2 LOAD_CONST           (None) ; TOS = None
               4 IMPORT_NAME          (sys) ; TOS = import_module(sys)
               6 STORE_NAME           (sys) ; sys = import_module(sys)

             # from sys import version_info
  5:           8 LOAD_CONST           (0) ; TOS = 0
              10 LOAD_CONST           (('version_info',)) ; TOS = ('version_info',)
              12 IMPORT_NAME          (sys) ; TOS = import_module(sys)
              14 IMPORT_FROM          (version_info) ; TOS = from sys import version_info
              16 STORE_NAME           (version_info) ; version_info = from sys import version_info
              18 POP_TOP

             # print(sys.version)
  7:          20 LOAD_NAME            (print) ; TOS = print
              22 LOAD_NAME            (sys) ; TOS = sys
              24 LOAD_ATTR            (version) ; TOS = sys.version
              26 CALL_FUNCTION        (1 positional argument) ; TOS = print(sys.version)
              28 POP_TOP

             # print(len(version_info))
  8:          30 LOAD_NAME            (print) ; TOS = print
              32 LOAD_NAME            (len) ; TOS = len
              34 LOAD_NAME            (version_info) ; TOS = version_info
              36 CALL_FUNCTION        (1 positional argument) ; TOS = len(version_info)
              38 CALL_FUNCTION        (1 positional argument) ; TOS = print(len(version_info))
              40 POP_TOP

             # major = sys.version_info[0]
  9:          42 LOAD_NAME            (sys) ; TOS = sys
              44 LOAD_ATTR            (version_info) ; TOS = sys.version_info
              46 LOAD_CONST           (0) ; TOS = 0
              48 BINARY_SUBSCR        TOS = sys.version_info[0]
              50 STORE_NAME           (major) ; major = sys.version_info[0]

             # power_of_two = major & (major - 1)
 10:          52 LOAD_NAME            (major) ; TOS = major
              54 LOAD_NAME            (major) ; TOS = major
              56 LOAD_CONST           (1) ; TOS = 1
              58 BINARY_SUBTRACT      TOS = major - (1)
              60 BINARY_AND           TOS = major & (major - (1))
              62 STORE_NAME           (power_of_two) ; power_of_two = major & (major - (1))

             # if power_of_two in (2, 4):
 11:          64 LOAD_NAME            (power_of_two) ; TOS = power_of_two
              66 LOAD_CONST           ((2, 4)) ; TOS = (2, 4)
              68 COMPARE_OP           (in) ; TOS = power_of_two in ((2, 4))
              70 POP_JUMP_IF_FALSE    (to 80)

             # print("Is small power of two")
 12:          72 LOAD_NAME            (print) ; TOS = print
              74 LOAD_CONST           ("Is small power of two") ; TOS = "Is small power of two"
              76 CALL_FUNCTION        (1 positional argument) ; TOS = print("Is small power of two")
              78 POP_TOP
         >>   80 LOAD_CONST           (None) ; TOS = None
              82 RETURN_VALUE         return None

Note that some operand interpretation is performed on items in the stack as shown above. For example, in:

24 LOAD_ATTR            (version) | sys.version

from the instruction, see that sys.version is the resolved attribute that is loaded.

Similarly, in:

68 COMPARE_OP           (in) | power_of_two in (2, 4)

we see that we can resolve the two arguments of the in operation. Finally, in some’ CALL_FUNCTIONS’, we can determine the name of the function and the arguments passed to it.

Testing

$ make check

A GNU makefile has been added to smooth over setting up running the right command, and running tests from fastest to slowest.

If you have remake installed, you can see the list of all tasks including tests via remake --tasks.

Usage

Run

$ ./bin/pydisasm -h

for usage help.

As a drop-in replacement for dis

xdis also provides some support as a drop-in replacement for the Python library dis module. This may be desirable when you want to use the improved API from Python 3.4 or later from an earlier Python version.

For example:

>>> # works in Python 2 and 3
>>> import xdis.std as dis
>>> [x.opname for x in dis.Bytecode('a = 10')]
['LOAD_CONST', 'STORE_NAME', 'LOAD_CONST', 'RETURN_VALUE']

There may be some small differences in output produced for formatted disassembly or how we show compiler flags. We expect you’ll find the xdis output more informative, though.

See Also

Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xdis-6.3.0.tar.gz (796.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

xdis-6.3.0-py313-none-any.whl (279.2 kB view details)

Uploaded Python 3.13

xdis-6.3.0-py312-none-any.whl (279.2 kB view details)

Uploaded Python 3.12

xdis-6.3.0-py311-none-any.whl (279.2 kB view details)

Uploaded Python 3.11

xdis-6.3.0-py310-none-any.whl (297.1 kB view details)

Uploaded Python 3.10

xdis-6.3.0-py39-none-any.whl (297.1 kB view details)

Uploaded Python 3.9

xdis-6.3.0-py38-none-any.whl (297.0 kB view details)

Uploaded Python 3.8

xdis-6.3.0-py37-none-any.whl (297.0 kB view details)

Uploaded Python 3.7

xdis-6.3.0-py36-none-any.whl (297.0 kB view details)

Uploaded Python 3.6

xdis-6.3.0-py35-none-any.whl (300.1 kB view details)

Uploaded Python 3.5

xdis-6.3.0-py34-none-any.whl (300.1 kB view details)

Uploaded Python 3.4

xdis-6.3.0-py2-none-any.whl (296.7 kB view details)

Uploaded Python 2

File details

Details for the file xdis-6.3.0.tar.gz.

File metadata

  • Download URL: xdis-6.3.0.tar.gz
  • Upload date:
  • Size: 796.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0.tar.gz
Algorithm Hash digest
SHA256 e78e3e7a0e2d31ac64c084afc782a0a233da06ac05002c42a0e17000ac51dcaa
MD5 5f8981fe56d2df994db52341122655e3
BLAKE2b-256 adedafaed59bd95244f7f1552887aa33637fd8bd2c49ffbd6f5afc1c47d135e6

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py313-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py313-none-any.whl
  • Upload date:
  • Size: 279.2 kB
  • Tags: Python 3.13
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py313-none-any.whl
Algorithm Hash digest
SHA256 d799030c7da0869e52be2feee80a7a74eb17ab2648a734a54f3ad3e6c9ba003e
MD5 7de521bb601fc6754d163669fd1ec1bd
BLAKE2b-256 74d6f8f6fff61f0bef6718c8ccd83b76fbe5a4813806168e3ff22eb983afbf8f

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py312-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py312-none-any.whl
  • Upload date:
  • Size: 279.2 kB
  • Tags: Python 3.12
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py312-none-any.whl
Algorithm Hash digest
SHA256 5eca081e4d33d077068d3da28330965ad4cc1f11da32f11773950aa492bad223
MD5 db0995440434c56182de197c95ce4f74
BLAKE2b-256 a970537235a1ea27d0a57a9ee17b554e6cc39ccb208af9acf43442eda6e0c2a9

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py311-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py311-none-any.whl
  • Upload date:
  • Size: 279.2 kB
  • Tags: Python 3.11
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py311-none-any.whl
Algorithm Hash digest
SHA256 9c2d7ab3366c0373619b210c8ee6142702132054fcf95560154f2a653293ffcb
MD5 e179670ac02e4c820dfa23a2d07388b2
BLAKE2b-256 7ff6f556a63b78e462a15343afcece599720006c77863c056a35612da89a7b88

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py310-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py310-none-any.whl
  • Upload date:
  • Size: 297.1 kB
  • Tags: Python 3.10
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py310-none-any.whl
Algorithm Hash digest
SHA256 e05ba311d9b0b587f10f1a7b4e29ada0679b886a9e90d8a6047dc8780cf521a8
MD5 790a0bd85f9ccd4adee5ae5fa86efcc4
BLAKE2b-256 9271efa1b19edfd52b21cbb6763034985543f01ce5a3cbee1f5ff5d40bf96096

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py39-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py39-none-any.whl
  • Upload date:
  • Size: 297.1 kB
  • Tags: Python 3.9
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py39-none-any.whl
Algorithm Hash digest
SHA256 d58adf8629aaf7862411a0bf3e4666e4aa56790bdbb54760879224fbeb41c59a
MD5 ad78f3309087c9909d1b37dbaaa348f7
BLAKE2b-256 d95ca5efe0634a4d27318c841f0dc646137ecffb9f93d3ec0a6a4826a5bc6238

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py38-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py38-none-any.whl
  • Upload date:
  • Size: 297.0 kB
  • Tags: Python 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py38-none-any.whl
Algorithm Hash digest
SHA256 6321c81113042dc2f835642c4d132d53eb6db87e65d06d02e5b979ecf7db7b08
MD5 247308ef7a64f4ec680c9f402d6bff5e
BLAKE2b-256 3a24ddd849f224539c1d168977fab74769665c3bc54427a95895d7f4e16a441b

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py37-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py37-none-any.whl
  • Upload date:
  • Size: 297.0 kB
  • Tags: Python 3.7
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py37-none-any.whl
Algorithm Hash digest
SHA256 15fab329c15b7e81954843fb2c9a1a6c2d054875f7f376cdd47041ed86805362
MD5 0bfdcfb7867ad53bcc9d4620d4a308c8
BLAKE2b-256 28cb1f441924105bacdfb0993edf29338a8174f21ec411052a05fe84f4154810

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py36-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py36-none-any.whl
  • Upload date:
  • Size: 297.0 kB
  • Tags: Python 3.6
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py36-none-any.whl
Algorithm Hash digest
SHA256 cdeb705f47ee7a439ada5a36f16fccd9910afa3d62cfcf48d2a3b49580d52317
MD5 9e047454d7bff480c47e108554da1b9f
BLAKE2b-256 a2b5eefccd4280b45e19391aa4fc815099d678c8dc2a7b00a18cbab8db62f598

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py35-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py35-none-any.whl
  • Upload date:
  • Size: 300.1 kB
  • Tags: Python 3.5
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py35-none-any.whl
Algorithm Hash digest
SHA256 87eff2a650aeca03bb86417d810fe1e0a5a026f4b0af69e2ddc418dd1f11f768
MD5 b98decf434956b4937f51c44660f19e5
BLAKE2b-256 9ece6ff0cfc1900e95c10aa3da84e61986fbdeea4f801bd46550474371af1a25

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py34-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py34-none-any.whl
  • Upload date:
  • Size: 300.1 kB
  • Tags: Python 3.4
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py34-none-any.whl
Algorithm Hash digest
SHA256 2ef7eb83f9d8ddb1bd181bc3b875258b4e77b364a8581208a00cb095edb525d6
MD5 4d4cac7732f706af8d91941c870b5b51
BLAKE2b-256 f0a5c1aa54b18cd35b49c754a5bab30be5fcbc283dd1611406f41fbcc81f2a86

See more details on using hashes here.

File details

Details for the file xdis-6.3.0-py2-none-any.whl.

File metadata

  • Download URL: xdis-6.3.0-py2-none-any.whl
  • Upload date:
  • Size: 296.7 kB
  • Tags: Python 2
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xdis-6.3.0-py2-none-any.whl
Algorithm Hash digest
SHA256 068538dd7788cb7cc452cbb7c59d4a6ae41856bbfb4cb9e19b7180365c415702
MD5 a1ef4076c6ede336720c367161817943
BLAKE2b-256 ea906eb111b48fe33e623b8d6911833831c3b677db233679ab8b1629346c2716

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page