dis.py 9.61 KB
Newer Older
1
"""Disassembler of Python byte code into mnemonics."""
Guido van Rossum's avatar
Guido van Rossum committed
2 3

import sys
4
import types
Guido van Rossum's avatar
Guido van Rossum committed
5

6 7 8
from opcode import *
from opcode import __all__ as _opcodes_all

9
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
10
           "findlinestarts", "findlabels", "show_code"] + _opcodes_all
11
del _opcodes_all
12

Benjamin Peterson's avatar
Benjamin Peterson committed
13 14
_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)

15 16 17 18 19 20 21 22 23 24 25 26 27
def _try_compile(source, name):
    """Attempts to compile the given source, first as an expression and
       then as a statement if the first approach fails.

       Utility function to accept strings in functions that otherwise
       expect code objects
    """
    try:
        c = compile(source, name, 'eval')
    except SyntaxError:
        c = compile(source, name, 'exec')
    return c

28
def dis(x=None):
Tim Peters's avatar
Tim Peters committed
29 30 31 32 33
    """Disassemble classes, methods, functions, or code.

    With no argument, disassemble the last traceback.

    """
34
    if x is None:
Tim Peters's avatar
Tim Peters committed
35 36
        distb()
        return
37
    if hasattr(x, '__func__'):  # Method
38
        x = x.__func__
39
    if hasattr(x, '__code__'):  # Function
40
        x = x.__code__
41
    if hasattr(x, '__dict__'):  # Class or module
42
        items = sorted(x.__dict__.items())
Tim Peters's avatar
Tim Peters committed
43
        for name, x1 in items:
Benjamin Peterson's avatar
Benjamin Peterson committed
44
            if isinstance(x1, _have_code):
45
                print("Disassembly of %s:" % name)
Tim Peters's avatar
Tim Peters committed
46 47
                try:
                    dis(x1)
48
                except TypeError as msg:
49 50
                    print("Sorry:", msg)
                print()
51
    elif hasattr(x, 'co_code'): # Code object
52
        disassemble(x)
53
    elif isinstance(x, (bytes, bytearray)): # Raw bytecode
54
        _disassemble_bytes(x)
55
    elif isinstance(x, str):    # Source code
56
        _disassemble_str(x)
Tim Peters's avatar
Tim Peters committed
57
    else:
58 59
        raise TypeError("don't know how to disassemble %s objects" %
                        type(x).__name__)
Guido van Rossum's avatar
Guido van Rossum committed
60

61
def distb(tb=None):
Tim Peters's avatar
Tim Peters committed
62
    """Disassemble a traceback (default: last traceback)."""
63
    if tb is None:
Tim Peters's avatar
Tim Peters committed
64 65 66
        try:
            tb = sys.last_traceback
        except AttributeError:
67
            raise RuntimeError("no last traceback to disassemble")
Tim Peters's avatar
Tim Peters committed
68 69
        while tb.tb_next: tb = tb.tb_next
    disassemble(tb.tb_frame.f_code, tb.tb_lasti)
Guido van Rossum's avatar
Guido van Rossum committed
70

71 72 73 74
# The inspect module interrogates this dictionary to build its
# list of CO_* constants. It is also used by pretty_flags to
# turn the co_flags field into a human readable list.
COMPILER_FLAG_NAMES = {
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
     1: "OPTIMIZED",
     2: "NEWLOCALS",
     4: "VARARGS",
     8: "VARKEYWORDS",
    16: "NESTED",
    32: "GENERATOR",
    64: "NOFREE",
}

def pretty_flags(flags):
    """Return pretty representation of code flags."""
    names = []
    for i in range(32):
        flag = 1<<i
        if flags & flag:
90
            names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
91 92 93 94 95 96 97
            flags ^= flag
            if not flags:
                break
    else:
        names.append(hex(flags))
    return ", ".join(names)

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
def code_info(x):
    """Formatted details of methods, functions, or code."""
    if hasattr(x, '__func__'): # Method
        x = x.__func__
    if hasattr(x, '__code__'): # Function
        x = x.__code__
    if isinstance(x, str):     # Source code
        x = _try_compile(x, "<code_info>")
    if hasattr(x, 'co_code'):  # Code object
        return _format_code_info(x)
    else:
        raise TypeError("don't know how to disassemble %s objects" %
                        type(x).__name__)

def _format_code_info(co):
    lines = []
    lines.append("Name:              %s" % co.co_name)
    lines.append("Filename:          %s" % co.co_filename)
    lines.append("Argument count:    %s" % co.co_argcount)
    lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
    lines.append("Number of locals:  %s" % co.co_nlocals)
    lines.append("Stack size:        %s" % co.co_stacksize)
    lines.append("Flags:             %s" % pretty_flags(co.co_flags))
121
    if co.co_consts:
122
        lines.append("Constants:")
123
        for i_c in enumerate(co.co_consts):
124
            lines.append("%4d: %r" % i_c)
125
    if co.co_names:
126
        lines.append("Names:")
127
        for i_n in enumerate(co.co_names):
128
            lines.append("%4d: %s" % i_n)
129
    if co.co_varnames:
130
        lines.append("Variable names:")
131
        for i_n in enumerate(co.co_varnames):
132
            lines.append("%4d: %s" % i_n)
133
    if co.co_freevars:
134
        lines.append("Free variables:")
135
        for i_n in enumerate(co.co_freevars):
136
            lines.append("%4d: %s" % i_n)
137
    if co.co_cellvars:
138
        lines.append("Cell variables:")
139
        for i_n in enumerate(co.co_cellvars):
140 141 142 143
            lines.append("%4d: %s" % i_n)
    return "\n".join(lines)

def show_code(co):
144
    """Print details of methods, functions, or code to stdout."""
145
    print(code_info(co))
146

147
def disassemble(co, lasti=-1):
Tim Peters's avatar
Tim Peters committed
148 149 150
    """Disassemble a code object."""
    code = co.co_code
    labels = findlabels(code)
151
    linestarts = dict(findlinestarts(co))
Tim Peters's avatar
Tim Peters committed
152 153 154
    n = len(code)
    i = 0
    extended_arg = 0
Jeremy Hylton's avatar
Jeremy Hylton committed
155
    free = None
Tim Peters's avatar
Tim Peters committed
156
    while i < n:
157
        op = code[i]
158
        if i in linestarts:
Michael W. Hudson's avatar
Michael W. Hudson committed
159
            if i > 0:
160 161
                print()
            print("%3d" % linestarts[i], end=' ')
Michael W. Hudson's avatar
Michael W. Hudson committed
162
        else:
163 164 165 166 167 168 169 170
            print('   ', end=' ')

        if i == lasti: print('-->', end=' ')
        else: print('   ', end=' ')
        if i in labels: print('>>', end=' ')
        else: print('  ', end=' ')
        print(repr(i).rjust(4), end=' ')
        print(opname[op].ljust(20), end=' ')
Tim Peters's avatar
Tim Peters committed
171 172
        i = i+1
        if op >= HAVE_ARGUMENT:
173
            oparg = code[i] + code[i+1]*256 + extended_arg
Tim Peters's avatar
Tim Peters committed
174 175 176
            extended_arg = 0
            i = i+2
            if op == EXTENDED_ARG:
177
                extended_arg = oparg*65536
178
            print(repr(oparg).rjust(5), end=' ')
Tim Peters's avatar
Tim Peters committed
179
            if op in hasconst:
180
                print('(' + repr(co.co_consts[oparg]) + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
181
            elif op in hasname:
182
                print('(' + co.co_names[oparg] + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
183
            elif op in hasjrel:
184
                print('(to ' + repr(i + oparg) + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
185
            elif op in haslocal:
186
                print('(' + co.co_varnames[oparg] + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
187
            elif op in hascompare:
188
                print('(' + cmp_op[oparg] + ')', end=' ')
Jeremy Hylton's avatar
Jeremy Hylton committed
189 190 191
            elif op in hasfree:
                if free is None:
                    free = co.co_cellvars + co.co_freevars
192 193
                print('(' + free[oparg] + ')', end=' ')
        print()
Tim Peters's avatar
Tim Peters committed
194

195
def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
196
                       constants=None):
Tim Peters's avatar
Tim Peters committed
197 198 199 200
    labels = findlabels(code)
    n = len(code)
    i = 0
    while i < n:
201
        op = code[i]
202 203 204 205 206 207
        if i == lasti: print('-->', end=' ')
        else: print('   ', end=' ')
        if i in labels: print('>>', end=' ')
        else: print('  ', end=' ')
        print(repr(i).rjust(4), end=' ')
        print(opname[op].ljust(15), end=' ')
Tim Peters's avatar
Tim Peters committed
208 209
        i = i+1
        if op >= HAVE_ARGUMENT:
210
            oparg = code[i] + code[i+1]*256
Tim Peters's avatar
Tim Peters committed
211
            i = i+2
212
            print(repr(oparg).rjust(5), end=' ')
Tim Peters's avatar
Tim Peters committed
213 214
            if op in hasconst:
                if constants:
215
                    print('(' + repr(constants[oparg]) + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
216
                else:
217
                    print('(%d)'%oparg, end=' ')
Tim Peters's avatar
Tim Peters committed
218 219
            elif op in hasname:
                if names is not None:
220
                    print('(' + names[oparg] + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
221
                else:
222
                    print('(%d)'%oparg, end=' ')
Tim Peters's avatar
Tim Peters committed
223
            elif op in hasjrel:
224
                print('(to ' + repr(i + oparg) + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
225 226
            elif op in haslocal:
                if varnames:
227
                    print('(' + varnames[oparg] + ')', end=' ')
Tim Peters's avatar
Tim Peters committed
228
                else:
229
                    print('(%d)' % oparg, end=' ')
Tim Peters's avatar
Tim Peters committed
230
            elif op in hascompare:
231 232
                print('(' + cmp_op[oparg] + ')', end=' ')
        print()
233

234 235 236 237
def _disassemble_str(source):
    """Compile the source string, then disassemble the code object."""
    disassemble(_try_compile(source, '<dis>'))

Tim Peters's avatar
Tim Peters committed
238
disco = disassemble                     # XXX For backwards compatibility
239

Guido van Rossum's avatar
Guido van Rossum committed
240
def findlabels(code):
Tim Peters's avatar
Tim Peters committed
241 242 243 244 245 246 247 248 249
    """Detect all offsets in a byte code which are jump targets.

    Return the list of offsets.

    """
    labels = []
    n = len(code)
    i = 0
    while i < n:
250
        op = code[i]
Tim Peters's avatar
Tim Peters committed
251 252
        i = i+1
        if op >= HAVE_ARGUMENT:
253
            oparg = code[i] + code[i+1]*256
Tim Peters's avatar
Tim Peters committed
254 255 256 257 258 259 260 261 262 263
            i = i+2
            label = -1
            if op in hasjrel:
                label = i+oparg
            elif op in hasjabs:
                label = oparg
            if label >= 0:
                if label not in labels:
                    labels.append(label)
    return labels
Guido van Rossum's avatar
Guido van Rossum committed
264

265 266 267 268 269 270
def findlinestarts(code):
    """Find the offsets in a byte code which are start of lines in the source.

    Generate pairs (offset, lineno) as described in Python/compile.c.

    """
271 272
    byte_increments = list(code.co_lnotab[0::2])
    line_increments = list(code.co_lnotab[1::2])
273 274 275 276 277 278 279 280 281 282 283 284 285

    lastlineno = None
    lineno = code.co_firstlineno
    addr = 0
    for byte_incr, line_incr in zip(byte_increments, line_increments):
        if byte_incr:
            if lineno != lastlineno:
                yield (addr, lineno)
                lastlineno = lineno
            addr += byte_incr
        lineno += line_incr
    if lineno != lastlineno:
        yield (addr, lineno)
286 287

def _test():
Tim Peters's avatar
Tim Peters committed
288 289 290 291 292 293 294 295 296 297
    """Simple test program to disassemble a file."""
    if sys.argv[1:]:
        if sys.argv[2:]:
            sys.stderr.write("usage: python dis.py [-|file]\n")
            sys.exit(2)
        fn = sys.argv[1]
        if not fn or fn == "-":
            fn = None
    else:
        fn = None
298
    if fn is None:
Tim Peters's avatar
Tim Peters committed
299 300 301 302
        f = sys.stdin
    else:
        f = open(fn)
    source = f.read()
303
    if fn is not None:
Tim Peters's avatar
Tim Peters committed
304 305 306 307 308
        f.close()
    else:
        fn = "<stdin>"
    code = compile(source, fn, "exec")
    dis(code)
309 310

if __name__ == "__main__":
Tim Peters's avatar
Tim Peters committed
311
    _test()