X Tutup
Skip to content

Commit 34ecd54

Browse files
committed
README.rst: note addition of pydisassemble
Remove duplicate disassembly printing from scanners and put common code in caller(s). Show source-code line numbers in disassembly output and fix alignment of byte offsets. disas.py: workaround Python 2/3 different layouts before we get to bytecodes in a code object.
1 parent 5e5da10 commit 34ecd54

File tree

11 files changed

+128
-95
lines changed

11 files changed

+128
-95
lines changed

PKG-INFO

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Metadata-Version: 2.0
22
Name: uncompyle6
33
Version: 2.0.1
44
Summary: Python byte-code to source-code converter
5-
Home-page: http://github.com/rocky/uncompyle6
5+
Home-page: http://github.com/rocky/python-uncompyle6
66
Author: Rocky
77
Author-email: rb@dustyfeet.com
88
License: GPLv3

README.rst

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,42 @@
11
uncompyle6
22
==========
33

4-
A CPython 2.x and possibly 3.x byte-code disassembler and
5-
adecompiler.
4+
A Python 2.x and possibly 3.x byte-code decompiler.
65

76
This is written in Python 2.7 but is Python3 compatible.
87

98

109
Introduction
1110
------------
1211

13-
'uncompyle6' converts Python byte-code back into equivalent Python
12+
_uncompyle6_ converts Python byte-code back into equivalent Python
1413
source code. It accepts byte-codes from Python version 2.5 to 2.7.
15-
It runs on Python 2.7 and, with a little more work, on Python 3 as well.
14+
It runs on Python 2.7 and with a little more work Python 3.
1615

1716
The generated source is fairly readable: docstrings, lists, tuples and
1817
hashes are somewhat pretty-printed.
1918

20-
'uncompyle6' is based on John Aycock's generic small languages
21-
compiler 'spark' (http://pages.cpsc.ucalgary.ca/~aycock/spark/) and his
19+
_uncompyle6_ is based on John Aycock's generic small languages
20+
compiler 'spark' (http://www.csr.uvic.ca/~aycock/python/) and his
2221
prior work on a tool called 'decompyle'. This was improved by Hartmut Goebel
23-
http://www.crazy-compilers.com
22+
`http://www.crazy-compilers.com/`_
2423

25-
In order to the decompile a program, we need to be able to disassemble
26-
it first. And this process may be useful in of itself. So we provide a
27-
utility for just that piece as well.
24+
# Additional note (3 July 2004):
2825

29-
'pydisassemble' gives a CPython disassembly of Python byte-code. How
30-
is this different than what Python already provides via the "dis"
31-
module? Here, we can cross disassemble bytecodes from different
32-
versions of CPython than the version of CPython that is doing the
33-
disassembly.
26+
This software is no longer available from the original website.
27+
However http://www.crazy-compilers.com/decompyle/ provides a
28+
decompilation service.
3429

35-
'pydisassemble works on the same versions as 'uncompyle6' and handles the
36-
same sets of CPython bytecode versions.
37-
38-
*Note from 3 July 2004:*
39-
40-
This software was original available from http://www.crazy-compilers.com;
41-
http://www.crazy-compilers.com/decompyle/ provides a decompilation service.
42-
43-
*Note (5 June 2012):*
30+
# Additional note (5 June 2012):
4431

4532
The decompilation of python bytecode 2.5 & 2.6 is based on the work of
4633
Eloi Vanderbeken. bytecode is translated to a pseudo 2.7 python bytecode
4734
and then decompiled.
4835

49-
*Note (12 Dec 2016):*
36+
# Additional note (12 Dec 2016):
5037

51-
This project will be used to deparse fragments of code inside my
52-
trepan_ debuggers_. For that, I need to record text fragements for all
38+
I will be using this to deparse fragments of code inside my trepan_
39+
debuggers_. For that, I need to record text fragements for all
5340
byte-code offsets (of interest). This purpose although largely
5441
compatible with the original intention is yet a little bit different.
5542

@@ -80,8 +67,6 @@ Installation
8067

8168
This uses setup.py, so it follows the standard Python routine:
8269

83-
::
84-
8570
python setup.py install # may need sudo
8671
# or if you have pyenv:
8772
python setup.py develop
@@ -103,18 +88,15 @@ Usage
10388

10489
Run
10590

106-
::
107-
10891
./scripts/uncompyle6 -h
10992

110-
11193
for usage help
11294

11395

11496
Known Bugs/Restrictions
11597
-----------------------
11698

117-
Support for Python 3 bytecode and syntax is lacking.
99+
Support Python 3 bytecode and syntax is lacking.
118100

119101
.. _trepan: https://pypi.python.org/pypi/trepan
120102
.. _debuggers: https://pypi.python.org/pypi/trepan3k

bin/pydissassemble

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ def disassemble_code(version, co, out=None):
3838
assert isinstance(co, types.CodeType)
3939

4040
# store final output stream for case of error
41-
__real_out = out or sys.stdout
42-
print('# Python %s' % version, file=__real_out)
41+
real_out = out or sys.stdout
42+
print('# Python %s' % version, file=real_out)
4343
if co.co_filename:
4444
print('# Embedded file name: %s' % co.co_filename,
45-
file=__real_out)
45+
file=real_out)
4646

4747
# Pick up appropriate scanner
4848
if version == 2.7:
@@ -63,6 +63,11 @@ def disassemble_code(version, co, out=None):
6363
scanner.setShowAsm(True, out)
6464
tokens, customize = scanner.disassemble(co)
6565

66+
for t in tokens:
67+
print(t, file=real_out)
68+
print(file=out)
69+
70+
6671

6772
def disassemble_file(filename, outstream=None, showasm=False, showast=False):
6873
"""

uncompyle6/__init__.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ def load_module(filename):
8181

8282
# print version
8383
fp.read(4) # timestamp
84+
magic_int = magics.magic2int(magic)
8485

8586
if version == PYTHON_VERSION:
86-
magic_int = magics.magic2int(magic)
8787
# Note: a higher magic number necessarily mean a later
88-
# release. At Pyton 3.0 the magic number decreased
88+
# release. At Python 3.0 the magic number decreased
8989
# significantly. Hence the range below. Also note
9090
# inclusion of the size info, occurred within a
9191
# Python magor/minor release. Hence the test on the
@@ -95,7 +95,7 @@ def load_module(filename):
9595
bytecode = fp.read()
9696
co = marshal.loads(bytecode)
9797
else:
98-
co = disas.load(fp)
98+
co = disas.load(fp, magic_int)
9999
pass
100100

101101
return version, co
@@ -108,11 +108,11 @@ def uncompyle(version, co, out=None, showasm=False, showast=False):
108108
assert isinstance(co, types.CodeType)
109109

110110
# store final output stream for case of error
111-
__real_out = out or sys.stdout
112-
print('# Python %s' % version, file=__real_out)
111+
real_out = out or sys.stdout
112+
print('# Python %s' % version, file=real_out)
113113
if co.co_filename:
114114
print('# Embedded file name: %s' % co.co_filename,
115-
file=__real_out)
115+
file=real_out)
116116

117117
# Pick up appropriate scanner
118118
if version == 2.7:
@@ -133,12 +133,17 @@ def uncompyle(version, co, out=None, showasm=False, showast=False):
133133
scanner.setShowAsm(showasm, out)
134134
tokens, customize = scanner.disassemble(co)
135135

136+
if showasm:
137+
for t in tokens:
138+
print(t, file=real_out)
139+
print(file=out)
140+
136141
# Build AST from disassembly.
137142
walk = walker.Walker(out, scanner, showast=showast)
138143
try:
139144
ast = walk.build_ast(tokens, customize)
140145
except walker.ParserError as e : # parser failed, dump disassembly
141-
print(e, file=__real_out)
146+
print(e, file=real_out)
142147
raise
143148
del tokens # save memory
144149

uncompyle6/disas.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def marshalLoad(fp):
3636
internStrings = []
3737
return load(fp)
3838

39-
def load(fp):
39+
def load(fp, magic_int):
4040
"""
4141
marshal.load() written in Python. When the Python bytecode magic loaded is the
4242
same magic for the running Python interpreter, we can simply use the
@@ -51,27 +51,34 @@ def load(fp):
5151
if marshalType == 'c':
5252
Code = types.CodeType
5353

54+
# FIXME If 'i' is deprecated, what would we use?
5455
co_argcount = unpack('i', fp.read(4))[0]
5556
co_nlocals = unpack('i', fp.read(4))[0]
5657
co_stacksize = unpack('i', fp.read(4))[0]
5758
co_flags = unpack('i', fp.read(4))[0]
58-
co_code = load(fp)
59-
co_consts = load(fp)
60-
co_names = load(fp)
61-
co_varnames = load(fp)
62-
co_freevars = load(fp)
63-
co_cellvars = load(fp)
64-
co_filename = load(fp)
65-
co_name = load(fp)
59+
# FIXME: somewhere between Python 2.7 and python 3.2 there's
60+
# another 4 bytes before we get to the bytecode. What's going on?
61+
# Again, because magic ints decreased between python 2.7 and 3.0 we need
62+
# a range here.
63+
if 3000 < magic_int < 20121:
64+
fp.read(4)
65+
co_code = load(fp, magic_int)
66+
co_consts = load(fp, magic_int)
67+
co_names = load(fp, magic_int)
68+
co_varnames = load(fp, magic_int)
69+
co_freevars = load(fp, magic_int)
70+
co_cellvars = load(fp, magic_int)
71+
co_filename = load(fp, magic_int)
72+
co_name = load(fp, magic_int)
6673
co_firstlineno = unpack('i', fp.read(4))[0]
67-
co_lnotab = load(fp)
74+
co_lnotab = load(fp, magic_int)
6875
# The Python3 code object is different than Python2's which
6976
# we are reading if we get here.
7077
# Also various parameters which were strings are now
7178
# bytes (which is probably more logical).
7279
if PYTHON3:
7380
if PYTHON_MAGIC_INT > 3020:
74-
# In later Python3 versions, there is a
81+
# In later Python3 magic_ints, there is a
7582
# kwonlyargcount parameter which we set to 0.
7683
return Code(co_argcount, 0, co_nlocals, co_stacksize, co_flags,
7784
bytes(co_code, encoding='utf-8'),
@@ -152,7 +159,7 @@ def load(fp):
152159
tuplesize = unpack('i', fp.read(4))[0]
153160
ret = tuple()
154161
while tuplesize > 0:
155-
ret += load(fp),
162+
ret += load(fp, magic_int),
156163
tuplesize -= 1
157164
return ret
158165
elif marshalType == '[':

uncompyle6/scanner.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
else:
2222
L65536 = long(65536)
2323

24-
from uncompyle6.opcodes import opcode_25, opcode_26, opcode_27, opcode_34
24+
from uncompyle6.opcodes import opcode_25, opcode_26, opcode_27, opcode_32, opcode_34
2525

2626

2727
class Token:
@@ -31,7 +31,7 @@ class Token:
3131
A byte-code token is equivalent to the contents of one line
3232
as output by dis.dis().
3333
'''
34-
def __init__(self, type_, attr=None, pattr=None, offset=-1, linestart=False):
34+
def __init__(self, type_, attr=None, pattr=None, offset=-1, linestart=None):
3535
self.type = intern(type_)
3636
self.attr = attr
3737
self.pattr = pattr
@@ -51,9 +51,9 @@ def __repr__(self):
5151
def __str__(self):
5252
pattr = self.pattr
5353
if self.linestart:
54-
return '\n%s\t%-17s %r' % (self.offset, self.type, pattr)
54+
return '\n%4d %6s\t%-17s %r' % (self.linestart, self.offset, self.type, pattr)
5555
else:
56-
return '%s\t%-17s %r' % (self.offset, self.type, pattr)
56+
return ' %6s\t%-17s %r' % (self.offset, self.type, pattr)
5757

5858
def __hash__(self):
5959
return hash(self.type)

uncompyle6/scanners/scanner25.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ def disassemble(self, co, classname=None):
3535
if self.code[i] in (RETURN_VALUE, END_FINALLY):
3636
n = i + 1
3737
self.code = array('B', co.co_code[:n])
38-
# linestarts contains bloc code adresse (addr,block)
38+
39+
# linestarts is a tuple of (offset, line number.
40+
# Turn that in a has that we can index
3941
self.linestarts = list(dis.findlinestarts(co))
42+
linestartoffsets = {}
43+
for offset, lineno in self.linestarts:
44+
linestartoffsets[offset] = lineno
45+
4046
self.prev = [0]
4147

4248
# class and names
@@ -72,7 +78,13 @@ def unmangle(name):
7278
linestarts = self.linestarts
7379
self.lines = []
7480
linetuple = namedtuple('linetuple', ['l_no', 'next'])
75-
linestartoffsets = {a for (a, _) in linestarts}
81+
82+
# linestarts is a tuple of (offset, line number).
83+
# Turn that in a has that we can index
84+
linestartoffsets = {}
85+
for offset, lineno in linestarts:
86+
linestartoffsets[offset] = lineno
87+
7688
(prev_start_byte, prev_line_no) = linestarts[0]
7789
for (start_byte, line_no) in linestarts[1:]:
7890
while j < start_byte:
@@ -202,16 +214,16 @@ def unmangle(name):
202214
if offset in self.return_end_ifs:
203215
op_name = 'RETURN_END_IF'
204216

217+
if offset in linestartoffsets:
218+
linestart = linestartoffsets[offset]
219+
else:
220+
linestart = None
221+
205222
if offset not in replace:
206-
rv.append(Token(op_name, oparg, pattr, offset, linestart = offset in linestartoffsets))
223+
rv.append(Token(op_name, oparg, pattr, offset, linestart))
207224
else:
208-
rv.append(Token(replace[offset], oparg, pattr, offset, linestart = offset in linestartoffsets))
225+
rv.append(Token(replace[offset], oparg, pattr, offset, linestart))
209226

210-
if self.showasm:
211-
out = self.out # shortcut
212-
for t in rv:
213-
print >>out, t
214-
print >>out
215227
return rv, customize
216228

217229
def getOpcodeToDel(self, i):

uncompyle6/scanners/scanner26.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from uncompyle6.opcodes.opcode_26 import *
1515
import dis
16-
import scanner as scan
16+
import uncompyle6.scanner as scan
1717

1818
class Scanner26(scan.Scanner):
1919
def __init__(self):
@@ -71,7 +71,13 @@ def unmangle(name):
7171
linestarts = self.linestarts
7272
self.lines = []
7373
linetuple = namedtuple('linetuple', ['l_no', 'next'])
74-
linestartoffsets = {a for (a, _) in linestarts}
74+
75+
# linestarts is a tuple of (offset, line number).
76+
# Turn that in a has that we can index
77+
linestartoffsets = {}
78+
for offset, lineno in linestarts:
79+
linestartoffsets[offset] = lineno
80+
7581
(prev_start_byte, prev_line_no) = linestarts[0]
7682
for (start_byte, line_no) in linestarts[1:]:
7783
while j < start_byte:
@@ -202,16 +208,16 @@ def unmangle(name):
202208
if offset in self.return_end_ifs:
203209
op_name = 'RETURN_END_IF'
204210

211+
if offset in linestartoffsets:
212+
linestart = linestartoffsets[offset]
213+
else:
214+
linestart = None
215+
205216
if offset not in replace:
206-
rv.append(Token(op_name, oparg, pattr, offset, linestart = offset in linestartoffsets))
217+
rv.append(Token(op_name, oparg, pattr, offset, linestart))
207218
else:
208-
rv.append(Token(replace[offset], oparg, pattr, offset, linestart = offset in linestartoffsets))
219+
rv.append(Token(replace[offset], oparg, pattr, offset, linestart))
209220

210-
if self.showasm:
211-
out = self.out # shortcut
212-
for t in rv:
213-
print >>out, t
214-
print >>out
215221
return rv, customize
216222

217223
def getOpcodeToDel(self, i):

0 commit comments

Comments
 (0)
X Tutup