codekingpro/portable-devtools
114k
1"""Disassembler of Python byte code into mnemonics."""2 3import sys4import types5import collections6import io7 8from opcode import *9from opcode import (10 __all__ as _opcodes_all,11 _cache_format,12 _inline_cache_entries,13 _nb_ops,14 _common_constants,15 _intrinsic_1_descs,16 _intrinsic_2_descs,17 _special_method_names,18 _specializations,19 _specialized_opmap,20)21 22from _opcode import get_executor23 24__all__ = ["code_info", "dis", "disassemble", "distb", "disco",25 "findlinestarts", "findlabels", "show_code",26 "get_instructions", "Instruction", "Bytecode"] + _opcodes_all27del _opcodes_all28 29_have_code = (types.MethodType, types.FunctionType, types.CodeType,30 classmethod, staticmethod, type)31 32CONVERT_VALUE = opmap['CONVERT_VALUE']33 34SET_FUNCTION_ATTRIBUTE = opmap['SET_FUNCTION_ATTRIBUTE']35FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure', 'annotate')36 37ENTER_EXECUTOR = opmap['ENTER_EXECUTOR']38LOAD_GLOBAL = opmap['LOAD_GLOBAL']39LOAD_SMALL_INT = opmap['LOAD_SMALL_INT']40BINARY_OP = opmap['BINARY_OP']41JUMP_BACKWARD = opmap['JUMP_BACKWARD']42FOR_ITER = opmap['FOR_ITER']43SEND = opmap['SEND']44LOAD_ATTR = opmap['LOAD_ATTR']45LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR']46CALL_INTRINSIC_1 = opmap['CALL_INTRINSIC_1']47CALL_INTRINSIC_2 = opmap['CALL_INTRINSIC_2']48LOAD_COMMON_CONSTANT = opmap['LOAD_COMMON_CONSTANT']49LOAD_SPECIAL = opmap['LOAD_SPECIAL']50LOAD_FAST_LOAD_FAST = opmap['LOAD_FAST_LOAD_FAST']51LOAD_FAST_BORROW_LOAD_FAST_BORROW = opmap['LOAD_FAST_BORROW_LOAD_FAST_BORROW']52STORE_FAST_LOAD_FAST = opmap['STORE_FAST_LOAD_FAST']53STORE_FAST_STORE_FAST = opmap['STORE_FAST_STORE_FAST']54IS_OP = opmap['IS_OP']55CONTAINS_OP = opmap['CONTAINS_OP']56END_ASYNC_FOR = opmap['END_ASYNC_FOR']57 58CACHE = opmap["CACHE"]59 60_all_opname = list(opname)61_all_opmap = dict(opmap)62for name, op in _specialized_opmap.items():63 # fill opname and opmap64 assert op < len(_all_opname)65 _all_opname[op] = name66 _all_opmap[name] = op67 68deoptmap = {69 specialized: base for base, family in _specializations.items() for specialized in family70}71 72def _try_compile(source, name):73 """Attempts to compile the given source, first as an expression and74 then as a statement if the first approach fails.75 76 Utility function to accept strings in functions that otherwise77 expect code objects78 """79 try:80 return compile(source, name, 'eval')81 except SyntaxError:82 pass83 return compile(source, name, 'exec')84 85def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False,86 show_offsets=False, show_positions=False):87 """Disassemble classes, methods, functions, and other compiled objects.88 89 With no argument, disassemble the last traceback.90 91 Compiled objects currently include generator objects, async generator92 objects, and coroutine objects, all of which store their code object93 in a special attribute.94 """95 if x is None:96 distb(file=file, show_caches=show_caches, adaptive=adaptive,97 show_offsets=show_offsets, show_positions=show_positions)98 return99 # Extract functions from methods.100 if hasattr(x, '__func__'):101 x = x.__func__102 # Extract compiled code objects from...103 if hasattr(x, '__code__'): # ...a function, or104 x = x.__code__105 elif hasattr(x, 'gi_code'): #...a generator object, or106 x = x.gi_code107 elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or108 x = x.ag_code109 elif hasattr(x, 'cr_code'): #...a coroutine.110 x = x.cr_code111 # Perform the disassembly.112 if hasattr(x, '__dict__'): # Class or module113 items = sorted(x.__dict__.items())114 for name, x1 in items:115 if isinstance(x1, _have_code):116 print("Disassembly of %s:" % name, file=file)117 try:118 dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)119 except TypeError as msg:120 print("Sorry:", msg, file=file)121 print(file=file)122 elif hasattr(x, 'co_code'): # Code object123 _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)124 elif isinstance(x, (bytes, bytearray)): # Raw bytecode125 labels_map = _make_labels_map(x)126 label_width = 4 + len(str(len(labels_map)))127 formatter = Formatter(file=file,128 offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0,129 label_width=label_width,130 show_caches=show_caches)131 arg_resolver = ArgResolver(labels_map=labels_map)132 _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter)133 elif isinstance(x, str): # Source code134 _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)135 else:136 raise TypeError("don't know how to disassemble %s objects" %137 type(x).__name__)138 139def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):140 """Disassemble a traceback (default: last traceback)."""141 if tb is None:142 try:143 if hasattr(sys, 'last_exc'):144 tb = sys.last_exc.__traceback__145 else:146 tb = sys.last_traceback147 except AttributeError:148 raise RuntimeError("no last traceback to disassemble") from None149 while tb.tb_next: tb = tb.tb_next150 disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)151 152# The inspect module interrogates this dictionary to build its153# list of CO_* constants. It is also used by pretty_flags to154# turn the co_flags field into a human readable list.155COMPILER_FLAG_NAMES = {156 1: "OPTIMIZED",157 2: "NEWLOCALS",158 4: "VARARGS",159 8: "VARKEYWORDS",160 16: "NESTED",161 32: "GENERATOR",162 64: "NOFREE",163 128: "COROUTINE",164 256: "ITERABLE_COROUTINE",165 512: "ASYNC_GENERATOR",166 0x4000000: "HAS_DOCSTRING",167 0x8000000: "METHOD",168}169 170def pretty_flags(flags):171 """Return pretty representation of code flags."""172 names = []173 for i in range(32):174 flag = 1<<i175 if flags & flag:176 names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))177 flags ^= flag178 if not flags:179 break180 else:181 names.append(hex(flags))182 return ", ".join(names)183 184class _Unknown:185 def __repr__(self):186 return "<unknown>"187 188# Sentinel to represent values that cannot be calculated189UNKNOWN = _Unknown()190 191def _get_code_object(x):192 """Helper to handle methods, compiled or raw code objects, and strings."""193 # Extract functions from methods.194 if hasattr(x, '__func__'):195 x = x.__func__196 # Extract compiled code objects from...197 if hasattr(x, '__code__'): # ...a function, or198 x = x.__code__199 elif hasattr(x, 'gi_code'): #...a generator object, or200 x = x.gi_code201 elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or202 x = x.ag_code203 elif hasattr(x, 'cr_code'): #...a coroutine.204 x = x.cr_code205 # Handle source code.206 if isinstance(x, str):207 x = _try_compile(x, "<disassembly>")208 # By now, if we don't have a code object, we can't disassemble x.209 if hasattr(x, 'co_code'):210 return x211 raise TypeError("don't know how to disassemble %s objects" %212 type(x).__name__)213 214def _deoptop(op):215 name = _all_opname[op]216 return _all_opmap[deoptmap[name]] if name in deoptmap else op217 218def _get_code_array(co, adaptive):219 if adaptive:220 code = co._co_code_adaptive221 res = []222 found = False223 for i in range(0, len(code), 2):224 op, arg = code[i], code[i+1]225 if op == ENTER_EXECUTOR:226 try:227 ex = get_executor(co, i)228 except (ValueError, RuntimeError):229 ex = None230 231 if ex:232 op, arg = ex.get_opcode(), ex.get_oparg()233 found = True234 235 res.append(op.to_bytes())236 res.append(arg.to_bytes())237 return code if not found else b''.join(res)238 else:239 return co.co_code240 241def code_info(x):242 """Formatted details of methods, functions, or code."""243 return _format_code_info(_get_code_object(x))244 245def _format_code_info(co):246 lines = []247 lines.append("Name: %s" % co.co_name)248 lines.append("Filename: %s" % co.co_filename)249 lines.append("Argument count: %s" % co.co_argcount)250 lines.append("Positional-only arguments: %s" % co.co_posonlyargcount)251 lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)252 lines.append("Number of locals: %s" % co.co_nlocals)253 lines.append("Stack size: %s" % co.co_stacksize)254 lines.append("Flags: %s" % pretty_flags(co.co_flags))255 if co.co_consts:256 lines.append("Constants:")257 for i_c in enumerate(co.co_consts):258 lines.append("%4d: %r" % i_c)259 if co.co_names:260 lines.append("Names:")261 for i_n in enumerate(co.co_names):262 lines.append("%4d: %s" % i_n)263 if co.co_varnames:264 lines.append("Variable names:")265 for i_n in enumerate(co.co_varnames):266 lines.append("%4d: %s" % i_n)267 if co.co_freevars:268 lines.append("Free variables:")269 for i_n in enumerate(co.co_freevars):270 lines.append("%4d: %s" % i_n)271 if co.co_cellvars:272 lines.append("Cell variables:")273 for i_n in enumerate(co.co_cellvars):274 lines.append("%4d: %s" % i_n)275 return "\n".join(lines)276 277def show_code(co, *, file=None):278 """Print details of methods, functions, or code to *file*.279 280 If *file* is not provided, the output is printed on stdout.281 """282 print(code_info(co), file=file)283 284Positions = collections.namedtuple(285 'Positions',286 [287 'lineno',288 'end_lineno',289 'col_offset',290 'end_col_offset',291 ],292 defaults=[None] * 4293)294 295_Instruction = collections.namedtuple(296 "_Instruction",297 [298 'opname',299 'opcode',300 'arg',301 'argval',302 'argrepr',303 'offset',304 'start_offset',305 'starts_line',306 'line_number',307 'label',308 'positions',309 'cache_info',310 ],311 defaults=[None, None, None]312)313 314_Instruction.opname.__doc__ = "Human readable name for operation"315_Instruction.opcode.__doc__ = "Numeric code for operation"316_Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None"317_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg"318_Instruction.argrepr.__doc__ = "Human readable description of operation argument"319_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence"320_Instruction.start_offset.__doc__ = (321 "Start index of operation within bytecode sequence, including extended args if present; "322 "otherwise equal to Instruction.offset"323)324_Instruction.starts_line.__doc__ = "True if this opcode starts a source line, otherwise False"325_Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None"326_Instruction.label.__doc__ = "A label (int > 0) if this instruction is a jump target, otherwise None"327_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction"328_Instruction.cache_info.__doc__ = "list of (name, size, data), one for each cache entry of the instruction"329 330_ExceptionTableEntryBase = collections.namedtuple("_ExceptionTableEntryBase",331 "start end target depth lasti")332 333class _ExceptionTableEntry(_ExceptionTableEntryBase):334 pass335 336_OPNAME_WIDTH = 20337_OPARG_WIDTH = 5338 339def _get_cache_size(opname):340 return _inline_cache_entries.get(opname, 0)341 342def _get_jump_target(op, arg, offset):343 """Gets the bytecode offset of the jump target if this is a jump instruction.344 345 Otherwise return None.346 """347 deop = _deoptop(op)348 caches = _get_cache_size(_all_opname[deop])349 if deop in hasjrel:350 if _is_backward_jump(deop):351 arg = -arg352 target = offset + 2 + arg*2353 target += 2 * caches354 elif deop in hasjabs:355 target = arg*2356 else:357 target = None358 return target359 360class Instruction(_Instruction):361 """Details for a bytecode operation.362 363 Defined fields:364 opname - human readable name for operation365 opcode - numeric code for operation366 arg - numeric argument to operation (if any), otherwise None367 argval - resolved arg value (if known), otherwise same as arg368 argrepr - human readable description of operation argument369 offset - start index of operation within bytecode sequence370 start_offset - start index of operation within bytecode sequence including extended args if present;371 otherwise equal to Instruction.offset372 starts_line - True if this opcode starts a source line, otherwise False373 line_number - source line number associated with this opcode (if any), otherwise None374 label - A label if this instruction is a jump target, otherwise None375 positions - Optional dis.Positions object holding the span of source code376 covered by this instruction377 cache_info - information about the format and content of the instruction's cache378 entries (if any)379 """380 381 @staticmethod382 def make(383 opname, arg, argval, argrepr, offset, start_offset, starts_line,384 line_number, label=None, positions=None, cache_info=None385 ):386 return Instruction(opname, _all_opmap[opname], arg, argval, argrepr, offset,387 start_offset, starts_line, line_number, label, positions, cache_info)388 389 @property390 def oparg(self):391 """Alias for Instruction.arg."""392 return self.arg393 394 @property395 def baseopcode(self):396 """Numeric code for the base operation if operation is specialized.397 398 Otherwise equal to Instruction.opcode.399 """400 return _deoptop(self.opcode)401 402 @property403 def baseopname(self):404 """Human readable name for the base operation if operation is specialized.405 406 Otherwise equal to Instruction.opname.407 """408 return opname[self.baseopcode]409 410 @property411 def cache_offset(self):412 """Start index of the cache entries following the operation."""413 return self.offset + 2414 415 @property416 def end_offset(self):417 """End index of the cache entries following the operation."""418 return self.cache_offset + _get_cache_size(_all_opname[self.opcode])*2419 420 @property421 def jump_target(self):422 """Bytecode index of the jump target if this is a jump operation.423 424 Otherwise return None.425 """426 return _get_jump_target(self.opcode, self.arg, self.offset)427 428 @property429 def is_jump_target(self):430 """True if other code jumps to here, otherwise False"""431 return self.label is not None432 433 def __str__(self):434 output = io.StringIO()435 formatter = Formatter(file=output)436 formatter.print_instruction(self, False)437 return output.getvalue()438 439 440class Formatter:441 442 def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0,443 line_offset=0, show_caches=False, *, show_positions=False):444 """Create a Formatter445 446 *file* where to write the output447 *lineno_width* sets the width of the source location field (0 omits it).448 Should be large enough for a line number or full positions (depending449 on the value of *show_positions*).450 *offset_width* sets the width of the instruction offset field451 *label_width* sets the width of the label field452 *show_caches* is a boolean indicating whether to display cache lines453 *show_positions* is a boolean indicating whether full positions should454 be reported instead of only the line numbers.455 """456 self.file = file457 self.lineno_width = lineno_width458 self.offset_width = offset_width459 self.label_width = label_width460 self.show_caches = show_caches461 self.show_positions = show_positions462 463 def print_instruction(self, instr, mark_as_current=False):464 self.print_instruction_line(instr, mark_as_current)465 if self.show_caches and instr.cache_info:466 offset = instr.offset467 for name, size, data in instr.cache_info:468 for i in range(size):469 offset += 2470 # Only show the fancy argrepr for a CACHE instruction when it's471 # the first entry for a particular cache value:472 if i == 0:473 argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"474 else:475 argrepr = ""476 self.print_instruction_line(477 Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset,478 False, None, None, instr.positions),479 False)480 481 def print_instruction_line(self, instr, mark_as_current):482 """Format instruction details for inclusion in disassembly output."""483 lineno_width = self.lineno_width484 offset_width = self.offset_width485 label_width = self.label_width486 487 new_source_line = (lineno_width > 0 and488 instr.starts_line and489 instr.offset > 0)490 if new_source_line:491 print(file=self.file)492 493 fields = []494 # Column: Source code locations information495 if lineno_width:496 if self.show_positions:497 # reporting positions instead of just line numbers498 if instr_positions := instr.positions:499 if all(p is None for p in instr_positions):500 positions_str = _NO_LINENO501 else:502 ps = tuple('?' if p is None else p for p in instr_positions)503 positions_str = f"{ps[0]}:{ps[2]}-{ps[1]}:{ps[3]}"504 fields.append(f'{positions_str:{lineno_width}}')505 else:506 fields.append(' ' * lineno_width)507 else:508 if instr.starts_line:509 lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds"510 lineno_fmt = lineno_fmt % lineno_width511 lineno = _NO_LINENO if instr.line_number is None else instr.line_number512 fields.append(lineno_fmt % lineno)513 else:514 fields.append(' ' * lineno_width)515 # Column: Label516 if instr.label is not None:517 lbl = f"L{instr.label}:"518 fields.append(f"{lbl:>{label_width}}")519 else:520 fields.append(' ' * label_width)521 # Column: Instruction offset from start of code sequence522 if offset_width > 0:523 fields.append(f"{repr(instr.offset):>{offset_width}} ")524 # Column: Current instruction indicator525 if mark_as_current:526 fields.append('-->')527 else:528 fields.append(' ')529 # Column: Opcode name530 fields.append(instr.opname.ljust(_OPNAME_WIDTH))531 # Column: Opcode argument532 if instr.arg is not None:533 arg = repr(instr.arg)534 # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into535 # the space reserved for oparg. This results in fewer misaligned opargs536 # in the disassembly output.537 opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH)538 fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess))539 # Column: Opcode argument details540 if instr.argrepr:541 fields.append('(' + instr.argrepr + ')')542 print(' '.join(fields).rstrip(), file=self.file)543 544 def print_exception_table(self, exception_entries):545 file = self.file546 if exception_entries:547 print("ExceptionTable:", file=file)548 for entry in exception_entries:549 lasti = " lasti" if entry.lasti else ""550 start = entry.start_label551 end = entry.end_label552 target = entry.target_label553 print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file)554 555 556class ArgResolver:557 def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None):558 self.co_consts = co_consts559 self.names = names560 self.varname_from_oparg = varname_from_oparg561 self.labels_map = labels_map or {}562 563 def offset_from_jump_arg(self, op, arg, offset):564 deop = _deoptop(op)565 if deop in hasjabs:566 return arg * 2567 elif deop in hasjrel:568 signed_arg = -arg if _is_backward_jump(deop) else arg569 argval = offset + 2 + signed_arg*2570 caches = _get_cache_size(_all_opname[deop])571 argval += 2 * caches572 return argval573 return None574 575 def get_label_for_offset(self, offset):576 return self.labels_map.get(offset, None)577 578 def get_argval_argrepr(self, op, arg, offset):579 get_name = None if self.names is None else self.names.__getitem__580 argval = None581 argrepr = ''582 deop = _deoptop(op)583 if arg is not None:584 # Set argval to the dereferenced value of the argument when585 # available, and argrepr to the string representation of argval.586 # _disassemble_bytes needs the string repr of the587 # raw name index for LOAD_GLOBAL, LOAD_CONST, etc.588 argval = arg589 if deop in hasconst:590 argval, argrepr = _get_const_info(deop, arg, self.co_consts)591 elif deop in hasname:592 if deop == LOAD_GLOBAL:593 argval, argrepr = _get_name_info(arg//2, get_name)594 if (arg & 1) and argrepr:595 argrepr = f"{argrepr} + NULL"596 elif deop == LOAD_ATTR:597 argval, argrepr = _get_name_info(arg//2, get_name)598 if (arg & 1) and argrepr:599 argrepr = f"{argrepr} + NULL|self"600 elif deop == LOAD_SUPER_ATTR:601 argval, argrepr = _get_name_info(arg//4, get_name)602 if (arg & 1) and argrepr:603 argrepr = f"{argrepr} + NULL|self"604 else:605 argval, argrepr = _get_name_info(arg, get_name)606 elif deop in hasjump or deop in hasexc:607 argval = self.offset_from_jump_arg(op, arg, offset)608 lbl = self.get_label_for_offset(argval)609 assert lbl is not None610 preposition = "from" if deop == END_ASYNC_FOR else "to"611 argrepr = f"{preposition} L{lbl}"612 elif deop in (LOAD_FAST_LOAD_FAST, LOAD_FAST_BORROW_LOAD_FAST_BORROW, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST):613 arg1 = arg >> 4614 arg2 = arg & 15615 val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg)616 val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg)617 argrepr = argrepr1 + ", " + argrepr2618 argval = val1, val2619 elif deop in haslocal or deop in hasfree:620 argval, argrepr = _get_name_info(arg, self.varname_from_oparg)621 elif deop in hascompare:622 argval = cmp_op[arg >> 5]623 argrepr = argval624 if arg & 16:625 argrepr = f"bool({argrepr})"626 elif deop == CONVERT_VALUE:627 argval = (None, str, repr, ascii)[arg]628 argrepr = ('', 'str', 'repr', 'ascii')[arg]629 elif deop == SET_FUNCTION_ATTRIBUTE:630 argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS)631 if arg & (1<<i))632 elif deop == BINARY_OP:633 _, argrepr = _nb_ops[arg]634 elif deop == CALL_INTRINSIC_1:635 argrepr = _intrinsic_1_descs[arg]636 elif deop == CALL_INTRINSIC_2:637 argrepr = _intrinsic_2_descs[arg]638 elif deop == LOAD_COMMON_CONSTANT:639 obj = _common_constants[arg]640 if isinstance(obj, type):641 argrepr = obj.__name__642 else:643 argrepr = repr(obj)644 elif deop == LOAD_SPECIAL:645 argrepr = _special_method_names[arg]646 elif deop == IS_OP:647 argrepr = 'is not' if argval else 'is'648 elif deop == CONTAINS_OP:649 argrepr = 'not in' if argval else 'in'650 return argval, argrepr651 652def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False):653 """Iterator for the opcodes in methods, functions or code654 655 Generates a series of Instruction named tuples giving the details of656 each operations in the supplied code.657 658 If *first_line* is not None, it indicates the line number that should659 be reported for the first source line in the disassembled code.660 Otherwise, the source line information (if any) is taken directly from661 the disassembled code object.662 """663 co = _get_code_object(x)664 linestarts = dict(findlinestarts(co))665 if first_line is not None:666 line_offset = first_line - co.co_firstlineno667 else:668 line_offset = 0669 670 original_code = co.co_code671 arg_resolver = ArgResolver(co_consts=co.co_consts,672 names=co.co_names,673 varname_from_oparg=co._varname_from_oparg,674 labels_map=_make_labels_map(original_code))675 return _get_instructions_bytes(_get_code_array(co, adaptive),676 linestarts=linestarts,677 line_offset=line_offset,678 co_positions=co.co_positions(),679 original_code=original_code,680 arg_resolver=arg_resolver)681 682def _get_const_value(op, arg, co_consts):683 """Helper to get the value of the const in a hasconst op.684 685 Returns the dereferenced constant if this is possible.686 Otherwise (if it is a LOAD_CONST and co_consts is not687 provided) returns the dis.UNKNOWN sentinel.688 """689 assert op in hasconst or op == LOAD_SMALL_INT690 691 if op == LOAD_SMALL_INT:692 return arg693 argval = UNKNOWN694 if co_consts is not None:695 argval = co_consts[arg]696 return argval697 698def _get_const_info(op, arg, co_consts):699 """Helper to get optional details about const references700 701 Returns the dereferenced constant and its repr if the value702 can be calculated.703 Otherwise returns the sentinel value dis.UNKNOWN for the value704 and an empty string for its repr.705 """706 argval = _get_const_value(op, arg, co_consts)707 argrepr = repr(argval) if argval is not UNKNOWN else ''708 return argval, argrepr709 710def _get_name_info(name_index, get_name, **extrainfo):711 """Helper to get optional details about named references712 713 Returns the dereferenced name as both value and repr if the name714 list is defined.715 Otherwise returns the sentinel value dis.UNKNOWN for the value716 and an empty string for its repr.717 """718 if get_name is not None:719 argval = get_name(name_index, **extrainfo)720 return argval, argval721 else:722 return UNKNOWN, ''723 724def _parse_varint(iterator):725 b = next(iterator)726 val = b & 63727 while b&64:728 val <<= 6729 b = next(iterator)730 val |= b&63731 return val732 733def _parse_exception_table(code):734 iterator = iter(code.co_exceptiontable)735 entries = []736 try:737 while True:738 start = _parse_varint(iterator)*2739 length = _parse_varint(iterator)*2740 end = start + length741 target = _parse_varint(iterator)*2742 dl = _parse_varint(iterator)743 depth = dl >> 1744 lasti = bool(dl&1)745 entries.append(_ExceptionTableEntry(start, end, target, depth, lasti))746 except StopIteration:747 return entries748 749def _is_backward_jump(op):750 return opname[op] in ('JUMP_BACKWARD',751 'JUMP_BACKWARD_NO_INTERRUPT',752 'END_ASYNC_FOR') # Not really a jump, but it has a "target"753 754def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None,755 original_code=None, arg_resolver=None):756 """Iterate over the instructions in a bytecode string.757 758 Generates a sequence of Instruction namedtuples giving the details of each759 opcode.760 761 """762 # Use the basic, unadaptive code for finding labels and actually walking the763 # bytecode, since replacements like ENTER_EXECUTOR and INSTRUMENTED_* can764 # mess that logic up pretty badly:765 original_code = original_code or code766 co_positions = co_positions or iter(())767 768 starts_line = False769 local_line_number = None770 line_number = None771 for offset, start_offset, op, arg in _unpack_opargs(original_code):772 if linestarts is not None:773 starts_line = offset in linestarts774 if starts_line:775 local_line_number = linestarts[offset]776 if local_line_number is not None:777 line_number = local_line_number + line_offset778 else:779 line_number = None780 positions = Positions(*next(co_positions, ()))781 deop = _deoptop(op)782 op = code[offset]783 784 if arg_resolver:785 argval, argrepr = arg_resolver.get_argval_argrepr(op, arg, offset)786 else:787 argval, argrepr = arg, repr(arg)788 789 caches = _get_cache_size(_all_opname[deop])790 # Advance the co_positions iterator:791 for _ in range(caches):792 next(co_positions, ())793 794 if caches:795 cache_info = []796 cache_offset = offset797 for name, size in _cache_format[opname[deop]].items():798 data = code[cache_offset + 2: cache_offset + 2 + 2 * size]799 cache_offset += size * 2800 cache_info.append((name, size, data))801 else:802 cache_info = None803 804 label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None805 yield Instruction(_all_opname[op], op, arg, argval, argrepr,806 offset, start_offset, starts_line, line_number,807 label, positions, cache_info)808 809 810def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False,811 show_offsets=False, show_positions=False):812 """Disassemble a code object."""813 linestarts = dict(findlinestarts(co))814 exception_entries = _parse_exception_table(co)815 if show_positions:816 lineno_width = _get_positions_width(co)817 else:818 lineno_width = _get_lineno_width(linestarts)819 labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries)820 label_width = 4 + len(str(len(labels_map)))821 formatter = Formatter(file=file,822 lineno_width=lineno_width,823 offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0,824 label_width=label_width,825 show_caches=show_caches,826 show_positions=show_positions)827 arg_resolver = ArgResolver(co_consts=co.co_consts,828 names=co.co_names,829 varname_from_oparg=co._varname_from_oparg,830 labels_map=labels_map)831 _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts,832 exception_entries=exception_entries, co_positions=co.co_positions(),833 original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter)834 835def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):836 disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)837 if depth is None or depth > 0:838 if depth is not None:839 depth = depth - 1840 for x in co.co_consts:841 if hasattr(x, 'co_code'):842 print(file=file)843 print("Disassembly of %r:" % (x,), file=file)844 _disassemble_recursive(845 x, file=file, depth=depth, show_caches=show_caches,846 adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions847 )848 849 850def _make_labels_map(original_code, exception_entries=()):851 jump_targets = set(findlabels(original_code))852 labels = set(jump_targets)853 for start, end, target, _, _ in exception_entries:854 labels.add(start)855 labels.add(end)856 labels.add(target)857 labels = sorted(labels)858 labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))}859 for e in exception_entries:860 e.start_label = labels_map[e.start]861 e.end_label = labels_map[e.end]862 e.target_label = labels_map[e.target]863 return labels_map864 865_NO_LINENO = ' --'866 867def _get_lineno_width(linestarts):868 if linestarts is None:869 return 0870 maxlineno = max(filter(None, linestarts.values()), default=-1)871 if maxlineno == -1:872 # Omit the line number column entirely if we have no line number info873 return 0874 lineno_width = max(3, len(str(maxlineno)))875 if lineno_width < len(_NO_LINENO) and None in linestarts.values():876 lineno_width = len(_NO_LINENO)877 return lineno_width878 879def _get_positions_width(code):880 # Positions are formatted as 'LINE:COL-ENDLINE:ENDCOL ' (note trailing space).881 # A missing component appears as '?', and when all components are None, we882 # render '_NO_LINENO'. thus the minimum width is 1 + len(_NO_LINENO).883 #884 # If all values are missing, positions are not printed (i.e. positions_width = 0).885 has_value = False886 values_width = 0887 for positions in code.co_positions():888 has_value |= any(isinstance(p, int) for p in positions)889 width = sum(1 if p is None else len(str(p)) for p in positions)890 values_width = max(width, values_width)891 if has_value:892 # 3 = number of separators in a normal format893 return 1 + max(len(_NO_LINENO), 3 + values_width)894 return 0895 896def _disassemble_bytes(code, lasti=-1, linestarts=None,897 *, line_offset=0, exception_entries=(),898 co_positions=None, original_code=None,899 arg_resolver=None, formatter=None):900 901 assert formatter is not None902 assert arg_resolver is not None903 904 instrs = _get_instructions_bytes(code, linestarts=linestarts,905 line_offset=line_offset,906 co_positions=co_positions,907 original_code=original_code,908 arg_resolver=arg_resolver)909 910 print_instructions(instrs, exception_entries, formatter, lasti=lasti)911 912 913def print_instructions(instrs, exception_entries, formatter, lasti=-1):914 for instr in instrs:915 # Each CACHE takes 2 bytes916 is_current_instr = instr.offset <= lasti \917 <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)])918 formatter.print_instruction(instr, is_current_instr)919 920 formatter.print_exception_table(exception_entries)921 922def _disassemble_str(source, **kwargs):923 """Compile the source string, then disassemble the code object."""924 _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs)925 926disco = disassemble # XXX For backwards compatibility927 928 929# Rely on C `int` being 32 bits for oparg930_INT_BITS = 32931# Value for c int when it overflows932_INT_OVERFLOW = 2 ** (_INT_BITS - 1)933 934def _unpack_opargs(code):935 extended_arg = 0936 extended_args_offset = 0 # Number of EXTENDED_ARG instructions preceding the current instruction937 caches = 0938 for i in range(0, len(code), 2):939 # Skip inline CACHE entries:940 if caches:941 caches -= 1942 continue943 op = code[i]944 deop = _deoptop(op)945 caches = _get_cache_size(_all_opname[deop])946 if deop in hasarg:947 arg = code[i+1] | extended_arg948 extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0949 # The oparg is stored as a signed integer950 # If the value exceeds its upper limit, it will overflow and wrap951 # to a negative integer952 if extended_arg >= _INT_OVERFLOW:953 extended_arg -= 2 * _INT_OVERFLOW954 else:955 arg = None956 extended_arg = 0957 if deop == EXTENDED_ARG:958 extended_args_offset += 1959 yield (i, i, op, arg)960 else:961 start_offset = i - extended_args_offset*2962 yield (i, start_offset, op, arg)963 extended_args_offset = 0964 965def findlabels(code):966 """Detect all offsets in a byte code which are jump targets.967 968 Return the list of offsets.969 970 """971 labels = []972 for offset, _, op, arg in _unpack_opargs(code):973 if arg is not None:974 label = _get_jump_target(op, arg, offset)975 if label is None:976 continue977 if label not in labels:978 labels.append(label)979 return labels980 981def findlinestarts(code):982 """Find the offsets in a byte code which are start of lines in the source.983 984 Generate pairs (offset, lineno)985 lineno will be an integer or None the offset does not have a source line.986 """987 988 lastline = False # None is a valid line number989 for start, end, line in code.co_lines():990 if line is not lastline:991 lastline = line992 yield start, line993 return994 995def _find_imports(co):996 """Find import statements in the code997 998 Generate triplets (name, level, fromlist) where999 name is the imported module and level, fromlist are1000 the corresponding args to __import__.1001 """1002 IMPORT_NAME = opmap['IMPORT_NAME']1003 1004 consts = co.co_consts1005 names = co.co_names1006 opargs = [(op, arg) for _, _, op, arg in _unpack_opargs(co.co_code)1007 if op != EXTENDED_ARG]1008 for i, (op, oparg) in enumerate(opargs):1009 if op == IMPORT_NAME and i >= 2:1010 from_op = opargs[i-1]1011 level_op = opargs[i-2]1012 if (from_op[0] in hasconst and1013 (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)):1014 level = _get_const_value(level_op[0], level_op[1], consts)1015 fromlist = _get_const_value(from_op[0], from_op[1], consts)1016 yield (names[oparg], level, fromlist)1017 1018def _find_store_names(co):1019 """Find names of variables which are written in the code1020 1021 Generate sequence of strings1022 """1023 STORE_OPS = {1024 opmap['STORE_NAME'],1025 opmap['STORE_GLOBAL']1026 }1027 1028 names = co.co_names1029 for _, _, op, arg in _unpack_opargs(co.co_code):1030 if op in STORE_OPS:1031 yield names[arg]1032 1033 1034class Bytecode:1035 """The bytecode operations of a piece of code1036 1037 Instantiate this with a function, method, other compiled object, string of1038 code, or a code object (as returned by compile()).1039 1040 Iterating over this yields the bytecode operations as Instruction instances.1041 """1042 def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):1043 self.codeobj = co = _get_code_object(x)1044 if first_line is None:1045 self.first_line = co.co_firstlineno1046 self._line_offset = 01047 else:1048 self.first_line = first_line1049 self._line_offset = first_line - co.co_firstlineno1050 self._linestarts = dict(findlinestarts(co))1051 self._original_object = x1052 self.current_offset = current_offset1053 self.exception_entries = _parse_exception_table(co)1054 self.show_caches = show_caches1055 self.adaptive = adaptive1056 self.show_offsets = show_offsets1057 self.show_positions = show_positions1058 1059 def __iter__(self):1060 co = self.codeobj1061 original_code = co.co_code1062 labels_map = _make_labels_map(original_code, self.exception_entries)1063 arg_resolver = ArgResolver(co_consts=co.co_consts,1064 names=co.co_names,1065 varname_from_oparg=co._varname_from_oparg,1066 labels_map=labels_map)1067 return _get_instructions_bytes(_get_code_array(co, self.adaptive),1068 linestarts=self._linestarts,1069 line_offset=self._line_offset,1070 co_positions=co.co_positions(),1071 original_code=original_code,1072 arg_resolver=arg_resolver)1073 1074 def __repr__(self):1075 return "{}({!r})".format(self.__class__.__name__,1076 self._original_object)1077 1078 @classmethod1079 def from_traceback(cls, tb, *, show_caches=False, adaptive=False):1080 """ Construct a Bytecode from the given traceback """1081 while tb.tb_next:1082 tb = tb.tb_next1083 return cls(1084 tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive1085 )1086 1087 def info(self):1088 """Return formatted information about the code object."""1089 return _format_code_info(self.codeobj)1090 1091 def dis(self):1092 """Return a formatted view of the bytecode operations."""1093 co = self.codeobj1094 if self.current_offset is not None:1095 offset = self.current_offset1096 else:1097 offset = -11098 with io.StringIO() as output:1099 code = _get_code_array(co, self.adaptive)1100 offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 01101 if self.show_positions:1102 lineno_width = _get_positions_width(co)1103 else:1104 lineno_width = _get_lineno_width(self._linestarts)1105 labels_map = _make_labels_map(co.co_code, self.exception_entries)1106 label_width = 4 + len(str(len(labels_map)))1107 formatter = Formatter(file=output,1108 lineno_width=lineno_width,1109 offset_width=offset_width,1110 label_width=label_width,1111 line_offset=self._line_offset,1112 show_caches=self.show_caches,1113 show_positions=self.show_positions)1114 1115 arg_resolver = ArgResolver(co_consts=co.co_consts,1116 names=co.co_names,1117 varname_from_oparg=co._varname_from_oparg,1118 labels_map=labels_map)1119 _disassemble_bytes(code,1120 linestarts=self._linestarts,1121 line_offset=self._line_offset,1122 lasti=offset,1123 exception_entries=self.exception_entries,1124 co_positions=co.co_positions(),1125 original_code=co.co_code,1126 arg_resolver=arg_resolver,1127 formatter=formatter)1128 return output.getvalue()1129 1130 1131def main(args=None):1132 import argparse1133 1134 parser = argparse.ArgumentParser(color=True)1135 parser.add_argument('-C', '--show-caches', action='store_true',1136 help='show inline caches')1137 parser.add_argument('-O', '--show-offsets', action='store_true',1138 help='show instruction offsets')1139 parser.add_argument('-P', '--show-positions', action='store_true',1140 help='show instruction positions')1141 parser.add_argument('-S', '--specialized', action='store_true',1142 help='show specialized bytecode')1143 parser.add_argument('infile', nargs='?', default='-')1144 args = parser.parse_args(args=args)1145 if args.infile == '-':1146 name = '<stdin>'1147 source = sys.stdin.buffer.read()1148 else:1149 name = args.infile1150 with open(args.infile, 'rb') as infile:1151 source = infile.read()1152 code = compile(source, name, "exec")1153 dis(code, show_caches=args.show_caches, adaptive=args.specialized,1154 show_offsets=args.show_offsets, show_positions=args.show_positions)1155 1156if __name__ == "__main__":1157 main()1158 