summaryrefslogtreecommitdiff
path: root/Cython/Debugger/Cygdb.py
blob: 596e5e11b010163ffca658a1c3f7f740304fe380 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python

"""
The Cython debugger

The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).

Additional gdb args can be provided only if a path to the project directory is
given.
"""

import os
import sys
import glob
import tempfile
import textwrap
import subprocess
import optparse
import logging

logger = logging.getLogger(__name__)


def make_command_file(path_to_debug_info, prefix_code='',
                      no_import=False, skip_interpreter=False):
    if not no_import:
        pattern = os.path.join(path_to_debug_info,
                               'cython_debug',
                               'cython_debug_info_*')
        debug_files = glob.glob(pattern)

        if not debug_files:
            sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
                                   usage, os.path.abspath(path_to_debug_info)))

    fd, tempfilename = tempfile.mkstemp()
    f = os.fdopen(fd, 'w')
    try:
        f.write(prefix_code)
        f.write(textwrap.dedent('''\
            # This is a gdb command file
            # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html

            set breakpoint pending on
            set print pretty on

            python
            try:
                # Activate virtualenv, if we were launched from one
                import os
                virtualenv = os.getenv('VIRTUAL_ENV')
                if virtualenv:
                    path_to_activate_this_py = os.path.join(virtualenv, 'bin', 'activate_this.py')
                    print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
                        virtualenv, path_to_activate_this_py))
                    with open(path_to_activate_this_py) as f:
                        exec(f.read(), dict(__file__=path_to_activate_this_py))
                from Cython.Debugger import libcython, libpython
            except Exception as ex:
                from traceback import print_exc
                print("There was an error in Python code originating from the file ''' + str(__file__) + '''")
                print("It used the Python interpreter " + str(sys.executable))
                print_exc()
                exit(1)
            end
            '''))

        if no_import:
            # don't do this, this overrides file command in .gdbinit
            # f.write("file %s\n" % sys.executable)
            pass
        else:
            if not skip_interpreter:
                # Point Cygdb to the interpreter that was used to generate
                # the debugging information.
                path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
                interpreter_file = open(path)
                try:
                    interpreter = interpreter_file.read()
                finally:
                    interpreter_file.close()
                f.write("file %s\n" % interpreter)

            f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))

            if not skip_interpreter:
                f.write(textwrap.dedent('''\
                    python
                    import sys
                    try:
                        gdb.lookup_type('PyModuleObject')
                    except RuntimeError:
                        sys.stderr.write(
                            "''' + interpreter + ''' was not compiled with debug symbols (or it was "
                            "stripped). Some functionality may not work (properly).\\n")
                    end
                '''))

            f.write("source .cygdbinit")
    finally:
        f.close()

    return tempfilename

usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"

def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
    """
    Start the Cython debugger. This tells gdb to import the Cython and Python
    extensions (libcython.py and libpython.py) and it enables gdb's pending
    breakpoints.

    path_to_debug_info is the path to the Cython build directory
    gdb_argv is the list of options to gdb
    no_import tells cygdb whether it should import debug information
    """
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("--gdb-executable",
        dest="gdb", default='gdb',
        help="gdb executable to use [default: gdb]")
    parser.add_option("--verbose", "-v",
        dest="verbosity", action="count", default=0,
        help="Verbose mode. Multiple -v options increase the verbosity")
    parser.add_option("--skip-interpreter",
                      dest="skip_interpreter", default=False, action="store_true",
                      help="Do not automatically point GDB to the same interpreter "
                           "used to generate debugging information")

    (options, args) = parser.parse_args()
    if path_to_debug_info is None:
        if len(args) > 1:
            path_to_debug_info = args[0]
        else:
            path_to_debug_info = os.curdir

    if gdb_argv is None:
        gdb_argv = args[1:]

    if path_to_debug_info == '--':
        no_import = True

    logging_level = logging.WARN
    if options.verbosity == 1:
        logging_level = logging.INFO
    if options.verbosity >= 2:
        logging_level = logging.DEBUG
    logging.basicConfig(level=logging_level)

    skip_interpreter = options.skip_interpreter

    logger.info("verbosity = %r", options.verbosity)
    logger.debug("options = %r; args = %r", options, args)
    logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
        path_to_debug_info, gdb_argv)

    tempfilename = make_command_file(path_to_debug_info,
                                     no_import=no_import,
                                     skip_interpreter=skip_interpreter)
    logger.info("Launching %s with command file: %s and gdb_argv: %s",
        options.gdb, tempfilename, gdb_argv)
    with open(tempfilename) as tempfile:
        logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, tempfile.read())
        logger.info("Spawning %s...", options.gdb)
        p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
        logger.info("Spawned %s (pid %d)", options.gdb, p.pid)
        while True:
            try:
                logger.debug("Waiting for gdb (pid %d) to exit...", p.pid)
                ret = p.wait()
                logger.debug("Wait for gdb (pid %d) to exit is done. Returned: %r", p.pid, ret)
            except KeyboardInterrupt:
                pass
            else:
                break
        logger.debug("Closing temp command file with fd: %s", tempfile.fileno())
    logger.debug("Removing temp command file: %s", tempfilename)
    os.remove(tempfilename)
    logger.debug("Removed temp command file: %s", tempfilename)