Search code examples
c++dynamicshared-librariesld

How to access & use string defined in shared library


I'm trying to bake a version string into a shared library, but can't figure out how to access the string in a client program of the library.

The method I'd like to work is: have the version string in a text file version.txt, turn that into an object file using objcopy, link that into a .so and link the client program against the dynamically shared library.

Here's my code.

version.txt

1.2.3.4

test.cpp

#include <iostream>
extern size_t _binary_version_txt_size;
int main() { std::cout << "Size: " << _binary_version_txt_size << std::endl; }

makefile

# abbreviate the tools we'll use
CXX:=g++-8
OC:=objcopy

# set up the compile and link options
CXXFLAGS:=-std=c++17 -Wall -pedantic -fPIC -g

# identify phony targets
.PHONY: clean

# set the default targets
all: test

# recipe to convert text files to object files
%.o: %.txt
    $(OC) --input binary --output elf64-x86-64 --binary-architecture i386:x86-64 $< $@

# recipes for building objects from source files
%.o: %.cpp 
    $(CXX) $(CXXFLAGS) $< -c -o $@

# recipe to build shared lib
libtest.so: version.o
    $(CXX) $(CXXFLAGS) $^ -shared -o $@

# recipe to build the target executable
test: test.o libtest.so
    $(CXX) $(CXXFLAGS)  $< -L. -ltest  -o $@

# recipe to clean up after ourselves
clean:
    rm -f *.o test

The symbols I expect are in the library:

$ nm libtest.so | grep version
0000000000201027 D _binary_version_txt_end
0000000000000007 A _binary_version_txt_size
0000000000201020 D _binary_version_txt_start

But, I get this warning from the linker:

/usr/bin/ld: warning: type and size of dynamic symbol `_binary_version_txt_size' are not defined

And the wrong value for the symbol is printed when the program runs:

$ LD_LIBRARY_PATH=$(pwd) ./test
Size: 0 <- expected 7

Is this a job for dlsym, or extern "C", or specifying the -E linker option? I've a few of these w/o success.

Thanks!


Solution

  • So, I found how to do it "my way" but still think Lorinczy's approach is better.

    I ultimately gave up trying to read the size of the string and came up with this code that works pretty well:

    #define DATASTRING(NAME)                                                 \
      []() -> std::string {                                                  \
        extern char _binary_##NAME##_txt_start[];                            \
        extern char _binary_##NAME##_txt_end[];                              \
        size_t sz = _binary_##NAME##_txt_end - _binary_##NAME##_txt_start; \
        std::string ans(_binary_##NAME##_txt_start, sz);                     \
        ans.pop_back();                                                      \
        return ans;                                                          \
      }()
    

    example usage

    std::string version = DATASTRING(version);
    

    Other things I learned:

    1. at least in this context char[] is not the same as char*.
    2. strings linked into the binary are NOT null terminated, you need to know their size or length.