Note: this post can be read as a series of terminal commands if you’re not interested in the intervening explanations.
Recently, I started writing a Python extension that wraps the procedural generation library libnoise using SIP-generated code, and encountered the inevitable segfaults. It didn’t take long to establish that the error was somewhere in my C++ code, but I couldn’t figure out where exactly it was. A couple of searches online revealed that the way to go about finding these things was to use a proper debugger, and because the GNU Debugger – ``gdb` – supports Python, that seemed to be the way to go.
Getting it working was not, however, particularly straightforward.
supports debugging Python code as of version 7. Unfortunately, Apple’s version
gdb is currently sitting at 6.3. So all I had to do was get a newer
version, right? Wrong.
Assuming you have Homebrew – if you don’t, go read about it – you can get a newer version of gdb from there. Since gdb is sort of a compiler, it’s not available in the main Homebrew repository (there’s a policy you can read on the wiki), so you’ll have to:
$ brew tap homebrew/dupes
first, and then run:
$ brew install gdb
You’ll notice immediately that if you run
gdb --version in a terminal you’ll
GNU gdb 6.3.50-20050815 (Apple version gdb-1822) (Sun Aug 5 03:00:42 UTC 2012) Copyright 2004 Free Software Foundation, Inc. ... This GDB was configured as "x86_64-apple-darwin".
(That’s right, the Apple version is circa 2004…) Since you now have two
gdb installed, the shell will by default start the one in
/usr/bin, while your Homebrew’d version is located in
are a few ways to fix this. One is to edit the order of the paths in
/etc/paths; you could, if you wanted, copy over the system version of
too. However, both of these solutions may run you into other problems,
particularly if you use XCode, which depends on Apple’s version of
debugging. I haven’t actually tried deleting the system
gdb, but I suspect you
could screw some related things up this way. Instead, I prefer the simple
$ mv /usr/local/bin/gdb /usr/local/bin/gdb7
Which allows you to run
gdb7 --version and get the expected::
GNU gdb (GDB) 7.6.1 Copyright (C) 2013 Free Software Foundation, Inc. ... This GDB was configured as "x86_64-apple-darwin12.5.0".
So far, so good. Unfortunately, debuggers need to inspect the internals of other
processes, which programs aren’t normally allowed to do. So in order to really
use gdb you need to give it permission to do this. Follow the instructions on
gdb website here to
create a certificate, and then run::
$ codesign -s gdb-cert gdb7
Now it’s time to test debugging Python code, which should be as easy as
gdb7 python. However, this gives:
"/usr/bin/python": not in executable format: File format not recognized
on OS X Mountain Lion. The reason? The system python executable is a ‘fat’ binary, meaning it can run on multiple system archtectures, i.e.:
$ file /usr/bin/python /usr/bin/python: Mach-O universal binary with 2 architectures /usr/bin/python (for architecture i386): Mach-O executable i386 /usr/bin/python (for architecture x86_64): Mach-O 64-bit executable x86_64
gdb does not support debugging fat binaries (at least according
to their mailing list). So you’ll have to extract the right system architecture
lipo (hah!) program. If you have an Intel Mac running a 64-bit
operating system (the mostly likely), that means the “x86_64” one. As a side
python program will actually call on the specific version of Python
when you launch it, meaning that the executable you’re really interested in is
$ file /usr/bin/python2.7 /usr/bin/python2.7: Mach-O universal binary with 2 architectures /usr/bin/python2.7 (for architecture i386): Mach-O executable i386 /usr/bin/python2.7 (for architecture x86_64): Mach-O 64-bit executable x86_64
From which we can extract a “thin”, architecture-specific binary (I’ve called
python64) as follows::
$ lipo /usr/bin/python2.7 -thin x86_64 -output /usr/bin/python64
You can now run:
$ gdb7 python64
And see that:
Reading symbols from /usr/bin/python64...(no debugging symbols found)...done.
As expected. I’m not quite sure what
gdb means when it says that python does
not have debugging symbols – backtraces seem to work fine anyway. To verify
that it works, try something like:
$ gdb7 python64 ... (gdb) run test.py Starting program: /usr/bin/python64 test.py Program received signal SIGTRAP, Trace/breakpoint trap. 0x00007fff5fc01028 in ?? () (gdb) continue Continuing. Hello, world!
Note that for some reason, Python always sends a SIGTRAP before it begins
executing the script. Don’t worry, just
continue. If you want to find out why
your program segfaults, you can run
Program received signal SIGSEGV, Segmentation fault. 0x000000010003441a in PyErr_Occurred () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python (gdb) bt #0 0x000000010003441a in PyErr_Occurred () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python #1 0x000000010003a81b in _PyArg_NoKeywords () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python #2 0x0000000100039c91 in _PyArg_NoKeywords () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python #3 0x0000000100038c43 in PyArg_Parse () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python #4 0x0000000100038f0d in PyArg_ParseTuple () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python #5 0x0000000100493335 in meth_Clamp_SetBounds () from /Users/Aaron/Code/Python/simplex-test/noisypy/noisypy.so #6 0x00000001000136c6 in PyObject_Call () from /System/Library/Frameworks/Python.framework/Versions/2.7/Python ...
Which located the error pretty quickly my wrapper of