Sharing global data between a shared library and main - c++

I've got a variable called global_count that I would like to share between the shared library and the main part of the program (and ultimately other libs, but for now I've simplified the testcase).
Given the declaration of global_count in globals.cpp:
extern "C" {
int* global_count;
}
We compile to create a global.o file:
gcc -c global.cpp
shared.cpp below will be used to create shared.so:
#include <stdio.h>
#include "global.h"
extern "C" {
void init_global_count(int* xp) {
printf("Initialize global count to: %d\n", *xp);
global_count = xp;
};
void print_global_count(){
if(global_count) {
printf("global_count is: %d\n",*global_count);
} else {
printf("global_count* is not initialized!\n");
}
};
}
global.h:
extern "C" {
extern int* global_count;
}
main.cpp:
#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>
#include "global.h"
int answer = 42;
int* gc_copy;
typedef void (*func)();
void (*init_global_count)(int*);
void (*print_global_count)();
void load(char* shared_lib){
void* handle;
handle = dlopen(shared_lib,RTLD_NOW | RTLD_GLOBAL) ;
printf("load:after dlopen\n");
if(!handle)
{
printf("DID NOT LOAD!\n");
fflush(stdout);
fputs (dlerror(), stderr);
exit(1);
} else {
printf("Loaded OK!\n");
fflush(stdout);
void (*init_global_count)(int*) = (void (*)(int*))dlsym(handle, "init_global_count");
(*init_global_count)(&answer);
void (*print_global_count)() = (void (*)())dlsym(handle, "print_global_count");
(*print_global_count)();
}
}
int main(){
printf("main...\n");
load((char*)"./shared.so");
if(global_count)
printf("main:global_count is: %d\n", *global_count);
return 0;
}
To compile the shared lib and main:
gcc -g -Wall -fno-omit-frame-pointer -fPIC -shared -o shared.so shared.cpp global.o
gcc -g -o main main.cpp global.o -ldl
Note that we're linking in global.o in both of those compilations.
Now we run it and the output is:
main...
load:after dlopen
Loaded OK!
Initialize global count to: 42
global_count is: 42
So the *global_count* reported from inside *print_global_count()* (defined in shared.cpp) is 42 as expected. However, global_count is not reported from main because the global_count pointer has not been initialized - so the global_count in main.cpp is not the same as the global_count in shared.cpp.
I'm wondering if it's possible to do what I'm trying to do here (to share some global data between a .so and the module that loads the .so)? If so, do I need to link differently?

All objects in a shared library need to be compiled as position independent (-fpic or -fPIC). So linking in globals.o into your shared library is in error.
You are actually creating two instances of the global_count pointer. You are trying to print out a pointer value as a decimal integer from main. The global pointer variable global_count in the main version is not yet initialized, so it has the starting value of all bytes set to 0.
You can remove globals.o from your shared library, and only have one instance of the global_count variable in main. However, for the shared library to see the main global variables, they have to be made visible to them. You can do this by adding -rdynamic to the flags to gcc in your link line for main.

Related

Is it possible to load an extra helper .so during current gdb session?

Is it possible to load an extra helper .so during current gdb session?
// point.h
class Point
{
public:
int x;
int y;
Point(int x1, int y1) {x = x1; y = y1;}
};
// main.cpp
#include "point.h"
#include <stdio.h>
int main()
{
Point p(3, 4);
printf("%d\n", p.x);
return 0;
}
g++ -g -c main.cpp -o main.o
g++ -g main.o -o main
When debugging, I need to add a helper function to dump the Point object. But I don't want to recompile and rerun. (It might take a long time.) So I am trying to build another helper.so.
// helper.cpp
#include "point.h"
#include <stdio.h>
void dump_point(Point *p)
{
printf("Point(%d, %d)\n", p->x, p->y);
}
g++ -g -fPIC -shared helper.cpp -o helper.so
Is it possible to load this helper.so in gdb so I can call dump_point() without rerunning?
When debugging, I need to add a helper function to dump the Point object.
The "normal" way to do this to write a custom pretty printer in Python.
One advantage of doing that is that the pretty printer will also work for a core dump, whereas dump_point() solution will not (regardless of whether it's linked in or loaded from a separate .so).
So I am trying to build another helper.so.
If your main was linked against libdl, you could do this:
(gdb) call dlopen("./helper.so", 0)
(gdb) call dlsym($_, "dump_point")
Note: you will want to make dump_point extern "C" to avoid name mangling.

why are RTLD_DEEPBIND and RTLD_LOCAL not preventing collision of static class member symbol

I am trying to write a simple plugin system for an application and would like to prevent plugins from stomping on each others symbols, however RTLD_DEEPBIND and RTLD_LOCAL don't seem to be enough when it comes to static class members when they happen to have the same name in different plugins.
I wrote a stripped down example to show what I mean.
I compiled and ran it like this:
g++ -c dumb-plugin.cpp -std=c++17 -fPIC
gcc -shared dumb-plugin.o -o dumb1.plugin
cp dumb1.plugin dumb2.plugin
g++ main.cpp -ldl -o main
./main
And the content of the output file for the second plugin showed that it reused the the class from the first plugin.
How can I avoid this?
EDIT: I compiled the plugin with clang(not main just the plugin) and it worked despite all of the RTLD_DEEPBIND stuff being in main.cpp which was still compiled with g++. It didn't work when the plugin was compiled with gcc 10.3 or 11.1 even when I tried -Bsymbolic. Is this a bug?
If I run readelf on the DSO compiled/linked with clang i see these 2 lines:
21: 00000000000040b0 4 OBJECT UNIQUE DEFAULT 26 _ZN9DumbClass7co[...]_ZN9DumbClass7co[...]
25: 00000000000040b0 4 OBJECT UNIQUE DEFAULT 26 _ZN9DumbClass7co[...]
and with gcc i get:
20: 00000000000040a8 4 OBJECT WEAK DEFAULT 24 _ZN9DumbClass7co[...]
27: 00000000000040a8 4 OBJECT WEAK DEFAULT 24 _ZN9DumbClass7co[...]
with WEAK instead of UNIQUE under the BIND column.
dumb-plugin.cpp:
#include <dlfcn.h>
#include <cstdio>
#include <string>
int global_counter = 0;
static int static_global_counter = 0;
std::string replace_slashes(const char * str) {
std::string s;
for (const char* c = str; *c != '\0'; c++)
s += (*c == '/')?
'#' : *c;
return s;
}
void foo() {}
class DumbClass {
public:
static inline int counter = 0;
};
extern "C" void plugin_func() {
static int static_local_counter = 0;
Dl_info info;
dladdr((void*)foo, &info);
std::string path = "plugin_func() from: " + replace_slashes(info.dli_fname);
auto fp = std::fopen(path.c_str(), "w");
fprintf(fp, "static local counter: %d\n", static_local_counter++);
fprintf(fp, "DumbClass::counter: %d\n", DumbClass::counter++);
fprintf(fp, "global counter: %d\n", global_counter++);
fprintf(fp, "static global counter: %d\n", static_global_counter++);
std::fclose(fp);
}
main.cpp:
#include <dlfcn.h>
#include <iostream>
#include <unistd.h>
#include <string.h>
int main () {
char path1[512], path2[512];
getcwd(path1, 512);
strcat(path1, "/dumb1.plugin");
getcwd(path2, 512);
strcat(path2, "/dumb2.plugin");
auto h1 = dlopen(path1, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
auto h2 = dlopen(path2, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
auto func = (void(*)()) dlsym(h1, "plugin_func");
func();
func = (void(*)()) dlsym(h2, "plugin_func");
func();
}
gcc implements static inline data members (and also static data members of class templates, inline or not, and static variables in inline functions, and perhaps other things as well) as global unique symbols (a GNU extension to the ELF format). There is only one such symbol with a given name per process, by design.
clang implements such things as normal weak symbols. These will not collide when RTLD_LOCAL and RTLD_DEEPBIND are used.
There are several ways to avoid collisions, but all of them require plugin writers to take an action. The best way IMO is to use hidden symbol visibility by default, only opening symbols that are meant to be dlsymd.

An action on executable's library load

Let's say, I have an executable a.out. It has shared libraries libcrypto.so, libmylib.so. I'd like to make some action at every of these libs load. Is there some function I can add my code to? So I will define some function inside my executable and it will be called twice for this example.
I'm aware of __attribute__ ((constructor)), but it should be defined for every library specifically, which is not possible.
Thanks.
I'd like to make some action at every of these libs load. Is there some function I can add my code to?
Your code, no. But you could LD_PRELOAD a dlopen interposer.
Example:
// foo.c
#include <stdio.h>
__attribute__((constructor))
void ctor()
{
printf("In %s:%d\n", __FILE__, __LINE__);
}
// main.c
#include <dlfcn.h>
#include <stdio.h>
int main()
{
printf(">>> main\n");
dlopen("./foo.so", RTLD_LAZY);
dlopen("./bar.so", RTLD_LAZY);
printf("<<< main\n");
return 0;
}
gcc -shared -fPIC -o foo.so foo.c
gcc -shared -fPIC -o bar.so foo.c
gcc main.c -ldl
./a.out
>>> main
In foo.c:6
In foo.c:6
<<< main
Now let's add a dlopen interposer:
// dlopen_preload.c
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
typedef void* (*FN)(const char *, int);
void *dlopen(const char *fname, int flags)
{
FN real_dlopen = (FN)dlsym(RTLD_NEXT, "dlopen");
void *ret = real_dlopen(fname, flags);
printf("my dlopen(%s, 0x%x) -> %p\n", fname, flags, ret);
return ret;
}
gcc -shared -o dlopen_preload.so dlopen_preload.c
LD_PRELOAD=./dlopen_preload.so ./a.out
>>> main
In foo.c:6
my dlopen(./foo.so, 0x1) -> 0x565039670690
In foo.c:6
my dlopen(./bar.so, 0x1) -> 0x565039670c90
<<< main
VoilĂ .

dynamic library issue: dlsym() failing to find smbol

I've been following Apple's Dynamic Library Programming Topics
docs to create and use a runtime-loaded library using dlopen() / dlsym().
It seems I'm getting a failure to find the desired symbol on my Mid 2012 MacBook Air, running macOS Mojave.
Library Source Code
// adder.h
int add(int x);
and
// adder.cpp
#include "adder.h"
int add(int x) {
return (x + 1);
}
Compiled with clang -dynamiclib adder.cpp -o libAdd.A.dylib
Main Source
// main.cpp
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include "adder.h"
int main() {
void* adder_handle = dlopen("libAdd.A.dylib", RTLD_LOCAL|RTLD_LAZY);
if (!adder_handle) {
printf("[%s] Unable to load library: %s\n\n", __FILE__, dlerror());
exit(EXIT_FAILURE);
}
while(true) {
void* voidptr = dlsym(adder_handle, "add");
int (*add)(int) = (int (*)(int))voidptr;
if (!add) {
printf("[%s] Unable to get symbol: %s\n\n", __FILE__, dlerror());
exit(EXIT_FAILURE);
}
printf("%d\n", add(0));
}
dlclose(adder_handle);
return 0;
}
Compiled with clang main.cpp -o main
I've also set the DYLD_LIBRARY_PATH environment variable to ensure the library can be found. Everything compiles ok.
Nevertheless, when I run the main executable, I get the error:
[main.cpp] Unable to get symbol: dlsym(0x7fb180500000, add): symbol not found
Running nm -gC libAdd.A.dylib outputs:
0000000000000fa0 T add(int)
U dyld_stub_binder
Any ideas on what could be wrong, or what I need to do to debug this issue?
Thanks!
C++ actually mangles the functionname which results in a different symbolname.
Your are able to spot these mangled symbol names using nm -g <yourlib.dylib>
You can change this behavior by wrapping your method into
extern "C" {
int add(int x);
}

Both static variables and global variables show different addresses in dynamic library and static library on Linux?

I have encountered on CentOS 6.5. As I have searched online that static variable behaves differently on Windows and on Linux when using dynamic library. That is, Windows would cause duplication of variables and Linux would not, like this one:
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
However, when I wrote a small program to validate this, I found that Linux also causes duplication. Here is my small program, including four files:
(1) A.h
#ifndef A_H
#define A_H
#include <cstdio>
static int b;
extern "C" class A {
public:
int mem;
A() {
printf("A's address: %p\n", this);
printf("B's address: %p\n", &b);
}
void print() {
printf("%p: %d\n", this, mem);
}
~A() {
printf("DELETE A!!!!! %p\n", this);
}
};
extern A a;
#endif
(2) A.cpp
#include "A.h"
A a;
(3) d.cpp
#include "A.h"
extern "C" void exec() {
a.print();
}
(4) main.cpp
#include "A.h"
#include <dlfcn.h>
typedef void (*fptr) ();
int main() {
a.mem = 22;
a.print();
void *handle;
handle = dlopen("d.so", RTLD_LAZY);
fptr exec = reinterpret_cast<fptr>(dlsym(handle, "exec"));
(*exec)();
dlclose(handle);
return 0;
}
Here is how I compile and run my program:
g++ d.cpp A.cpp -shared -rdynamic -o d.so -ldl -I. -fPIC -g -std=c++1y
g++ main.cpp A.cpp -ldl -I. -g -std=c++1y
./a.out
Both the dynamic part d.cpp and the static part main.cpp use the variables a and b declared in A.cpp and A.h. And here is the result of the program on my machine:
A's address: 0x600f8c
B's address: 0x600f90
0x600f8c: 22
A's address: 0x7fb8fe859e4c
B's address: 0x7fb8fe859e50
0x7fb8fe859e4c: 0
DELETE A!!!!! 0x7fb8fe859e4c
DELETE A!!!!! 0x600f8c
This surprises me a lot, because the addresses of global variable a and static variable b should be the same in the dynamic part and the static part. And it seems that modification on a in static part does not effect the a in dynamic part. Would anyone please answer my question, or help find out some mistakes in the program (if any)?
By the way, to be honest, on another project I am working on, I find that addresses of global variables are the same in dynamic library and in static library. But that project is too big and I cannot provide a small program to reproduce the behavior.
Thanks a lot !
The first command you showed builds a shared object d.so. Based on the context of your question, I surmise that you also intended to link with d.so, but your second command seems to be missing that part. I'm assuming that it's a typo, as this is the only explanation for the program output you showed -- that A.cpp is both linked to directly, and is also built into your d.so library.
Given that, quoting from the article you linked:
Object code routines used by both should not be duplicated in each.
This is especially true for code which use static variables such as
singleton classes. A static variable is global and thus can only be
represented once. Including it twice will provide unexpected results.
But that's exactly the rule you seem to be breaking, you're representing the statically-scoped instance of the A class twice, in your d.so, and in your main application executable.
So, that seems to be the indicated outcome: "unexpected results".