/lab/disgen.py

https://gitlab.com/0072016/coveragepy · Python · 206 lines · 163 code · 20 blank · 23 comment · 55 complexity · fe91d7ed83f7e3f835ae6883b2908937 MD5 · raw file

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