pydisasm.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. # Mode: -*- python -*-
  2. # Copyright (c) 2015-2021 by Rocky Bernstein <rb@dustyfeet.com>
  3. #
  4. # Note: we can't start with #! because setup.py bdist_wheel will look for that
  5. # and change that into something that's not portable. Thank you, Python!
  6. #
  7. #
  8. from __future__ import print_function
  9. import os
  10. import os.path as osp
  11. import sys
  12. import click
  13. from xdis import disassemble_file
  14. from xdis.version import __version__
  15. from xdis.version_info import PYTHON_VERSION_STR, PYTHON_VERSION_TRIPLE
  16. program, ext = os.path.splitext(os.path.basename(__file__))
  17. PATTERNS = ("*.pyc", "*.pyo")
  18. if click.__version__ >= "7.":
  19. case_sensitive = {"case_sensitive": False}
  20. else:
  21. case_sensitive = {}
  22. @click.command()
  23. @click.option(
  24. "--format",
  25. "-F",
  26. type=click.Choice(
  27. ["xasm", "bytes", "classic", "dis", "extended", "extended-bytes", "header"],
  28. **case_sensitive
  29. ),
  30. help="Select disassembly style.",
  31. )
  32. @click.option(
  33. "--method",
  34. "-m",
  35. metavar="FUNCTION-OR-METHOD",
  36. multiple=True,
  37. type=str,
  38. help=("Specify which specific methods or functions to show. "
  39. "If omitted all, functions are shown. "
  40. "Can be given multiple times.")
  41. )
  42. @click.option(
  43. "--show-source/--no-show-source",
  44. "-S",
  45. help="Intersperse Python source text from linecache if available.",
  46. )
  47. @click.version_option(version=__version__)
  48. @click.argument("files", nargs=-1, type=click.Path(readable=True), required=True)
  49. def main(format: list[str], method: tuple, show_source: bool, files):
  50. """Disassembles a Python bytecode file.
  51. We handle bytecode for virtually every release of Python and some releases of PyPy.
  52. The version of Python in the bytecode doesn't have to be the same version as
  53. the Python interpreter used to run this program. For example, you can disassemble Python 3.6.9
  54. bytecode from Python 2.7.15 and vice versa.
  55. """
  56. if not ((2, 7) <= PYTHON_VERSION_TRIPLE < (3, 16)):
  57. mess = "This code works on 3.6 to 3.15; you have %s."
  58. if (2, 4) <= PYTHON_VERSION_TRIPLE <= (2, 7):
  59. mess += " Code that works for %s can be found in the python-2.4 branch\n"
  60. elif (3, 1) <= PYTHON_VERSION_TRIPLE <= (3, 2):
  61. mess += " Code that works for %s can be found in the python-3.1 branch\n"
  62. elif (3, 3) <= PYTHON_VERSION_TRIPLE <= (3, 5):
  63. mess += " Code that works for %s can be found in the python-3.3 branch\n"
  64. sys.stderr.write(mess % PYTHON_VERSION_STR)
  65. sys.exit(2)
  66. rc = 0
  67. for path in files:
  68. # Some sanity checks
  69. if not osp.exists(path):
  70. sys.stderr.write("File name: '%s' doesn't exist\n" % path)
  71. continue
  72. elif not osp.isfile(path):
  73. sys.stderr.write("File name: '%s' isn't a file\n" % path)
  74. continue
  75. elif osp.getsize(path) < 50 and not path.endswith(".py"):
  76. sys.stderr.write(
  77. "File name: '%s (%d bytes)' is too short to be a valid pyc file\n"
  78. % (path, osp.getsize(path))
  79. )
  80. continue
  81. try:
  82. disassemble_file(path, sys.stdout, format, show_source=show_source, methods=method)
  83. except (ImportError, NotImplementedError, ValueError) as e:
  84. print(e)
  85. rc = 3
  86. sys.exit(rc)
  87. if __name__ == "__main__":
  88. main(sys.argv[1:])