#!/usr/bin/env python # Mode: -*- python -*- # # Copyright (c) 2015 by Rocky Bernstein # """ Usage: pydisassemble [OPTIONS]... FILE Examples: pydisassemble foo.pyc pydisassemble foo.py pydisassemble -o foo.pydis foo.pyc pydisassemble -o /tmp foo.pyc Options: -o output decompiled files to this path: if multiple input files are decompiled, the common prefix is stripped from these names and the remainder appended to --help show this message """ from __future__ import print_function Usage_short = \ "pydissassemble [--help] [--verify] [--showasm] [--showast] [-o ] FILE|DIR..." import sys, os, getopt, time, types import os.path import uncompyle6 def disassemble_code(version, co, out=None): """ diassembles and deparses a given code block 'co' """ assert isinstance(co, types.CodeType) # 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) # Pick up appropriate scanner if version == 2.7: import uncompyle6.scanners.scanner27 as scan scanner = scan.Scanner27() elif version == 2.6: import uncompyle6.scanners.scanner26 as scan scanner = scan.Scanner26() elif version == 2.5: import uncompyle6.scanners.scanner25 as scan scanner = scan.Scanner25() elif version == 3.2: import uncompyle6.scanners.scanner32 as scan scanner = scan.Scanner32() elif version == 3.4: import uncompyle6.scanners.scanner34 as scan scanner = scan.Scanner34() scanner.setShowAsm(True, out) tokens, customize = scanner.disassemble(co) def disassemble_file(filename, outstream=None, showasm=False, showast=False): """ disassemble Python byte-code file (.pyc) """ version, co = uncompyle6.load_module(filename) if type(co) == list: for con in co: disassemble_code(version, con, outstream) else: from trepan.api import debug; debug disassemble_code(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, file) + '_dis' outstream = _get_outstream(outfile) # print(outfile, file=sys.stderr) pass # try to decomyple the input file try: disassemble_file(infile, outstream, showasm=True, showast=False) 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 decompyling', infile) sys.stdout.flush() if outfile: sys.stdout.write("\n") sys.stdout.flush() return if sys.version[:3] != '2.7' and sys.version[:3] != '3.4': print('Error: pydisassemble requires Python 2.7 or 3.4.', file=sys.stderr) sys.exit(-1) outfile = '-' out_base = None try: opts, files = getopt.getopt(sys.argv[1:], 'ho:', ['help']) except getopt.GetoptError as e: print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr) sys.exit(-1) for opt, val in opts: if opt in ('-h', '--help'): print(__doc__) sys.exit(0) elif opt == '-o': outfile = val else: print(opt) print(Usage_short) sys.exit(1) # argl, commonprefix works on strings, not on path parts, # thus we must handle the case with files in 'some/classes' # and 'some/cmds' src_base = os.path.commonprefix(files) if src_base[-1:] != os.sep: src_base = os.path.dirname(src_base) if src_base: sb_len = len( os.path.join(src_base, '') ) files = [f[sb_len:] for f in files] del sb_len if outfile == '-': outfile = None # use stdout elif outfile and os.path.isdir(outfile): out_base = outfile; outfile = None elif outfile and len(files) > 1: out_base = outfile; outfile = None disassemble_files(src_base, out_base, files, outfile)