15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)#!/usr/bin/env python
25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""
45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)The Cython debugger
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)The current directory should contain a directory named 'cython_debug', or a
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)path to the cython project directory should be given (the parent directory of
85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cython_debug).
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)Additional gdb args can be provided only if a path to the project directory is
115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)given.
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import os
155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import sys
165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import glob
175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import tempfile
185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import textwrap
195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import subprocess
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import optparse
215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import logging
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)logger = logging.getLogger(__name__)
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if not no_import:
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        pattern = os.path.join(path_to_debug_info,
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               'cython_debug',
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               'cython_debug_info_*')
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        debug_files = glob.glob(pattern)
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if not debug_files:
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                   usage, os.path.abspath(path_to_debug_info)))
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    fd, tempfilename = tempfile.mkstemp()
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    f = os.fdopen(fd, 'w')
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    try:
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        f.write(prefix_code)
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        f.write('set breakpoint pending on\n')
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        f.write("set print pretty on\n")
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        f.write('python from Cython.Debugger import libcython, libpython\n')
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if no_import:
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            # don't do this, this overrides file command in .gdbinit
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            # f.write("file %s\n" % sys.executable)
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            pass
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            interpreter_file = open(path)
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            try:
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                interpreter = interpreter_file.read()
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            finally:
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                interpreter_file.close()
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            f.write("file %s\n" % interpreter)
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            f.write(textwrap.dedent('''\
585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                python
595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                import sys
605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                try:
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    gdb.lookup_type('PyModuleObject')
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                except RuntimeError:
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    sys.stderr.write(
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                        'Python was not compiled with debug symbols (or it was '
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                        'stripped). Some functionality may not work (properly).\\n')
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                end
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                source .cygdbinit
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            '''))
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    finally:
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        f.close()
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return tempfilename
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Start the Cython debugger. This tells gdb to import the Cython and Python
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    extensions (libcython.py and libpython.py) and it enables gdb's pending
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    breakpoints.
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    path_to_debug_info is the path to the Cython build directory
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    gdb_argv is the list of options to gdb
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    no_import tells cygdb whether it should import debug information
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    parser = optparse.OptionParser(usage=usage)
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    parser.add_option("--gdb-executable",
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        dest="gdb", default='gdb',
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        help="gdb executable to use [default: gdb]")
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    parser.add_option("--verbose", "-v",
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        dest="verbosity", action="count", default=0,
935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        help="Verbose mode. Multiple -v options increase the verbosity")
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    (options, args) = parser.parse_args()
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if path_to_debug_info is None:
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if len(args) > 1:
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            path_to_debug_info = args[0]
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            path_to_debug_info = os.curdir
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if gdb_argv is None:
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        gdb_argv = args[1:]
1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if path_to_debug_info == '--':
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        no_import = True
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logging_level = logging.WARN
1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if options.verbosity == 1:
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        logging_level = logging.INFO
1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if options.verbosity == 2:
1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        logging_level = logging.DEBUG
1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logging.basicConfig(level=logging_level)
1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.info("verbosity = %r", options.verbosity)
1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.debug("options = %r; args = %r", options, args)
1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        path_to_debug_info, gdb_argv)
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.info("Launching %s with command file: %s and gdb_argv: %s",
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        options.gdb, tempfilename, gdb_argv)
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, open(tempfilename).read())
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.info("Spawning %s...", options.gdb)
1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.info("Spawned %s (pid %d)", options.gdb, p.pid)
1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    while True:
1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        try:
1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            logger.debug("Waiting for gdb (pid %d) to exit...", p.pid)
1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            ret = p.wait()
1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            logger.debug("Wait for gdb (pid %d) to exit is done. Returned: %r", p.pid, ret)
1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        except KeyboardInterrupt:
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            pass
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            break
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.debug("Removing temp command file: %s", tempfilename)
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    os.remove(tempfilename)
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    logger.debug("Removed temp command file: %s", tempfilename)
139