[issue22273] abort when passing certain structs by value using ctypes

Eryk Sun report at bugs.python.org
Mon Feb 20 15:48:21 EST 2017


Eryk Sun added the comment:

classify_argument is in Modules/_ctypes/libffi/src/x86/ffi64.c. This file is for the 64-bit Unix ABI. libffi doesn't use it for 64-bit Windows. 

Some (all?) Linux distros link ctypes to the system libffi. In my experience, building 3.6 on Linux defaults to the system libffi, and I don't personally know how to override this. Configuring "--without-system-ffi" seems to be ignored.

Regarding your Test struct example, it's ok in this particular case to classify an 8-byte integer array the same as an 8-byte pointer. It doesn't abort() because the 2nd word isn't left unclassified (i.e. X86_64_NO_CLASS). 

    import ctypes

    class Test(ctypes.Structure):
        _fields_ = (('foo', ctypes.c_int),
                    ('bar', ctypes.c_int),
                    ('data', ctypes.c_uint8 * 8))

    @ctypes.CFUNCTYPE(None, Test)
    def func(t):
        print('foo:', t.foo)
        print('bar:', t.bar)
        print('data:', t.data[:])

    t = Test(5, 10, tuple(range(8)))

    >>> hex(id(Test))
    '0x9d8ad8'

The ctypes Structure has 3 elements. The first two are ffi_type_sint32 (FFI_TYPE_SINT32 == 10), and the third is ffi_type_pointer (FFI_TYPE_POINTER == 14):

    (gdb) set $dict = (StgDictObject *)(((PyTypeObject *)0x9d8ad8)->tp_dict)
    (gdb) p *$dict->ffi_type_pointer->elements[0]
    $1 = {size = 4, alignment = 4, type = 10, elements = 0x0}
    (gdb) p *$dict->ffi_type_pointer->elements[1]
    $2 = {size = 4, alignment = 4, type = 10, elements = 0x0}
    (gdb) p *$dict->ffi_type_pointer->elements[2]
    $3 = {size = 8, alignment = 8, type = 14, elements = 0x0}
    (gdb) p $dict->ffi_type_pointer->elements[3]
    $4 = (struct _ffi_type *) 0x0

classify_argument() recursively classifies and merges these elements. The first two get merged as X86_64_INTEGER_CLASS, and the 'pointer' (actually an array) is X86_64_INTEGER_CLASS.

    >>> func(t)

    Breakpoint 1, ffi_call (cif=cif at entry=0x7fffffffd570, fn=fn at entry=0x7ffff7fee010,
        rvalue=rvalue at entry=0x7fffffffd630, avalue=avalue at entry=0x7fffffffd610)
        at ../src/x86/ffi64.c:424

    [...snip...]

    458	  for (i = 0; i < avn; ++i)
    (gdb) 
    462	      n = examine_argument (arg_types[i], classes, 0, &ngpr, &nsse);
    (gdb) 
    463	      if (n == 0
    (gdb) p n
    $6 = 2
    (gdb) p ngpr
    $7 = 2
    (gdb) p classes[0]
    $8 = X86_64_INTEGER_CLASS
    (gdb) p classes[1]
    $9 = X86_64_INTEGER_CLASS

The struct is passed in two general-purpose integer registers, rdi and rsi:

    Breakpoint 2, ffi_call_unix64 () at ../src/x86/unix64.S:49
    49		movq	(%rsp), %r10		/* Load return address.  */

    [...snip...]
    76		call	*%r11

    (gdb) p/x $rdi
    $10 = 0xa00000005
    (gdb) p/x $rsi
    $11 = 0x706050403020100
    (gdb) c
    Continuing.

    foo: 5
    bar: 10
    data: [0, 1, 2, 3, 4, 5, 6, 7]

----------

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue22273>
_______________________________________


More information about the Python-bugs-list mailing list