Skip to content

Commit

Permalink
Redo disasembler options handling --
Browse files Browse the repository at this point in the history
* --show-bytes and --asm options removed. Use --asm-format -F instead.
* asm_fmt is now a string choice.
* Don't show integer arg value when there is something more useful to
  show. Note that -F=bytes will show this separately
  • Loading branch information
rocky committed Jun 21, 2020
1 parent 6063906 commit e6e146f
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 421 deletions.
2 changes: 1 addition & 1 deletion xdis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
lineoffsets_in_module,
)

from xdis.main import (
from xdis.disasm import (
get_opcode,
show_module_header,
disco_loop,
Expand Down
23 changes: 7 additions & 16 deletions xdis/bin/pydisasm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Mode: -*- python -*-
# Copyright (c) 2015-2019 by Rocky Bernstein <[email protected]>
# Copyright (c) 2015-2020 by Rocky Bernstein <[email protected]>
#
# Note: we can't start with #! because setup.py bdist_wheel will look for that
# and change that into something that's not portable. Thank you, Python!
Expand All @@ -21,23 +21,14 @@

@click.command()
@click.option(
"--asm/--noasm",
default=False,
help="Produce output suitable for the xasm assembler",
)
@click.option(
"--show-bytes/--noshow-bytes",
default=False,
help="include bytecode bytes in output",
"--format",
"-F",
type=click.Choice(["xasm", "bytes", "std", "extended", "header"],
case_sensitive=False),
)
@click.version_option(version=VERSION)
@click.option(
"--header/--no-header",
default=False,
help="Show only the module header information",
)
@click.argument("files", nargs=-1, type=click.Path(readable=True), required=True)
def main(asm, show_bytes, header, files):
def main(format, files):
"""Disassembles a Python bytecode file.
We handle bytecode for virtually every release of Python and some releases of PyPy.
Expand Down Expand Up @@ -72,7 +63,7 @@ def main(asm, show_bytes, header, files):
)
continue

disassemble_file(path, sys.stdout, asm, header, show_bytes)
disassemble_file(path, sys.stdout, format)
return


Expand Down
9 changes: 4 additions & 5 deletions xdis/bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def info(self):
"""Return formatted information about the code object."""
return format_code_info(self.codeobj, self.opc.version)

def dis(self, asm_format=False, show_bytes=False):
def dis(self, asm_format="std"):
"""Return a formatted view of the bytecode operations."""
co = self.codeobj
if self.current_offset is not None:
Expand All @@ -291,8 +291,7 @@ def dis(self, asm_format=False, show_bytes=False):
line_offset=self._line_offset,
file=output,
lasti=offset,
asm_format=asm_format,
show_bytes=show_bytes)
asm_format=asm_format)
return output.getvalue()

def distb(self, tb=None):
Expand All @@ -308,7 +307,7 @@ def distb(self, tb=None):
def disassemble_bytes(self, code, lasti=-1, varnames=None, names=None,
constants=None, cells=None, linestarts=None,
file=sys.stdout, line_offset=0,
asm_format=False, show_bytes=False):
asm_format="std"):
# Omit the line number column entirely if we have no line number info
show_lineno = linestarts is not None
# TODO?: Adjust width upwards if max(linestarts.values()) >= 1000?
Expand All @@ -323,7 +322,7 @@ def disassemble_bytes(self, code, lasti=-1, varnames=None, names=None,
file.write("\n")
is_current_instr = instr.offset == lasti
file.write(instr.disassemble(lineno_width, is_current_instr,
asm_format, show_bytes)
asm_format)
+ "\n")
pass
return
Expand Down
101 changes: 51 additions & 50 deletions xdis/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@

from collections import namedtuple

_Instruction = namedtuple("_Instruction",
"opname opcode optype inst_size arg argval argrepr has_arg offset starts_line is_jump_target has_extended_arg")
# "opname opcode optype inst_size arg argval argrepr has_arg offset starts_line is_jump_target has_extended_arg fallthrough")
_Instruction = namedtuple(
"_Instruction",
"opname opcode optype inst_size arg argval argrepr has_arg offset starts_line is_jump_target has_extended_arg",
)
# "opname opcode optype inst_size arg argval argrepr has_arg offset starts_line is_jump_target has_extended_arg fallthrough")
try:
_Instruction.opname.__doc__ = "Human readable name for operation"
_Instruction.opcode.__doc__ = "Numeric code for operation"
Expand All @@ -34,15 +36,22 @@
_Instruction.argrepr.__doc__ = "Human readable description of operation argument"
_Instruction.has_arg.__doc__ = "True if instruction has an operand, otherwise False"
_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence"
_Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None"
_Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False"
_Instruction.has_extended_arg.__doc__ = "True there were EXTENDED_ARG opcodes before this, otherwise False"
_Instruction.starts_line.__doc__ = (
"Line started by this opcode (if any), otherwise None"
)
_Instruction.is_jump_target.__doc__ = (
"True if other code jumps to here, otherwise False"
)
_Instruction.has_extended_arg.__doc__ = (
"True there were EXTENDED_ARG opcodes before this, otherwise False"
)
except:
pass

_OPNAME_WIDTH = 20
_OPARG_WIDTH = 6


class Instruction(_Instruction):
"""Details for a bytecode operation
Expand Down Expand Up @@ -71,104 +80,98 @@ class Instruction(_Instruction):
instruction. Note conditionals are in this category, but
returns, raise, and unconditional jumps are not
"""

# FIXME: remove has_arg from initialization but keep it as a field.

def disassemble(self, lineno_width=3,
mark_as_current=False,
asm_format=False,
show_bytes=False):
def disassemble(self, lineno_width=3, mark_as_current=False, asm_format="std"):
"""Format instruction details for inclusion in disassembly output
*lineno_width* sets the width of the line number field (0 omits it)
*mark_as_current* inserts a '-->' marker arrow as part of the line
"""
fields = []
if asm_format:
indexed_operand = set(['name', 'local', 'compare', 'free'])
indexed_operand = set(["name", "local", "compare", "free"])
# Column: Source code line number
if lineno_width:
if self.starts_line is not None:
if asm_format:
if asm_format == "xasm":
lineno_fmt = "%%%dd:\n" % lineno_width
fields.append(lineno_fmt % self.starts_line)
fields.append(' ' * (lineno_width))
fields.append(" " * (lineno_width))
if self.is_jump_target:
fields.append(' ' * (lineno_width-1))
fields.append(" " * (lineno_width - 1))
else:
lineno_fmt = "%%%dd:" % lineno_width
fields.append(lineno_fmt % self.starts_line)
else:
fields.append(' ' * (lineno_width+1))
fields.append(" " * (lineno_width + 1))
# Column: Current instruction indicator
if mark_as_current and not asm_format:
fields.append('-->')
if mark_as_current and asm_format != "xasm":
fields.append("-->")
else:
fields.append(' ')
fields.append(" ")
# Column: Jump target marker
if self.is_jump_target:
if not asm_format:
fields.append('>>')
if asm_format != "xasm":
fields.append(">>")
else:
fields = ["L%d:\n" % self.offset] + fields
if not self.starts_line:
fields.append(' ')
fields.append(" ")
else:
fields.append(' ')
fields.append(" ")
# Column: Instruction offset from start of code sequence
if not asm_format:
fields.append(repr(self.offset).rjust(4))

if show_bytes:
if asm_format == "bytes":
hex_bytecode = "|%02x" % self.opcode
if self.inst_size == 1:
# Not 3.6 or later
hex_bytecode += ' ' * (2*3)
hex_bytecode += " " * (2 * 3)
if self.inst_size == 2:
# Must by Python 3.6 or later
if self.has_arg:
hex_bytecode += " %02x" % (self.arg % 256)
else :
hex_bytecode += ' 00'
else:
hex_bytecode += " 00"
elif self.inst_size == 3:
# Not 3.6 or later
hex_bytecode += " %02x %02x" % (
(self.arg >> 8, self.arg % 256))
hex_bytecode += " %02x %02x" % divmod(self.arg, 256)

fields.append(hex_bytecode + '|')
fields.append(hex_bytecode + "|")

# Column: Opcode name
fields.append(self.opname.ljust(_OPNAME_WIDTH))

# Column: Opcode argument
if self.arg is not None:
argrepr = self.argrepr
if asm_format:
if self.optype == 'jabs':
fields.append('L' + str(self.arg))
elif self.optype == 'jrel':
if asm_format == "xasm":
if self.optype == "jabs":
fields.append("L" + str(self.arg))
elif self.optype == "jrel":
argval = self.offset + self.arg + self.inst_size
fields.append('L' + str(argval))
fields.append("L" + str(argval))
elif self.optype in indexed_operand:
fields.append('(%s)' % argrepr)
fields.append("(%s)" % argrepr)
argrepr = None
elif (self.optype == 'const'
and not re.search(r'\s', argrepr)):
fields.append('(%s)' % argrepr)
elif self.optype == "const" and not re.search(r"\s", argrepr):
fields.append("(%s)" % argrepr)
argrepr = None
elif (self.optype == 'const'
and not re.search(r'\s', argrepr)):
fields.append('(%s)' % argrepr)
elif self.optype == "const" and not re.search(r"\s", argrepr):
fields.append("(%s)" % argrepr)
argrepr = None
else:
fields.append(repr(self.arg))
elif not (show_bytes and argrepr):
fields.append(repr(self.arg).rjust(_OPARG_WIDTH))
elif not (asm_format != "compact" and argrepr):
fields.append(repr(self.arg))
# Column: Opcode argument details
if argrepr:
fields.append('(%s)' % argrepr)
fields.append("(%s)" % argrepr)
pass
pass
return ' '.join(fields).rstrip()
return " ".join(fields).rstrip()

def is_jump(self):
"""
Expand All @@ -180,10 +183,8 @@ def jumps_forward(self):
"""
Return True if instruction is jump backwards
"""
return (
self.is_jump()
and self.offset < self.argval
)
return self.is_jump() and self.offset < self.argval


# if __name__ == '__main__':
# pass
Loading

0 comments on commit e6e146f

Please sign in to comment.