C++ Dynamic Shared Library on Linux

C++ Dynamic Shared Library on Linux

myclass.h

#ifndef __MYCLASS_H__
#define __MYCLASS_H__

class MyClass
{
public:
MyClass();

/* use virtual otherwise linker will try to perform static linkage */
virtual void DoSomething();

private:
int x;
};

#endif

myclass.cc

#include "myclass.h"
#include <iostream>

using namespace std;

extern "C" MyClass* create_object()
{
return new MyClass;
}

extern "C" void destroy_object( MyClass* object )
{
delete object;
}

MyClass::MyClass()
{
x = 20;
}

void MyClass::DoSomething()
{
cout<<x<<endl;
}

class_user.cc

#include <dlfcn.h>
#include <iostream>
#include "myclass.h"

using namespace std;

int main(int argc, char **argv)
{
/* on Linux, use "./myclass.so" */
void* handle = dlopen("myclass.so", RTLD_LAZY);

MyClass* (*create)();
void (*destroy)(MyClass*);

create = (MyClass* (*)())dlsym(handle, "create_object");
destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");

MyClass* myClass = (MyClass*)create();
myClass->DoSomething();
destroy( myClass );
}

On Mac OS X, compile with:

g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user

On Linux, compile with:

g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user

If this were for a plugin system, you would use MyClass as a base class and define all the required functions virtual. The plugin author would then derive from MyClass, override the virtuals and implement create_object and destroy_object. Your main application would not need to be changed in any way.

what would happened if I changed the C++ Dynamic Shared Library on Linux while my executable program using on it

Without the possibility to investigate it myself, this becomes speculative but using:

rm abc.so
cp new_version.so abc.so

has no effect on programs that has already loaded abc.so. For programs linked with abc.so (or using dlopen to load it) it will present a problem if they are started (or uses dlopen) while the file is removed or it's being copied into place. A core dump could very well be the result.

A better way to replace your abc.so:

copy new_version.so to the same filesystem as abc.so (like the same directory)
mv new_version.so abc.so

This assures that there is always a complete version of abc.so where it's expected to be. Any program needing it will either get the old version or the new version - and there's nothing in between.

Linking shared lib on Linux with duplicate yet modified class/struct causes segfault

I followed the recommendations found in the last answer and Is there symbol conflict when loading two shared libraries with a same symbol :

  • running 'nm Master' and 'nm libSlave.so' showed the same automatically generated constructor symbols:
...
000000000000612a W _ZN5DummyC1EOS_
00000000000056ae W _ZN5DummyC1ERKS_
0000000000004fe8 W _ZN5DummyC1Ev
...

So, the mangled function signatures match in both the master's binary and the slave.

When loading the library, the master's function is used instead of the library's version. To study this further, I created an even more minimalistic example like in the post referenced above:

master.cpp

#include <iostream>

#include <dlfcn.h> // shared library loading on Unix systems

// prototype for imported slave function
void hello();
typedef void F_hello();

void printHello() {
std::cout << "Hello world from master" << std::endl;
}

int main() {
printHello();

// now load dynamic library
void *soHandle = nullptr;
const char * const sharedLibPath = "libSlave.so";
// I tested different RTLD_xxx options, see text for explanations
soHandle = dlopen( sharedLibPath, RTLD_NOW | RTLD_DEEPBIND);
if (soHandle == nullptr)
return 1;

// now load shared lib function and execute it
F_hello * helloFn = reinterpret_cast<F_hello*>(dlsym( soHandle, "hello" ) );
helloFn();

return 0;
}

slave.h

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

void hello();

#ifdef __cplusplus
}
#endif

slave.cpp

#include "slave.h"
#include <iostream>

void printHello() {
std::cout << "Hello world from slave" << std::endl;
}

void hello() {
printHello(); // should call our own hello() function
}

You notice the same function printHello() exists both in the library and the master.

I compiled both manually this time (without CMake) and the following flags:

# build master
/usr/bin/c++ -fPIC -o tmp/master.o -c master.cpp
/usr/bin/c++ -rdynamic tmp/master.o -o Master -ldl

# build slave
/usr/bin/c++ -fPIC -o tmp/slave.o -c slave.cpp
/usr/bin/c++ -fPIC -shared -Wl,-soname,libSlave.so -o libSlave.so tmp/slave.o

Mind the use of -fPIC in both master and slave-library.

I now tried several combinations of RTLD_xx flags and compile flags:

1.

dlopen() flags: RTLD_NOW | RTLD_DEEPBIND
-fPIC for both libs

Hello world from master
Hello world from slave

-> result as expected (this is what I wanted to achieve)

2.

dlopen() flags: RTLD_NOW | RTLD_DEEPBIND
-fPIC for only the library

Hello world from master
Speicherzugriffsfehler (Speicherabzug geschrieben) ./Master

-> Here, a segfault happens in the line where the iostream libraries cout call is made; still, the printHello()s function in the library is called

3.

dlopen() flags: RTLD_NOW
-fPIC for only the library

Hello world from master
Hello world from master

-> This is my original behavior; so RTLD_DEEPBIND is definitely what I need, in conjunction with -fPIC in the master's binary;

Note: while CMake automatically adds -fPIC when building shared libraries, it does not generally do this for executables; here you need to manually add this flag when building with CMake

Note2: Using RTLD_NOW or RTLD_LAZY does not make a difference.

Using the combination of -fPIC on both executable and shared lib, with RTLD_DEEPBIND lets the original example with the different Dummy classes work without problems.

Can't link dynamic library when running C language with root privileges?

The linker flag -L just tells the linker where to look for the library (or a library stub, if such is used) at link time. It does not influence the library search path at runtime.

For a system wide installed library you'd place the library in a place that's been configured in the global linker search path, set through /etc/ld.so.conf and files in /etc/ld.so.conf.d.

However it is perfectly possible to specify additional search paths specific to certain binaries by means of the so callled rpath. The rpath is set using the (you guessed it) rpath extra linker flag -Wl,-rpath.

Linking the program with

gcc -o … -Wl,-rpath='${ORIGIN}' …

would make the ELF interpreter (the piece of code that loads ELF binaries and does dynamic linkage) to also look for additional libraries right next to the program binary. You can read up on the details of rpaths in the ld.so manpage.

Be aware that rpaths invoke certain security considerations.

Why shared library linked with static library behaves like it was linked with dynamic one?

Shared libraries and static libraries are very different beasts.

A static library is literally an archive file, similar to a .tar file, whose contents are object files. Typically these are augmented with an index of the external symbols defined in the object files. During static linking, the linker extracts some or all of those object files from the library and links those with any other specified objects, pretty much as if the extracted object files had been specified directly. There is no inherent distinction between external symbols based on which object provided them, or on whether that object was drawn from a (static) library.

Shared libraries are integrated objects, very much along the lines of programs. They can be executable programs. There are no individual object files within. In shared-library formats that feature position-independent code, all external symbols provided by the library must be position-independent.

When you link a static library into a shared one, you are asking the linker to include the contents of some or all of the object files from the static library in the resulting shared library. That's the only way it can work. The external symbols resolved from the static library will be external symbols in the shared one, too, so references to them need to be relative, including from other functions in the shared lib.

Will an executable access shared-libraries' global variable via GOT?

Part of the point of a shared library is that one copy gets loaded into memory, and multiple processes can access that one copy. But every program has its own copy of each of the library's variables. If they were accessed relative to the library's GOT then those would instead be shared among the processes using the library, just like the functions are.

There are other possibilities, but it is clean and consistent for each executable to provide for itself all the variables it needs. That requires the library functions to access all of its variables with static storage duration (not just external ones) indirectly, relative to the program. This is ordinary dynamic linking, just going the opposite direction from what you usually think of.



Related Topics



Leave a reply



Submit