debug.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # SPDX-FileCopyrightText: Copyright (C) 2023 MH3SP Server Project
  4. # SPDX-License-Identifier: AGPL-3.0-or-later
  5. """Debugging helper module.
  6. - Python 2.7 debugging (VS Code)
  7. https://stackoverflow.com/questions/72214043/how-to-debug-python-2-7-code-with-vs-code
  8. - Python 2.7 debugging (Visual Studio)
  9. Debug > Attach to Process... > Python remote (debugpy)
  10. - Debuggers
  11. https://wiki.python.org/moin/PythonDebuggingTools
  12. """
  13. import traceback
  14. import signal
  15. try:
  16. # Python 2
  17. import ConfigParser
  18. except ImportError:
  19. # Python 3
  20. import configparser as ConfigParser
  21. DEBUG_INI_PATH = "debug.ini"
  22. def debugpy_handler(sig, frame, addr="127.0.0.1", port="5678", **kwargs):
  23. """Handler for debugpy on Visual Studio and VS Code.
  24. References:
  25. https://github.com/microsoft/debugpy/
  26. https://code.visualstudio.com/docs/python/debugging
  27. https://learn.microsoft.com/visualstudio/python/debugging-python-in-visual-studio
  28. """
  29. import debugpy
  30. s = (addr, int(port)) # config's items are str
  31. debugpy.listen(s)
  32. print("Waiting for client on {}:{}\n".format(*s))
  33. debugpy.wait_for_client()
  34. def trepan_handler(sig, frame, **kwargs):
  35. """Handler for trepan2/trepan3k.
  36. References:
  37. https://github.com/rocky/python2-trepan/
  38. https://github.com/rocky/python3-trepan/
  39. https://python2-trepan.readthedocs.io/en/latest/entry-exit.html
  40. """
  41. from trepan.api import debug
  42. debug()
  43. def pudb_handler(sig, frame, **kwargs):
  44. """Handler for pudb on Linux and Cygwin.
  45. References:
  46. https://github.com/inducer/pudb
  47. https://documen.tician.de/pudb/
  48. """
  49. import pudb
  50. pudb.set_trace()
  51. def breakpoint_handler(sig, frame, **kwargs):
  52. """PDB/breakpoint handler.
  53. References:
  54. https://peps.python.org/pep-0553/
  55. https://realpython.com/python-debugging-pdb/
  56. """
  57. try:
  58. breakpoint() # Python >= 3.7
  59. except NameError:
  60. import pdb
  61. pdb.set_trace()
  62. def code_interact_handler(sig, frame, **kwargs):
  63. """Python interpreter handler.
  64. References:
  65. https://docs.python.org/2.7/library/code.html
  66. https://docs.python.org/3/library/code.html
  67. """
  68. import code
  69. local = {"_frame": frame}
  70. local.update(frame.f_globals)
  71. local.update(frame.f_locals)
  72. code.interact(local=local)
  73. DEBUG_HANDLERS = {
  74. "DEBUGPY": debugpy_handler,
  75. "TREPAN": trepan_handler,
  76. "PUDB": pudb_handler,
  77. "BREAKPOINT": breakpoint_handler,
  78. "CODE": code_interact_handler
  79. }
  80. def load_config(path=DEBUG_INI_PATH):
  81. config = ConfigParser.RawConfigParser()
  82. config.read(path)
  83. return config
  84. def load_handler_config(name, config):
  85. if config.has_section(name) and config.getboolean(name, "Enabled"):
  86. return {
  87. k.lower(): v
  88. for k, v in config.items(name)
  89. if k.lower() != "enabled"
  90. }
  91. def debug_signal_handler(sig, frame):
  92. """Default debug signal handler.
  93. Might raise EINTR/IOError when occuring during some syscalls on Python 2.
  94. """
  95. print("\n# Entering debug signal handler...\n\nTraceback:\n{}\n".format(
  96. "".join(traceback.format_stack(frame))
  97. ))
  98. try:
  99. config = load_config()
  100. except Exception:
  101. traceback.print_exc()
  102. print("\n# Aborting debug signal handler...\n")
  103. return
  104. for handler_name, handler_callback in DEBUG_HANDLERS.items():
  105. try:
  106. handler_config = load_handler_config(handler_name, config)
  107. if handler_config is not None:
  108. print("\nTrying handler `{}`".format(handler_name))
  109. print(" - handler config = `{}`\n".format(handler_config))
  110. handler_callback(sig, frame, **handler_config)
  111. break
  112. except Exception:
  113. traceback.print_exc()
  114. else:
  115. print("\nNo handler found!\n")
  116. print("\n# Exiting debug signal handler...\n")
  117. def register_debug_signal(fn=debug_signal_handler):
  118. """Register a debug handler on SIGBREAK (Windows) or SIGUSR1 (Linux).
  119. On Windows, press CTRL+Pause/Break to trigger.
  120. On Linux, send a SIGUSR1 signal.
  121. Will raise ValueError exception if not called from the main thread.
  122. """
  123. if hasattr(signal, "SIGBREAK"):
  124. signal.signal(signal.SIGBREAK, fn)
  125. else:
  126. signal.signal(signal.SIGUSR1, fn)