I have written a program in c using libcurl to load url and send the return value to Python (I am passing 2 integer value from Python to C. i am yet to enhance the code, currently trying the logic and variable accessibility between Python and C.). I am able to compile the program successfully. When i load the module in Python i am getting error saying "undefined symbol: curl_easy_getinfo". Please let me know how to fix the issue.
Code:
#include <Python.h>
#include <stdio.h>
#include <time.h>
#include <stdio.h>
#include <pthread.h>
#include <curl/curl.h>
#define NUMT 4
/*
List of URLs to fetch.
If you intend to use a SSL-based protocol here you MUST setup the OpenSSL
callback functions as described here:
http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION
*/
const char * const urls[NUMT]= {
"http://www.google.com",
"http://www.yahoo.com/",
"http://www.haxx.se/done.html",
"http://www.haxx.se/"
};
#define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 3
struct myprogress {
double lastruntime;
curl_off_t totdnld;
void *url;
CURL *curl;
};
static PyObject *foo1_add(PyObject *self, PyObject *args)
{
int a;
int b;
int s;
if (!PyArg_ParseTuple(args, "ii", &a, &b))
{
return NULL;
}
s = sum (a, b);
return Py_BuildValue("i", s);
// return Py_BuildValue("i", a + b);
}
static PyMethodDef foo1_methods[] = {
{ "add", (PyCFunction)foo1_add, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initfoo1()
{
Py_InitModule3("foo1", foo1_methods, "My first extension module.");
}
int sum(int x, int y) {
int z;
z = x + y;
z = geturl (x, y);
return (z);
}
/* this is how the CURLOPT_XFERINFOFUNCTION callback works */
#ifdef 0
static int xferinfo(void *p,
curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{
struct myprogress *myp = (struct myprogress *)p;
CURL *curl = myp->curl;
double curtime = 0;
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &curtime);
/* under certain circumstances it may be desirable for certain functionality
to only run every N seconds, in order to do this the transaction time can
be used */
if((curtime - myp->lastruntime) >= MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL) {
myp->lastruntime = curtime;
fprintf(stderr, "TOTAL TIME: %f \r\n", curtime);
}
if (dlnow > 0) {
fprintf(stderr, "UP: %" CURL_FORMAT_CURL_OFF_T " of %" CURL_FORMAT_CURL_OFF_T
" DOWN: %" CURL_FORMAT_CURL_OFF_T " of %" CURL_FORMAT_CURL_OFF_T
"\r\n",
ulnow, ultotal, dlnow, dltotal);
}
myp->totdnld = myp->totdnld + dlnow;
if (dlnow > 0) {
fprintf(stderr, "TOTAL Download: %" CURL_FORMAT_CURL_OFF_T " url is: %s \r\n", myp->totdnld, myp->url);
}
// if(dlnow > STOP_DOWNLOAD_AFTER_THIS_MANY_BYTES)
// return 1;
return 0;
}
#endif
/* for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION) */
static int older_progress(void *p,
double dltotal, double dlnow,
double ultotal, double ulnow)
{
return xferinfo(p,
(curl_off_t)dltotal,
(curl_off_t)dlnow,
(curl_off_t)ultotal,
(curl_off_t)ulnow);
}
static void *pull_one_url(void *url)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct myprogress prog;
curl = curl_easy_init();
if(curl) {
prog.lastruntime = 0;
prog.curl = curl;
prog.url = url;
prog.totdnld = (curl_off_t) 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, older_progress);
/* pass the struct pointer into the progress function */
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog);
#ifdef 0
#if LIBCURL_VERSION_NUM >= 0x072000
/* xferinfo was introduced in 7.32.0, no earlier libcurl versions will
compile as they won't have the symbols around.
If built with a newer libcurl, but running with an older libcurl:
curl_easy_setopt() will fail in run-time trying to set the new
callback, making the older callback get used.
New libcurls will prefer the new callback and instead use that one even
if both callbacks are set. */
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo);
/* pass the struct pointer into the xferinfo function, note that this is
an alias to CURLOPT_PROGRESSDATA */
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog);
#endif
#endif
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "%s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
return NULL;
}
/*
int pthread_create(pthread_t *new_thread_ID,
const pthread_attr_t *attr,
void * (*start_func)(void *), void *arg);
*/
int geturl(int x, int y)
{
pthread_t tid[NUMT];
int i;
int error;
/* Must initialize libcurl before any threads are started */
curl_global_init(CURL_GLOBAL_ALL);
for(i=0; i< NUMT; i++) {
error = pthread_create(&tid[i],
NULL, /* default attributes please */
pull_one_url,
(void *)urls[i]);
if(0 != error)
fprintf(stderr, "Couldn't run thread number %d, errno %d\n", i, error);
else
fprintf(stderr, "Thread %d, gets %s\n", i, urls[i]);
}
/* now wait for all threads to terminate */
for(i=0; i< NUMT; i++) {
error = pthread_join(tid[i], NULL);
fprintf(stderr, "Thread %d terminated\n", i);
}
return (x * y);
}
Command used for compilation:
gcc -lcurl -lpthread -shared -I/usr/include/python2.7 -fPIC sample.c –o add.so
Error:
>>> import foo1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: ./foo1.so: undefined symbol: curl_easy_perform
>>>
Try moving -lcurl and -lpthread to after sample.c in your compilation command. The linker resolves symbols in left-to-right order, so references from sample.c (e.g., curl_easy_getinfo) will be resolved from libraries specified after it.
It's better to use -pthread than -lpthread by the way. It sets preprocessor flags to make some functions reentrant for example.
Related
I was trying to figure out how the Mach VM Api works as there is almost 0 documentation around it and to do that I was messing around with reading/writing to other processes' memory.
To start I basically created a c program that constantly printed a string and its address. Then I used this program to try modifying the string mid execution and it worked fine:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <libproc.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#define EXIT_ON_MACH_ERROR(msg, retval) \
if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); exit((retval)); }
uint32_t* get_pids(uint16_t* size) {
// Gets all PIDS on the system to locate specific PID later. Probably inefficient
// but I don't care
uint32_t number_of_pids = proc_listpids(1, 0, NULL, 0);
uint32_t* buffer = malloc(sizeof(uint32_t) * number_of_pids);
uint8_t return_code = proc_listpids(1, 0, buffer, sizeof(buffer) * number_of_pids);
uint16_t sum = 0;
for(int i = 0; i < number_of_pids; i++) {
if(buffer[i] != 0) {
sum++;
}
}
uint32_t* final = malloc(sizeof(uint32_t) * sum);
for(int i = 0, t = 0; i < number_of_pids; i++) {
if(buffer[i]) {
final[t++] = buffer[i];
}
}
*size = sum;
return final;
}
int main() {
// Locate correct PID according to process name
uint16_t size;
uint32_t* pids = get_pids(&size);
uint16_t maxpathlength = 1024;
uint16_t path_size = maxpathlength * 4;
char path_buffer[path_size];
uint32_t process_pid = 0;
for(int i = 0; i < size; i++) {
memset(path_buffer, '\0', sizeof(path_buffer));
uint8_t return_code = proc_pidpath(pids[i], path_buffer, path_size);
if(strstr(path_buffer, "Python")) {
printf("%d\n", i);
process_pid = pids[i];
}
//printf("PID: %d, Process: %s\n", pids[i], path_buffer);
}
printf("%d\n", process_pid);
struct proc_taskallinfo pro_info;
uint32_t status = proc_pidinfo(process_pid, PROC_PIDTASKALLINFO, 0, &pro_info, sizeof(pro_info));
printf("Python PID: %d\n", process_pid);
printf("Self PID: %d\n", mach_host_self());
mach_port_t port = 0;
kern_return_t kr = task_for_pid(mach_task_self(), process_pid, &port);
EXIT_ON_MACH_ERROR("task_for_pid", kr);
printf("Port: %d\n\n\n", port);
// STUFF
mach_vm_address_t address = 0x102d4b770;
mach_vm_address_t address_a = 0x102d4b770;
char data[50] = "wow";
vm_offset_t buf;
mach_msg_type_number_t sz;
// MEMORY DEALLOCATION
kern_return_t suc = mach_vm_deallocate(port, (mach_vm_address_t) address, (mach_vm_size_t) 1000);
if (suc!=KERN_SUCCESS)
{
printf("mach_vm_deallocate() failed with message %s!\n", mach_error_string(suc));
}
// MEMORY ALLOCATION
kern_return_t all_suc = mach_vm_allocate(port, (mach_vm_address_t *) &address_a, (vm_size_t) 26, false);
if (all_suc!=KERN_SUCCESS)
{
printf("mach_vm_allocate() failed with message %s!\n", mach_error_string(all_suc));
}
// WRITE TO MEMORY
kern_return_t success = mach_vm_write(port, (vm_address_t) address, (vm_address_t)data, 26);
if (success!=KERN_SUCCESS)
{
printf("mach_vm_write() failed with message %s!\n", mach_error_string(success));
}
// READ FROM MEMORY
kern_return_t read_success = mach_vm_read(port, (vm_address_t) 0x6000018c4030, 26, &buf, &sz);
if (read_success!=KERN_SUCCESS)
{
printf("mach_vm_read() failed with message %s!\n", mach_error_string(read_success));
}
char * newstr = (char *) buf;
printf("%s\n", newstr);
return 0;
}
address and address_a were entered manually after figuring out the variable's address. However, when I tried this with a python process where I again just constantly printed out the string and address, I got the following error message the instant I ran the code above:
zsh: segmentation fault python3 strtest.py
I have no knowledge about CPython, so even after playing around a bit and trying to make it work, nothing happened. How can I make this work even on programs? I know its possible as Bit-Slicer made it work but I wasn't able to found out how.
I built a kernel module that uses ioctl to communicate with a program in python in user space. The program in python should use ioctl to get a string from the kernel module and then print it on the screen.
I do not understand how to use what python in ioctl as we talk in the example at the following link:
enter link description here
In addition it is important to note that I use ubuntu 22.04 in kernel5.15.
My module is:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include<linux/slab.h>
//kmalloc()
#include<linux/uaccess.h>
//copy_to/from_user()
#include <linux/ioctl.h>
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',char*)
int32_t value = 0;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
/*
** Function Prototypes
*/
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
static int etx_open(struct inode *inode,
struct file *file);
static int etx_release(struct inode
*inode, struct file *file);
static ssize_t etx_read(struct file *filp,
char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp,
const char *buf, size_t len, loff_t * off);
static long etx_ioctl(struct file *file,
unsigned int cmd, unsigned long arg);
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
};
/*
** This function will be called when we open
the Device file
*/
static int etx_open(struct inode *inode, struct
file *file)
{
pr_info("Device File Opened...!!!\n");
return 0;
}
/*
** This function will be called when we close
the Device file
*/
static int etx_release(struct inode *inode,
struct file *file)
{
pr_info("Device File Closed...!!!\n");
return 0;
}
/*
** This function will be called when we the
Device file
*/
char array[40] = "hello world hhhhhhhhhhhhhh";
static ssize_t etx_read(struct file *filp, char
__user *buf, size_t len, loff_t *off)
{
pr_info("Read Function\n");
return 0;
}
/*
** This function will be called when we
write the Device file
*/
static ssize_t etx_write(struct file *filp,
const char __user *buf, size_t len, loff_t
*off)
{
pr_info("Write function\n");
return len;
}
/*
** This function will be called when we write
IOCTL on the Device file
*/
static long etx_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WR_VALUE:
if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) )
{
pr_err("Data Write : Err!\n");
}
pr_info("Value = %d\n", value);
break;
case RD_VALUE:
if( copy_to_user((char*) arg, array, 30) )
{
pr_err("Data Read : Err!\n");
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
pr_err("Cannot allocate major number\n");
return -1;
}
pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
pr_err("Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
pr_err("Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
pr_err("Cannot create the Device 1\n");
goto r_device;
}
pr_info("Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
pr_info("Device Driver
Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
If there is a better way to do it using sysfs I would love to know.
From reading another post, I am trying to embbed some some Python code into C:
main.c
#include <Python.h>
int callModuleFunc(int array[], size_t size) {
PyObject *mymodule = PyImport_ImportModule("py_function");
PyObject *myfunc = PyObject_GetAttrString(mymodule, "printlist");
PyObject *mylist = PyList_New(size);
for (size_t i = 0; i != size; ++i) {
PyList_SET_ITEM(mylist, i, PyInt_FromLong(array[i]));
}
PyObject *arglist = Py_BuildValue("(o)", mylist);
PyObject *result = PyObject_CallObject(myfunc, arglist);
int retval = (int)PyInt_AsLong(result);
Py_DECREF(result);
Py_DECREF(arglist);
Py_DECREF(mylist);
Py_DECREF(myfunc);
Py_DECREF(mymodule);
return retval;
}
int main(int argc, char *argv[])
{
int a[] = {1,2,3,4};
callModuleFunc(a, 4);
return 0;
}
py_function.py
'''py_function.py - Python source designed to '''
'''demonstrate the use of python embedding'''
def printlist(mylist):
print mylist
Then I compiled with:
gcc main.c -I/usr/include/python2.7 -lpython2.7
But then I ran the app, it gives me a segmentation fault error:
/a.out
[1] 18890 segmentation fault ./a.out
Is there something that I am missing?
There were several problems with your code:
Py_Initialize() was not called.
PyImport_ImportModule() failed to find your python file, since in embedded Python you start without an initial module, relative to which the search can work. The fix is to explicitly include the current directory in sys.path.
"(O)" in Py_BuildValue() should use a capital 'O'.
The printlist function should return a value (since that is what the C-code expects).
This should work:
main.c
#include <Python.h>
void initPython()
{
Py_Initialize();
PyObject *sysmodule = PyImport_ImportModule("sys");
PyObject *syspath = PyObject_GetAttrString(sysmodule, "path");
PyList_Append(syspath, PyString_FromString("."));
Py_DECREF(syspath);
Py_DECREF(sysmodule);
}
int callModuleFunc(int array[], size_t size) {
PyObject *mymodule = PyImport_ImportModule("py_function");
assert(mymodule != NULL);
PyObject *myfunc = PyObject_GetAttrString(mymodule, "printlist");
assert(myfunc != NULL);
PyObject *mylist = PyList_New(size);
for (size_t i = 0; i != size; ++i) {
PyList_SET_ITEM(mylist, i, PyInt_FromLong(array[i]));
}
PyObject *arglist = Py_BuildValue("(O)", mylist);
assert(arglist != NULL);
PyObject *result = PyObject_CallObject(myfunc, arglist);
assert(result != NULL);
int retval = (int)PyInt_AsLong(result);
Py_DECREF(result);
Py_DECREF(arglist);
Py_DECREF(mylist);
Py_DECREF(myfunc);
Py_DECREF(mymodule);
return retval;
}
int main(int argc, char *argv[])
{
initPython();
int a[] = {1,2,3,4,5,6,7};
callModuleFunc(a, 4);
callModuleFunc(a+2, 5);
Py_Finalize();
return 0;
}
py_function.py
'''py_function.py - Python source designed to '''
'''demonstrate the use of python embedding'''
def printlist(mylist):
print mylist
return 0
I'm testing a scenario that when C++ set a function pointer to a python class variable, and then use PyObject_CallMethod to run another python method, which contain that class variable.
whole process would like this.
(1). PyCFunction_NewEx() make a py function -> (2). PyDict_SetItemString() assign to class variable under __dict__ -> (3). PyObject_CallMethod() call python method witch contain (1).
When I put all the code inside main() function (whitout void setCallback() and all code inside void setCallback() were placed in main()), It runs perfectly fine. However, after I put some code into a function, sometimes get seg fault, sometimes doesn't call function pointer in python and sometimes get correct answer.
How do I resolve this problem?
C++ Code: main.cpp
#include <python3.7/Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <python3.7/methodobject.h>
// func ptr.
PyObject *myCallFunc(PyObject *self,PyObject *args) {
printf(" aaaaaaaaaaaaaaaaaaaaaaa\n");
return NULL;
}
// def func ptr
typedef PyObject *(*PyCallFunc)(PyObject *self,PyObject *arg);
// set func ptr into python member var
void setCallback(PyObject *ClassObj){
PyCallFunc pyCallFunc = myCallFunc;
PyMethodDef methd = {"methd",pyCallFunc,METH_VARARGS,"py call func"};
PyObject *fName = PyUnicode_FromString(methd.ml_name);
if(fName == NULL) {
printf(" fName\n");
exit(0);
}
PyObject *pyRunFunc = PyCFunction_NewEx(&methd,NULL,fName);
if(pyRunFunc == NULL){
printf(" can not create py function. exit.");
exit(0);
}
Py_DECREF(fName);
PyObject* classAttrDict = PyObject_GetAttrString(ClassObj, "__dict__"); // extract instance Dictionary.
if(classAttrDict == NULL) {
printf(" classAttrDict\n");
exit(0);
}
int pRetSetCurrPrice = PyDict_SetItemString(classAttrDict, "callFunc", pyRunFunc);
if(pRetSetCurrPrice != 0){
printf(" set error. exit.");
exit(0);
}
}
int main(int argc,char **argv){
Py_SetProgramName((wchar_t *)argv[0]);
void *pyMem = PyMem_Malloc(sizeof(wchar_t*)*argc);
wchar_t** _argv = (wchar_t**)&pyMem;
for (int i=0; i<argc; i++) {
wchar_t* arg = Py_DecodeLocale(argv[i], NULL);
_argv[i] = arg;
}
Py_Initialize();
PySys_SetArgv(argc, _argv);
PyObject* programName = PyUnicode_FromString("test");
if(programName == NULL) {
printf(" programName\n");
exit(0);
}
PyObject* pCustomFunc = PyImport_Import(programName); // import test
Py_DECREF(programName);
if(pCustomFunc == NULL) {
printf(" pCustomFunc\n");
exit(0);
}
PyObject* pClass = PyObject_GetAttrString(pCustomFunc, "Test"); // pClass = test.Test
if(pClass == NULL) {
printf(" pClass\n");
exit(0);
}
PyObject* pNewInstance = PyObject_CallObject(pClass,NULL); // pNewInstance = test.Test()
if(pNewInstance == NULL) {
printf(" pNewInstance\n");
exit(0);
}
setCallback(pNewInstance);
PyObject* pCallRet = PyObject_CallMethod(pNewInstance, "runCustomFunc",NULL); // pCallRet = pNewInstance.callFunc()
if(pCallRet == NULL) {
printf(" pCallRet\n");
//exit(0);
}
sleep(2);
printf(" \n\nend\n\n");
Py_Finalize();
return 0;
}
Python code: test.py
import sys
def dummyFunc():
pass
class Test:
def __init__(self):
self.aaa = 0
self.callFunc = dummyFunc
def runCustomFunc(self):
print(" print from python.")
print(" ref count of self.callFunc 1 is %d" %(sys.getrefcount(self.callFunc)))
self.callFunc()
print(" ref count of self.callFunc 2 is %d" %(sys.getrefcount(self.callFunc)))
return 1
cmake for this test project: CMakeLists.txt
# set cmake and compiler.
cmake_minimum_required(VERSION 3.12...3.15)
set(CMAKE_CXX_FLAGS -std=c++17)
# set variable
set(CMAKE_POSITION_INDEPENDENT_CODE ON) # test if this can resolve the problem
set(THREADS_PREFER_PTHREAD_FLAG ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_FLAGS "-Wall -Wextra") # test if optimize cause the problem
set(CMAKE_CXX_FLAGS_DEBUG "-g") # test if optimize cause the problem
set(CMAKE_CXX_FLAGS_RELEASE "-O0") # test if optimize cause the problem
set(LINK_LIB "/usr/local/lib")
set(PYTHON3_LINKER "-lpython3.7")
#set(PTHREAD "-lpthread")
set(PYTHON3_HEADER "/usr/include/python3.7")
set(PYTHON3_LIB "/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu")
set(CPP_FILE_LIST "main.cpp")
include_directories( ${PYTHON3_HEADER})
link_directories( ${PYTHON3_LIB} ${LINK_LIB})
add_executable(pyEmbedFunPtrTest ${CPP_FILE_LIST})
target_link_libraries(pyEmbedFunPtrTest ${PYTHON3_LINKER})
find_package(Threads REQUIRED)
target_link_libraries(pyEmbedFunPtrTest Threads::Threads)
#target_compile_options(pyEmbedFunPtrTest PUBLIC "-pthread")
It could be because the PyMethodDef is created on the stack of the setCallback
You can verify it in the source code of cpython here.
the PyMethodDef is not copied, it is referenced instead.
In Ubuntu 14.04, I wrote a C file called hash.c:
/* hash.c: hash table with linear probing */
typedef struct {
void *key;
void *value;
} ht_entry;
typedef struct {
ht_entry *table;
int len;
int num_entries;
int (*hash_fn)(void *key);
int (*key_cmp)(void *k1, void *k2);
} hashtable;
and compiled it with
gcc -shared hash.c -o test.so -fPIC
Afterwards, I tried to load test.so in a Python script (for testing), but I got the following error: "OSError: .../test.so: undefined symbol: hash_fn"
hash_fn is a function pointer in the hashtable struct. It is referenced a number of times by functions later in the file.
I do not understand why this error is happening. I have Googled but all other cases either concern C++ or includes. In my case I just have 1 C file that includes only stdio and stdlib.
here is the FULL code.
When I comment out all but hash_create and print_info, it loads succesfully. When I uncomment find(), it the error happens.
(print_info is just for testing that ctypes works)
/* hash.c: hash table with linear probing */
#include <stdio.h>
#include <stdlib.h>
typedef struct {
void *key;
void *value;
} ht_entry;
typedef struct {
ht_entry *table;
int len;
int num_entries;
int (*hash_fn)(void *key);
int (*key_cmp)(void *k1, void *k2);
} hashtable;
static void close_gap(hashtable *ht, int i);
static int find(hashtable *ht, void *key);
hashtable* hash_create(int len, int (*hash_fn)(void*), int (*key_cmp)(void*, void*))
{
hashtable* ht = (hashtable*) malloc(sizeof(hashtable));
ht->len = len;
ht->table = calloc(len, sizeof(ht_entry));
ht->hash_fn = hash_fn;
ht->key_cmp = key_cmp;
ht->table[0].key = 2;
ht->table[0].value = 3;
return ht;
}
void print_info(hashtable *ht)
{
printf("%d, %d, %d\n", ht->len, ht->table[0].key, ht->table[0].value);
}
void* hash_retrieve(hashtable* ht, void *key)
{
int i = find(ht, key);
if(i < 0) {
return NULL;
}
return ht->table[i].value;
}
void hash_insert(hashtable* ht, void *key, void *value)
{
if(ht->num_entries == ht->len) {
return;
}
int i = hash_fn(key) % ht->len;
while(ht->table[i].key != NULL) {
i = (i + i) % ht->len;
}
ht->table[i].key = key;
ht->table[i].value = value;
}
void hash_remove(hashtable *ht, void *key)
{
int i = find(ht, key);
if(i < 0) {
return;
}
ht->table[i].key = 0;
ht->table[i].value = 0;
close_gap(ht, i);
}
static int find(hashtable *ht, void *key)
{
int i = hash_fn(key) % ht->len;
int num_checked = 0;
while(ht->table[i].key && num_checked != ht->len) {
if(!ht->key_cmp(ht->table[i].key, key)) {
return i;
}
num_checked++;
i = (i + i) % ht->len;
}
return -1;
}
static void close_gap(hashtable *ht, int i)
{
int j = (i + 1) % ht->len;
while(ht->table[j].key) {
int loc = ht->hash_fn(ht->table[j].key);
if((j > i && (loc <= i || loc > j)) || (j < i && (loc <= i && loc > j))) {
ht->table[i] = ht->table[j];
ht->table[j].key = 0;
ht->table[j].value = 0;
close_gap(ht, j);
return;
}
}
}
When I use your compilation line I get five warnings. There are several problems here. First you are trying to assign an int to void * in several places. That raises a warning, and it would crash at runtime because you are passing 2 and 3 as addresses.
Second, you are calling hash_fn in a couple of places instead of ht->hash_fn. That causes the linker error, but you should consider my other changes, otherwise it will crash at runtime with a SIGSEGV:
/* hash.c: hash table with linear probing */
#include <stdio.h>
#include <stdlib.h>
typedef struct {
void *key;
void *value;
} ht_entry;
typedef struct {
ht_entry *table;
int len;
int num_entries;
int (*hash_fn)(void *key);
int (*key_cmp)(void *k1, void *k2);
} hashtable;
static void close_gap(hashtable *ht, int i);
static int find(hashtable *ht, void *key);
hashtable* hash_create(int len, int (*hash_fn)(void*), int (*key_cmp)(void*, void*))
{
hashtable* ht = (hashtable*) malloc(sizeof(hashtable));
ht->len = len;
ht->table = calloc(len, sizeof(ht_entry));
ht->hash_fn = hash_fn;
ht->key_cmp = key_cmp;
// <<< Code changed here
/*
ht->table[0].key = 2;
ht->table[0].value = 3;
*/
{
int *p = malloc(sizeof(int));
*p = 2;
ht->table[0].key = p;
p = malloc(sizeof(int));
*p = 3;
ht->table[0].value = p;
}
// end of code change
return ht;
}
void print_info(hashtable *ht)
{
// <<<< Code changed
printf("%d, %d, %d\n", ht->len,
*(int *)ht->table[0].key, *(int *)ht->table[0].value);
}
void* hash_retrieve(hashtable* ht, void *key)
{
int i = find(ht, key);
if(i < 0) {
return NULL;
}
return ht->table[i].value;
}
void hash_insert(hashtable* ht, void *key, void *value)
{
if(ht->num_entries == ht->len) {
return;
}
// <<< Code changed
int i = ht->hash_fn(key) % ht->len;
while(ht->table[i].key != NULL) {
i = (i + i) % ht->len;
}
ht->table[i].key = key;
ht->table[i].value = value;
}
void hash_remove(hashtable *ht, void *key)
{
int i = find(ht, key);
if(i < 0) {
return;
ht->table[i].key = 0;
ht->table[i].value = 0;
close_gap(ht, i);
}
static int find(hashtable *ht, void *key)
{
// <<< Code changed
int i = ht->hash_fn(key) % ht->len;
int num_checked = 0;
while(ht->table[i].key && num_checked != ht->len) {
if(!ht->key_cmp(ht->table[i].key, key)) {
return i;
}
num_checked++;
i = (i + i) % ht->len;
}
return -1;
}
static void close_gap(hashtable *ht, int i)
{
int j = (i + 1) % ht->len;
while(ht->table[j].key) {
int loc = ht->hash_fn(ht->table[j].key);
if((j > i && (loc <= i || loc > j)) || (j < i && (loc <= i && loc > j))) {
ht->table[i] = ht->table[j];
ht->table[j].key = 0;
ht->table[j].value = 0;
close_gap(ht, j);
return;
}
}
}
I only coded around the errors and warnings, I did not check the logic. You will see that I have used malloc to allocate memory for key and value. Obviously you will need memory management on these two (i.e. free()).