2

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?

4
  • Can you reproduce the problem if you compile without ASLR (or if you enable ASLR under gdb)? I'm assuming that you are using ASLR, of course Commented Jan 12, 2016 at 17:43
  • No. It does make it more sensitive. For example, this time I get <refcnt -1222254038 at 0xb725e200>, but it doesn't actually segfault. Commented Jan 12, 2016 at 18:47
  • If you see <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? Commented Jan 12, 2016 at 19:02
  • Yes, I did have to disable ASLR, as you suggested — and also compiled with -g -O0 flags. Commented Jan 12, 2016 at 19:04

2 Answers 2

1

You said that by Py_INCREF some values before returning them from your methods, the problem seems to disappear.

Inspired by your discovery, I found the problem:

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);

You are Py_DECREFing arguments. You should not do that! Refcounts should be increased and decreased only when objects are stored somewhere.

Sign up to request clarification or add additional context in comments.

5 Comments

Ohhh. Okay. That's the clearest most concise answer I've seen so far. Do you want to incorporate my answer into yours and I'll mark yours as accepted? (Thanks!!)
@Dr.JohnnyMohawk: did I incorporate your answer into mine?
@Dr.JohnnyMohawk: actually what I wrote is not accurate. Your Py_INCREF from your answer is indeed required, because you are in fact returning a new reference to it. However, still, you should not Py_DECREF arguments. Tomorrow I'm writing a clearer and more detailed answer, time to sleep now! :)
in the meantime, be sure to read docs.python.org/3/extending/…
...no, I'm saying if you would combine your answer with mine, I would upvote it and mark it as correct. It seems that both answers are correct. I wanted to give you credit for your assistance.
0

Okay, I think I found a solution.

The problem is in my in-place functions, which also return self.

For example,

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;

  Py_INCREF(self); # THIS

  return self;
}

I had to add a Py_INCREF(self) since self is both an argument and the return value.

This appears to fix the problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.