ctypes segfault on OSX only - python

I created a very simple C library binding in Python using ctypes. All it does is accept a string and return a string.
I did the development on Ubuntu, and everything looked fine. Unfortunately, on OSX the exact same code fails. I'm completely stumped.
I've put together a minimal case showing the issue I'm having.
main.py
import ctypes
# Compile for:
# Linux: `gcc -fPIC -shared hello.c -o hello.so`
# OSX: `gcc -shared hello.c -o hello.so`
lib = ctypes.cdll.LoadLibrary('./hello.so')
# Call the library
ptr = lib.hello("Frank")
data = ctypes.c_char_p(ptr).value # segfault here on OSX only
lib.free_response(ptr)
# Prove it worked
print data
hello.c
#include <stdlib.h>
#include <string.h>
// This is the actual binding call.
char* hello(char* name) {
char* response = malloc(sizeof(char) * 100);
strcpy(response, "Hello, ");
strcat(response, name);
strcat(response, "!\n");
return response;
}
// This frees the response memory and must be called by the binding.
void free_response(char *ptr) { free(ptr); }

You should specify the return type of your function. Specifically, declare it to be ctypes.POINTER(ctypes.c_char).
import ctypes
lib = ctypes.CDLL('./hello.so')
lib.hello.restype = ctypes.POINTER(ctypes.c_char)
ptr = lib.hello("Frank")
print repr(ctypes.cast(ptr, ctypes.c_char_p).value)
lib.free_response(ptr)

Related

How can I compile multiple C++ files to use them with Python ctypes?

I have a little problem with the compilation of multiple C++ on Windows.
I implemented four classes in C++ for cryptography with gmp. I want to call them from Python with ctypes.
I wrote a cpp file with the extern keyword:
#include "integer.h"
#include "modular_number.h"
#include "padic_number.h"
#include "rational_number.h"
extern "C" {
__declspec(dllexport) ModNum* newModNum(const char * n, const char * p) { return new ModNum(Integer(n), Integer(p)); }
__declspec(dllexport) const char* getModValue(const ModNum& mod){ return mod.getValue().getValue(); }
__declspec(dllexport) RationalNum* newRationalNum(const char* mpq) { return new RationalNum(mpq); }
__declspec(dllexport) const char* getRationalValue(const RationalNum& rat){ return rat.getValue(); }
__declspec(dllexport) PadicNum* newPadicNum(const char* n, const char* base) { return new PadicNum(Integer(n), Integer(base)); }
__declspec(dllexport) const char* getPadicValue(const PadicNum& padic){ return padic.getValue().getValue(); }
}
I compiled my files with:
mingw32-g++ -fexceptions -g -fexpensive-optimizations -flto -O3 -Weffc++ -Wextra -Wall -std=c++14 -fPIC -Og -IC:\MinGW\include -flto -s -lgmp -lmpfr -lpthread -c -fPIC *.cpp -I"C:\Program Files\Python38-32\include" -I"C:\Program Files\Python38-32\libs"
mingw32-g++.exe -shared -Wl,-dll -o numeric.dll *.o -lgmp -lmpfr -lgmpxx -static
But when I use these commands in Python:
import ctypes;
x = ctypes.DLL("./numeric.dll");
The variable x does not have the functions: newModNum, getModValue, etc...
Could anyone tell me what I'm doing wrong? I get no error and I do not understand.
My other files are common C ++ files with header and implementation.
Thanks in advance and have a nice day!
ctypes functions are imported on first use. Using libc as an example:
>>> import ctypes
>>> libc = ctypes.CDLL("libc.so.06")
>>> "printf" in dir(libc)
False
>>> libc.printf
<_FuncPtr object at 0x7f6512c23430>
>>> "printf" in dir(libc)
True
ctypes assumes all parameters and the return value are int. You should give type hints which also conveniently import the functions.
import ctypes
x = ctypes.DLL("./numeric.dll")
x.newModNum.argtypes = [ctypes.c_char_p, ctypes.c_char_p] # <-- also imports
x.newModNum.rettype = ctypes.c_void_p
And remove the semicolons from the end of lines. It causes dangerous blood pressure spikes in python programmers.

How to replace libm when using ctypes?

I'm getting different results for the same shared library when a function (from the shared library) is executed by a standalone executable and when using Python's ctypes for calling the exact same function.
The difference was narrowed down to be the version of libm used by the library.
When using the library from a standalone executable we link against openlibm, but when the library is linked by ctypes, it's linked against Ubuntu's libm.
How can I make ctypes link my shared library against openlibm?
The shared library header (fft.h):
extern "C" {
void fft(const char* srcPath);
};
The shared library implementation (fft.cpp):
#include <iostream>
#include <fstream>
#include <nlohmann/json.hpp>
#include <Eigen/Eigen>
#include <unsupported/Eigen/FFT>
void fft(const char* srcPath) {
std::cout.precision(17);
std::ifstream fs(srcPath, std::ofstream::in);
// Get length of file
fs.seekg(0, std::ifstream::end);
auto length = fs.tellg();
fs.seekg(0, std::ifstream::beg);
// Read the file
auto srcRaw = new char[length];
fs.read(srcRaw, length);
// Convert data to vector of doubles
nlohmann::json j = nlohmann::json::parse(srcRaw);
std::vector<double> srcDoubles = j;
int n = 65536;
Eigen::VectorXd srcEigen(n);
Eigen::VectorXcd dstEigen(n);
std::copy(srcDoubles.data(), srcDoubles.data() + srcDoubles.size(), srcEigen.data());
Eigen::FFT<double> fft;
fft.fwd(dstEigen, srcEigen);
std::vector<std::complex<double>> dstTmp(dstEigen.data(), dstEigen.data() + dstEigen.size());
for (size_t i = 0; i < dstTmp.size(); i++) {
std::cout << "Result[" << i << "] = " << dstTmp[i].real() << "+i*" << dstTmp[i].imag() << std::endl;
}
delete[] srcRaw;
}
Using the library from a standalone executable:
#include <fft.h>
int main(int argc, char** argv) {
fft("input_data");
}
Using Python's ctypes:
from ctypes import c_char_p, CDLL
class LibCFft(CDLL):
def __init__(self):
super(LibCFft, self).__init__("libexample.so")
self._addfunc('fft', [c_char_p], None)
def _addfunc(self, name, argtypes, restype):
attr = getattr(self, name)
attr.argtypes = argtypes
attr.restype = restype
return lambda x: x
class CFft(object):
def fft(self):
LibCFft().fft("input_data")
if __name__ == '__main__':
CFft().fft()
Lib build:
clang++ /opt/eigen-eigen-43d9075b23ef -isystem /opt/spdlog/include -isystem /opt/nlohmann/include -O3 -DNDEBUG -fPIC -std=gnu++14 -o CMakeFiles/example.dir/fft.cpp.o -c /home/user/fft.cpp
clang++ -fPIC -O3 -DNDEBUG -shared -Wl,-soname,libexample.so -o libbugatone.so CMakeFiles/example.dir/generated/fft.cpp.o -lopenlibm
Executable build:
clang++ -O3 -DNDEBUG -latomic -nodefaultlibs -lopenlibm -lc -lc++ -lgcc -lgcc_s -lstdc++ -latomic -lm CMakeFiles/eigen_tester.dir/eigen_tester.cpp.o -o eigen_tester libexample.so -lopenlibm
Thanks in advance for the help.
In the executable link step you use BOTH -lm and -lopenlibm which is probably not what you want.
-nodefaultlibs disables automatic inclusion of all standard libs, so if you simply not include -lm but keep -lopenlibm this should probably do what you want.
You colud update LD_LIBRARY_PATH in your environment, to point to your local library folder first, and keep your libopenlibm.so there and also create a soft link to it: 'ln -s libopenlibm.so libm.so'.
In most cases that would cause any shared objects to use this as a substitution to a regular libm.so

Abort when passing Python CTypes struct containing array of 3 ints

I'm running Python 3.4.0-0ubuntu2 on Kubuntu Trusty 64 bit.
When trying to find a minimal example for my other related (but not duplicate) question, I found that the following apparently trivial code using Python CTypes causes a SIGABRT upon the call to fn1. (Note that in the other case, fn1 worked fine and it was only fn2 which didn't work and the signal was SIGSEGV there.)
lib.c:
#include <stdio.h>
typedef struct {
int data[3];
} Triplet;
void fn1(Triplet t)
{
fprintf(stderr, "%d, %d, %d\n", t.data[0], t.data[1], t.data[2]);
}
Triplet fn2(Triplet t)
{
Triplet temp = {{t.data[0] + 1, t.data[1] + 1, t.data[2] + 1}};
return temp;
}
main.py:
from ctypes import *
Array3 = c_int * 3
class Triplet(Union):
_fields_ = [("data", Array3)]
_lib = CDLL("libuniontest.so")
_lib.fn1.argtypes = [Triplet]
_lib.fn2.restype = Triplet
_lib.fn2.argtypes = [Triplet]
t = Triplet(Array3(99, 129, 39))
_lib.fn1(t) # this causes segfault
tt = _lib.fn2(t)
print(tuple(tt.data))
Makefile:
test:
$(CC) -fPIC -shared -o libuniontest.so lib.c
sudo cp libuniontest.so /usr/local/lib/
sudo ldconfig
python3 main.py
It didn't make a difference if I changed Union to Structure.
What is the reason for this abort and how can I fix it? Thanks.
This seems to be a bug in libffi (which is what ctypes uses) that only occurs, if you pass a struct by value and that struct is between 9-16 bytes long: https://bugs.python.org/issue22273
I'm able to reproduce this on python 2.7. Try increasing the size of the struct or use a pointer parameter for the methods.
Unfortunately it seems that this is not fixed until now

Python 3.4 ctypes wrap for a mingw-w64 compiled C-based dll

I have problems wraping a mingw-w64 compiled dll into py 3.4 with ctypes.
Minimal (not) working example:
/* sample.c */
#include <math.h>
/* Compute the greatest common divisor */
int gcd(int x, int y) {
int g = y;
while (x > 0) {
g = x;
x = y % x;
y = g;
}
return g;
}
Header file
/* sample.h */
#include <math.h>
extern int gcd(int, int);
I am compiling it with:
g++ -c sample.c
g++ -shared -o sample.dll sample.o -Wl,--out-implib,libsample.a
And I get an error (AttributeError: function 'gcd' not found) at the last line of this python code:
# sample.py
import ctypes
import os
# Try to locate the file in the same directory as this file
_file = 'sample'
_path = os.path.join(*(os.path.split(__file__)[:-1] + (_file,)))
_mod = ctypes.cdll.LoadLibrary(_path)
gcd = _mod.gcd
I have an minimal example written in c++ and it works with a similar procedure than above. What am I doing wrong?
I found this problem to be similar:
VB dll doesnt work in python with ctypes (function * not found)
But I was not able to register it as a COM object (the entry-point DllRegisterServer was not found):
How to Register a COM Object for All Users

Segmentation fault in python while using ctypes

I am trying to read some registers in hardware (FPGA) using Python.
I already have a C code to read the registers and it works fine. I want to use them in python using ctypes.
rdaxi.c
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define NF10_IOCTL_CMD_READ_STAT (SIOCDEVPRIVATE+0)
#define NF10_IOCTL_CMD_WRITE_REG (SIOCDEVPRIVATE+1)
#define NF10_IOCTL_CMD_READ_REG (SIOCDEVPRIVATE+2)
int main(int argc, char* argv[]){
int f;
uint64_t v;
uint64_t addr;
if(argc < 2){
printf("usage: rdaxi reg_addr(in hex)\n\n");
return 0;
}
else{
sscanf(argv[1], "%llx", &addr);
}
//----------------------------------------------------
//-- open nf10 file descriptor
//----------------------------------------------------
f = open("/dev/nf10", O_RDWR);
if(f < 0){
perror("/dev/nf10");
return 0;
}
printf("\n");
v = addr;
if(ioctl(f, NF10_IOCTL_CMD_READ_REG, &v) < 0){
perror("nf10 ioctl failed");
return 0;
}
// upper 32bits contain the address and are masked away here
// lower 32bits contain the data
v &= 0xffffffff;
printf("AXI reg 0x%llx=0x%llx\n", addr, v);
printf("\n");
close(f);
return 0;
}
After comilation and getting the executable, I just do the following to get my result
./rdaxi 0x5a000008
AXI reg 0x5a000008 = 2
I want to do the same thing using Python.I came to know that I have to use Ctypes. Then I created shared library (.so) for my c file.The following is the python code i wrote. I am a beginner in python, so pardon me for my errors. I get a segmentation fault. How can I solve this and read the registers.
rdaxi.py
#!/usr/bin/env python
# For creating shared library
# gcc -c -Wall -fPIC rdaxi.c
# gcc -shared -o librdaxi.so rdaxi.o
import os
import sys
from ctypes import *
print "opening device descriptor"
nf = os.open( "/dev/nf10", os.O_RDWR )
print "loading the .so file"
librdaxi=cdll.LoadLibrary('/root/Desktop/apps/librdaxi.so')
librdaxi.main(nf,0x5b000008)
For more information about my application. Please see below.
reading registers in hw using python
thanks
main() takes two parameters, argc and argv, but you are passing it a single parameter which is your address.
Here's an example of how you might call it:
librdaxi.main.argtypes = [c_int,POINTER(c_char_p)]
argv=(c_char_p*2)()
argv[0]='rdaxi'
argv[1]='0x5a000008'
librdaxi.main(2,argv)

Categories