# Copyright (c) 1999 John Aycock # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2015 by Rocky Bernstein """ CPython magic- and version- independent disassembly routines There are two reasons we can't use Python's built-in routines from dis. First, the bytecode we are extracting may be from a different version of Python (different magic number) than the version of Python that is doing the extraction. Second, we need structured instruction information for the (de)-parsing step. Python 3.4 and up provides this, but we still do want to run on Python 2.7. """ from __future__ import print_function import inspect, os, py_compile, sys, tempfile import uncompyle6 from uncompyle6 import PYTHON3 from uncompyle6.scanner import get_scanner def check_object_path(path): if path.endswith(".py"): try: import importlib return importlib.util.cache_from_source(path, optimization='') except: try: import imp imp.cache_from_source(path, debug_override=False) except: pass pass basename = os.path.basename(path)[0:-3] spath = path if PYTHON3 else path.decode('utf-8') path = tempfile.mkstemp(prefix=basename + '-', suffix='.pyc', text=False)[1] py_compile.compile(spath, cfile=path) if not path.endswith(".pyc") and not path.endswith(".pyo"): raise ValueError("path %s must point to a .py or .pyc file" % path) return path def disco(version, co, out=None): """ diassembles and deparses a given code block 'co' """ assert inspect.iscode(co) # store final output stream for case of error real_out = out or sys.stdout print('# Python %s' % version, file=real_out) if co.co_filename: print('# Embedded file name: %s' % co.co_filename, file=real_out) scanner = get_scanner(version) tokens, customize = scanner.disassemble(co) for t in tokens: print(t, file=real_out) print(file=out) def disassemble_file(filename, outstream=None): """ disassemble Python byte-code file (.pyc) If given a Python source file (".py") file, we'll try to find the corresponding compiled object. """ filename = check_object_path(filename) version, magic_int, co = uncompyle6.load_module(filename) if type(co) == list: for con in co: disco(version, con, outstream) else: disco(version, co, outstream) co = None def disassemble_files(in_base, out_base, files, outfile=None): """ in_base base directory for input files out_base base directory for output files (ignored when files list of filenames to be uncompyled (relative to src_base) outfile write output to this filename (overwrites out_base) For redirecting output to - outfile= (out_base is ignored) - files below out_base out_base=... - stdout out_base=None, outfile=None """ def _get_outstream(outfile): dir = os.path.dirname(outfile) failed_file = outfile + '_failed' if os.path.exists(failed_file): os.remove(failed_file) try: os.makedirs(dir) except OSError: pass return open(outfile, 'w') of = outfile if outfile == '-': outfile = None # use stdout elif outfile and os.path.isdir(outfile): out_base = outfile; outfile = None elif outfile: out_base = outfile; outfile = None for filename in files: infile = os.path.join(in_base, filename) # print (infile, file=sys.stderr) if of: # outfile was given as parameter outstream = _get_outstream(outfile) elif out_base is None: outstream = sys.stdout else: outfile = os.path.join(out_base, filename) + '_dis' outstream = _get_outstream(outfile) # print(outfile, file=sys.stderr) pass # try to decomyple the input file try: disassemble_file(infile, outstream) except KeyboardInterrupt: if outfile: outstream.close() os.remove(outfile) raise except: if outfile: outstream.close() os.rename(outfile, outfile + '_failed') else: sys.stderr.write("\n# Can't disassemble %s\n" % infile) import traceback traceback.print_exc() else: # uncompyle successfull if outfile: outstream.close() if not outfile: print('\n# okay disassembling', infile) sys.stdout.flush() if outfile: sys.stdout.write("\n") sys.stdout.flush() return def _test(): """Simple test program to disassemble a file.""" argc = len(sys.argv) if argc != 2: if argc == 1 and uncompyle6.PYTHON3: fn = __file__ else: sys.stderr.write("usage: %s [-|CPython compiled file]\n" % __file__) sys.exit(2) else: fn = sys.argv[1] disassemble_file(fn) if __name__ == "__main__": _test()