libcython.py 43.1 KB
Newer Older
1 2 3 4
"""
GDB extension that adds Cython support.
"""

Mark Florisson's avatar
Mark Florisson committed
5 6
from __future__ import with_statement

Mark Florisson's avatar
Mark Florisson committed
7
import os
8
import sys
Mark Florisson's avatar
Mark Florisson committed
9
import textwrap
Mark Florisson's avatar
Mark Florisson committed
10
import operator
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
import traceback
import functools
import itertools
import collections

import gdb

try:
  from lxml import etree
  have_lxml = True
except ImportError:
    have_lxml = False
    try:
        # Python 2.5
        from xml.etree import cElementTree as etree
    except ImportError:
        try:
            # Python 2.5
            from xml.etree import ElementTree as etree
        except ImportError:
            try:
                # normal cElementTree install
                import cElementTree as etree
            except ImportError:
                # normal ElementTree install
                import elementtree.ElementTree as etree

Mark Florisson's avatar
Mark Florisson committed
38 39 40 41 42 43 44
try:
    import pygments.lexers
    import pygments.formatters
except ImportError:
    pygments = None
    sys.stderr.write("Install pygments for colorized source code.\n")

45 46 47 48 49 50 51 52
if hasattr(gdb, 'string_to_argv'):
    from gdb import string_to_argv
else:
    from shlex import split as string_to_argv

from Cython.Debugger import libpython

# C or Python type
Mark Florisson's avatar
Mark Florisson committed
53 54
CObject = 'CObject'
PythonObject = 'PythonObject'
55

Mark Florisson's avatar
Mark Florisson committed
56
_data_types = dict(CObject=CObject, PythonObject=PythonObject)
57 58
_filesystemencoding = sys.getfilesystemencoding() or 'UTF-8'

Mark Florisson's avatar
Mark Florisson committed
59 60
# decorators

61
def dont_suppress_errors(function):
Mark Florisson's avatar
Mark Florisson committed
62
    "*sigh*, readline"
63 64 65 66 67 68 69 70 71 72
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        try:
            return function(*args, **kwargs)
        except Exception:
            traceback.print_exc()
            raise
    
    return wrapper

Mark Florisson's avatar
Mark Florisson committed
73 74 75
def default_selected_gdb_frame(err=True):
    def decorator(function):
        @functools.wraps(function)
Mark Florisson's avatar
Mark Florisson committed
76
        def wrapper(self, frame=None, *args, **kwargs):
Mark Florisson's avatar
Mark Florisson committed
77 78 79 80 81 82 83 84
            try:
                frame = frame or gdb.selected_frame()
            except RuntimeError:
                raise gdb.GdbError("No frame is currently selected.")
                
            if err and frame.name() is None:
                raise NoFunctionNameInFrameError()
    
Mark Florisson's avatar
Mark Florisson committed
85
            return function(self, frame, *args, **kwargs)
Mark Florisson's avatar
Mark Florisson committed
86 87
        return wrapper
    return decorator
Mark Florisson's avatar
Mark Florisson committed
88

Mark Florisson's avatar
Mark Florisson committed
89 90
def require_cython_frame(function):
    @functools.wraps(function)
Mark Florisson's avatar
Mark Florisson committed
91
    @require_running_program
Mark Florisson's avatar
Mark Florisson committed
92
    def wrapper(self, *args, **kwargs):
Mark Florisson's avatar
Mark Florisson committed
93 94
        frame = kwargs.get('frame') or gdb.selected_frame()
        if not self.is_cython_function(frame):
Mark Florisson's avatar
Mark Florisson committed
95 96 97 98
            raise gdb.GdbError('Selected frame does not correspond with a '
                               'Cython function we know about.')
        return function(self, *args, **kwargs)
    return wrapper 
Mark Florisson's avatar
Mark Florisson committed
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

def dispatch_on_frame(c_command, python_command=None):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(self, *args, **kwargs):
            is_cy = self.is_cython_function()
            is_py = self.is_python_function()
            
            if is_cy or (is_py and not python_command):
                function(self, *args, **kwargs)
            elif is_py:
                gdb.execute(python_command)
            elif self.is_relevant_function():
                gdb.execute(c_command)
            else:
                raise gdb.GdbError("Not a function cygdb knows about. "
                                   "Use the normal GDB commands instead.")
Mark Florisson's avatar
Mark Florisson committed
116
        
Mark Florisson's avatar
Mark Florisson committed
117 118 119
        return wrapper
    return decorator

Mark Florisson's avatar
Mark Florisson committed
120 121 122 123 124 125 126 127 128 129 130
def require_running_program(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        try:
            gdb.selected_frame()
        except RuntimeError:
            raise gdb.GdbError("No frame is currently selected.")
        
        return function(*args, **kwargs)
    return wrapper
    
Mark Florisson's avatar
Mark Florisson committed
131

Mark Florisson's avatar
Mark Florisson committed
132 133 134 135 136 137 138 139 140 141
def gdb_function_value_to_unicode(function):
    @functools.wraps(function)
    def wrapper(self, string, *args, **kwargs):
        if isinstance(string, gdb.Value):
            string = string.string()

        return function(self, string, *args, **kwargs)
    return wrapper


Mark Florisson's avatar
Mark Florisson committed
142 143 144
# Classes that represent the debug information
# Don't rename the parameters of these classes, they come directly from the XML

145
class CythonModule(object):
Mark Florisson's avatar
Mark Florisson committed
146
    def __init__(self, module_name, filename, c_filename):
147 148
        self.name = module_name
        self.filename = filename
Mark Florisson's avatar
Mark Florisson committed
149
        self.c_filename = c_filename
150
        self.globals = {}
Mark Florisson's avatar
Mark Florisson committed
151 152 153 154
        # {cython_lineno: min(c_linenos)}
        self.lineno_cy2c = {}
        # {c_lineno: cython_lineno}
        self.lineno_c2cy = {}
Mark Florisson's avatar
Mark Florisson committed
155 156
        self.functions = {}
        
157
class CythonVariable(object):
Mark Florisson's avatar
Mark Florisson committed
158

159
    def __init__(self, name, cname, qualified_name, type, lineno):
160 161 162 163
        self.name = name
        self.cname = cname
        self.qualified_name = qualified_name
        self.type = type
164
        self.lineno = int(lineno)
165 166 167 168 169 170 171 172 173

class CythonFunction(CythonVariable):
    def __init__(self, 
                 module, 
                 name, 
                 cname, 
                 pf_cname,
                 qualified_name, 
                 lineno, 
174 175
                 type=CObject,
                 is_initmodule_function="False"):
Mark Florisson's avatar
Mark Florisson committed
176 177 178
        super(CythonFunction, self).__init__(name, 
                                             cname, 
                                             qualified_name, 
179 180
                                             type,
                                             lineno)
181 182
        self.module = module
        self.pf_cname = pf_cname
183
        self.is_initmodule_function = is_initmodule_function == "True"
184 185
        self.locals = {}
        self.arguments = []
Mark Florisson's avatar
Mark Florisson committed
186 187
        self.step_into_functions = set()

Mark Florisson's avatar
Mark Florisson committed
188 189 190 191 192 193 194 195 196 197 198

# General purpose classes

class CythonBase(object):
    
    @default_selected_gdb_frame(err=False)
    def is_cython_function(self, frame):
        return frame.name() in self.cy.functions_by_cname

    @default_selected_gdb_frame(err=False)
    def is_python_function(self, frame):
Mark Florisson's avatar
Mark Florisson committed
199 200 201 202 203 204 205 206 207
        """
        Tells if a frame is associated with a Python function.
        If we can't read the Python frame information, don't regard it as such.
        """
        if frame.name() == 'PyEval_EvalFrameEx':
            pyframe = libpython.Frame(frame).get_pyop()
            return pyframe and not pyframe.is_optimized_out()
        return False
        
Mark Florisson's avatar
Mark Florisson committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    @default_selected_gdb_frame()
    def get_c_function_name(self, frame):
        return frame.name()

    @default_selected_gdb_frame()
    def get_c_lineno(self, frame):
        return frame.find_sal().line
    
    @default_selected_gdb_frame()
    def get_cython_function(self, frame):
        result = self.cy.functions_by_cname.get(frame.name())
        if result is None:
            raise NoCythonFunctionInFrameError()
            
        return result
    
    @default_selected_gdb_frame()
    def get_cython_lineno(self, frame):
Mark Florisson's avatar
Mark Florisson committed
226 227 228 229
        """
        Get the current Cython line number. Returns 0 if there is no 
        correspondence between the C and Cython code.
        """
Mark Florisson's avatar
Mark Florisson committed
230
        cyfunc = self.get_cython_function(frame)
Mark Florisson's avatar
Mark Florisson committed
231 232
        return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame), 0)
    
Mark Florisson's avatar
Mark Florisson committed
233 234 235
    @default_selected_gdb_frame()
    def get_source_desc(self, frame):
        filename = lineno = lexer = None
Mark Florisson's avatar
Mark Florisson committed
236
        if self.is_cython_function(frame):
Mark Florisson's avatar
Mark Florisson committed
237 238 239
            filename = self.get_cython_function(frame).module.filename
            lineno = self.get_cython_lineno(frame)
            if pygments:
Mark Florisson's avatar
Mark Florisson committed
240
                lexer = pygments.lexers.CythonLexer(stripall=False)
Mark Florisson's avatar
Mark Florisson committed
241
        elif self.is_python_function(frame):
Mark Florisson's avatar
Mark Florisson committed
242 243 244
            pyframeobject = libpython.Frame(frame).get_pyop()

            if not pyframeobject:
245 246
                raise gdb.GdbError(
                            'Unable to read information on python frame')
Mark Florisson's avatar
Mark Florisson committed
247

248 249
            filename = pyframeobject.filename()
            lineno = pyframeobject.current_line_num()
250
            
Mark Florisson's avatar
Mark Florisson committed
251
            if pygments:
Mark Florisson's avatar
Mark Florisson committed
252
                lexer = pygments.lexers.PythonLexer(stripall=False)
Mark Florisson's avatar
Mark Florisson committed
253 254
        else:
            symbol_and_line_obj = frame.find_sal()
255
            if not symbol_and_line_obj or not symbol_and_line_obj.symtab:
Mark Florisson's avatar
Mark Florisson committed
256 257 258
                filename = None
                lineno = 0
            else:
259
                filename = symbol_and_line_obj.symtab.fullname()
Mark Florisson's avatar
Mark Florisson committed
260 261
                lineno = symbol_and_line_obj.line
                if pygments:
Mark Florisson's avatar
Mark Florisson committed
262
                    lexer = pygments.lexers.CLexer(stripall=False)
Mark Florisson's avatar
Mark Florisson committed
263
            
Mark Florisson's avatar
Mark Florisson committed
264 265 266 267 268 269
        return SourceFileDescriptor(filename, lexer), lineno

    @default_selected_gdb_frame()
    def get_source_line(self, frame):
        source_desc, lineno = self.get_source_desc()
        return source_desc.get_source(lineno)
Mark Florisson's avatar
Mark Florisson committed
270
    
Mark Florisson's avatar
Mark Florisson committed
271
    @default_selected_gdb_frame()
Mark Florisson's avatar
Mark Florisson committed
272 273 274 275 276 277 278 279 280
    def is_relevant_function(self, frame):
        """
        returns whether we care about a frame on the user-level when debugging
        Cython code
        """
        name = frame.name()
        older_frame = frame.older()
        if self.is_cython_function(frame) or self.is_python_function(frame):
            return True
281
        elif older_frame and self.is_cython_function(older_frame):
Mark Florisson's avatar
Mark Florisson committed
282 283 284 285 286
            # direct C function call from a Cython function
            cython_func = self.get_cython_function(older_frame)
            return name in cython_func.step_into_functions

        return False
Mark Florisson's avatar
Mark Florisson committed
287
    
288
    @default_selected_gdb_frame(err=False)
Mark Florisson's avatar
Mark Florisson committed
289 290 291 292 293 294 295 296 297 298
    def print_stackframe(self, frame, index, is_c=False):
        """
        Print a C, Cython or Python stack frame and the line of source code
        if available.
        """
        # do this to prevent the require_cython_frame decorator from
        # raising GdbError when calling self.cy.cy_cvalue.invoke()
        selected_frame = gdb.selected_frame()
        frame.select()
        
299 300 301 302 303 304
        try:
            source_desc, lineno = self.get_source_desc(frame)
        except NoFunctionNameInFrameError:
            print '#%-2d Unknown Frame (compile with -g)' % index
            return

Mark Florisson's avatar
Mark Florisson committed
305 306 307 308 309 310
        if not is_c and self.is_python_function(frame):
            pyframe = libpython.Frame(frame).get_pyop()
            if pyframe is None or pyframe.is_optimized_out():
                # print this python function as a C function
                return self.print_stackframe(frame, index, is_c=True)
            
311
            func_name = pyframe.co_name
Mark Florisson's avatar
Mark Florisson committed
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
            func_cname = 'PyEval_EvalFrameEx'
            func_args = []
        elif self.is_cython_function(frame):
            cyfunc = self.get_cython_function(frame)
            f = lambda arg: self.cy.cy_cvalue.invoke(arg, frame=frame)
            
            func_name = cyfunc.name
            func_cname = cyfunc.cname
            func_args = [] # [(arg, f(arg)) for arg in cyfunc.arguments]
        else:
            source_desc, lineno = self.get_source_desc(frame)
            func_name = frame.name()
            func_cname = func_name
            func_args = []
        
327 328 329 330 331 332 333
        try:
            gdb_value = gdb.parse_and_eval(func_cname)
        except RuntimeError:
            func_address = 0
        else:
            # Seriously? Why is the address not an int?
            func_address = int(str(gdb_value.address).split()[0], 0)
Mark Florisson's avatar
Mark Florisson committed
334
        
335 336 337 338 339 340 341
        a = ', '.join('%s=%s' % (name, val) for name, val in func_args)
        print '#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a),
            
        if source_desc.filename is not None:
            print 'at %s:%s' % (source_desc.filename, lineno),
        
        print
Mark Florisson's avatar
Mark Florisson committed
342 343 344 345 346 347 348
            
        try:
            print '    ' + source_desc.get_source(lineno)
        except gdb.GdbError:
            pass
        
        selected_frame.select()
Mark Florisson's avatar
Mark Florisson committed
349
    
350
    def get_remote_cython_globals_dict(self):
Mark Florisson's avatar
Mark Florisson committed
351 352 353 354 355 356 357 358 359 360
        m = gdb.parse_and_eval('__pyx_m')
        
        try:
            PyModuleObject = gdb.lookup_type('PyModuleObject')
        except RuntimeError:
            raise gdb.GdbError(textwrap.dedent("""\
                Unable to lookup type PyModuleObject, did you compile python 
                with debugging support (-g)?"""))
            
        m = m.cast(PyModuleObject.pointer())
361 362 363 364 365 366 367 368 369 370
        return m['md_dict']
        
    
    def get_cython_globals_dict(self):
        """
        Get the Cython globals dict where the remote names are turned into
        local strings.
        """
        remote_dict = self.get_remote_cython_globals_dict()
        pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)
Mark Florisson's avatar
Mark Florisson committed
371 372 373 374 375
        
        result = {}
        seen = set()
        for k, v in pyobject_dict.iteritems():
            result[k.proxyval(seen)] = v
Mark Florisson's avatar
Mark Florisson committed
376
            
Mark Florisson's avatar
Mark Florisson committed
377
        return result
Mark Florisson's avatar
Mark Florisson committed
378

379 380 381 382 383 384 385 386 387 388 389 390
    def print_gdb_value(self, name, value, max_name_length=None, prefix=''):
        if libpython.pretty_printer_lookup(value):
            typename = ''
        else:
            typename = '(%s) ' % (value.type,)
                
        if max_name_length is None:
            print '%s%s = %s%s' % (prefix, name, typename, value)
        else:
            print '%s%-*s = %s%s' % (prefix, max_name_length, name, typename, 
                                     value)

391
    def is_initialized(self, cython_func, local_name):
392 393 394 395
        islocal = local_name in cython_func.locals
        if islocal:
            cyvar = cython_func.locals[local_name]
            if '->' in cyvar.cname:
396 397 398 399
                # Closed over free variable
                if self.get_cython_lineno() >= cython_func.lineno + 1:
                    if cyvar.type == PythonObject:
                        return long(gdb.parse_and_eval(cyvar.cname))
400
                    return True
401
                return False
402
        
403
        cur_lineno = self.get_cython_lineno()
404
        return (local_name in cython_func.arguments or
405
                (islocal and cur_lineno > cyvar.lineno))
406

Mark Florisson's avatar
Mark Florisson committed
407
class SourceFileDescriptor(object):
Mark Florisson's avatar
Mark Florisson committed
408
    def __init__(self, filename, lexer, formatter=None):
Mark Florisson's avatar
Mark Florisson committed
409 410 411 412 413 414 415 416
        self.filename = filename
        self.lexer = lexer
        self.formatter = formatter

    def valid(self):
        return self.filename is not None

    def lex(self, code):
Mark Florisson's avatar
Mark Florisson committed
417 418
        if pygments and self.lexer and parameters.colorize_code:
            bg = parameters.terminal_background.value
Mark Florisson's avatar
Mark Florisson committed
419 420 421 422 423 424 425 426 427
            if self.formatter is None:
                formatter = pygments.formatters.TerminalFormatter(bg=bg)
            else:
                formatter = self.formatter

            return pygments.highlight(code, self.lexer, formatter)

        return code

428
    def _get_source(self, start, stop, lex_source, mark_line, lex_entire):
Mark Florisson's avatar
Mark Florisson committed
429
        with open(self.filename) as f:
430 431 432 433 434 435 436 437 438
            # to provide "correct" colouring, the entire code needs to be
            # lexed. However, this makes a lot of things terribly slow, so
            # we decide not to. Besides, it's unlikely to matter.
            
            if lex_source and lex_entire:
                f = self.lex(f.read()).splitlines()
            
            slice = itertools.islice(f, start - 1, stop - 1)
            
Mark Florisson's avatar
Mark Florisson committed
439
            for idx, line in enumerate(slice):
Mark Florisson's avatar
Mark Florisson committed
440 441 442 443 444
                if start + idx == mark_line:
                    prefix = '>'
                else:
                    prefix = ' '
                
445 446 447 448
                if lex_source and not lex_entire:
                    line = self.lex(line)

                yield '%s %4d    %s' % (prefix, start + idx, line.rstrip())
Mark Florisson's avatar
Mark Florisson committed
449

450 451
    def get_source(self, start, stop=None, lex_source=True, mark_line=0, 
                   lex_entire=False):
Mark Florisson's avatar
Mark Florisson committed
452 453
        exc = gdb.GdbError('Unable to retrieve source code')
        
Mark Florisson's avatar
Mark Florisson committed
454
        if not self.filename:
Mark Florisson's avatar
Mark Florisson committed
455
            raise exc
Mark Florisson's avatar
Mark Florisson committed
456 457
        
        start = max(start, 1)
Mark Florisson's avatar
Mark Florisson committed
458 459
        if stop is None:
            stop = start + 1
Mark Florisson's avatar
Mark Florisson committed
460 461 462

        try:
            return '\n'.join(
463
                self._get_source(start, stop, lex_source, mark_line, lex_entire))
Mark Florisson's avatar
Mark Florisson committed
464 465 466
        except IOError:
            raise exc

Mark Florisson's avatar
Mark Florisson committed
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492

# Errors

class CyGDBError(gdb.GdbError):
    """
    Base class for Cython-command related erorrs
    """
    
    def __init__(self, *args):
        args = args or (self.msg,)
        super(CyGDBError, self).__init__(*args)
    
class NoCythonFunctionInFrameError(CyGDBError):
    """
    raised when the user requests the current cython function, which is 
    unavailable
    """
    msg = "Current function is a function cygdb doesn't know about"

class NoFunctionNameInFrameError(NoCythonFunctionInFrameError):
    """
    raised when the name of the C function could not be determined 
    in the current C stack frame
    """
    msg = ('C function name could not be determined in the current C stack '
           'frame')
493 494


Mark Florisson's avatar
Mark Florisson committed
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
# Parameters

class CythonParameter(gdb.Parameter):
    """
    Base class for cython parameters
    """
    
    def __init__(self, name, command_class, parameter_class, default=None):
        self.show_doc = self.set_doc = self.__class__.__doc__
        super(CythonParameter, self).__init__(name, command_class, 
                                              parameter_class)
        if default is not None:
            self.value = default
   
    def __nonzero__(self):
        return bool(self.value)
    
    __bool__ = __nonzero__ # python 3

class CompleteUnqualifiedFunctionNames(CythonParameter):
    """
    Have 'cy break' complete unqualified function or method names.
    """ 

class ColorizeSourceCode(CythonParameter):
    """
Mark Florisson's avatar
Mark Florisson committed
521
    Tell cygdb whether to colorize source code.
Mark Florisson's avatar
Mark Florisson committed
522 523 524 525
    """

class TerminalBackground(CythonParameter):
    """
Mark Florisson's avatar
Mark Florisson committed
526
    Tell cygdb about the user's terminal background (light or dark).
Mark Florisson's avatar
Mark Florisson committed
527
    """
Mark Florisson's avatar
Mark Florisson committed
528

Mark Florisson's avatar
Mark Florisson committed
529
class CythonParameters(object):
Mark Florisson's avatar
Mark Florisson committed
530 531
    """
    Simple container class that might get more functionality in the distant
Mark Florisson's avatar
Mark Florisson committed
532
    future (mostly to remind us that we're dealing with parameters).
Mark Florisson's avatar
Mark Florisson committed
533
    """
Mark Florisson's avatar
Mark Florisson committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
    
    def __init__(self):
        self.complete_unqualified = CompleteUnqualifiedFunctionNames(
            'cy_complete_unqualified',
            gdb.COMMAND_BREAKPOINTS,
            gdb.PARAM_BOOLEAN,
            True)
        self.colorize_code = ColorizeSourceCode(
            'cy_colorize_code',
            gdb.COMMAND_FILES,
            gdb.PARAM_BOOLEAN,
            True)
        self.terminal_background = TerminalBackground(
            'cy_terminal_background_color',
            gdb.COMMAND_FILES,
            gdb.PARAM_STRING,
            "dark")
Mark Florisson's avatar
Mark Florisson committed
551
        
Mark Florisson's avatar
Mark Florisson committed
552 553
parameters = CythonParameters()

Mark Florisson's avatar
Mark Florisson committed
554 555 556

# Commands

Mark Florisson's avatar
Mark Florisson committed
557 558 559 560
class CythonCommand(gdb.Command, CythonBase):
    """
    Base class for Cython commands
    """
Mark Florisson's avatar
Mark Florisson committed
561 562 563
    
    command_class = gdb.COMMAND_NONE
    
Mark Florisson's avatar
Mark Florisson committed
564
    @classmethod
Mark Florisson's avatar
Mark Florisson committed
565
    def _register(cls, clsname, args, kwargs):
Mark Florisson's avatar
Mark Florisson committed
566
        if not hasattr(cls, 'completer_class'):
567
            return cls(clsname, cls.command_class, *args, **kwargs)
Mark Florisson's avatar
Mark Florisson committed
568
        else:
569
            return cls(clsname, cls.command_class, cls.completer_class, 
Mark Florisson's avatar
Mark Florisson committed
570
                       *args, **kwargs)
Mark Florisson's avatar
Mark Florisson committed
571 572 573 574 575 576 577 578
    
    @classmethod
    def register(cls, *args, **kwargs):
        alias = getattr(cls, 'alias', None)
        if alias:
            cls._register(cls.alias, args, kwargs)
            
        return cls._register(cls.name, args, kwargs)
Mark Florisson's avatar
Mark Florisson committed
579

Mark Florisson's avatar
Mark Florisson committed
580 581

class CyCy(CythonCommand):
582 583 584 585 586 587
    """
    Invoke a Cython command. Available commands are:
        
        cy import
        cy break
        cy step
Mark Florisson's avatar
Mark Florisson committed
588
        cy next
589 590
        cy run
        cy cont
591
        cy finish
592 593
        cy up
        cy down
594
        cy select
Mark Florisson's avatar
Mark Florisson committed
595
        cy bt / cy backtrace
596
        cy list
597
        cy print
598 599
        cy locals
        cy globals
600
        cy exec
601
    """
Mark Florisson's avatar
Mark Florisson committed
602
    
Mark Florisson's avatar
Mark Florisson committed
603 604 605 606
    name = 'cy'
    command_class = gdb.COMMAND_NONE
    completer_class = gdb.COMPLETE_COMMAND
    
607 608 609 610
    def __init__(self, name, command_class, completer_class):
        # keep the signature 2.5 compatible (i.e. do not use f(*a, k=v)
        super(CythonCommand, self).__init__(name, command_class, 
                                            completer_class, prefix=True)
Mark Florisson's avatar
Mark Florisson committed
611
        
Mark Florisson's avatar
Mark Florisson committed
612 613 614 615 616
        commands = dict(
            import_ = CyImport.register(),
            break_ = CyBreak.register(),
            step = CyStep.register(),
            next = CyNext.register(),
617 618
            run = CyRun.register(),
            cont = CyCont.register(),
619
            finish = CyFinish.register(),
620 621
            up = CyUp.register(),
            down = CyDown.register(),
Mark Florisson's avatar
Mark Florisson committed
622
            select = CySelect.register(),
Mark Florisson's avatar
Mark Florisson committed
623
            bt = CyBacktrace.register(),
Mark Florisson's avatar
Mark Florisson committed
624 625 626 627
            list = CyList.register(),
            print_ = CyPrint.register(),
            locals = CyLocals.register(),
            globals = CyGlobals.register(),
628 629
            exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
            _exec = CyExec.register(),
Mark Florisson's avatar
Mark Florisson committed
630
            cy_cname = CyCName('cy_cname'),
Mark Florisson's avatar
Mark Florisson committed
631 632
            cy_cvalue = CyCValue('cy_cvalue'),
            cy_lineno = CyLine('cy_lineno'),
Mark Florisson's avatar
Mark Florisson committed
633
        )
Mark Florisson's avatar
Mark Florisson committed
634
            
Mark Florisson's avatar
Mark Florisson committed
635 636 637
        for command_name, command in commands.iteritems():
            command.cy = self
            setattr(self, command_name, command)
Mark Florisson's avatar
Mark Florisson committed
638 639 640
        
        self.cy = self
        
Mark Florisson's avatar
Mark Florisson committed
641 642 643 644 645 646 647 648 649 650 651 652 653
        # Cython module namespace
        self.cython_namespace = {}
        
        # maps (unique) qualified function names (e.g. 
        # cythonmodule.ClassName.method_name) to the CythonFunction object
        self.functions_by_qualified_name = {}
        
        # unique cnames of Cython functions
        self.functions_by_cname = {}
        
        # map function names like method_name to a list of all such 
        # CythonFunction objects
        self.functions_by_name = collections.defaultdict(list)
654

Mark Florisson's avatar
Mark Florisson committed
655 656

class CyImport(CythonCommand):
657 658 659 660
    """
    Import debug information outputted by the Cython compiler
    Example: cy import FILE...
    """
Mark Florisson's avatar
Mark Florisson committed
661 662 663 664 665
    
    name = 'cy import'
    command_class = gdb.COMMAND_STATUS
    completer_class = gdb.COMPLETE_FILENAME
    
666 667 668 669 670 671
    def invoke(self, args, from_tty):
        args = args.encode(_filesystemencoding)
        for arg in string_to_argv(args):
            try:
                f = open(arg)
            except OSError, e:
Mark Florisson's avatar
Mark Florisson committed
672 673
                raise gdb.GdbError('Unable to open file %r: %s' % 
                                                (args, e.args[1]))
674 675 676 677 678
            
            t = etree.parse(f)
            
            for module in t.getroot():
                cython_module = CythonModule(**module.attrib)
Mark Florisson's avatar
Mark Florisson committed
679
                self.cy.cython_namespace[cython_module.name] = cython_module
680 681 682 683 684 685 686 687
                
                for variable in module.find('Globals'):
                    d = variable.attrib
                    cython_module.globals[d['name']] = CythonVariable(**d)
                
                for function in module.find('Functions'):
                    cython_function = CythonFunction(module=cython_module, 
                                                     **function.attrib)
Mark Florisson's avatar
Mark Florisson committed
688

689
                    # update the global function mappings
Mark Florisson's avatar
Mark Florisson committed
690
                    name = cython_function.name
Mark Florisson's avatar
Mark Florisson committed
691
                    qname = cython_function.qualified_name
Mark Florisson's avatar
Mark Florisson committed
692 693
                    
                    self.cy.functions_by_name[name].append(cython_function)
Mark Florisson's avatar
Mark Florisson committed
694
                    self.cy.functions_by_qualified_name[
695
                        cython_function.qualified_name] = cython_function
Mark Florisson's avatar
Mark Florisson committed
696 697
                    self.cy.functions_by_cname[
                        cython_function.cname] = cython_function
698
                    
Mark Florisson's avatar
Mark Florisson committed
699
                    d = cython_module.functions[qname] = cython_function
Mark Florisson's avatar
Mark Florisson committed
700
                    
701 702 703
                    for local in function.find('Locals'):
                        d = local.attrib
                        cython_function.locals[d['name']] = CythonVariable(**d)
Mark Florisson's avatar
Mark Florisson committed
704 705 706 707

                    for step_into_func in function.find('StepIntoFunctions'):
                        d = step_into_func.attrib
                        cython_function.step_into_functions.add(d['name'])
708 709 710
                    
                    cython_function.arguments.extend(
                        funcarg.tag for funcarg in function.find('Arguments'))
Mark Florisson's avatar
Mark Florisson committed
711 712 713 714 715 716 717

                for marker in module.find('LineNumberMapping'):
                    cython_lineno = int(marker.attrib['cython_lineno'])
                    c_linenos = map(int, marker.attrib['c_linenos'].split())
                    cython_module.lineno_cy2c[cython_lineno] = min(c_linenos)
                    for c_lineno in c_linenos:
                        cython_module.lineno_c2cy[c_lineno] = cython_lineno
718

719

Mark Florisson's avatar
Mark Florisson committed
720
class CyBreak(CythonCommand):
721 722 723
    """
    Set a breakpoint for Cython code using Cython qualified name notation, e.g.:
        
Mark Florisson's avatar
Mark Florisson committed
724
        cy break cython_modulename.ClassName.method_name...
725 726 727
    
    or normal notation:
        
Mark Florisson's avatar
Mark Florisson committed
728 729 730 731 732
        cy break function_or_method_name...
    
    or for a line number:
    
        cy break cython_module:lineno...
733 734 735 736 737 738 739 740 741
    
    Set a Python breakpoint:
        Break on any function or method named 'func' in module 'modname'
            
            cy break -p modname.func...
        
        Break on any function or method named 'func'
            
            cy break -p func...
742 743
    """
    
Mark Florisson's avatar
Mark Florisson committed
744 745 746
    name = 'cy break'
    command_class = gdb.COMMAND_BREAKPOINTS
    
Mark Florisson's avatar
Mark Florisson committed
747 748 749
    def _break_pyx(self, name):
        modulename, _, lineno = name.partition(':')
        lineno = int(lineno)
750 751 752 753 754
        if modulename:
            cython_module = self.cy.cython_namespace[modulename]
        else:
            cython_module = self.get_cython_function().module

Mark Florisson's avatar
Mark Florisson committed
755 756
        if lineno in cython_module.lineno_cy2c:
            c_lineno = cython_module.lineno_cy2c[lineno]
Mark Florisson's avatar
Mark Florisson committed
757
            breakpoint = '%s:%s' % (cython_module.c_filename, c_lineno)
Mark Florisson's avatar
Mark Florisson committed
758 759
            gdb.execute('break ' + breakpoint)
        else:
Mark Florisson's avatar
Mark Florisson committed
760 761
            raise GdbError("Not a valid line number. "
                           "Does it contain actual code?")
Mark Florisson's avatar
Mark Florisson committed
762 763
    
    def _break_funcname(self, funcname):
Mark Florisson's avatar
Mark Florisson committed
764
        func = self.cy.functions_by_qualified_name.get(funcname)
765 766 767 768
        
        if func and func.is_initmodule_function:
            func = None
        
Mark Florisson's avatar
Mark Florisson committed
769 770 771
        break_funcs = [func]
        
        if not func:
772 773 774
            funcs = self.cy.functions_by_name.get(funcname) or []
            funcs = [f for f in funcs if not f.is_initmodule_function]
            
Mark Florisson's avatar
Mark Florisson committed
775 776 777
            if not funcs:
                gdb.execute('break ' + funcname)
                return
778
            
Mark Florisson's avatar
Mark Florisson committed
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
            if len(funcs) > 1:
                # multiple functions, let the user pick one
                print 'There are multiple such functions:'
                for idx, func in enumerate(funcs):
                    print '%3d) %s' % (idx, func.qualified_name)
                
                while True:
                    try:
                        result = raw_input(
                            "Select a function, press 'a' for all "
                            "functions or press 'q' or '^D' to quit: ")
                    except EOFError:
                        return
                    else:
                        if result.lower() == 'q':
794
                            return
Mark Florisson's avatar
Mark Florisson committed
795 796 797 798 799 800 801
                        elif result.lower() == 'a':
                            break_funcs = funcs
                            break
                        elif (result.isdigit() and 
                            0 <= int(result) < len(funcs)):
                            break_funcs = [funcs[int(result)]]
                            break
802
                        else:
Mark Florisson's avatar
Mark Florisson committed
803 804 805 806 807 808 809 810 811 812
                            print 'Not understood...'
            else:
                break_funcs = [funcs[0]]
        
        for func in break_funcs:
            gdb.execute('break %s' % func.cname)
            if func.pf_cname:
                gdb.execute('break %s' % func.pf_cname)
    
    def invoke(self, function_names, from_tty):
813 814 815 816 817 818 819 820 821 822 823
        argv = string_to_argv(function_names.encode('UTF-8'))
        if function_names.startswith('-p'):
            argv = argv[1:]
            python_breakpoints = True
        else:
            python_breakpoints = False
        
        for funcname in argv:
            if python_breakpoints:
                gdb.execute('py-break %s' % funcname)
            elif ':' in funcname:
Mark Florisson's avatar
Mark Florisson committed
824 825 826
                self._break_pyx(funcname)
            else:
                self._break_funcname(funcname)
827 828 829
    
    @dont_suppress_errors
    def complete(self, text, word):
830 831 832 833 834 835 836
        # Filter init-module functions (breakpoints can be set using 
        # modulename:linenumber).
        names =  [n for n, L in self.cy.functions_by_name.iteritems() 
                        if any(not f.is_initmodule_function for f in L)]
        qnames = [n for n, f in self.cy.functions_by_qualified_name.iteritems()
                        if not f.is_initmodule_function]
        
Mark Florisson's avatar
Mark Florisson committed
837
        if parameters.complete_unqualified:
838 839 840
            all_names = itertools.chain(qnames, names)
        else:
            all_names = qnames
Mark Florisson's avatar
Mark Florisson committed
841

842
        words = text.strip().split()
843 844
        if not words or '.' not in words[-1]:
            # complete unqualified
845
            seen = set(text[:-len(word)].split())
846 847 848 849 850 851
            return [n for n in all_names 
                          if n.startswith(word) and n not in seen]
        
        # complete qualified name
        lastword = words[-1]
        compl = [n for n in qnames if n.startswith(lastword)]
852 853 854 855 856 857 858 859 860
        
        if len(lastword) > len(word):
            # readline sees something (e.g. a '.') as a word boundary, so don't
            # "recomplete" this prefix
            strip_prefix_length = len(lastword) - len(word)
            compl = [n[strip_prefix_length:] for n in compl]
            
        return compl

Mark Florisson's avatar
Mark Florisson committed
861

Mark Florisson's avatar
Mark Florisson committed
862
class CythonInfo(CythonBase, libpython.PythonInfo):
Mark Florisson's avatar
Mark Florisson committed
863
    """
864
    Implementation of the interface dictated by libpython.LanguageInfo.
Mark Florisson's avatar
Mark Florisson committed
865
    """
Mark Florisson's avatar
Mark Florisson committed
866
    
Mark Florisson's avatar
Mark Florisson committed
867 868 869 870 871 872 873
    def lineno(self, frame):
        # Take care of the Python and Cython levels. We need to care for both
        # as we can't simply dispath to 'py-step', since that would work for
        # stepping through Python code, but it would not step back into Cython-
        # related code. The C level should be dispatched to the 'step' command.
        if self.is_cython_function(frame):
            return self.get_cython_lineno(frame)
Mark Florisson's avatar
Mark Florisson committed
874
        return super(CythonInfo, self).lineno(frame)
Mark Florisson's avatar
Mark Florisson committed
875 876
    
    def get_source_line(self, frame):
877
        try:
878
            line = super(CythonInfo, self).get_source_line(frame)
879 880 881 882
        except gdb.GdbError:
            return None
        else:
            return line.strip() or None
Mark Florisson's avatar
Mark Florisson committed
883

Mark Florisson's avatar
Mark Florisson committed
884 885 886 887
    def exc_info(self, frame):
        if self.is_python_function:
            return super(CythonInfo, self).exc_info(frame)

888
    def runtime_break_functions(self):
889
        if self.is_cython_function():
890
            return self.get_cython_function().step_into_functions
891
        return ()
892 893 894
    
    def static_break_functions(self):
        result = ['PyEval_EvalFrameEx']
895 896 897
        result.extend(self.cy.functions_by_cname)
        return result

898

899 900 901 902 903 904 905 906 907
class CythonExecutionControlCommand(CythonCommand, 
                                    libpython.ExecutionControlCommandBase):
    
    @classmethod
    def register(cls):
        return cls(cls.name, cython_info)


class CyStep(CythonExecutionControlCommand, libpython.PythonStepperMixin):
908 909
    "Step through Cython, Python or C code."
    
910
    name = 'cy -step'
911 912
    stepinto = True
    
913
    def invoke(self, args, from_tty):
914
        if self.is_python_function():
915
            self.python_step(self.stepinto)
916
        elif not self.is_cython_function():
917 918 919
            if self.stepinto:
                command = 'step'
            else:
920
                command = 'next'
921 922 923
                
            self.finish_executing(gdb.execute(command, to_string=True))
        else:
924
            self.step(stepinto=self.stepinto)
Mark Florisson's avatar
Mark Florisson committed
925

926

927
class CyNext(CyStep):
928
    "Step-over Cython, Python or C code."
Mark Florisson's avatar
Mark Florisson committed
929

930
    name = 'cy -next'
931
    stepinto = False
Mark Florisson's avatar
Mark Florisson committed
932 933


934
class CyRun(CythonExecutionControlCommand):
935 936 937 938 939 940 941
    """
    Run a Cython program. This is like the 'run' command, except that it 
    displays Cython or Python source lines as well
    """
    
    name = 'cy run'
    
942
    invoke = CythonExecutionControlCommand.run
943 944


945
class CyCont(CythonExecutionControlCommand):
946 947 948 949 950 951
    """
    Continue a Cython program. This is like the 'run' command, except that it 
    displays Cython or Python source lines as well.
    """
    
    name = 'cy cont'
952
    invoke = CythonExecutionControlCommand.cont
953 954


955
class CyFinish(CythonExecutionControlCommand):
956 957 958 959 960
    """
    Execute until the function returns.
    """
    name = 'cy finish'

961
    invoke = CythonExecutionControlCommand.finish
962 963


964
class CyUp(CythonCommand):
965 966 967 968 969
    """
    Go up a Cython, Python or relevant C frame.
    """
    name = 'cy up'
    _command = 'up'
970 971
    
    def invoke(self, *args):
Mark Florisson's avatar
Mark Florisson committed
972 973 974 975 976 977 978 979 980 981 982 983 984 985
        try:
            gdb.execute(self._command, to_string=True)
            while not self.is_relevant_function(gdb.selected_frame()):
                gdb.execute(self._command, to_string=True)
        except RuntimeError, e:
            raise gdb.GdbError(*e.args)
        
        frame = gdb.selected_frame()
        index = 0
        while frame:
            frame = frame.older()
            index += 1
            
        self.print_stackframe(index=index - 1)
986 987


988 989 990 991 992 993 994 995 996
class CyDown(CyUp):
    """
    Go down a Cython, Python or relevant C frame.
    """
    
    name = 'cy down'
    _command = 'down'


997
class CySelect(CythonCommand):
Mark Florisson's avatar
Mark Florisson committed
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
    """
    Select a frame. Use frame numbers as listed in `cy backtrace`.
    This command is useful because `cy backtrace` prints a reversed backtrace.
    """
    
    name = 'cy select'
    
    def invoke(self, stackno, from_tty):
        try:
            stackno = int(stackno)
        except ValueError:
            raise gdb.GdbError("Not a valid number: %r" % (stackno,))
        
        frame = gdb.selected_frame()
        while frame.newer():
            frame = frame.newer()
        
1015
        stackdepth = libpython.stackdepth(frame)
Mark Florisson's avatar
Mark Florisson committed
1016 1017 1018 1019 1020 1021 1022
        
        try:
            gdb.execute('select %d' % (stackdepth - stackno - 1,))
        except RuntimeError, e:
            raise gdb.GdbError(*e.args)


Mark Florisson's avatar
Mark Florisson committed
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
class CyBacktrace(CythonCommand):
    'Print the Cython stack'
    
    name = 'cy bt'
    alias = 'cy backtrace'
    command_class = gdb.COMMAND_STACK
    completer_class = gdb.COMPLETE_NONE
    
    @require_running_program
    def invoke(self, args, from_tty):
        # get the first frame
        selected_frame = frame = gdb.selected_frame()
        while frame.older():
            frame = frame.older()
        
        print_all = args == '-a'
        
        index = 0
        while frame:
            is_c = False
            
1044 1045 1046 1047 1048 1049 1050 1051
            is_relevant = False
            try:
                is_relevant = self.is_relevant_function(frame)
            except CyGDBError:
                pass
                
            if print_all or is_relevant:
                self.print_stackframe(frame, index)
Mark Florisson's avatar
Mark Florisson committed
1052 1053 1054 1055 1056 1057
            
            index += 1
            frame = frame.newer()
        
        selected_frame.select()

1058

Mark Florisson's avatar
Mark Florisson committed
1059
class CyList(CythonCommand):
Mark Florisson's avatar
Mark Florisson committed
1060 1061 1062 1063
    """
    List Cython source code. To disable to customize colouring see the cy_*
    parameters.
    """
Mark Florisson's avatar
Mark Florisson committed
1064
    
Mark Florisson's avatar
Mark Florisson committed
1065 1066 1067 1068
    name = 'cy list'
    command_class = gdb.COMMAND_FILES
    completer_class = gdb.COMPLETE_NONE
    
1069
    # @dispatch_on_frame(c_command='list')
Mark Florisson's avatar
Mark Florisson committed
1070
    def invoke(self, _, from_tty):
Mark Florisson's avatar
Mark Florisson committed
1071
        sd, lineno = self.get_source_desc()
1072 1073
        source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno, 
                               lex_entire=True)
Mark Florisson's avatar
Mark Florisson committed
1074
        print source
1075

Mark Florisson's avatar
Mark Florisson committed
1076 1077

class CyPrint(CythonCommand):
1078 1079 1080 1081
    """
    Print a Cython variable using 'cy-print x' or 'cy-print module.function.x'
    """
    
Mark Florisson's avatar
Mark Florisson committed
1082 1083 1084
    name = 'cy print'
    command_class = gdb.COMMAND_DATA
    
Mark Florisson's avatar
Mark Florisson committed
1085
    def invoke(self, name, from_tty, max_name_length=None):
Mark Florisson's avatar
Mark Florisson committed
1086 1087 1088
        if self.is_python_function():
            return gdb.execute('py-print ' + name)
        elif self.is_cython_function():
1089 1090 1091 1092 1093 1094 1095 1096
            value = self.cy.cy_cvalue.invoke(name.lstrip('*'))
            for c in name:
                if c == '*':
                    value = value.dereference()
                else:
                    break
                
            self.print_gdb_value(name, value, max_name_length)
Mark Florisson's avatar
Mark Florisson committed
1097 1098
        else:
            gdb.execute('print ' + name)
Mark Florisson's avatar
Mark Florisson committed
1099
        
1100
    def complete(self):
Mark Florisson's avatar
Mark Florisson committed
1101
        if self.is_cython_function():
Mark Florisson's avatar
Mark Florisson committed
1102 1103
            f = self.get_cython_function()
            return list(itertools.chain(f.locals, f.globals))
Mark Florisson's avatar
Mark Florisson committed
1104 1105
        else:
            return []
Mark Florisson's avatar
Mark Florisson committed
1106

1107

Mark Florisson's avatar
Mark Florisson committed
1108 1109
sortkey = lambda (name, value): name.lower()

Mark Florisson's avatar
Mark Florisson committed
1110
class CyLocals(CythonCommand):
Mark Florisson's avatar
Mark Florisson committed
1111 1112 1113 1114 1115 1116 1117 1118 1119
    """
    List the locals from the current Cython frame.
    """
    
    name = 'cy locals'
    command_class = gdb.COMMAND_STACK
    completer_class = gdb.COMPLETE_NONE
    
    @dispatch_on_frame(c_command='info locals', python_command='py-locals')
Mark Florisson's avatar
Mark Florisson committed
1120
    def invoke(self, args, from_tty):
1121 1122 1123 1124 1125 1126 1127
        cython_function = self.get_cython_function()
        
        if cython_function.is_initmodule_function:
            self.cy.globals.invoke(args, from_tty)
            return

        local_cython_vars = cython_function.locals
Mark Florisson's avatar
Mark Florisson committed
1128
        max_name_length = len(max(local_cython_vars, key=len))
Mark Florisson's avatar
Mark Florisson committed
1129
        for name, cyvar in sorted(local_cython_vars.iteritems(), key=sortkey):
1130 1131 1132 1133 1134
            if self.is_initialized(self.get_cython_function(), cyvar.name):
                value = gdb.parse_and_eval(cyvar.cname)
                if not value.is_optimized_out:
                    self.print_gdb_value(cyvar.name, value, 
                                         max_name_length, '')
Mark Florisson's avatar
Mark Florisson committed
1135

Mark Florisson's avatar
Mark Florisson committed
1136 1137

class CyGlobals(CyLocals):
Mark Florisson's avatar
Mark Florisson committed
1138 1139 1140 1141 1142 1143 1144 1145 1146
    """
    List the globals from the current Cython module.
    """
    
    name = 'cy globals'
    command_class = gdb.COMMAND_STACK
    completer_class = gdb.COMPLETE_NONE
    
    @dispatch_on_frame(c_command='info variables', python_command='py-globals')
Mark Florisson's avatar
Mark Florisson committed
1147
    def invoke(self, args, from_tty):
Mark Florisson's avatar
Mark Florisson committed
1148 1149
        global_python_dict = self.get_cython_globals_dict()
        module_globals = self.get_cython_function().module.globals
Mark Florisson's avatar
Mark Florisson committed
1150
        
Mark Florisson's avatar
Mark Florisson committed
1151 1152 1153 1154 1155 1156
        max_globals_len = 0
        max_globals_dict_len = 0
        if module_globals:
            max_globals_len = len(max(module_globals, key=len))
        if global_python_dict:
            max_globals_dict_len = len(max(global_python_dict))
Mark Florisson's avatar
Mark Florisson committed
1157
            
Mark Florisson's avatar
Mark Florisson committed
1158
        max_name_length = max(max_globals_len, max_globals_dict_len)
Mark Florisson's avatar
Mark Florisson committed
1159 1160
        
        seen = set()
Mark Florisson's avatar
Mark Florisson committed
1161 1162
        print 'Python globals:'
        for k, v in sorted(global_python_dict.iteritems(), key=sortkey):
Mark Florisson's avatar
Mark Florisson committed
1163 1164
            v = v.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
            seen.add(k)
Mark Florisson's avatar
Mark Florisson committed
1165
            print '    %-*s = %s' % (max_name_length, k, v)
Mark Florisson's avatar
Mark Florisson committed
1166
        
Mark Florisson's avatar
Mark Florisson committed
1167 1168 1169
        print 'C globals:'
        for name, cyvar in sorted(module_globals.iteritems(), key=sortkey):
            if name not in seen:
1170 1171 1172 1173 1174 1175 1176 1177
                try:
                    value = gdb.parse_and_eval(cyvar.cname)
                except RuntimeError:
                    pass
                else:
                    if not value.is_optimized_out:
                        self.print_gdb_value(cyvar.name, value,
                                             max_name_length, '    ')
Mark Florisson's avatar
Mark Florisson committed
1178 1179


1180
class CyExec(CythonCommand, libpython.PyExec):
1181 1182 1183 1184
    """
    Execute Python code in the nearest Python or Cython frame.
    """
    
1185 1186 1187 1188 1189 1190 1191
    name = '-cy-exec'
    command_class = gdb.COMMAND_STACK
    completer_class = gdb.COMPLETE_NONE
    
    def _fill_locals_dict(self, executor, local_dict_pointer):
        "Fill a remotely allocated dict with values from the Cython C stack"
        cython_func = self.get_cython_function()
1192
        current_lineno = self.get_cython_lineno()
1193 1194
        
        for name, cyvar in cython_func.locals.iteritems():
1195 1196 1197
            if (cyvar.type == PythonObject and 
                self.is_initialized(cython_func, name)):
                
1198 1199 1200 1201 1202 1203 1204 1205
                try:
                    val = gdb.parse_and_eval(cyvar.cname)
                except RuntimeError:
                    continue
                else:
                    if val.is_optimized_out:
                        continue
                
1206 1207
                pystringp = executor.alloc_pystring(name)
                code = '''
1208 1209 1210
                    (PyObject *) PyDict_SetItem(
                        (PyObject *) %d,
                        (PyObject *) %d,
1211 1212
                        (PyObject *) %s)
                ''' % (local_dict_pointer, pystringp, cyvar.cname)
1213 1214 1215 1216 1217 1218 1219 1220

                try:
                    if gdb.parse_and_eval(code) < 0:
                        gdb.parse_and_eval('PyErr_Print()')
                        raise gdb.GdbError("Unable to execute Python code.")
                finally:
                    # PyDict_SetItem doesn't steal our reference
                    executor.decref(pystringp)
1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238
    
    def _find_first_cython_or_python_frame(self):
        frame = gdb.selected_frame()
        while frame:
            if (self.is_cython_function(frame) or 
                self.is_python_function(frame)):
                return frame
            
            frame = frame.older()
        
        raise gdb.GdbError("There is no Cython or Python frame on the stack.")
        
    def invoke(self, expr, from_tty):
        frame = self._find_first_cython_or_python_frame()
        if self.is_python_function(frame):
            libpython.py_exec.invoke(expr, from_tty)
            return
        
1239
        expr, input_type = self.readcode(expr)
1240 1241
        executor = libpython.PythonCodeExecutor()
        
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256
        with libpython.FetchAndRestoreError():
            # get the dict of Cython globals and construct a dict in the 
            # inferior with Cython locals
            global_dict = gdb.parse_and_eval(
                '(PyObject *) PyModule_GetDict(__pyx_m)')
            local_dict = gdb.parse_and_eval('(PyObject *) PyDict_New()')
            
            cython_function = self.get_cython_function()
            
            try:
                self._fill_locals_dict(executor, 
                                       libpython.pointervalue(local_dict))
                executor.evalcode(expr, input_type, global_dict, local_dict)
            finally:
                executor.decref(libpython.pointervalue(local_dict))
1257 1258


Mark Florisson's avatar
Mark Florisson committed
1259 1260 1261 1262
# Functions

class CyCName(gdb.Function, CythonBase):
    """
Mark Florisson's avatar
Mark Florisson committed
1263 1264 1265 1266 1267 1268
    Get the C name of a Cython variable in the current context.
    Examples:
        
        print $cy_cname("function")
        print $cy_cname("Class.method")
        print $cy_cname("module.function")
Mark Florisson's avatar
Mark Florisson committed
1269 1270 1271
    """
    
    @require_cython_frame
Mark Florisson's avatar
Mark Florisson committed
1272
    @gdb_function_value_to_unicode
Mark Florisson's avatar
Mark Florisson committed
1273
    def invoke(self, cyname, frame=None):
Mark Florisson's avatar
Mark Florisson committed
1274 1275 1276 1277 1278 1279 1280 1281 1282
        frame = frame or gdb.selected_frame()
        cname = None
        
        if self.is_cython_function(frame):
            cython_function = self.get_cython_function(frame)
            if cyname in cython_function.locals:
                cname = cython_function.locals[cyname].cname
            elif cyname in cython_function.module.globals:
                cname = cython_function.module.globals[cyname].cname
Mark Florisson's avatar
Mark Florisson committed
1283 1284 1285 1286 1287 1288 1289 1290
            else:
                qname = '%s.%s' % (cython_function.module.name, cyname)
                if qname in cython_function.module.functions:
                    cname = cython_function.module.functions[qname].cname
            
        if not cname:
            cname = self.cy.functions_by_qualified_name.get(cyname)
            
Mark Florisson's avatar
Mark Florisson committed
1291 1292 1293
        if not cname:
            raise gdb.GdbError('No such Cython variable: %s' % cyname)
        
Mark Florisson's avatar
Mark Florisson committed
1294
        return cname
Mark Florisson's avatar
Mark Florisson committed
1295 1296


Mark Florisson's avatar
Mark Florisson committed
1297 1298 1299 1300 1301 1302
class CyCValue(CyCName):
    """
    Get the value of a Cython variable.
    """
    
    @require_cython_frame
Mark Florisson's avatar
Mark Florisson committed
1303
    @gdb_function_value_to_unicode
Mark Florisson's avatar
Mark Florisson committed
1304
    def invoke(self, cyname, frame=None):
1305 1306 1307 1308
        globals_dict = self.get_cython_globals_dict()
        cython_function = self.get_cython_function(frame)

        if self.is_initialized(cython_function, cyname):
1309
            cname = super(CyCValue, self).invoke(cyname, frame=frame)
Mark Florisson's avatar
Mark Florisson committed
1310
            return gdb.parse_and_eval(cname)
1311 1312 1313 1314
        elif cyname in globals_dict:
            return globals_dict[cyname]._gdbval
        else:
            raise gdb.GdbError("Variable %s is not initialized." % cyname)
Mark Florisson's avatar
Mark Florisson committed
1315

Mark Florisson's avatar
Mark Florisson committed
1316

Mark Florisson's avatar
Mark Florisson committed
1317 1318 1319 1320 1321 1322 1323 1324 1325
class CyLine(gdb.Function, CythonBase):
    """
    Get the current Cython line.
    """
    
    @require_cython_frame
    def invoke(self):
        return self.get_cython_lineno()

1326
cython_info = CythonInfo()
Robert Bradshaw's avatar
Robert Bradshaw committed
1327
cy = CyCy.register()
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
cython_info.cy = cy

def register_defines():
    libpython.source_gdb_script(textwrap.dedent("""\
        define cy step
        cy -step
        end
        
        define cy next
        cy -next
        end
        
        document cy step
        %s
        end
        
        document cy next
        %s
        end
    """) % (CyStep.__doc__, CyNext.__doc__))

register_defines()