OLD | NEW |
(Empty) | |
| 1 """Disassembler of Python byte code into mnemonics.""" |
| 2 |
| 3 # Adapted from stdlib dis.py, but returns structured information |
| 4 # instead of printing to stdout. |
| 5 |
| 6 import sys |
| 7 import types |
| 8 import collections |
| 9 |
| 10 from opcode import * |
| 11 from opcode import __all__ as _opcodes_all |
| 12 |
| 13 __all__ = ["dis", "disassemble", "distb", "disco", |
| 14 "findlinestarts", "findlabels"] + _opcodes_all |
| 15 del _opcodes_all |
| 16 |
| 17 def dis(x=None): |
| 18 for disline in disgen(x): |
| 19 if disline.first and disline.offset > 0: |
| 20 print() |
| 21 print(format_dis_line(disline)) |
| 22 |
| 23 def format_dis_line(disline): |
| 24 if disline.first: |
| 25 lineno = "%3d" % disline.lineno |
| 26 else: |
| 27 lineno = " " |
| 28 if disline.target: |
| 29 label = ">>" |
| 30 else: |
| 31 label = " " |
| 32 if disline.oparg is not None: |
| 33 oparg = repr(disline.oparg) |
| 34 else: |
| 35 oparg = "" |
| 36 return "%s %s %4r %-20s %5s %s" % (lineno, label, disline.offset, disline
.opcode, oparg, disline.argstr) |
| 37 |
| 38 def disgen(x=None): |
| 39 """Disassemble methods, functions, or code. |
| 40 |
| 41 With no argument, disassemble the last traceback. |
| 42 |
| 43 """ |
| 44 if x is None: |
| 45 return distb() |
| 46 if hasattr(x, 'im_func'): |
| 47 x = x.im_func |
| 48 if hasattr(x, 'func_code'): |
| 49 x = x.func_code |
| 50 if hasattr(x, 'co_code'): |
| 51 return disassemble(x) |
| 52 else: |
| 53 raise TypeError( |
| 54 "don't know how to disassemble %s objects" % |
| 55 type(x).__name__ |
| 56 ) |
| 57 |
| 58 def distb(tb=None): |
| 59 """Disassemble a traceback (default: last traceback).""" |
| 60 if tb is None: |
| 61 try: |
| 62 tb = sys.last_traceback |
| 63 except AttributeError: |
| 64 raise RuntimeError("no last traceback to disassemble") |
| 65 while tb.tb_next: |
| 66 tb = tb.tb_next |
| 67 return disassemble(tb.tb_frame.f_code, tb.tb_lasti) |
| 68 |
| 69 DisLine = collections.namedtuple( |
| 70 'DisLine', |
| 71 "lineno first target offset opcode oparg argstr" |
| 72 ) |
| 73 |
| 74 def disassemble(co, lasti=-1): |
| 75 """Disassemble a code object.""" |
| 76 code = co.co_code |
| 77 labels = findlabels(code) |
| 78 linestarts = dict(findlinestarts(co)) |
| 79 n = len(code) |
| 80 i = 0 |
| 81 extended_arg = 0 |
| 82 free = None |
| 83 |
| 84 dislines = [] |
| 85 lineno = linestarts[0] |
| 86 |
| 87 while i < n: |
| 88 op = byte_from_code(code, i) |
| 89 first = i in linestarts |
| 90 if first: |
| 91 lineno = linestarts[i] |
| 92 |
| 93 #if i == lasti: print '-->', |
| 94 #else: print ' ', |
| 95 target = i in labels |
| 96 offset = i |
| 97 opcode = opname[op] |
| 98 i = i+1 |
| 99 if op >= HAVE_ARGUMENT: |
| 100 oparg = byte_from_code(code, i) + byte_from_code(code, i+1)*256 + ex
tended_arg |
| 101 extended_arg = 0 |
| 102 i = i+2 |
| 103 if op == EXTENDED_ARG: |
| 104 extended_arg = oparg*65536 |
| 105 if op in hasconst: |
| 106 argstr = '(' + repr(co.co_consts[oparg]) + ')' |
| 107 elif op in hasname: |
| 108 argstr = '(' + co.co_names[oparg] + ')' |
| 109 elif op in hasjabs: |
| 110 argstr = '(-> ' + repr(oparg) + ')' |
| 111 elif op in hasjrel: |
| 112 argstr = '(-> ' + repr(i + oparg) + ')' |
| 113 elif op in haslocal: |
| 114 argstr = '(' + co.co_varnames[oparg] + ')' |
| 115 elif op in hascompare: |
| 116 argstr = '(' + cmp_op[oparg] + ')' |
| 117 elif op in hasfree: |
| 118 if free is None: |
| 119 free = co.co_cellvars + co.co_freevars |
| 120 argstr = '(' + free[oparg] + ')' |
| 121 else: |
| 122 argstr = "" |
| 123 else: |
| 124 oparg = None |
| 125 argstr = "" |
| 126 yield DisLine(lineno=lineno, first=first, target=target, offset=offset,
opcode=opcode, oparg=oparg, argstr=argstr) |
| 127 |
| 128 |
| 129 def byte_from_code(code, i): |
| 130 byte = code[i] |
| 131 if not isinstance(byte, int): |
| 132 byte = ord(byte) |
| 133 return byte |
| 134 |
| 135 def findlabels(code): |
| 136 """Detect all offsets in a byte code which are jump targets. |
| 137 |
| 138 Return the list of offsets. |
| 139 |
| 140 """ |
| 141 labels = [] |
| 142 n = len(code) |
| 143 i = 0 |
| 144 while i < n: |
| 145 op = byte_from_code(code, i) |
| 146 i = i+1 |
| 147 if op >= HAVE_ARGUMENT: |
| 148 oparg = byte_from_code(code, i) + byte_from_code(code, i+1)*256 |
| 149 i = i+2 |
| 150 label = -1 |
| 151 if op in hasjrel: |
| 152 label = i+oparg |
| 153 elif op in hasjabs: |
| 154 label = oparg |
| 155 if label >= 0: |
| 156 if label not in labels: |
| 157 labels.append(label) |
| 158 return labels |
| 159 |
| 160 def findlinestarts(code): |
| 161 """Find the offsets in a byte code which are start of lines in the source. |
| 162 |
| 163 Generate pairs (offset, lineno) as described in Python/compile.c. |
| 164 |
| 165 """ |
| 166 byte_increments = [byte_from_code(code.co_lnotab, i) for i in range(0, len(c
ode.co_lnotab), 2)] |
| 167 line_increments = [byte_from_code(code.co_lnotab, i) for i in range(1, len(c
ode.co_lnotab), 2)] |
| 168 |
| 169 lastlineno = None |
| 170 lineno = code.co_firstlineno |
| 171 addr = 0 |
| 172 for byte_incr, line_incr in zip(byte_increments, line_increments): |
| 173 if byte_incr: |
| 174 if lineno != lastlineno: |
| 175 yield (addr, lineno) |
| 176 lastlineno = lineno |
| 177 addr += byte_incr |
| 178 lineno += line_incr |
| 179 if lineno != lastlineno: |
| 180 yield (addr, lineno) |
| 181 |
| 182 def _test(): |
| 183 """Simple test program to disassemble a file.""" |
| 184 if sys.argv[1:]: |
| 185 if sys.argv[2:]: |
| 186 sys.stderr.write("usage: python dis.py [-|file]\n") |
| 187 sys.exit(2) |
| 188 fn = sys.argv[1] |
| 189 if not fn or fn == "-": |
| 190 fn = None |
| 191 else: |
| 192 fn = None |
| 193 if fn is None: |
| 194 f = sys.stdin |
| 195 else: |
| 196 f = open(fn) |
| 197 source = f.read() |
| 198 if fn is not None: |
| 199 f.close() |
| 200 else: |
| 201 fn = "<stdin>" |
| 202 code = compile(source, fn, "exec") |
| 203 dis(code) |
| 204 |
| 205 if __name__ == "__main__": |
| 206 _test() |
OLD | NEW |