disas.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. # Copyright (c) 2015-2016, 2818-2020, 2024 by Rocky Bernstein
  2. # Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
  3. # Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
  4. # Copyright (c) 1999 John Aycock
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. CPython magic- and version- independent disassembly routines
  20. There are two reasons we can't use Python's built-in routines
  21. from dis. First, the bytecode we are extracting may be from a different
  22. version of Python (different magic number) than the version of Python
  23. that is doing the extraction.
  24. Second, we need structured instruction information for the
  25. (de)-parsing step. Python 3.4 and up provides this, but we still do
  26. want to run on earlier Python versions.
  27. """
  28. import sys
  29. from collections import deque
  30. from xdis import check_object_path, iscode, load_module
  31. from decompyle3.scanner import get_scanner
  32. def disco(version: str, co, out=None, is_pypy=False) -> None:
  33. """
  34. diassembles and deparses a given code block 'co'
  35. """
  36. assert iscode(co)
  37. # store final output stream for case of error
  38. real_out = out or sys.stdout
  39. print(f"# Python {version}", file=real_out)
  40. if co.co_filename:
  41. print(f"# Embedded file name: {co.co_filename}", file=real_out)
  42. scanner = get_scanner(version, is_pypy=is_pypy)
  43. queue = deque([co])
  44. disco_loop(scanner.ingest, queue, real_out)
  45. def disco_loop(disasm, queue, real_out):
  46. while len(queue) > 0:
  47. co = queue.popleft()
  48. if co.co_name != "<module>":
  49. print(
  50. "\n# %s line %d of %s"
  51. % (co.co_name, co.co_firstlineno, co.co_filename),
  52. file=real_out,
  53. )
  54. tokens, customize = disasm(co)
  55. for t in tokens:
  56. if iscode(t.pattr):
  57. queue.append(t.pattr)
  58. elif iscode(t.attr):
  59. queue.append(t.attr)
  60. print(t, file=real_out)
  61. pass
  62. pass
  63. # def disassemble_fp(fp, outstream=None):
  64. # """
  65. # disassemble Python byte-code from an open file
  66. # """
  67. # (version, timestamp, magic_int, co, is_pypy,
  68. # source_size) = load_from_fp(fp)
  69. # if type(co) == list:
  70. # for con in co:
  71. # disco(version, con, outstream)
  72. # else:
  73. # disco(version, co, outstream, is_pypy=is_pypy)
  74. # co = None
  75. def disassemble_file(filename: str, outstream=None) -> None:
  76. """
  77. disassemble Python byte-code file (.pyc)
  78. If given a Python source file (".py") file, we'll
  79. try to find the corresponding compiled object.
  80. """
  81. filename = check_object_path(filename)
  82. (version, timestamp, magic_int, co, is_pypy, source_size, sip_hash) = load_module(
  83. filename
  84. )
  85. if isinstance(co, list):
  86. for con in co:
  87. disco(version, con, outstream)
  88. else:
  89. disco(version, co, outstream, is_pypy=is_pypy)
  90. co = None
  91. def _test() -> None:
  92. """Simple test program to disassemble a file."""
  93. argc = len(sys.argv)
  94. if argc != 2:
  95. if argc == 1:
  96. fn = __file__
  97. else:
  98. sys.stderr.write(f"usage: {__file__} [-|CPython compiled file]\n")
  99. sys.exit(2)
  100. else:
  101. fn = sys.argv[1]
  102. disassemble_file(fn)
  103. if __name__ == "__main__":
  104. _test()