diff --git a/pytest/testdata/if-2.7.right b/pytest/testdata/if-2.7.right index 3591a24a..3a8f7768 100644 --- a/pytest/testdata/if-2.7.right +++ b/pytest/testdata/if-2.7.right @@ -1,13 +1,12 @@ # Python 2.7 # Embedded file name: simple_source/branching/05_if.py - 6 0 LOAD_NAME 'True' - 3 POP_JUMP_IF_FALSE '15' + 6 0 LOAD_NAME 0 'True' + 3 POP_JUMP_IF_FALSE 15 '15' - 7 6 LOAD_NAME 'False' - 9 STORE_NAME 'b' - 12 JUMP_FORWARD '15' - 15_0 COME_FROM '12' - 15 LOAD_CONST '' - 18 RETURN_VALUE '' + 7 6 LOAD_NAME 1 'False' + 9 STORE_NAME 2 'b' + 12 JUMP_FORWARD 0 '15' + 15 LOAD_CONST 0 '' + 18 RETURN_VALUE '' diff --git a/pytest/testdata/ifelse-2.7.right b/pytest/testdata/ifelse-2.7.right index 223f0a04..ddf804ba 100644 --- a/pytest/testdata/ifelse-2.7.right +++ b/pytest/testdata/ifelse-2.7.right @@ -1,16 +1,15 @@ # Python 2.7 # Embedded file name: simple_source/branching/05_ifelse.py - 3 0 LOAD_NAME 'True' - 3 POP_JUMP_IF_FALSE '15' + 3 0 LOAD_NAME 0 'True' + 3 POP_JUMP_IF_FALSE 15 '15' - 4 6 LOAD_CONST 1 - 9 STORE_NAME 'b' - 12 JUMP_FORWARD '21' + 4 6 LOAD_CONST 0 1 + 9 STORE_NAME 1 'b' + 12 JUMP_FORWARD 6 '21' - 6 15 LOAD_CONST 2 - 18 STORE_NAME 'd' - 21_0 COME_FROM '12' - 21 LOAD_CONST '' - 24 RETURN_VALUE '' + 6 15 LOAD_CONST 1 2 + 18 STORE_NAME 2 'd' + 21 LOAD_CONST 2 '' + 24 RETURN_VALUE '' diff --git a/uncompyle6/bin/pydisassemble.py b/uncompyle6/bin/pydisassemble.py index 0b9f76e0..d16bd573 100755 --- a/uncompyle6/bin/pydisassemble.py +++ b/uncompyle6/bin/pydisassemble.py @@ -7,7 +7,7 @@ from __future__ import print_function import sys, os, getopt from uncompyle6 import check_python_version -from uncompyle6.disas import disassemble_files +from uncompyle6.disas import disassemble_file from uncompyle6.version import VERSION program, ext = os.path.splitext(os.path.basename(__file__)) @@ -18,36 +18,37 @@ Usage: %s [--help | -h | -V | --version] Examples: - %s foo.pyc - %s foo.py - %s -o foo.pydis foo.pyc - %s -o /tmp foo.pyc + {0} foo.pyc + {0} foo.py # same thing as above but find the file + {0} foo.pyc bar.pyc # disassemble foo.pyc and bar.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 + -U | --uncompyle6 show instructions with uncompyle6 mangling + -V | --version show version and stop + -h | --help show this message -""" % ((program,) * 6) +""".format(program) + +PATTERNS = ('*.pyc', '*.pyo') def main(): - Usage_short = \ - "%s [--help] [--verify] [--showasm] [--showast] [-o ] FILE|DIR..." % program + Usage_short = """usage: %s FILE... +Type -h for for full help.""" % program check_python_version(program) outfile = '-' out_base = None + use_uncompyle6_format = False if len(sys.argv) == 1: - print("No file(s) or directory given", file=sys.stderr) + print("No file(s) given", file=sys.stderr) print(Usage_short, file=sys.stderr) sys.exit(1) try: - opts, files = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version']) + opts, files = getopt.getopt(sys.argv[1:], 'hVU', + ['help', 'version', 'uncompyle6']) except getopt.GetoptError as e: print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr) sys.exit(-1) @@ -59,33 +60,22 @@ def main(): elif opt in ('-V', '--version'): print("%s %s" % (program, VERSION)) sys.exit(0) - elif opt == '-o': - outfile = val + elif opt in ('-U', '--uncompyle6'): + use_uncompyle6_format = True else: print(opt) print(Usage_short, file=sys.stderr) 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, True) + for file in files: + if os.path.exists(files[0]): + disassemble_file(file, sys.stdout, use_uncompyle6_format) + else: + print("Can't read %s - skipping" % files[0], + file=sys.stderr) + pass + pass return if __name__ == '__main__': diff --git a/uncompyle6/disas.py b/uncompyle6/disas.py index bc4a2c72..85d8c929 100644 --- a/uncompyle6/disas.py +++ b/uncompyle6/disas.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 by Rocky Bernstein +# Copyright (c) 2015-2016 by Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock @@ -25,7 +25,7 @@ from uncompyle6.code import iscode from uncompyle6.load import check_object_path, load_module from uncompyle6.scanner import get_scanner -def disco(version, co, out=None, native=False): +def disco(version, co, out=None, use_uncompyle6_format=False): """ diassembles and deparses a given code block 'co' """ @@ -40,13 +40,15 @@ def disco(version, co, out=None, native=False): file=real_out) scanner = get_scanner(version) - if native and hasattr(scanner, 'disassemble_native'): + if (not use_uncompyle6_format) and hasattr(scanner, 'disassemble_native'): tokens, customize = scanner.disassemble_native(co, True) else: tokens, customize = scanner.disassemble(co) + # FIXME: This should go in another routine and + # a queue should be kept of code to disassemble. for t in tokens: - print(t, file=real_out) + print(t.format(), file=real_out) print(file=out) @@ -112,7 +114,7 @@ def disassemble_files(in_base, out_base, files, outfile=None, # print(outfile, file=sys.stderr) pass - # try to decomyple the input file + # try to disassemble the input file try: disassemble_file(infile, outstream, native) except KeyboardInterrupt: diff --git a/uncompyle6/scanners/scanner27.py b/uncompyle6/scanners/scanner27.py index 858d9f8b..02bc3f27 100755 --- a/uncompyle6/scanners/scanner27.py +++ b/uncompyle6/scanners/scanner27.py @@ -192,6 +192,73 @@ class Scanner27(scan.Scanner): tokens.append(Token(replace[offset], oparg, pattr, offset, linestart)) return tokens, customize + def disassemble_native(self, co, opnames, classname=None, code_objects={}): + """ + Like disassemble3 but doesn't try to adjust any opcodes. + """ + + # Container for tokens + tokens = [] + + customize = {} + Token = self.Token # shortcut + + n = self.setup_code(co) + self.build_lines_data(co, n) + + # self.lines contains (block,addrLastInstr) + if classname: + classname = '_' + classname.lstrip('_') + '__' + + def unmangle(name): + if name.startswith(classname) and name[-2:] != '__': + return name[len(classname) - 2:] + return name + + free = [ unmangle(name) for name in (co.co_cellvars + co.co_freevars) ] + names = [ unmangle(name) for name in co.co_names ] + varnames = [ unmangle(name) for name in co.co_varnames ] + else: + free = co.co_cellvars + co.co_freevars + names = co.co_names + varnames = co.co_varnames + + extended_arg = 0 + for offset in self.op_range(0, n): + op = self.code[offset] + op_name = opname[op] + + oparg = None; pattr = None + if op >= HAVE_ARGUMENT: + oparg = self.get_argument(offset) + extended_arg + extended_arg = 0 + if op == EXTENDED_ARG: + extended_arg = oparg * scan.L65536 + continue + if op in hasconst: + pattr = co.co_consts[oparg] + elif op in hasname: + pattr = names[oparg] + elif op in hasjrel: + pattr = repr(offset + 3 + oparg) + elif op in hasjabs: + pattr = repr(oparg) + elif op in haslocal: + pattr = varnames[oparg] + elif op in hascompare: + pattr = cmp_op[oparg] + elif op in hasfree: + pattr = free[oparg] + + if offset in self.linestartoffsets: + linestart = self.linestartoffsets[offset] + else: + linestart = None + + tokens.append(Token(op_name, oparg, pattr, offset, linestart)) + pass + return tokens, customize + def setup_code(self, co): """ Creates Python-independent bytecode structure (byte array) in diff --git a/uncompyle6/scanners/tok.py b/uncompyle6/scanners/tok.py index bc54b32e..e70605bc 100644 --- a/uncompyle6/scanners/tok.py +++ b/uncompyle6/scanners/tok.py @@ -41,6 +41,13 @@ class Token: return (prefix + ('%6s\t%-17s %r' % (self.offset, self.type, pattr))) + def format(self): + prefix = '\n%4d ' % self.linestart if self.linestart else (' ' * 5) + offset_opname = '%6s\t%-17s' % (self.offset, self.type) + argstr = "%6d " % self.attr if isinstance(self.attr, int) else (' '*7) + pattr = self.pattr if self.pattr is not None else '' + return "%s%s%s %r" % (prefix, offset_opname, argstr, pattr) + def __hash__(self): return hash(self.type)