xdis: Python Cross-Version Byte-Code Disassembler
xdis is a Python library that provides a cross-version byte-code disassembler and marshal routines. It enables the inspection and manipulation of Python bytecode across a wide range of Python versions, from 1.0 up to 3.15+. The current version is 6.3.0, and it maintains an active release cadence, frequently updating with support for new Python versions and their respective bytecode changes.
Common errors
-
KeyError: 'X.Y.Z' (e.g., 'KeyError: '3.7.5'') when importing xdis or a library using it (like uncompyle6).
cause This error often indicates that `xdis` is trying to process bytecode from a Python version for which it doesn't have the necessary opcode or magic number definitions, or there's a mismatch between the bytecode version and the `xdis` version's capabilities. This can be exacerbated by incorrect installation for older Python versions.fixEnsure `xdis` is up-to-date (current version is 6.3.0). If you are processing bytecode from an older or very new Python version, verify that your installed `xdis` version explicitly supports that target Python version. For installations on Python < 3.11, ensure you followed the specific installation instructions for pre-built wheels/tarballs. If using `uncompyle6`, ensure both `uncompyle6` and `xdis` are the latest compatible versions. -
TypeError: unsupported operand type(s) for +: 'function' and 'function' (or similar runtime errors) after disassembling bytecode with `pydisasm --asm` and reassembling with `pyc-xasm`.
cause This error typically occurs when bytecode is reassembled (e.g., using `xasm`) and then executed. The reassembly process, particularly when dealing with complex code objects, multiple functions, or specific optimizations, may not perfectly replicate the original bytecode structure required for Python's interpreter, leading to runtime type mismatches or incorrect references.fixThe primary purpose of `xdis` is analysis, not necessarily perfect round-trip bytecode editing and re-execution. If you encounter this, understand that direct bytecode manipulation for execution can be very fragile. Verify the `xasm` project's documentation and current status for any known limitations or specific usage patterns to ensure executable output. Consider higher-level tools like `uncompyle6` for dekompilation if source code re-generation is the goal for execution. -
Incorrect or unexpected disassembly output, especially for `MAKE_FUNCTION` or exception handling blocks when analyzing Python 3.11+ bytecode.
cause Older versions of `xdis` (pre-6.1.7) had less robust handling for changes in Python 3.11's bytecode, specifically regarding `MAKE_FUNCTION` formatting and an O(n^2) performance issue in exception handling. This could lead to misinterpretation or incomplete display of the bytecode structure.fixUpgrade `xdis` to version 6.1.7 or newer to get the latest fixes and improvements for Python 3.11+ bytecode analysis. This ensures that the disassembly accurately reflects the behavior of newer Python interpreters.
Warnings
- breaking Version 6.1.0 introduced major API changes, particularly affecting how disassembly options are specified (e.g., via `--format` on the `pydisasm` command-line utility) and new output formats like 'extended' or 'bytes'. Code written for previous versions may need updates.
- gotcha Installing `xdis` via `pip install xdis` is primarily for Python 3.11 and newer. For older Python versions (e.g., 2.x, 3.0-3.10), you must manually download specific tarballs or wheels from the GitHub Releases page that are pre-compiled for those Python versions. Installing the generic package on older Python versions may lead to unexpected behavior or `ModuleNotFoundError` if dependencies are not correctly resolved or specific bytecode definitions are missing.
- gotcha The `MAKE_FUNCTION` opcode's formatting and behavior changed significantly in Python 3.11 compared to Python 3.6. `xdis` versions 6.1.7 and later include adjustments to handle these differences, but older `xdis` versions might produce incorrect or confusing disassembly output for Python 3.11 bytecode, especially around function creation.
- gotcha An O(n^2) performance issue was identified and fixed in `xdis` version 6.1.7, specifically affecting the handling of exceptions in Python 3.11+. Users disassembling large codebases or complex exception structures with `xdis` versions prior to 6.1.7 on a Python 3.11+ interpreter may experience significant slowdowns.
- gotcha Attempting to disassemble bytecode and then reassemble it using `xasm` (a related project) and execute the result can lead to `TypeError` or other runtime issues, particularly with functions that have the same name or complex closures. `xdis` focuses on disassembly and analysis, and perfect round-tripping for execution via reassembly is not guaranteed or fully supported across all cases.
Install
-
pip install xdis -
For Python versions before 3.11, consult the GitHub Releases page for specific tarballs or wheels matching your Python version (e.g., xdis_36-x.y.z.tar.gz for Python 3.6-3.11, or older eggs/tarballs for Python 2.x/3.x up to 3.5).
Imports
- Bytecode
from xdis.std import Bytecode
- load_bytecode
from xdis import load
- disassemble
from xdis.bytecode import disassemble
Quickstart
import marshal
import types
from xdis.std import Bytecode
def example_function():
a = 10
b = 'hello'
if a > 5:
print(b)
return a
# Get the code object of the function
code_obj = example_function.__code__
# Disassemble using xdis.std.Bytecode (similar to stdlib dis)
print(f"\nDisassembly for Python {code_obj.co_firstlineno} via xdis.std:")
for instr in Bytecode(code_obj):
print(instr)
# Example of loading bytecode from a marshal.dumps() output (simulated .pyc content)
# For true cross-version loading, you'd use xdis.load.load_bytecode_from_file
# or xdis.load.load_bytecode_from_cstring to specify the Python magic number.
def mock_marshal_loader(code_obj, python_version):
# This is a simplified example; xdis.load handles actual magic numbers/versions
# The real power is in loading *other* Python versions' bytecode.
# For simplicity here, we'll just demonstrate disassembling a marshalled object.
marshalled_code = marshal.dumps(code_obj)
return marshalled_code
marshalled_bytes = mock_marshal_loader(code_obj, (3, 9)) # Simulate Python 3.9 bytecode
try:
# To truly demonstrate xdis's cross-version capability, you'd load a .pyc file
# generated by a *different* Python version than the one running this code.
# For this example, we'll just show disassembling an arbitrary code object.
# For actual cross-version loading, investigate xdis.load functions.
print(f"\nDisassembly of a marshalled code object (Python {code_obj.co_firstlineno}):")
# Note: For actual different Python versions, you'd need the specific magic number
# and potentially xdis.load.load_bytecode_from_cstring or file-based loaders.
# We'll re-disassemble the current code_obj for a runnable example.
from xdis.bytecode import disassemble
disassemble(code_obj)
except Exception as e:
print(f"Could not fully demonstrate cross-version load in quickstart due to complexity: {e}")