Skip to content

DebuggingTechniques

patstew edited this page Jun 18, 2014 · 8 revisions

Cygdb

With Cython 0.14 comes a Cython and Python debugger, its documentation can be viewed here: docs.cython.org/src/userguide/debugging.html. The rest of this document will cover how Cython code could previously be debugged.

Symbolic debugging of Cython/Pyrex Code

Introduction

Generally speaking, a great many bugs in Cython/Pyrex code can be eliminated with simple print statements, or if needing to go low-level, printf() calls. But there are times when one needs to go in to much greater depth.

This page describes one approach for performing source-level debugging of code written in Cython or Pyrex on GNU/Linux and *BSD operating systems. It doesn't claim to be the only approach, or even the best approach, but I've used it many times to weed out bugs much more quickly.

The approach uses GNU DDD (Data Display Debugger), which is a reasonably useful graphical front end to the more console-oriented GNU GDB debugger.

Prerequisites

To use this approach, you will need to have installed ddd and gdb, both of which are available on the feeds of all decent Linux/BSD distributions.

Debugger-Friendly Compilation

To be able to step through your code, you will need to ensure that your Pyrex/Cython extension module is compiled and linked with the -g switch. To ensure this happens in your distutils script, add in the following options:

Extension(
    ...
    extra_compile_args=["-g"],
    extra_link_args=["-g"],
    ...
    )

and rebuild your extension. Note that the extra_compile_args=["-g"] option may be unnecessary - check the output of your python setup.py build command, and if you see two -g options being passed during the compilation of the .c file, remove the extra_compile_args keyword.

Making Your Module Loadable

Before starting up the debugger, you need to ensure that you'll be able to load your extension module within the debugger environment. There are two ways of doing this:
  • The easy way - make sure you have installed your module in the Python system lib tree, with sudo python setup.py install, or
  • The local way - locate your built module in the build subdirectory created by distutils, and symlink it into your current directory.

Before even attempting to run the debugger, start up a normal interactive Python session and ensure you can load your module. If your module is called 'foo', then try:

$ python
>>> import foo
>>> (ctrl-D)
$

If the module loads successfully within a normal non-debugging Python session, without exceptions or unresolved symbols, then you are ready to debug.

Running The Debugger

We should be ready for the debugging session.

First step is to get the debugger window open and start up python within it:

$ ddd python

When the ddd window opens, click in the bottom-most pane, and type run. When the interactive python interpreter starts up, import your module:

(gdb) run
>>> import foo
>>> (press ESCAPE)
(gdb)

If you were able to load your module successfully in the previous step, your module should load just as well within the ddd environment. On pressing ESCAPE after loading your module within ddd, you will have broken out of the Python interpreter loop and back into GDB command mode, hence the (gdb) prompt.

Now, you need to choose a debugging strategy. A very common one is to break at the top of a function which you suspect is causing your troubles. To do this, open up your Cython/Pyrex source file in your favourite editor, also open up the corresponding C source file which Pyrex/Cython generated. In the C source file, find the function or method where you want to break, and determine its 'munged' name. For example, in one of my projects, there's an extension class called NodeTerminal with a method called connectTo(). In the C file, this gets munged into the C function __pyx_pf_6dvedit_5nodes_12NodeTerminal_connectTo.

Using this example, in the ddd window's gdb pane, I would type:

(gdb) b __pyx_pf_6dvedit_5nodes_12NodeTerminal_connectTo
Breakpoint 1 at 0xb7f39749: file dvedit/nodes.c, line 1818.
(gdb)

Given that you see the Breakpoint 1 at... confirmation message, you are now ready to start your program. The next step is to resume the Python interpreter running within the ddd/gdb environment:

(gdb) c    [press ENTER twice]
>>>

Now that your extension is loaded, and the python interpreter is running again, you should now type in whatever Python commands are needed to cause your chosen method to get executed. For example, you might just write a small python script which creates a couple of objects and invokes a method, in which case you can, within the Python interpreter, just import your test script.

When you cause your breakpointed method to execute, DDD/GDB will stop at that point, and display the C source from your Cython/Pyrex module in the source window, and the gdb command window will show something like:

>>> import mytestscript
Breakpoint 1, __pyx_pf_6dvedit_5nodes_12NodeTerminal_connectTo (__pyx_v_self=0xb78e846c, __pyx_v_terminal=0xb78e69bc) at dvedit/nodes.c:1818
(gdb)

At this point, execution has stopped at the top of your function or method, and the C source generated from your Cython/Pyrex source will be showing in the source code pane.

Here's where we see a significant difference between Cython and Pyrex. Pyrex just inserts C comments stating the original Pyrex source file and line number, so you're constantly having to cross-reference between the DDD code window and a text editor window just to find where you are. On the other hand, Cython generates very useful comment blocks which include the line of code being executed, and the neighbouring 2-3 lines of code above and below it. It also flags which is the current line. For example:

/* "/home/david/work/video/myprogs/dvedit/dvedit/nodes.pyx":108
  • Makes a connection from an output node to this input
  • """
  • if self.src != None: # <<<<<<<<<<<<<<
  • raise Exception("Input %s of %s is already connected"

* % (self.id, self.node)) / __pyx_1 = PyObject_RichCompare(((PyObject)((struct __pyx_obj_6dvedit_5nodes

NodeTerminal *)__pyx_v_self)->src), Py_None, Py_NE); if (unlikely(!__pyx_1)) {__ pyx_filename = __pyx_f[0]; __pyx_lineno = 108; goto __pyx_L1;} __pyx_2 = __Pyx_PyObject_IsTrue(__pyx_1); if (unlikely(__pyx_2 < 0)) {__pyx_fi lename = __pyx_f[0]; __pyx_lineno = 108; goto __pyx_L1;} Py_DECREF(__pyx_1); __pyx_1 = 0; if (__pyx_2) {

This extra source-code peppering on the part of Cython eliminates a painful distraction when stepping through your C code.

Next Steps

Your next steps will be to master the ddd keyboard shortcuts for stepping-in, stepping-along, stepping-out, continue etc within the symbolic debugging environment. Otherwise, continually typing gdb commands could prove tiring. But you should also learn as many console-level gdb commands as you can.

With many of the C-level variables, you should be able to hover your mouse above them and see their value in the ddd statusbar. Alternately, you can right-click on variables to display them, even dereference them if they are pointers.

For Windows Users

Microsoft Tools

For debugging on Windows, the Microsoft Visual C++ or Visual Studio software is a popular (but proprietary) choice for visual C-level debugging. Doing a debug build using the -g flag to setup.py won't work unless you have a debug build of python (and numpy, and any other c extension you need to load). To do a debug build of your code only modify the following to setup.py: :: Extension( ... extra_compile_args=["-Zi", "/Od"], extra_link_args=["-debug"], ... ) This will allow you to do c-source debugging of your code, but not any python internals.

Open a normal python terminal (this also works with ipython or IDEs that embed python) and import your cython module. Open Visual Studio, select 'Tools->Attach To Process' and select the python process you've just opened. You can now use the debugging controls in Visual Studio (pause, continue, step, view variables etc). If you want to add a breakpoint, open one of your .c files in Visual Studio and right click->Breakpoint->Insert Breakpoint on the appropriate line.

MinGW32

(Can anyone offer some wisdom here for debugging Cython/Pyrex with the MinGW Dev-C++ IDE?)

Distutils will make your build_ext with symbols if you use the -g switch:

python setup.py build_ext -g --compiler=mingw32

There are a couple caveats. Distutils outputs a .pyd file named <module-name>_d.pyd instead of <module-name>.pyd. You can successfully rename the <module-name>_d.pyd file to <module-name>.pyd and run GDB with symbols. Also, distutils requires the Python debugging libraries libpython25_d.a and possibly python25_d.lib (located in the "libs" directory of your Python installation). My installed version did not have these; I copied and renamed libpython25.a and python25.lib to libpython25_d.a and python25_d.lib and distutils compiled and linked correctly.

I used gdb to debug with symbols (I have both Cygwin and MinGW installed, I compile with MinGW, but use Cygwin's gdb). The directions are similar to ddd: start GDB like "gdb python" (and make sure it does not pick up Cygwin's Python). Type "run" at the prompt to run Python. Import your module, then type, "exit()". At this point, your symbols should be loaded and available to you when you run Python a second time.

For Sage Users

Typing

sage -gdb

at the command line will start up Sage but under gdb, so you can get back traces, etc. We don't have any special support for ddd yet in Sage.

Conclusion

This page has described one method of saving much time and frustration when tracking down difficult bugs in your Cython/Pyrex programs. If you can think of anything missing from this article, or can think of better ways, then please edit this page and share.

Clone this wiki locally