Cygdb.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python
  2. """
  3. The Cython debugger
  4. The current directory should contain a directory named 'cython_debug', or a
  5. path to the cython project directory should be given (the parent directory of
  6. cython_debug).
  7. Additional gdb args can be provided only if a path to the project directory is
  8. given.
  9. """
  10. import os
  11. import sys
  12. import glob
  13. import tempfile
  14. import textwrap
  15. import subprocess
  16. import optparse
  17. import logging
  18. logger = logging.getLogger(__name__)
  19. def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
  20. if not no_import:
  21. pattern = os.path.join(path_to_debug_info,
  22. 'cython_debug',
  23. 'cython_debug_info_*')
  24. debug_files = glob.glob(pattern)
  25. if not debug_files:
  26. sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
  27. usage, os.path.abspath(path_to_debug_info)))
  28. fd, tempfilename = tempfile.mkstemp()
  29. f = os.fdopen(fd, 'w')
  30. try:
  31. f.write(prefix_code)
  32. f.write(textwrap.dedent('''\
  33. # This is a gdb command file
  34. # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html
  35. set breakpoint pending on
  36. set print pretty on
  37. python
  38. # Activate virtualenv, if we were launched from one
  39. import os
  40. virtualenv = os.getenv('VIRTUAL_ENV')
  41. if virtualenv:
  42. path_to_activate_this_py = os.path.join(virtualenv, 'bin', 'activate_this.py')
  43. print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
  44. virtualenv, path_to_activate_this_py))
  45. with open(path_to_activate_this_py) as f:
  46. exec(f.read(), dict(__file__=path_to_activate_this_py))
  47. from Cython.Debugger import libcython, libpython
  48. end
  49. '''))
  50. if no_import:
  51. # don't do this, this overrides file command in .gdbinit
  52. # f.write("file %s\n" % sys.executable)
  53. pass
  54. else:
  55. path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
  56. interpreter_file = open(path)
  57. try:
  58. interpreter = interpreter_file.read()
  59. finally:
  60. interpreter_file.close()
  61. f.write("file %s\n" % interpreter)
  62. f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
  63. f.write(textwrap.dedent('''\
  64. python
  65. import sys
  66. try:
  67. gdb.lookup_type('PyModuleObject')
  68. except RuntimeError:
  69. sys.stderr.write(
  70. 'Python was not compiled with debug symbols (or it was '
  71. 'stripped). Some functionality may not work (properly).\\n')
  72. end
  73. source .cygdbinit
  74. '''))
  75. finally:
  76. f.close()
  77. return tempfilename
  78. usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"
  79. def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
  80. """
  81. Start the Cython debugger. This tells gdb to import the Cython and Python
  82. extensions (libcython.py and libpython.py) and it enables gdb's pending
  83. breakpoints.
  84. path_to_debug_info is the path to the Cython build directory
  85. gdb_argv is the list of options to gdb
  86. no_import tells cygdb whether it should import debug information
  87. """
  88. parser = optparse.OptionParser(usage=usage)
  89. parser.add_option("--gdb-executable",
  90. dest="gdb", default='gdb',
  91. help="gdb executable to use [default: gdb]")
  92. parser.add_option("--verbose", "-v",
  93. dest="verbosity", action="count", default=0,
  94. help="Verbose mode. Multiple -v options increase the verbosity")
  95. (options, args) = parser.parse_args()
  96. if path_to_debug_info is None:
  97. if len(args) > 1:
  98. path_to_debug_info = args[0]
  99. else:
  100. path_to_debug_info = os.curdir
  101. if gdb_argv is None:
  102. gdb_argv = args[1:]
  103. if path_to_debug_info == '--':
  104. no_import = True
  105. logging_level = logging.WARN
  106. if options.verbosity == 1:
  107. logging_level = logging.INFO
  108. if options.verbosity >= 2:
  109. logging_level = logging.DEBUG
  110. logging.basicConfig(level=logging_level)
  111. logger.info("verbosity = %r", options.verbosity)
  112. logger.debug("options = %r; args = %r", options, args)
  113. logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
  114. path_to_debug_info, gdb_argv)
  115. tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
  116. logger.info("Launching %s with command file: %s and gdb_argv: %s",
  117. options.gdb, tempfilename, gdb_argv)
  118. with open(tempfilename) as tempfile:
  119. logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, tempfile.read())
  120. logger.info("Spawning %s...", options.gdb)
  121. p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
  122. logger.info("Spawned %s (pid %d)", options.gdb, p.pid)
  123. while True:
  124. try:
  125. logger.debug("Waiting for gdb (pid %d) to exit...", p.pid)
  126. ret = p.wait()
  127. logger.debug("Wait for gdb (pid %d) to exit is done. Returned: %r", p.pid, ret)
  128. except KeyboardInterrupt:
  129. pass
  130. else:
  131. break
  132. logger.debug("Closing temp command file with fd: %s", tempfile.fileno())
  133. logger.debug("Removing temp command file: %s", tempfilename)
  134. os.remove(tempfilename)
  135. logger.debug("Removed temp command file: %s", tempfilename)