diff --git a/MANIFEST b/MANIFEST new file mode 100755 index 00000000..7f00a26d --- /dev/null +++ b/MANIFEST @@ -0,0 +1,22 @@ +README +compile_tests +setup.cfg +setup.py +test/* +uncompyle2/__init__.py +uncompyle2/disas.py +uncompyle2/magics.py +uncompyle2/parser.py +uncompyle2/scanner.py +uncompyle2/scanner25.py +uncompyle2/scanner26.py +uncompyle2/scanner27.py +uncompyle2/spark.py +uncompyle2/verify.py +uncompyle2/walker.py +uncompyle2/opcode/__init__.py +uncompyle2/opcode/opcode_23.py +uncompyle2/opcode/opcode_24.py +uncompyle2/opcode/opcode_25.py +uncompyle2/opcode/opcode_26.py +uncompyle2/opcode/opcode_27.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100755 index 9ca6c9be..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include MANIFEST -include MANIFEST.in -include README -include ANNOUNCE CHANGES TODO -include uncompyle -include test_pythonlib -include test_one -include compile_tests -graft test -graft scripts -global-exclude *~ .*~ diff --git a/README b/README index b930ea94..244eaadf 100755 --- a/README +++ b/README @@ -1,59 +1,49 @@ +uncompyle2 +========== - uncompyle2 - A Python 2.5, 2.6, 2.7 byte-code decompiler, written in Python 2.7 - 0.13 - 2012-6-5 +A Python 2.5, 2.6, 2.7 byte-code decompiler, written in Python 2.7 Introduction ------------ 'uncompyle2' converts Python byte-code back into equivalent Python -source. It accepts byte-code from Python version 2.5, 2.6 & 2.7. Additionally, -it will only run on Python 2.7. +source code. It accepts byte-code from Python version 2.5 to 2.7. +Additionally, it will only run on Python 2.7. The generated source is very readable: docstrings, lists, tuples and hashes get pretty-printed. -'uncompyle2' may also verify the equivalence of the generated source by -by compiling it and comparing both byte-codes. - 'uncompyle2' is based on John Aycock's generic small languages compiler 'spark' (http://www.csr.uvic.ca/~aycock/python/) and his prior work on 'uncompyle'. -Additional note (3 July 2004, Ben Burton): +### Additional note (3 July 2004, Ben Burton): - The original website from which this software was obtained is no longer - available. It has now become a commercial decompilation service, with - no software available for download. +The original website from which this software was obtained is no longer +available. It has now become a commercial decompilation service, with +no software available for download. - Any developers seeking to make alterations or enhancements to this code - should therefore consider these debian packages an appropriate starting - point. +Any developers seeking to make alterations or enhancements to this code +should therefore consider these debian packages an appropriate starting +point. -Additional note (5 June 2012): +### Additional note (5 June 2012): - The decompilation of python bytecode 2.5 & 2.6 is based on the work of - Eloi Vanderbeken. bytecode is translated to a pseudo 2.7 python bytecode - and then decompiled. +The decompilation of python bytecode 2.5 & 2.6 is based on the work of +Eloi Vanderbeken. bytecode is translated to a pseudo 2.7 python bytecode +and then decompiled. Features -------- - * decompiles Python byte-code into equivalent Python source +- decompiles Python byte-code into equivalent Python source +- decompiles byte-code from Python version 2.5, 2.6, 2.7 +- pretty-prints docstrings, hashes, lists and tuples +- reads directly from .pyc/.pyo files, bulk-decompile whole directories +- output may be written to file, a directory or to stdout +- option for including byte-code disassembly into generated source - * decompiles byte-code from Python version 2.5, 2.6, 2.7 - - * pretty-prints docstrings, hashes, lists and tuples - - * reads directly from .pyc/.pyo files, bulk-decompile whole - directories - - * output may be written to file, a directory or to stdout - - * option for including byte-code disassembly into generated source - - For a list of changes please refer to the 'CHANGES' file. +For a list of changes please refer to the 'CHANGES' file. Requirements @@ -70,27 +60,26 @@ the source distribution. Creating RPMS: - python setup.py bdist_rpm + python setup.py bdist_rpm - If you need to force the python interpreter to eg. pyton2: - python2 setup.py bdist_rpm --python=python2 +If you need to force the python interpreter to eg. pyton2: + python2 setup.py bdist_rpm --python=python2 -Installation from the source distribution: +### Installation from the source distribution: - python setup.py install + python setup.py install - To install to a user's home-dir: - python setup.py install --home= +To install to a user's home-dir: - To install to another prefix (eg. /usr/local) - python setup.py install --prefix=/usr/local + python setup.py install --home= - If you need to force the python interpreter to eg. pyton2: - python2 setup.py install +To install to another prefix (eg. /usr/local) - For more information on 'Installing Python Modules' please refer to - http://www.python.org/doc/current/inst/inst.html + python setup.py install --prefix=/usr/local + +For more information on 'Installing Python Modules' please refer to +http://www.python.org/doc/current/inst/inst.html Usage @@ -103,4 +92,6 @@ uncompyle2 --help prints long usage Known Bugs/Restrictions ----------------------- -I have some known bug in the 2.5 & 2.6 decompilation version but this will be fixed in a few. +No support for python 3.2 + +It currently reconstructs most of Python code but probably needs to be tested more thoroughly. All feedback welcome diff --git a/test_pythonlib.py b/test_pythonlib.py index baed1a35..447a6385 100755 --- a/test_pythonlib.py +++ b/test_pythonlib.py @@ -13,10 +13,10 @@ Usage-Examples: Adding own test-trees: Step 1) Edit this file and add a new entry to 'test_options', eg. - test_options['mylib'] = ('/usr/lib/mylib', PYOC, 'mylib') + test_options['mylib'] = ('/usr/lib/mylib', PYOC, 'mylib') Step 2: Run the test: - test_pythonlib --mylib # decompile 'mylib' - test_pythonlib --mylib --verify # decompile verify 'mylib' + test_pythonlib --mylib # decompile 'mylib' + test_pythonlib --mylib --verify # decompile verify 'mylib' ''' from uncompyle2 import main, verify @@ -105,7 +105,7 @@ if __name__ == '__main__': test_dirs.append(test_options[val]) else: help() - + for src_dir, pattern, target_dir in test_dirs: for libpath in lib_prefix: testpath = os.path.join(libpath, src_dir) @@ -114,6 +114,7 @@ if __name__ == '__main__': if os.path.exists(testlibfile) or os.path.exists(testfile): src_dir = testpath checked_dirs.append([src_dir, pattern, target_dir]) + break for src_dir, pattern, target_dir in checked_dirs: target_dir = os.path.join(target_base, target_dir) diff --git a/uncompyle2/disas.py b/uncompyle2/disas.py index 4884bb32..74f43d2f 100755 --- a/uncompyle2/disas.py +++ b/uncompyle2/disas.py @@ -139,7 +139,8 @@ def disassemble_string(code, lasti=-1, varnames=None, names=None, print '(' + cmp_op[oparg] + ')', print -disco = disassemble # XXX For backwards compatibility +disco = disassemble +# XXX For backwards compatibility def findlabels(code): """Detect all offsets in a byte code which are jump targets. diff --git a/uncompyle2/scanner25.py b/uncompyle2/scanner25.py index 23153f94..46587b4e 100755 --- a/uncompyle2/scanner25.py +++ b/uncompyle2/scanner25.py @@ -505,7 +505,7 @@ class Scanner25(scan.Scanner): last_stmt = s slist += [s] * (s-i) i = s - slist += [len(code)] * (len(code)-len(slist)) + slist += [end] * (end-len(slist)) def next_except_jump(self, start): ''' @@ -610,7 +610,7 @@ class Scanner25(scan.Scanner): test = self.prev[next_line_byte] if test == pos: loop_type = 'while 1' - else: + elif self.code[test] in hasjabs+hasjrel: self.ignore_if.add(test) test_target = self.get_target(test) if test_target > (jump_back+3): diff --git a/uncompyle2/scanner26.py b/uncompyle2/scanner26.py index 75046b78..14d9797c 100755 --- a/uncompyle2/scanner26.py +++ b/uncompyle2/scanner26.py @@ -502,7 +502,7 @@ class Scanner26(scan.Scanner): last_stmt = s slist += [s] * (s-i) i = s - slist += [len(code)] * (len(code)-len(slist)) + slist += [end] * (end-len(slist)) def next_except_jump(self, start): ''' @@ -607,7 +607,7 @@ class Scanner26(scan.Scanner): test = self.prev[next_line_byte] if test == pos: loop_type = 'while 1' - else: + elif self.code[test] in hasjabs+hasjrel: self.ignore_if.add(test) test_target = self.get_target(test) if test_target > (jump_back+3): diff --git a/uncompyle2/scanner27.py b/uncompyle2/scanner27.py index 02f2c3ac..4a7509c2 100755 --- a/uncompyle2/scanner27.py +++ b/uncompyle2/scanner27.py @@ -18,11 +18,10 @@ import scanner as scan class Scanner27(scan.Scanner): def __init__(self): self.Token = scan.Scanner.__init__(self, 2.6) - + def disassemble(self, co, classname=None): ''' Disassemble a code object, returning a list of 'Token'. - The main part of this procedure is modelled after dis.disassemble(). ''' @@ -98,14 +97,13 @@ class Scanner27(scan.Scanner): extended_arg = 0 for offset in self.op_range(0, n): - if offset in cf: k = 0 for j in cf[offset]: rv.append(Token('COME_FROM', None, repr(j), - offset="%s_%d" % (offset, k) )) + offset="%s_%d" % (offset, k))) k += 1 - + op = code[offset] op_name = opname[op] oparg = None; pattr = None @@ -274,7 +272,7 @@ class Scanner27(scan.Scanner): last_stmt = s slist += [s] * (s-i) i = s - slist += [len(code)] * (len(code)-len(slist)) + slist += [end] * (end-len(slist)) def remove_mid_line_ifs(self, ifs): filtered = [] @@ -341,13 +339,13 @@ class Scanner27(scan.Scanner): start = pos+3 target = self.get_target(pos, op) end = self.restrict_to_parent(target, parent) - + if target != end: self.fixed_jumps[pos] = end - (line_no, next_line_byte) = self.lines[pos] jump_back = self.last_instr(start, end, JA, next_line_byte, False) + if jump_back and jump_back != self.prev[end] and code[jump_back+3] in (JA, JF): if code[self.prev[end]] == RETURN_VALUE or \ (code[self.prev[end]] == POP_BLOCK and code[self.prev[self.prev[end]]] == RETURN_VALUE): @@ -365,8 +363,7 @@ class Scanner27(scan.Scanner): end = jump_back + 3 else: if self.get_target(jump_back) >= next_line_byte: - jump_back = self.last_instr(start, end, JA, - start, False) + jump_back = self.last_instr(start, end, JA, start, False) if end > jump_back+4 and code[end] in (JF, JA): if code[jump_back+4] in (JA, JF): if self.get_target(jump_back+4) == self.get_target(end): @@ -375,9 +372,8 @@ class Scanner27(scan.Scanner): elif target < pos: self.fixed_jumps[pos] = jump_back+4 end = jump_back+4 - target = self.get_target(jump_back, JA) - + if code[target] in (FOR_ITER, GET_ITER): loop_type = 'for' else: @@ -385,7 +381,7 @@ class Scanner27(scan.Scanner): test = self.prev[next_line_byte] if test == pos: loop_type = 'while 1' - else: + elif self.code[test] in hasjabs+hasjrel: self.ignore_if.add(test) test_target = self.get_target(test) if test_target > (jump_back+3): @@ -455,7 +451,7 @@ class Scanner27(scan.Scanner): #does this jump to right after another cond jump? # if so, it's part of a larger conditional if (code[pre[target]] in (JUMP_IF_FALSE_OR_POP, JUMP_IF_TRUE_OR_POP, - PJIF, PJIT)) and (target > pos): + PJIF, PJIT)) and (target > pos): self.fixed_jumps[pos] = pre[target] self.structs.append({'type': 'and/or', 'start': start,