1 | n/a | """Disassembler of Python byte code into mnemonics.""" |
---|
2 | n/a | |
---|
3 | n/a | import sys |
---|
4 | n/a | import types |
---|
5 | n/a | import collections |
---|
6 | n/a | import io |
---|
7 | n/a | |
---|
8 | n/a | from opcode import * |
---|
9 | n/a | from opcode import __all__ as _opcodes_all |
---|
10 | n/a | |
---|
11 | n/a | __all__ = ["code_info", "dis", "disassemble", "distb", "disco", |
---|
12 | n/a | "findlinestarts", "findlabels", "show_code", |
---|
13 | n/a | "get_instructions", "Instruction", "Bytecode"] + _opcodes_all |
---|
14 | n/a | del _opcodes_all |
---|
15 | n/a | |
---|
16 | n/a | _have_code = (types.MethodType, types.FunctionType, types.CodeType, |
---|
17 | n/a | classmethod, staticmethod, type) |
---|
18 | n/a | |
---|
19 | n/a | FORMAT_VALUE = opmap['FORMAT_VALUE'] |
---|
20 | n/a | |
---|
21 | n/a | def _try_compile(source, name): |
---|
22 | n/a | """Attempts to compile the given source, first as an expression and |
---|
23 | n/a | then as a statement if the first approach fails. |
---|
24 | n/a | |
---|
25 | n/a | Utility function to accept strings in functions that otherwise |
---|
26 | n/a | expect code objects |
---|
27 | n/a | """ |
---|
28 | n/a | try: |
---|
29 | n/a | c = compile(source, name, 'eval') |
---|
30 | n/a | except SyntaxError: |
---|
31 | n/a | c = compile(source, name, 'exec') |
---|
32 | n/a | return c |
---|
33 | n/a | |
---|
34 | n/a | def dis(x=None, *, file=None): |
---|
35 | n/a | """Disassemble classes, methods, functions, generators, or code. |
---|
36 | n/a | |
---|
37 | n/a | With no argument, disassemble the last traceback. |
---|
38 | n/a | |
---|
39 | n/a | """ |
---|
40 | n/a | if x is None: |
---|
41 | n/a | distb(file=file) |
---|
42 | n/a | return |
---|
43 | n/a | if hasattr(x, '__func__'): # Method |
---|
44 | n/a | x = x.__func__ |
---|
45 | n/a | if hasattr(x, '__code__'): # Function |
---|
46 | n/a | x = x.__code__ |
---|
47 | n/a | if hasattr(x, 'gi_code'): # Generator |
---|
48 | n/a | x = x.gi_code |
---|
49 | n/a | if hasattr(x, '__dict__'): # Class or module |
---|
50 | n/a | items = sorted(x.__dict__.items()) |
---|
51 | n/a | for name, x1 in items: |
---|
52 | n/a | if isinstance(x1, _have_code): |
---|
53 | n/a | print("Disassembly of %s:" % name, file=file) |
---|
54 | n/a | try: |
---|
55 | n/a | dis(x1, file=file) |
---|
56 | n/a | except TypeError as msg: |
---|
57 | n/a | print("Sorry:", msg, file=file) |
---|
58 | n/a | print(file=file) |
---|
59 | n/a | elif hasattr(x, 'co_code'): # Code object |
---|
60 | n/a | disassemble(x, file=file) |
---|
61 | n/a | elif isinstance(x, (bytes, bytearray)): # Raw bytecode |
---|
62 | n/a | _disassemble_bytes(x, file=file) |
---|
63 | n/a | elif isinstance(x, str): # Source code |
---|
64 | n/a | _disassemble_str(x, file=file) |
---|
65 | n/a | else: |
---|
66 | n/a | raise TypeError("don't know how to disassemble %s objects" % |
---|
67 | n/a | type(x).__name__) |
---|
68 | n/a | |
---|
69 | n/a | def distb(tb=None, *, file=None): |
---|
70 | n/a | """Disassemble a traceback (default: last traceback).""" |
---|
71 | n/a | if tb is None: |
---|
72 | n/a | try: |
---|
73 | n/a | tb = sys.last_traceback |
---|
74 | n/a | except AttributeError: |
---|
75 | n/a | raise RuntimeError("no last traceback to disassemble") |
---|
76 | n/a | while tb.tb_next: tb = tb.tb_next |
---|
77 | n/a | disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file) |
---|
78 | n/a | |
---|
79 | n/a | # The inspect module interrogates this dictionary to build its |
---|
80 | n/a | # list of CO_* constants. It is also used by pretty_flags to |
---|
81 | n/a | # turn the co_flags field into a human readable list. |
---|
82 | n/a | COMPILER_FLAG_NAMES = { |
---|
83 | n/a | 1: "OPTIMIZED", |
---|
84 | n/a | 2: "NEWLOCALS", |
---|
85 | n/a | 4: "VARARGS", |
---|
86 | n/a | 8: "VARKEYWORDS", |
---|
87 | n/a | 16: "NESTED", |
---|
88 | n/a | 32: "GENERATOR", |
---|
89 | n/a | 64: "NOFREE", |
---|
90 | n/a | 128: "COROUTINE", |
---|
91 | n/a | 256: "ITERABLE_COROUTINE", |
---|
92 | n/a | 512: "ASYNC_GENERATOR", |
---|
93 | n/a | } |
---|
94 | n/a | |
---|
95 | n/a | def pretty_flags(flags): |
---|
96 | n/a | """Return pretty representation of code flags.""" |
---|
97 | n/a | names = [] |
---|
98 | n/a | for i in range(32): |
---|
99 | n/a | flag = 1<<i |
---|
100 | n/a | if flags & flag: |
---|
101 | n/a | names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag))) |
---|
102 | n/a | flags ^= flag |
---|
103 | n/a | if not flags: |
---|
104 | n/a | break |
---|
105 | n/a | else: |
---|
106 | n/a | names.append(hex(flags)) |
---|
107 | n/a | return ", ".join(names) |
---|
108 | n/a | |
---|
109 | n/a | def _get_code_object(x): |
---|
110 | n/a | """Helper to handle methods, functions, generators, strings and raw code objects""" |
---|
111 | n/a | if hasattr(x, '__func__'): # Method |
---|
112 | n/a | x = x.__func__ |
---|
113 | n/a | if hasattr(x, '__code__'): # Function |
---|
114 | n/a | x = x.__code__ |
---|
115 | n/a | if hasattr(x, 'gi_code'): # Generator |
---|
116 | n/a | x = x.gi_code |
---|
117 | n/a | if isinstance(x, str): # Source code |
---|
118 | n/a | x = _try_compile(x, "<disassembly>") |
---|
119 | n/a | if hasattr(x, 'co_code'): # Code object |
---|
120 | n/a | return x |
---|
121 | n/a | raise TypeError("don't know how to disassemble %s objects" % |
---|
122 | n/a | type(x).__name__) |
---|
123 | n/a | |
---|
124 | n/a | def code_info(x): |
---|
125 | n/a | """Formatted details of methods, functions, or code.""" |
---|
126 | n/a | return _format_code_info(_get_code_object(x)) |
---|
127 | n/a | |
---|
128 | n/a | def _format_code_info(co): |
---|
129 | n/a | lines = [] |
---|
130 | n/a | lines.append("Name: %s" % co.co_name) |
---|
131 | n/a | lines.append("Filename: %s" % co.co_filename) |
---|
132 | n/a | lines.append("Argument count: %s" % co.co_argcount) |
---|
133 | n/a | lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount) |
---|
134 | n/a | lines.append("Number of locals: %s" % co.co_nlocals) |
---|
135 | n/a | lines.append("Stack size: %s" % co.co_stacksize) |
---|
136 | n/a | lines.append("Flags: %s" % pretty_flags(co.co_flags)) |
---|
137 | n/a | if co.co_consts: |
---|
138 | n/a | lines.append("Constants:") |
---|
139 | n/a | for i_c in enumerate(co.co_consts): |
---|
140 | n/a | lines.append("%4d: %r" % i_c) |
---|
141 | n/a | if co.co_names: |
---|
142 | n/a | lines.append("Names:") |
---|
143 | n/a | for i_n in enumerate(co.co_names): |
---|
144 | n/a | lines.append("%4d: %s" % i_n) |
---|
145 | n/a | if co.co_varnames: |
---|
146 | n/a | lines.append("Variable names:") |
---|
147 | n/a | for i_n in enumerate(co.co_varnames): |
---|
148 | n/a | lines.append("%4d: %s" % i_n) |
---|
149 | n/a | if co.co_freevars: |
---|
150 | n/a | lines.append("Free variables:") |
---|
151 | n/a | for i_n in enumerate(co.co_freevars): |
---|
152 | n/a | lines.append("%4d: %s" % i_n) |
---|
153 | n/a | if co.co_cellvars: |
---|
154 | n/a | lines.append("Cell variables:") |
---|
155 | n/a | for i_n in enumerate(co.co_cellvars): |
---|
156 | n/a | lines.append("%4d: %s" % i_n) |
---|
157 | n/a | return "\n".join(lines) |
---|
158 | n/a | |
---|
159 | n/a | def show_code(co, *, file=None): |
---|
160 | n/a | """Print details of methods, functions, or code to *file*. |
---|
161 | n/a | |
---|
162 | n/a | If *file* is not provided, the output is printed on stdout. |
---|
163 | n/a | """ |
---|
164 | n/a | print(code_info(co), file=file) |
---|
165 | n/a | |
---|
166 | n/a | _Instruction = collections.namedtuple("_Instruction", |
---|
167 | n/a | "opname opcode arg argval argrepr offset starts_line is_jump_target") |
---|
168 | n/a | |
---|
169 | n/a | _Instruction.opname.__doc__ = "Human readable name for operation" |
---|
170 | n/a | _Instruction.opcode.__doc__ = "Numeric code for operation" |
---|
171 | n/a | _Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None" |
---|
172 | n/a | _Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg" |
---|
173 | n/a | _Instruction.argrepr.__doc__ = "Human readable description of operation argument" |
---|
174 | n/a | _Instruction.offset.__doc__ = "Start index of operation within bytecode sequence" |
---|
175 | n/a | _Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None" |
---|
176 | n/a | _Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False" |
---|
177 | n/a | |
---|
178 | n/a | class Instruction(_Instruction): |
---|
179 | n/a | """Details for a bytecode operation |
---|
180 | n/a | |
---|
181 | n/a | Defined fields: |
---|
182 | n/a | opname - human readable name for operation |
---|
183 | n/a | opcode - numeric code for operation |
---|
184 | n/a | arg - numeric argument to operation (if any), otherwise None |
---|
185 | n/a | argval - resolved arg value (if known), otherwise same as arg |
---|
186 | n/a | argrepr - human readable description of operation argument |
---|
187 | n/a | offset - start index of operation within bytecode sequence |
---|
188 | n/a | starts_line - line started by this opcode (if any), otherwise None |
---|
189 | n/a | is_jump_target - True if other code jumps to here, otherwise False |
---|
190 | n/a | """ |
---|
191 | n/a | |
---|
192 | n/a | def _disassemble(self, lineno_width=3, mark_as_current=False): |
---|
193 | n/a | """Format instruction details for inclusion in disassembly output |
---|
194 | n/a | |
---|
195 | n/a | *lineno_width* sets the width of the line number field (0 omits it) |
---|
196 | n/a | *mark_as_current* inserts a '-->' marker arrow as part of the line |
---|
197 | n/a | """ |
---|
198 | n/a | fields = [] |
---|
199 | n/a | # Column: Source code line number |
---|
200 | n/a | if lineno_width: |
---|
201 | n/a | if self.starts_line is not None: |
---|
202 | n/a | lineno_fmt = "%%%dd" % lineno_width |
---|
203 | n/a | fields.append(lineno_fmt % self.starts_line) |
---|
204 | n/a | else: |
---|
205 | n/a | fields.append(' ' * lineno_width) |
---|
206 | n/a | # Column: Current instruction indicator |
---|
207 | n/a | if mark_as_current: |
---|
208 | n/a | fields.append('-->') |
---|
209 | n/a | else: |
---|
210 | n/a | fields.append(' ') |
---|
211 | n/a | # Column: Jump target marker |
---|
212 | n/a | if self.is_jump_target: |
---|
213 | n/a | fields.append('>>') |
---|
214 | n/a | else: |
---|
215 | n/a | fields.append(' ') |
---|
216 | n/a | # Column: Instruction offset from start of code sequence |
---|
217 | n/a | fields.append(repr(self.offset).rjust(4)) |
---|
218 | n/a | # Column: Opcode name |
---|
219 | n/a | fields.append(self.opname.ljust(20)) |
---|
220 | n/a | # Column: Opcode argument |
---|
221 | n/a | if self.arg is not None: |
---|
222 | n/a | fields.append(repr(self.arg).rjust(5)) |
---|
223 | n/a | # Column: Opcode argument details |
---|
224 | n/a | if self.argrepr: |
---|
225 | n/a | fields.append('(' + self.argrepr + ')') |
---|
226 | n/a | return ' '.join(fields).rstrip() |
---|
227 | n/a | |
---|
228 | n/a | |
---|
229 | n/a | def get_instructions(x, *, first_line=None): |
---|
230 | n/a | """Iterator for the opcodes in methods, functions or code |
---|
231 | n/a | |
---|
232 | n/a | Generates a series of Instruction named tuples giving the details of |
---|
233 | n/a | each operations in the supplied code. |
---|
234 | n/a | |
---|
235 | n/a | If *first_line* is not None, it indicates the line number that should |
---|
236 | n/a | be reported for the first source line in the disassembled code. |
---|
237 | n/a | Otherwise, the source line information (if any) is taken directly from |
---|
238 | n/a | the disassembled code object. |
---|
239 | n/a | """ |
---|
240 | n/a | co = _get_code_object(x) |
---|
241 | n/a | cell_names = co.co_cellvars + co.co_freevars |
---|
242 | n/a | linestarts = dict(findlinestarts(co)) |
---|
243 | n/a | if first_line is not None: |
---|
244 | n/a | line_offset = first_line - co.co_firstlineno |
---|
245 | n/a | else: |
---|
246 | n/a | line_offset = 0 |
---|
247 | n/a | return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names, |
---|
248 | n/a | co.co_consts, cell_names, linestarts, |
---|
249 | n/a | line_offset) |
---|
250 | n/a | |
---|
251 | n/a | def _get_const_info(const_index, const_list): |
---|
252 | n/a | """Helper to get optional details about const references |
---|
253 | n/a | |
---|
254 | n/a | Returns the dereferenced constant and its repr if the constant |
---|
255 | n/a | list is defined. |
---|
256 | n/a | Otherwise returns the constant index and its repr(). |
---|
257 | n/a | """ |
---|
258 | n/a | argval = const_index |
---|
259 | n/a | if const_list is not None: |
---|
260 | n/a | argval = const_list[const_index] |
---|
261 | n/a | return argval, repr(argval) |
---|
262 | n/a | |
---|
263 | n/a | def _get_name_info(name_index, name_list): |
---|
264 | n/a | """Helper to get optional details about named references |
---|
265 | n/a | |
---|
266 | n/a | Returns the dereferenced name as both value and repr if the name |
---|
267 | n/a | list is defined. |
---|
268 | n/a | Otherwise returns the name index and its repr(). |
---|
269 | n/a | """ |
---|
270 | n/a | argval = name_index |
---|
271 | n/a | if name_list is not None: |
---|
272 | n/a | argval = name_list[name_index] |
---|
273 | n/a | argrepr = argval |
---|
274 | n/a | else: |
---|
275 | n/a | argrepr = repr(argval) |
---|
276 | n/a | return argval, argrepr |
---|
277 | n/a | |
---|
278 | n/a | |
---|
279 | n/a | def _get_instructions_bytes(code, varnames=None, names=None, constants=None, |
---|
280 | n/a | cells=None, linestarts=None, line_offset=0): |
---|
281 | n/a | """Iterate over the instructions in a bytecode string. |
---|
282 | n/a | |
---|
283 | n/a | Generates a sequence of Instruction namedtuples giving the details of each |
---|
284 | n/a | opcode. Additional information about the code's runtime environment |
---|
285 | n/a | (e.g. variable names, constants) can be specified using optional |
---|
286 | n/a | arguments. |
---|
287 | n/a | |
---|
288 | n/a | """ |
---|
289 | n/a | labels = findlabels(code) |
---|
290 | n/a | starts_line = None |
---|
291 | n/a | for offset, op, arg in _unpack_opargs(code): |
---|
292 | n/a | if linestarts is not None: |
---|
293 | n/a | starts_line = linestarts.get(offset, None) |
---|
294 | n/a | if starts_line is not None: |
---|
295 | n/a | starts_line += line_offset |
---|
296 | n/a | is_jump_target = offset in labels |
---|
297 | n/a | argval = None |
---|
298 | n/a | argrepr = '' |
---|
299 | n/a | if arg is not None: |
---|
300 | n/a | # Set argval to the dereferenced value of the argument when |
---|
301 | n/a | # available, and argrepr to the string representation of argval. |
---|
302 | n/a | # _disassemble_bytes needs the string repr of the |
---|
303 | n/a | # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. |
---|
304 | n/a | argval = arg |
---|
305 | n/a | if op in hasconst: |
---|
306 | n/a | argval, argrepr = _get_const_info(arg, constants) |
---|
307 | n/a | elif op in hasname: |
---|
308 | n/a | argval, argrepr = _get_name_info(arg, names) |
---|
309 | n/a | elif op in hasjrel: |
---|
310 | n/a | argval = offset + 2 + arg |
---|
311 | n/a | argrepr = "to " + repr(argval) |
---|
312 | n/a | elif op in haslocal: |
---|
313 | n/a | argval, argrepr = _get_name_info(arg, varnames) |
---|
314 | n/a | elif op in hascompare: |
---|
315 | n/a | argval = cmp_op[arg] |
---|
316 | n/a | argrepr = argval |
---|
317 | n/a | elif op in hasfree: |
---|
318 | n/a | argval, argrepr = _get_name_info(arg, cells) |
---|
319 | n/a | elif op == FORMAT_VALUE: |
---|
320 | n/a | argval = ((None, str, repr, ascii)[arg & 0x3], bool(arg & 0x4)) |
---|
321 | n/a | argrepr = ('', 'str', 'repr', 'ascii')[arg & 0x3] |
---|
322 | n/a | if argval[1]: |
---|
323 | n/a | if argrepr: |
---|
324 | n/a | argrepr += ', ' |
---|
325 | n/a | argrepr += 'with format' |
---|
326 | n/a | yield Instruction(opname[op], op, |
---|
327 | n/a | arg, argval, argrepr, |
---|
328 | n/a | offset, starts_line, is_jump_target) |
---|
329 | n/a | |
---|
330 | n/a | def disassemble(co, lasti=-1, *, file=None): |
---|
331 | n/a | """Disassemble a code object.""" |
---|
332 | n/a | cell_names = co.co_cellvars + co.co_freevars |
---|
333 | n/a | linestarts = dict(findlinestarts(co)) |
---|
334 | n/a | _disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names, |
---|
335 | n/a | co.co_consts, cell_names, linestarts, file=file) |
---|
336 | n/a | |
---|
337 | n/a | def _disassemble_bytes(code, lasti=-1, varnames=None, names=None, |
---|
338 | n/a | constants=None, cells=None, linestarts=None, |
---|
339 | n/a | *, file=None, line_offset=0): |
---|
340 | n/a | # Omit the line number column entirely if we have no line number info |
---|
341 | n/a | show_lineno = linestarts is not None |
---|
342 | n/a | # TODO?: Adjust width upwards if max(linestarts.values()) >= 1000? |
---|
343 | n/a | lineno_width = 3 if show_lineno else 0 |
---|
344 | n/a | for instr in _get_instructions_bytes(code, varnames, names, |
---|
345 | n/a | constants, cells, linestarts, |
---|
346 | n/a | line_offset=line_offset): |
---|
347 | n/a | new_source_line = (show_lineno and |
---|
348 | n/a | instr.starts_line is not None and |
---|
349 | n/a | instr.offset > 0) |
---|
350 | n/a | if new_source_line: |
---|
351 | n/a | print(file=file) |
---|
352 | n/a | is_current_instr = instr.offset == lasti |
---|
353 | n/a | print(instr._disassemble(lineno_width, is_current_instr), file=file) |
---|
354 | n/a | |
---|
355 | n/a | def _disassemble_str(source, *, file=None): |
---|
356 | n/a | """Compile the source string, then disassemble the code object.""" |
---|
357 | n/a | disassemble(_try_compile(source, '<dis>'), file=file) |
---|
358 | n/a | |
---|
359 | n/a | disco = disassemble # XXX For backwards compatibility |
---|
360 | n/a | |
---|
361 | n/a | def _unpack_opargs(code): |
---|
362 | n/a | extended_arg = 0 |
---|
363 | n/a | for i in range(0, len(code), 2): |
---|
364 | n/a | op = code[i] |
---|
365 | n/a | if op >= HAVE_ARGUMENT: |
---|
366 | n/a | arg = code[i+1] | extended_arg |
---|
367 | n/a | extended_arg = (arg << 8) if op == EXTENDED_ARG else 0 |
---|
368 | n/a | else: |
---|
369 | n/a | arg = None |
---|
370 | n/a | yield (i, op, arg) |
---|
371 | n/a | |
---|
372 | n/a | def findlabels(code): |
---|
373 | n/a | """Detect all offsets in a byte code which are jump targets. |
---|
374 | n/a | |
---|
375 | n/a | Return the list of offsets. |
---|
376 | n/a | |
---|
377 | n/a | """ |
---|
378 | n/a | labels = [] |
---|
379 | n/a | for offset, op, arg in _unpack_opargs(code): |
---|
380 | n/a | if arg is not None: |
---|
381 | n/a | if op in hasjrel: |
---|
382 | n/a | label = offset + 2 + arg |
---|
383 | n/a | elif op in hasjabs: |
---|
384 | n/a | label = arg |
---|
385 | n/a | else: |
---|
386 | n/a | continue |
---|
387 | n/a | if label not in labels: |
---|
388 | n/a | labels.append(label) |
---|
389 | n/a | return labels |
---|
390 | n/a | |
---|
391 | n/a | def findlinestarts(code): |
---|
392 | n/a | """Find the offsets in a byte code which are start of lines in the source. |
---|
393 | n/a | |
---|
394 | n/a | Generate pairs (offset, lineno) as described in Python/compile.c. |
---|
395 | n/a | |
---|
396 | n/a | """ |
---|
397 | n/a | byte_increments = code.co_lnotab[0::2] |
---|
398 | n/a | line_increments = code.co_lnotab[1::2] |
---|
399 | n/a | |
---|
400 | n/a | lastlineno = None |
---|
401 | n/a | lineno = code.co_firstlineno |
---|
402 | n/a | addr = 0 |
---|
403 | n/a | for byte_incr, line_incr in zip(byte_increments, line_increments): |
---|
404 | n/a | if byte_incr: |
---|
405 | n/a | if lineno != lastlineno: |
---|
406 | n/a | yield (addr, lineno) |
---|
407 | n/a | lastlineno = lineno |
---|
408 | n/a | addr += byte_incr |
---|
409 | n/a | if line_incr >= 0x80: |
---|
410 | n/a | # line_increments is an array of 8-bit signed integers |
---|
411 | n/a | line_incr -= 0x100 |
---|
412 | n/a | lineno += line_incr |
---|
413 | n/a | if lineno != lastlineno: |
---|
414 | n/a | yield (addr, lineno) |
---|
415 | n/a | |
---|
416 | n/a | class Bytecode: |
---|
417 | n/a | """The bytecode operations of a piece of code |
---|
418 | n/a | |
---|
419 | n/a | Instantiate this with a function, method, string of code, or a code object |
---|
420 | n/a | (as returned by compile()). |
---|
421 | n/a | |
---|
422 | n/a | Iterating over this yields the bytecode operations as Instruction instances. |
---|
423 | n/a | """ |
---|
424 | n/a | def __init__(self, x, *, first_line=None, current_offset=None): |
---|
425 | n/a | self.codeobj = co = _get_code_object(x) |
---|
426 | n/a | if first_line is None: |
---|
427 | n/a | self.first_line = co.co_firstlineno |
---|
428 | n/a | self._line_offset = 0 |
---|
429 | n/a | else: |
---|
430 | n/a | self.first_line = first_line |
---|
431 | n/a | self._line_offset = first_line - co.co_firstlineno |
---|
432 | n/a | self._cell_names = co.co_cellvars + co.co_freevars |
---|
433 | n/a | self._linestarts = dict(findlinestarts(co)) |
---|
434 | n/a | self._original_object = x |
---|
435 | n/a | self.current_offset = current_offset |
---|
436 | n/a | |
---|
437 | n/a | def __iter__(self): |
---|
438 | n/a | co = self.codeobj |
---|
439 | n/a | return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names, |
---|
440 | n/a | co.co_consts, self._cell_names, |
---|
441 | n/a | self._linestarts, |
---|
442 | n/a | line_offset=self._line_offset) |
---|
443 | n/a | |
---|
444 | n/a | def __repr__(self): |
---|
445 | n/a | return "{}({!r})".format(self.__class__.__name__, |
---|
446 | n/a | self._original_object) |
---|
447 | n/a | |
---|
448 | n/a | @classmethod |
---|
449 | n/a | def from_traceback(cls, tb): |
---|
450 | n/a | """ Construct a Bytecode from the given traceback """ |
---|
451 | n/a | while tb.tb_next: |
---|
452 | n/a | tb = tb.tb_next |
---|
453 | n/a | return cls(tb.tb_frame.f_code, current_offset=tb.tb_lasti) |
---|
454 | n/a | |
---|
455 | n/a | def info(self): |
---|
456 | n/a | """Return formatted information about the code object.""" |
---|
457 | n/a | return _format_code_info(self.codeobj) |
---|
458 | n/a | |
---|
459 | n/a | def dis(self): |
---|
460 | n/a | """Return a formatted view of the bytecode operations.""" |
---|
461 | n/a | co = self.codeobj |
---|
462 | n/a | if self.current_offset is not None: |
---|
463 | n/a | offset = self.current_offset |
---|
464 | n/a | else: |
---|
465 | n/a | offset = -1 |
---|
466 | n/a | with io.StringIO() as output: |
---|
467 | n/a | _disassemble_bytes(co.co_code, varnames=co.co_varnames, |
---|
468 | n/a | names=co.co_names, constants=co.co_consts, |
---|
469 | n/a | cells=self._cell_names, |
---|
470 | n/a | linestarts=self._linestarts, |
---|
471 | n/a | line_offset=self._line_offset, |
---|
472 | n/a | file=output, |
---|
473 | n/a | lasti=offset) |
---|
474 | n/a | return output.getvalue() |
---|
475 | n/a | |
---|
476 | n/a | |
---|
477 | n/a | def _test(): |
---|
478 | n/a | """Simple test program to disassemble a file.""" |
---|
479 | n/a | import argparse |
---|
480 | n/a | |
---|
481 | n/a | parser = argparse.ArgumentParser() |
---|
482 | n/a | parser.add_argument('infile', type=argparse.FileType(), nargs='?', default='-') |
---|
483 | n/a | args = parser.parse_args() |
---|
484 | n/a | with args.infile as infile: |
---|
485 | n/a | source = infile.read() |
---|
486 | n/a | code = compile(source, args.infile.name, "exec") |
---|
487 | n/a | dis(code) |
---|
488 | n/a | |
---|
489 | n/a | if __name__ == "__main__": |
---|
490 | n/a | _test() |
---|