9

I have some static library that I can't change or rebuild. The library uses global variables. Something like this:

//lib A
#include <iostream>

static int i = 0;

void printA(){
    std::cout << i++ << std::endl;
}

I want to create two shared libraries that have their own "copy" of static library and its global state:

//lib B
#include "liba.h"

void printB(){
    printA();
}

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

//lib C
#include "liba.h"

void printC(){
    printA();
}

... and use them simultaneously:

#include "libb.h"
#include "libc.h"

int main(){
    printB();
    printB();
    printC();
    printC();
}

I expect following output:

0
1
0
1

.. but actually get:

0
1
2
3

Seems like libB and libC share common counter variable. If had access to libA source code, I would rebuild it with -fvisibility=hidden. But unfortunately I have only binary.

Is there any way to achieve expected behavior without libA rebuilding?

7
  • 3
    If had access to I have only binary. - so you have access libA.a or not? You can unpack libA.a to .o files, then rename all symbols using ex. objcopy --redefine-sym printA=printAcopy, then rebuild libAcopy.a then from printC() call printAcopy - printC() { printAcopy(); }. Commented May 28, 2019 at 11:04
  • Put your copy of the library in a separate namespace. Or, build a wrapper shared object around the static lib. The wrapper makes all symbols private, and only exports functions of interest with a different name. Commented May 28, 2019 at 11:13
  • Are the two libraries linked separately? Commented May 28, 2019 at 11:20
  • @KamilCuk, Yes, I have libA.a, but I'd rather not change it. Because it may be updated once. Commented May 28, 2019 at 11:23
  • 3
    Shouldn't this be tagged with "Linux"? Because the behavior of shared libraries differs between Linux and Windows. Under Windows, a shared library (dll) will have its own version of those globals, just as the OP expects. Commented May 28, 2019 at 12:13

2 Answers 2

3

If LibA uses a static counter which libB and libC increment by calling printA, then there's no way to do what you want without object file manipulation or non-portable hacks.

The linker resolves all references to global variables (even statics) to the same symbol at link time.

If you're willing to manipulate object files, then the following should work for you:

$ objcopy --prefix-symbols=copy_ liba.a liba-copy.a

#define printA copy_ printA
#include "liba.h"
/* ... */

If you can get the symbols from the static library using nm (the name you'll be looking for will be in the form of <counter name>.<process ID>) and you do something like the following, then you can read and write the static counter variable at runtime:

int counter asm("<counter name>.<process ID>");
counter = 0;

Note that this process will have to be repeated after every update of the library.

Sign up to request clarification or add additional context in comments.

2 Comments

you don't need ar x just objcopy --prefix-symbols=copy_ liba.a liba-copy.a There is however a real problem, because the names are mangled.
@KamilCuk Thank you. Didn't know that.
3

You can copy the static library and rename all symbols that use the global state. Because the symbols are compiled with c++, you are out of luck, the symbols are mangled.

You can write a C interface for all the accesses and recompile the static library hiding it's symbols and then use some objcopy --prefix-symbols org++ -Wl,--wrap=printA to prefix/rename the C symbols.

Or you need to know beforehand the already mangled C++ names, and then call objcopy --redefine-sym _Z6printAv=_Z10printAcopyv etc. for each symbol the library exports.

Below is the test setup that calls the objcopy on the mangled names. I found out the symbol names by inspecting the object files, nm a.o and nm c.o. Here it is:

cat <<EOF >Makefile
all: liba.a b.o main.o  c.o
    # we have access only to liba.a only
    objcopy --redefine-sym _Z6printAv=_Z10printAcopyv liba.a libacopy.a
    g++ main.o b.o c.o liba.a libacopy.a -o a.out
    ./a.out

liba.a: a.o
    ar rcs liba.a a.o

clean:  
    rm -fr *.o *.a *.out tmp

EOF

cat <<EOF >a.cpp
#include <iostream>

static int i = 0;

void printA(){
    std::cout << i++ << std::endl;
}
EOF

cat <<EOF >b.cpp
void printA();
void printB(){
    printA();
}
EOF

cat <<EOF >c.cpp
void printAcopy();
void printC(){
    printAcopy();
}
EOF

cat <<EOF >main.cpp
void printB();
void printC();
int main(){
    printB();
    printB();
    printC();
    printC();
}
EOF

You can compile with make and run:

g++    -c -o a.o a.cpp
ar rcs liba.a a.o
g++    -c -o b.o b.cpp
g++    -c -o main.o main.cpp
g++    -c -o c.o c.cpp
# we have access only to liba.a only
objcopy --redefine-sym _Z6printAv=_Z10printAcopyv liba.a libacopy.a
g++ main.o b.o c.o liba.a libacopy.a -o a.out
./a.out
0
1
0
1

3 Comments

Makefiles don't work unless they have tabs. 8 spaces don't work.
Steckoverflow messes up with formatting, just sed 's/^[[:space:]]\+/\t/' it ; )
Perhaps you should add that to your answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.