I am trying to implement my pthread_create function. After searching online I found few examples but I could not compile them and run the code. I have these 2 files, first one is pthread.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
int (*original_pthread_create)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL;
void load_original_pthread_create() {
void *handle = dlopen("libpthread-2.15.so", RTLD_LAZY);
char *err = dlerror();
if (err) {
printf("%s\n", err);
}
original_pthread_create = dlsym(handle, "pthread_create");
err = dlerror();
if (err) {
printf("%s\n", err);
}
}
int my_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
if (original_pthread_create == NULL) {
load_original_pthread_create();
}
printf("I am creating thread from my pthread_create\n");
return original_pthread_create(thread, attr, start_routine, arg);
}
I compiled this using the below command and got a shared object named libpthread.so
gcc pthread.c -o libmypthread.so -shared -fpic -ldl
Now the second file, main.cpp
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <ostream>
#include <iostream>
#include <thread>
#include <chrono>
#include <pthread.h>
#include <signal.h>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <execinfo.h>
#include <sstream>
#include <dlfcn.h>
#include <unistd.h>
void *dummyThread(void *t)
{
// just spin/sleep until done
//std::cout<<"This thread ID is "<<std::this_thread::get_id()<<std::endl;
//long thID = (long) id;
printf("thread Id is pthread id is %lu\n", pthread_self());
while(!done)
{
sleep(10);
}
}
int main(int argc, char*argv[])
{
printf("Hello World!\n");
pthread_t ptid1, ptid2;
int ret1 = my_pthread_create(&ptid1, NULL, dummyThread, NULL);
int ret2 = my_pthread_create(&ptid2, NULL, dummyThread, NULL);
pthread_exit(NULL);
printf ("Goodbye Cruel World!\n");
}
To compile this above code, I use a Makefile
CC=gcc
CXX=g
RM=rm -f
CPPFLAGS=-g
CXXFLAGS=-std=c 17
LDFLAGS=-g -rdynamic
LDLIBS=-lpthread -ldl
SRCS=main.cpp
OBJS=$(subst .cpp,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
when I run make, I get this following error:
undefined reference to
my_pthread_create(unsigned long*, pthread_attr_t const*, void* (*)(void*), void*)' /usr/bin/ld: /home/hgovindh/perf/main.cpp:: undefined reference to
my_pthread_create(unsigned long*, pthread_attr_t const*, void* ()(void), void*)' collect2: error: ld returned 1 exit status make: *** [Makefile:16: tool] Error 1
Now, how do I run this main.cpp so that it calls the my_pthread_create defined in the pthread.c file?
CodePudding user response:
Are you familiar with C 's name mangling? In C, if you have a function named foo
-- regardless of the arguments it receives -- the symbol in the .o file is called foo
. In C , the name is mangled to include type information. This is how you can engage in method name overloading based on different arguments.
In your include file, you need to use
extern "C" {
....
}
This tells the C compiler that the symbols inside those braces are C symbols, and don't name-mangle them.
It will actually look something like this:
#ifdef __cplusplus
extern "C" {
#endif
With similar ifdef
around the closing brace.
You should wrap your entire C .h file in this type of guard so it can be included in your C files. The other choice is to guard it when you include it, but it's better if you do the guard in the include file itself.
So you'll ultimately have something like this:
#ifdef __cplusplus
extern "C" {
#endif
void load_original_pthread_create();
int my_pthread_create(pthread_t *, const pthread_attr_t *, void *(*) (void *), void *);
#ifdef __cplusplus
}
#endif
CodePudding user response:
You should be able do create different compilation rules depending on the file extension. Note that my preferred approach of using cmake is presented at the end of the answer.
CC=gcc
CXX=g
RM=rm -f
CPPFLAGS=-g
CXXFLAGS=-std=c 17
CFLAGS=-g
LDFLAGS=-g -rdynamic
LDLIBS=-lpthread -ldl
SRCS=main.cpp pthread.c
OBJS=$(addsuffix .o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
%.cpp.o: %.cpp
$(CXX) -c -o $@ $(CPPFLAGS) $(CXXFLAGS) $<
%.c.o: %.c
$(CC) -c -o $@ $(CFLAGS) $<
this appends the .o
to the file name including extension to be able to tell .c
files appart from .c
files in the rules for the .o
file.
Alternative: Let CMake generate the makefiles
CMake can take care of generating the project files for you. It will automatically choose some reasonable defaults and allows you to change the build system you're using without much effort e.g. to ninja instead of unix makefiles.
CMakeLists.txt
(Place in the directory containing the source files)
cmake_minimum_required(VERSION 3.16)
# compile features for c 17 only documented in v 3.16, but may be available earlier
project(Tool)
add_executable(tool
main.cpp
pthread.c
)
# make sure at least C 17 is used
target_compile_features(tool PRIVATE cxx_std_17)
set_target_properties(tool PROPERTIES
CXX_EXTENSIONS Off # no -std=gnu-...
# CXX_STANDARD 17 # require an exact standard (remove target_compile_feature, if commented in)
)
target_link_libraries(tool PRIVATE pthread ${CMAKE_DL_LIBS})
Building the project on linux should use gcc
and g
as default, unless you modified the environment variables to point to other compilers, in addition to using the generator "Unix Makefiles" by default.
Choose the name of a directory where CMake is free to modify/remove/overwrite any files without messing with your project files. I use build_release
and build_debug
below. From the directory containing the CMakeLists.txt
file, execute the following commands to create makefile projects for the release and debug version respectively. This step is necessary only once.
cmake -D CMAKE_BUILD_TYPE=Debug -S . -B build_debug
cmake -D CMAKE_BUILD_TYPE=Release -S . -B build_release
Now the equivalent of running make all would be
cmake --build build_debug
cmake --build build_release
(alternatively you could change the working directory to build_debug
or build_release
and run make all
, but the cmake --build ...
version works with any Generator type even without changing the working directory.)