From charlesreid1

This page explains how to run Cantera in debug mode via Python.

Note: thanks to Apple's continually-changing compiler toolchain, and their irritating obsession with abandoning standard tools, these instructions no longer work on OS X. I have not figured out how to debug C++ code called from Python using lldb, which has replaced gdb in the latest version of Mac OS X. To do this, you'll need to be running on Linux.

Creating a Cantera Debug Build

Before following this guide, you'll probably have to rebuild Cantera to include debugging symbols. See the Debugging Cantera page for instructions.

The Relation between Cantera and Python

Cantera is, at the core, a C++ library. When you download a copy of Cantera and build it, you are compiling C++ code into libraries of executable code. So, what does this have to do with Python?

Cantera has a Python interface - essentially, Python classes that are wrappers for the C++ interface. You have much of the same functionality with the Python and C++ libraries, but instead of a Cantera developer rewriting everything from C++ to Python (and probably making it run a lot slower in the process), the wrapper classes provide Python bindings for the underlying C++ code.

To run Cantera in debug mode through Python, what we'll do is run a Python driver, attach GDB to that process, and when Python calls the C++ library, GDB will follow the execution thread into the C++ library. At that point, GDB can halt, hit breakpoints, print stack traces, etc.

Running Cantera Through GDB and Python

Create Your Driver

Now that you have a debug build of Cantera, you'll need to create a driver, in Python, that uses the Cantera libraries. I'm going to issue a few simple commands. First I'll create a GRI Mech gas, then I'll equilibrate it. But I'll put a breakpoint in the equilibrate routine, to stop the code.

For this example, I'll use the following simple sequence of Python commands:

>>> from Cantera import *
>>> from Cantera.Reactor import *
>>> gas = GRI30()
>>> r = Reactor(gas)
>>> n = ReactorNet([r])

We will jump into the C++ core of Cantera, at the equilibrate() function, using gdb.

I'll need to attach GDB to the Python process in the midst of these commands. Order is very important here.

Debugging Cantera 2.0

Step 1: Import Python Module

The first step is to run

>>> from Cantera import *
>>> from Cantera.Reactor import *

This will load all of the Cantera libraries and hook them into Python.

Step 2: Determine Python PID, Attach GDB

The second step is to fire up GDB and attach it to the Python process. Do this by running ps, and determine the process id (pid) of the Python instance.

For example, if you see this:

$ ps

  PID TTY           TIME CMD
11832 ttys000    0:00.39 -bash
12071 ttys001    0:01.11 -bash
86612 ttys001    0:02.05 python

then your PID is 88612.

Fire up gdb, and run the attach command:

$ gdb
GNU gdb 6.3.50-20050815 (Apple version gdb-1515) (Sat Jan 15 08:33:48 UTC 2011)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin".

(gdb) attach 86612

Now you will see the reason for importing the module from Python before attaching gdb to the Python process: the import command loads the C++ libraries.

Running this attach command will spew a bunch of stuff about libraries and such, debug information not being available, unfound object files, etc. Don't worry. What's happening here is, gdb is loading every library that Python is using (including, now that you've executed from Cantera import *, every library that Cantera loads in).

A Note on Passing Control Between Python and GDB

When you attach gdb to the Python process, it will freeze the Python process and hand control over to gdb. When you issue the gdb command continue, it will hand control back over to Python. Note that control must be passed to Python to execute Python commands, and must be passed to gdb to execute gdb commands.

If Python has control of the program, and you want to give control to gdb, you can switch to your gdb window and use Control+C to stop execution. You can do this with an interactive Python shell because it is interactive, and always listening.

If you want to switch control from gdb back to Python, you can use gdb's continue command, which hands control of execution back to Python. Because Python is running as an interactive shell, Python returns to "listening" mode.

Step 3: Set Breakpoint

As mentioned, we'll be looking at a simple breakpoint example, specifically the ReactorNet constructor. First, we'll set the breakpoint in gdb:

Set a breakpoint in the ReactorNet constructor:

(gdb) b ReactorNet.cpp:24
Breakpoint 7 at 0x10efae0f9: file ReactorNet.cpp, line 24.

Now run the continue command to pass control back to Python:

(gdb) continue
Continuing.

If you see something like this:

(gdb) break ReactorNet.cpp:24
No source file named ReactorNet.cpp.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (ReactorNet.cpp:24) pending.

it means you didn't follow the instructions I gave at the top of the page! You have to create a debug build of Cantera.

Next, hand control back to Python:

(gdb) continue

Control has now been passed back to Python.

Step 4: Run Python Driver

Next step is to run the Python driver (the short set of commands listed above, which create a reactor network).

>>> gas = GRI30()
>>> r = Reactor(gas)
>>> n = ReactorNet([r])

The Python process should hang, and when you switch over to gdb, you should see a message about the breakpoint you have reached:

(gdb) continue
Continuing.

Breakpoint 7, Cantera::ReactorNet::ReactorNet (this=0x7f966ac37de0) at ReactorNet.cpp:24
24	    m_integ = newIntegrator("CVODE");// CVodeInt;
(gdb)
Continuing.

Voila!

Now you can, e.g., get a backtrace:

(gdb) bt
#0  Cantera::ReactorNet::ReactorNet (this=0x7f966ac37de0) at ReactorNet.cpp:24
#1  0x000000010efc3d1c in reactornet_new () at ctreactor.cpp:291
#2  0x000000010ed5f953 in py_reactornet_new (self=0x7fff51242b28, args=0x7f966ac00000) at ctreactor_methods.cpp:631
#3  0x000000010e9da5a9 in PyEval_EvalFrameEx ()
#4  0x000000010e9d8147 in PyEval_EvalCodeEx ()
#5  0x000000010ea11d7a in PyFunction_SetClosure ()
#6  0x000000010e9d06c6 in PyObject_Call ()
#7  0x000000010e9ed9bf in PyMethod_New ()
#8  0x000000010e9d06c6 in PyObject_Call ()
#9  0x000000010e9de018 in PyEval_CallObjectWithKeywords ()
#10 0x000000010e9eba7c in PyInstance_New ()
#11 0x000000010e9d06c6 in PyObject_Call ()
#12 0x000000010e9da78d in PyEval_EvalFrameEx ()
#13 0x000000010e9d8147 in PyEval_EvalCodeEx ()
#14 0x000000010e9d79b3 in PyEval_EvalCode ()
#15 0x000000010ea13c70 in PyParser_ASTFromFile ()
#16 0x000000010ea13ae9 in PyRun_InteractiveOneFlags ()
#17 0x000000010ea13578 in PyRun_InteractiveLoopFlags ()
#18 0x000000010ea13414 in PyRun_AnyFileExFlags ()
#19 0x000000010ea37e27 in Py_Main ()
#20 0x00007fff957e77e1 in start ()
Current language:  auto; currently c++

You can see how Cantera's Python wrapper works: Python calls a binding function py_reactornet_new in ctreactor_methods.cpp, which in turn calls a C binding for creating a new reactor, which calls the actual C++ core of Cantera.

Debugging Cantera 1.8

The following is an example that works for Cantera 1.8 (but not for Cantera 2.0...):

>>> from Cantera import *
>>> gas = GRI30()
>>> gas.set(T=320.0, P=OneAtm, X='CH4:1,O2:2,N2:7.52')
>>> gas.equilibrate('HP')

(Cantera 1.8) Step 1: Import Python Module

From Python, import the Cantera module:

>>> from Cantera import *
>>> from Cantera.Reactor import *

(Cantera 1.8) Step 2: Determine Python PID, Attach GDB

Run the ps command to see what Python's process ID is, then run the gdb command attach [pid].

$ gdb
GNU gdb 6.3.50-20050815 (Apple version gdb-1705) (Fri Jul  1 10:50:06 UTC 2011)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin".
(gdb) attach 446
Attaching to process 446.
[...]


(gdb) break equilibrate.cpp:44
Breakpoint 1 at 0x101099bd9: file equilibrate.cpp, line 44.

Note: the equilibrate class is located in /path/to/cantera/src/equil/equilibrate.cpp.


(Cantera 2.0) Step 3: Set Breakpoint

Set the breakpoint in the equilibrate class:

(gdb) break equilibrate.cpp:44
Breakpoint 1 at 0x101099bd9: file equilibrate.cpp, line 44.

Note: the equilibrate class is located in /path/to/cantera/src/equil/equilibrate.cpp.

(Cantera 2.0) Step 4: Run Python Driver

Now run the remainder of the Python driver:

>>> gas = GRI30()
>>> gas.set(T=320.0, P=OneAtm, X='CH4:1,O2:2,N2:7.52')
>>> gas.equilibrate('HP')

Python should hang on the last command, and when you switch to the gdb window, you should see this:

(gdb) continue
Continuing.

Breakpoint 2, Cantera::equilibrate (s=@0x101f29130, XY=0x101b8a4b4 "HP", tol=1.0000000000000001e-09, maxsteps=1000, maxiter=100, loglevel=-1) at equilibrate.cpp:44
44	    if (ixy == TP || ixy == HP || ixy == SP || ixy == TV) {
(gdb)
Continuing.

Voila! From here you can get a backtrace, step through code, etc etc etc.


Flags