Go over disassembly routine...

* tok.py:
  - add arg value when it is an integer
* pydisassemble.py:
  - add option -U --uncomplyle for which flavor of disassembly
  - remove -o option
* scanner27.py:
  - allow for native (non-uncompyle6) output
This commit is contained in:
rocky 2016-05-17 22:44:51 -04:00
parent a3dd61c981
commit d42f84a59c
6 changed files with 123 additions and 59 deletions

View File

@ -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 ''

View File

@ -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 ''

View File

@ -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 <path> 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
<path>
--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 <path>] 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__':

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015 by Rocky Bernstein
# Copyright (c) 2015-2016 by Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# 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:

View File

@ -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

View File

@ -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)