While writing applications in linux , the developer can use tons of libraries. Each library can packs functions, classes and variables and it is very important to understand how to create and work with it.
Lets start with simple example: Implicit link
int add(int a,int b) { return a+b; } int sub(int a,int b) { return a-b; }
To compile the library we use:
# gcc -g3 -shared -o libSimp.so -fPIC ./simp.c
Here we use -g3 to add debugging information , -shared to specify that we build a shared object and -fPIC to specify that this build a position independent code (we don’t know where in the memory the shared library will be loaded)
Using the library:
#include<stdio.h> #include "simp.h" void main() { int a,b; a=add(10,20); b=sub(80,20); printf("a=%d b=%d\n",a,b); }
To build the application we need to use -l with the library name and -L to specify the library location. If we put the library in one of the lib directories (/lib , /usr/lib) we don’t need to specify the location using -L flag but usually we want to put our code in a separate place
# gcc -g3 -o simp -L . usesimp.c -lSimp
Here we use -L with the current directory. note that don’t write the lib prefix and the extension so any library should named libXXX.so and to link with it we specify -lXXX
To run the app we need to put the library in one of the path locations or specify the library path use LD_LIBRARY_PATH environment variable:
# export LD_LIBRARY_PATH=. # ./simp a=30 b=60
Explicit link
What if we don’t know on build time the name of the loaded library ? For example we want to build an app that support plugins – for each plugin the user should implement 2 functions that we specify the prototype for. In this case we can use dlopen(3)
We build the library the same way , the different is the application using it:
#include<stdio.h> #include<stdlib.h> #include<dlfcn.h> int main() { int (*fptr)(int,int); void *libh=dlopen("./libSimp.so",RTLD_LAZY); if(libh == NULL) { puts("Error loading library"); exit(0); } fptr=dlsym(libh,"add"); if(fptr == NULL) { puts("Error finding function"); exit(0); } printf("sum = %d\n",fptr(20,10)); dlclose(libh); return 0; }
To compile it use:
gcc -o usedlopen ./usedlopen.c -ldl
We need to add -ldl to use dlopen.
Note about C++ libraries
Note that we are using the symbol name. In C the function name is also the symbol name but if we compile it to c++ as a result of function overloading the symbol name is different. To see the symbols , use the nm utility:
As you can see in the last 2 lines the symbol names are different because the compiler decode also the function prototype so if we want to use the above example with this library we need to use the correct symbol name:
fptr=dlsym(libh,"_Z3addii");
Another option is to add extern “C” on the library functions prototype to make the compiler use the name as a symbol name.
If we can’t find the function name we can ask nm to display the functions prototype using -C :
Now we can see the functions, using their address we can find the symbol name
Global Variables
One basic concept of any general purpose operating system is the private process address space – means each process has its on private memory space and the system maps pages from the physical address space to virtual for every process. Shared libraries are code sharing mechanism and not data means that every process has its own copy for example if we add a global variable to our library:
int add(int a,int b) { return a+b; } int global=100; int sub(int a,int b) { return a-b; }
and we use it by 2 separate processes :
/* simp.h */ #ifndef SIMPLIB #define SIMPLIB int add(int a,int b); int sub(int a,int b); extern int global; #endif /*****************************/ /* simp.c */ #include<stdio.h> #include "simp.h" void main() { int a,b; a=add(10,20); b=sub(80,20); printf("a=%d b=%d global=%d\n",a,b,global); global=200; sleep(100); } /*****************************/ /* simp2.c */ #include<stdio.h> #include "simp.h" void main() { printf("global=%d\n",global); sleep(100); }
If you run simp.c and wait until it gets to sleep so global=200 and then run simp2.c you will get global = 100
To create a shared data we need to use shared memory (shm_open)
First we define a structure to hold our data – Do Not Use Pointers !!! :
/* simp.h */ #ifndef SIMPLIB #define SIMPLIB int add(int a,int b); int sub(int a,int b); struct shared_data{ int data1; char buf[100]; int num; }; extern struct shared_data *global; #endif
Then we create a shared memory on library load (note the constructor attribute to declare a function as initializer)
#include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include "simp.h" static void __attribute__ ((constructor)) init_lib(void); struct shared_data *global; void init_lib(void) { int fd=shm_open("/libmem", O_RDWR | O_CREAT , 0660); ftruncate(fd,sizeof(struct shared_data)); global = mmap(0,sizeof(struct shared_data),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); } int add(int a,int b) { return a+b; } int sub(int a,int b) { return a-b; }
Now if we create 2 or more processes accessing global , the data will be shared
This example is for learning purpose just to show a simple way to share data, If you do that for real application it will be better to hide the structure, define a set of getters/setters and check on init if this is the first process loading to library so we can initialize the data.
To initialize the shared memory on the first instance creating it use O_EXCL flag in shm_open
#include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include "simp.h" static void __attribute__ ((constructor)) init_lib(void); struct shared_data *global; void init_lib(void) { int fd=shm_open("/libmem", O_RDWR | O_CREAT | O_EXCL, 0660); if(fd == -1) { sleep(1); // wait just in case the other instance didnt finish the initialization fd=shm_open("/libmem", O_RDWR, 0); global = mmap(0,sizeof(struct shared_data),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); } else { ftruncate(fd,sizeof(struct shared_data)); // set the size global = mmap(0,sizeof(struct shared_data),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); global->data1 = 100; // some init examples global->num= 50; } } int add(int a,int b) { return a+b; } int sub(int a,int b) { return a-b; }
If we want to delete the shared memory while no process is mapped we can also implement destructor add add a reference count + mutex in the shared memory
1 thought on “Linux Shared Libraries”
Comments are closed.
[…] can build the fault handler into a shared library and use the constructor to set it. Then you can inject the library to any process using […]