If a dynamic library exports the address to a global variable defined within the library, how are accesses to that variable translated during dynamic linking so that a running application can interact with it?
How does a dynamic library’s references to a global variable get translated once in the running app
cdynamic-linking
Related Solutions
What the ELF linker does is that it ensures that static and dynamic linker produce the same result. So using shared objects does not create any new problems. You can create some if you use of non-default visibility or version or linker scripts, but you obviously wouldn't do that if it isn't safe in that particular case.
Now if you use incompatible sets of compiler flags for building different objects, whether shared or static, in one application, you will have problems. You can have two kinds of problems:
The flags are so incompatible that calling convention, structure layout or some other such parameter is different between the modules. Obviously the only solution is to know which compiler options always have to remain set to the platform defaults.
The flags modify content of the headers and thus violate one definition rule. Authors of standard libraries, at least the open-source ones, know well how to avoid breaking compatibility between objects. But if you have some special library who's author were not careful, you can get in trouble and will have to make sure the compilation flags are compatible.
In Linux I've not seen these problems happen. Everybody knows not to touch the compiler flags that could break anything and there are no special incompatible versions for debug or such (gcc can emit debug information in optimizing build, so you normally just get unstripped version of the same object or lately even just the debug information for the normal object split out to separate file).
This is different from Windows, where:
- Due to the way shared libraries need explicit export and import there will not merge symbols (like template instances or class impedimenta) generated into different shared libraries.
- Have separate debug and release runtimes that differ in both the shared library used and lot of preprocessor magic. It means that you can't link your debug build to libraries built with the release version, so every library needs to ship debug and release variant. And often variant for static and dynamic linking. And despite all this trouble I don't think the debug support on Windows is better; e.g. Linux libc has hooks for replacing allocator (with debug one) which is always gross hack on Windows and Linux has great tools like Valgrind.
- Have lots of compiler flags that get flipped for various compatibility reasons, causing more complications. It has to be said that Microsoft is in more difficult position here. Since they cater primarily for the closed-source programmers, they have to assume things can't be recompiled and have to provide various compatibility kludges to keep things working. Linux people just assume things can be recompiled and do a breaking change when things get too complicated, like the switch to ELF format was; The ELF format was created between 1997 and 1999, while Windows objects are still backward compatible to the old Windows 3.1 ones. At the cost of all the mess.
In general, mutable memory location will not be shared between applications unless explicitly asked for. Thus, when libcnt
writes to cnt_loads
, the page on which cnt_loads
resides on will be duplicated. This behaviour is known as copy-on-write (COW). The same thing happens if you duplicated a process with fork()
: the child process will not share writeable memory with the parent, if any write occurs to "forked" page, the page will be duplicated.
If you want to use shared memory for interprocess communication, you should use SystemV shared memory, POSIX shared memory or mmap instead. Note that these methods are somewhat persistent, i.e. you will need to remove the shared memory object after use.
You can approximate the behaviour you originally wanted using shared memory by defining __attribute__((constructor))
and __attribute__((destructor))
functions for your shared library. The constructor
function is run every time the library is opened, so you can use it to initialize/open the shared memory and increment the the load count. If you also maintain a reference count (how many times the shared library is open in the system right now) with the destructor
---in addition to the load count---, your can properly remove the shared memory once the reference count falls to 0.
Note that using shared memory for inter-process communication absolutely requires some form of mutual exclusion, for example a semaphore or mutex. Failing to synchronize properly will lead to race conditions (for example, what happens when two processes open your library at precisely same time?). You may avoid mutual exclusion if you can increment/decrement the counters in an atomic way. I recommend you to use OS provided inter-process semaphores instead of atomics, because atomic operations are tricky to utilize properly, and your problem is not performance critial at all (thus no need for lock-free operations).
Best Answer
Dynamic linking is operating system specific (and very different on Linux and on Windows; read Levine's Linkers and Loaders book).
For Linux, a good explanation happens in Drepper's How to write shared libraries paper.
In general, the access to such a global variable may involve some indirection. Read about the Global Offset Table.