I have a Python class I've written in C called pyquat.Quat, and it includes methods for multiplying by another Quat, returning a NumPy array (Quat#to_matrix()) and for printing the representation.
Sometimes when I call these methods, I get a SIGSEGV. However, the segmentation fault never occurs when I'm running the program in GDB.
Although I can't seem to trace the problem, I am seeing some weird cases of memory being overwritten (apparently). For example, here's my script and output:
>>> from pyquat import Quat
>>> z = Quat(4,3,2,1) * Quat(1,2,3,4)
>>> z
Quat{{-12, 6, 24, 12}}
>>> z.normalize()
Quat{{-0.40000000000000002, 0.20000000000000001, 0.80000000000000004, 0.40000000000000002}}
>>> m = z.to_matrix()
>>> m
array([[-0.6 , 0. , 0.8 ],
[ 0.64, 0.6 , 0.48],
[-0.48, 0.8 , -0.36]])
>>> z
'exc_traceback'
I've been doing a lot of searching, but I have no clue why it would be overwriting the caller with a string here.
The function in question is:
static PyObject* pyquat_Quat_to_matrix(PyObject* self) {
npy_intp dims[2] = {3,3};
pyquat_Quat* q = (pyquat_Quat*)(self);
PyArrayObject* ary = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_DOUBLE);
double* T = (double*)ary->data;
T[0] = 1.0 - 2.0 * (q->v[2] * q->v[2] + q->v[1] * q->v[1]);
T[1] = 2.0 * (q->v[1] * q->v[0] + q->s * q->v[2]);
T[2] = 2.0 * (q->v[2] * q->v[0] - q->s * q->v[1]);
T[3] = 2.0 * (q->v[1] * q->v[0] - q->s * q->v[2]);
T[4] = 1.0 - 2.0 * (q->v[2] * q->v[2] + q->v[0] * q->v[0]);
T[5] = 2.0 * (q->v[2] * q->v[1] + q->s * q->v[0]);
T[6] = 2.0 * (q->v[2] * q->v[0] + q->s * q->v[1]);
T[7] = 2.0 * (q->v[2] * q->v[1] - q->s * q->v[0]);
T[8] = 1.0 - 2.0 * (q->v[1] * q->v[1] + q->v[0] * q->v[0]);
return PyArray_Return(ary);
}
and my type looks like
typedef struct {
PyObject_HEAD
/* Type-specific fields go here */
double s; // scalar component
double v[3]; // vector components
} pyquat_Quat;
I'm fairly certain I'm correctly including numpy, and creating both my module and my class:
/* Initialize the pyquat module and add pyquat.Quat to it. */
PyMODINIT_FUNC initpyquat(void) {
PyObject* m;
pyquat_QuatType.tp_new = PyType_GenericNew;
if (PyType_Ready(&pyquat_QuatType) < 0)
return;
// Define the pyquat module.
m = Py_InitModule3("pyquat", pyquat_methods,
"Quaternion module with fast unit (right) quaternion math written in C.");
// Import NumPy to prevent a segfault when we call a function that uses NumPy API.
import_array();
// Create the Quat class in the pyquat module.
Py_INCREF(&pyquat_QuatType);
PyModule_AddObject(m, "Quat", (PyObject *)&pyquat_QuatType);
}
static int pyquat_Quat_init(pyquat_Quat* self, PyObject* args) {
double scalar, vx, vy, vz;
if (!PyArg_ParseTuple(args, "dddd", &scalar, &vx, &vy, &vz))
return -1;
// Read the scalar and vector components of the quaternion.
self->s = scalar;
self->v[0] = vx;
self->v[1] = vy;
self->v[2] = vz;
return 0;
}
My repr method seems pretty typical, too, but I notice that this method also occasionally causes obj to be overwritten.
static PyObject* pyquat_Quat_repr(PyObject* obj) {
pyquat_Quat* self = (pyquat_Quat*)(obj);
return PyString_FromFormat("Quat{{\%s, \%s, \%s, \%s}}",
PyOS_double_to_string(self->s, 'g', 17, 0, NULL),
PyOS_double_to_string(self->v[0], 'g', 17, 0, NULL),
PyOS_double_to_string(self->v[1], 'g', 17, 0, NULL),
PyOS_double_to_string(self->v[2], 'g', 17, 0, NULL));
}
static PyObject * pyquat_Quat_mul(PyObject* self, PyObject* arg) {
// Expects the one argument to be a pyquat_Quat
if (!PyObject_IsInstance(arg, (PyObject*)&pyquat_QuatType)) {
Py_DECREF(arg);
PyErr_SetString(PyExc_IOError, "expected quaternion");
return NULL;
}
pyquat_Quat* rhs = (pyquat_Quat*)(arg);
pyquat_Quat* lhs = (pyquat_Quat*)(self);
pyquat_Quat* result = (pyquat_Quat *)Py_TYPE(self)->tp_alloc(Py_TYPE(self), 0);
result->s = lhs->s * rhs->s - (lhs->v[0] * rhs->v[0] + lhs->v[1] * rhs->v[1] + lhs->v[2] * rhs->v[2]);
result->v[0] = lhs->s * rhs->v[0] + rhs->s * lhs->v[0] - (lhs->v[1] * rhs->v[2] - lhs->v[2] * rhs->v[1]);
result->v[1] = lhs->s * rhs->v[1] + rhs->s * lhs->v[1] - (lhs->v[2] * rhs->v[0] - lhs->v[0] * rhs->v[2]);
result->v[2] = lhs->s * rhs->v[2] + rhs->s * lhs->v[2] - (lhs->v[0] * rhs->v[1] - lhs->v[1] * rhs->v[0]);
return (PyObject*)(result);
}
This is one I have some concern about. It's returning self — but why does the multiplication function, which also returns an object, not also cause Python to echo the repr for the object?
Edit: After some investigation, it looks like the problem only happens when I call the in-place normalization routine. Do I need to increment or decrement some reference counter here?
static PyObject* pyquat_Quat_inplace_normalize(PyObject* self) {
pyquat_Quat* q = (pyquat_Quat*)(self);
double q_mag = sqrt(q->s * q->s + q->v[0] * q->v[0] + q->v[1] * q->v[1] + q->v[2] * q->v[2]);
if (q_mag > PYQUAT_QUAT_SMALL) q_mag = 1.0 / q_mag;
else q_mag = 0.0;
q->s *= q_mag;
q->v[0] *= q_mag;
q->v[1] *= q_mag;
q->v[2] *= q_mag;
return self;
}
Here is the multiplication function:
static PyObject * pyquat_Quat_mul(PyObject* self, PyObject* arg) {
// Expects the one argument to be a pyquat_Quat
if (!PyObject_IsInstance(arg, (PyObject*)&pyquat_QuatType)) {
Py_DECREF(arg);
PyErr_SetString(PyExc_IOError, "expected quaternion");
return NULL;
}
pyquat_Quat* rhs = (pyquat_Quat*)(arg);
pyquat_Quat* lhs = (pyquat_Quat*)(self);
pyquat_Quat* result = (pyquat_Quat *)Py_TYPE(self)->tp_alloc(Py_TYPE(self), 0);
result->s = lhs->s * rhs->s - (lhs->v[0] * rhs->v[0] + lhs->v[1] * rhs->v[1] + lhs->v[2] * rhs->v[2]);
result->v[0] = lhs->s * rhs->v[0] + rhs->s * lhs->v[0] - (lhs->v[1] * rhs->v[2] - lhs->v[2] * rhs->v[1]);
result->v[1] = lhs->s * rhs->v[1] + rhs->s * lhs->v[1] - (lhs->v[2] * rhs->v[0] - lhs->v[0] * rhs->v[2]);
result->v[2] = lhs->s * rhs->v[2] + rhs->s * lhs->v[2] - (lhs->v[0] * rhs->v[1] - lhs->v[1] * rhs->v[0]);
return (PyObject*)(result);
}
Does anyone have any suggestions on how I might debug this more effectively, or see what the problem could be?
<refcnt -1222254038 at 0xb725e200>, but it doesn't actually segfault.<refcnt -1222254038 at 0xb725e200>you are still experiencing an erratic behavior. That memory region is readable, so you don't get a segfault, but still that is not a valid Python object. This can be a good starting point for debugging. By the way, I guess that to obtain such result you have disable ASLR, which is why you have more chances of getting a readable memory address. Am I right?-g -O0flags.