0

I have this code that calls a libc function:

import sys
import ctypes
libc = ctypes.CDLL('libc.so.6')

class CpuSet(ctypes.Structure):
    _fields_ = [('bits', ctypes.c_ulonglong * 16)]

print(ctypes.sizeof(CpuSet))
cpu_set = CpuSet()
tid = int(sys.argv[1])
rc = libc.sched_getaffinity(int(tid), ctypes.sizeof(CpuSet), ctypes.addressof(cpu_set))
print(rc)

When I run this with Python 2, I get a success rc "0", and when run it with Python 3 the printed rc is -1. The errno value is set to EFAULT, which according to the docs, means "A supplied memory address is invalid". However, ctypes.addressof(cpu_set) seems to return a valid pointer. I printed the sizeof value, just to be safe, and it's 128 on both pythons.

# python3 /tmp/test.py 2740
128
-1
# python2 /tmp/test.py 2740
128
0
# python2 -V
Python 2.6.6
# python3 -V
Python 3.8.1

I run the code under root on both cases.

Any idea why this happens, or what further tests I can perform?

UPDATE: I ran both pythons with strace, and there is a difference:

# strace python2 /tmp/test.py 17526 2>&1 | grep sched_getaffinity
sched_getaffinity(17526, 128, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) = 8
# strace python3 /tmp/test.py 17526 2>&1 | grep sched_getaffinity
sched_getaffinity(17526, 128, 0x1da22030) = -1 EFAULT (Bad address)

It looks like python 3 passes the wrong pointer? Or maybe the struct doesn't get initialized? Still not sure.

itsadok
  • 28,822
  • 30
  • 126
  • 171
  • [\[SO\]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)](https://stackoverflow.com/questions/58610333/c-function-called-from-python-via-ctypes-returns-incorrect-value/58611011#58611011) – CristiFati Sep 15 '20 at 15:05

1 Answers1

0

Found it!

I printed the return value from ctypes.addressof and noticed that in python 3, the pointer got truncated:

# strace -e raw=sched_getaffinity python3 /tmp/test.py 2740 2>&1 | grep 'getaffini\|addressof'
write(1, "128\naddressof cpuset: 0x7fabee87"..., 37128
addressof cpuset: 0x7fabee8710b0
sched_getaffinity(0xab4, 0x80, 0xee8710b0) = -1 (errno 14)

This wasn't happening in python 2, probably because the pointer fit in 32 bits:

# strace -e raw=sched_getaffinity python2 /tmp/test.py 2740 2>&1 | grep 'getaffini\|addressof'
write(1, "128\naddressof cpuset: 0x193aae0\n", 32128
addressof cpuset: 0x193aae0
sched_getaffinity(0xab4, 0x80, 0x193aae0) = 0x8

Looking at the docs again, I saw that the recommended way to pass a pointer was using byref and not addressof. I changed the code to use byref, and it worked:

# strace -e raw=sched_getaffinity python3 /tmp/test.py 2740 2>&1 | grep 'getaffini\|addressof'
write(1, "128\naddressof cpuset: 0x7fb5d46f"..., 37128
addressof cpuset: 0x7fb5d46fb0b0
sched_getaffinity(0xab4, 0x80, 0x7fb5d46fb0b0) = 0x8

Not sure why the value returned from addressof got truncated. Perhaps casting it to a pointer type would also have worked?

itsadok
  • 28,822
  • 30
  • 126
  • 171
  • ctypes assumes integers are passed as 32-bit and pointers are passed according to architecture size. `addressof` returns an integer address and is truncated when passed on the stack if architecture is 64-bit and the parameter isn't declared as a pointer. Set `.argtypes` and `.restype` for your functions to avoid problems and allow `ctypes` to perform better error checking. – Mark Tolonen Sep 15 '20 at 16:23