Search code examples
c++booststatic-librariesdebug-symbols

Boost.DLL cannot find symbols in executable using statically linked library


I am currently writing a small test application to explore statically linking libraries and accessing their symbols at runtime with Boost.DLL. I am compiling a very simple static library and link it with a very simple application, using MinGW-w64, and am doing this with a Makefile and mingw32-make.

A.cpp defines a class A that stores and modifies a reference to an int. The library's only module B.cpp defines a subclass of A called B, as well as a factory method. The main module defines a class C which stores a reference to an instance of A, and attempts to access the symbols defined in B.cpp to store an instance of B in C. When I attempt to run the application, the output file is successfully loaded but the program fails to locate the statically linked symbols.

A.hpp:

#pragma once

#ifndef A_HPP_
#define A_HPP_

#include <memory>
#include <boost/dll/config.hpp>
#include <boost/dll.hpp>
#include <boost/function.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>

class A {
protected:
    int *_p;
public:
    A(int *p = NULL);
    virtual ~A() = default;
    
    void setp(int *p);
    int deref();

    virtual void add();
};

#endif

B.cpp:

#include "A.hpp"

class B : public A {
public:
    B();
    ~B();

    void add();
};

const size_t B_SIZE = sizeof(B);
void B_get(void *addr) {
    B *tmp = new B();
    memcpy(addr, tmp, B_SIZE);
    delete tmp;
}

B::B() {}
B::~B() {}

void B::add() {
    if (_p != NULL)
        *_p = *_p + 100;
    std::cout << "B: _p is now " << *_p << std::endl;
}

main.cpp:

#include "A.hpp"

class C {
public:
    std::shared_ptr<A> a;

    C(std::shared_ptr<A> aparam, int *x) : a(aparam) { a->setp(x); }
    int deref() { return a->deref(); }
};

int main() {
    std::string loc = boost::dll::program_location().string();

    std::cout << "loading file '" << loc << "'" << std::endl;
    boost::dll::shared_library lib(loc);
    
    std::cout << "getting symbol 'B_SIZE'" << std::endl;
    size_t B_SIZE = lib.get<size_t>("B_SIZE");

    std::cout << "getting symbol 'B_get(void*)'" << std::endl;
    boost::function<void(void*)> B_get = lib.get<void(void*)>("B_get");
    
    std::cout << "getting instance of B" << std::endl;
    void *b = new char[B_SIZE];
    B_get(b);
    
    //assign x and C
    int x = 5;
    C c(std::shared_ptr<A>((A*)b), &x);

    std::cout << "x: " << x << std::endl;
    std::cout << "c.a->deref(): " << c.a->deref() << std::endl;

    std::cout << "executing c.a->add()" << std::endl;
    c.a->add();

    std::cout << "x: " << x << std::endl;
    std::cout << "c.a->deref(): " << c.a->deref() << std::endl;
    
    return 0;
}

Makefile:

#build executable
out:
    g++ -Wall -c -IC:/Unix/boost_1_78_0/ B.cpp -o B.o
    ar -rcs -o libB.a B.o
    g++ -Wall -IC:/Unix/boost_1_78_0/ -LC:/Unix/boost_1_78_0/stage/lib -L. main.cpp A.cpp -llibboost_filesystem-mgw8-mt-x64-1_78 -llibboost_system-mgw8-mt-x64-1_78 -Wl,-Bstatic -lB -o out

#build and run executable
run: out
    ./out

Output:

C:\Unix\VSC\Static_Test>mingw32-make run
g++ -Wall -c -IC:/Unix/boost_1_78_0/ B.cpp -o B.o
ar -rcs -o libB.a B.o
g++ -Wall -IC:/Unix/boost_1_78_0/ -LC:/Unix/boost_1_78_0/stage/lib -L. main.cpp A.cpp -llibboost_filesystem-mgw8-mt-x64-1_78 -llibboost_system-mgw8-mt-x64-1_78 -Wl,-Bstatic -lB -o out
./out
loading file 'C:\Unix\VSC\Static_Test\out.exe'
getting symbol 'B_SIZE'
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  boost::dll::shared_library::get() failed: The specified procedure could not be found [system:127]
mingw32-make: *** [Makefile:9: run] Error 3

I have used nm to inspect out.exe and libB.a, and all relevant symbols for A, B, and C are defined, but the symbols for B are missing from out.exe.

I have also attempted to compile the executable with combinations of the flags -g and -O0, with no success.

As I understand it, a header for B.cpp included in main.cpp shouldn't be necessary to locate the symbols, nor are any symbol exporting qualifiers like extern "C" or __declspec(dllexport) (though I have tried all of those things as well).


Solution

  • boost::DLL works only with exported symbols. Your symbols are not exported. "Exporting" symbols is something that is outside the scope of the C++ language. It needs to be done with __declspec(dllexport) or similar.

    Statically linked symbols are not accessible at runtime. The linker discards them.


    The error says "B_SIZE" not found. B_SIZE is defined in B.cpp as

    const size_t B_SIZE = sizeof(B);
    

    This symbol is never exported. It is linked statically to your .exe and it's definition will then be discarded.

    You probably could change this to

    const size_t __declspec(dllexport) B_SIZE = sizeof(B);
    

    to force it to be exported (although exporting a variable as opposed to a function comes with it's own problems). Then, in theory, the compiler should tag it as "export this" and the lib command ar should honor this information as it builds the static lib "B.o"

    Finally, when the implicit linker call in your g++ -Wall ... exe -lB command get's executed, the linker should bind the static lib "B.o" to your exe, find the "please export me" flag and copy the symbol information for B_SIZE to the .exe.

    Only if you export a symbol, the info will be written to the .dll. Then other programs will be able to look it up.


    One more thing, hidden in the boost DLL tutorial. Under Windows, __declspec(dllexport) will export the symbols even for a .EXE file. So if you statically link your B.o to the .EXE, the windows tools will honor the dllexport tag and export the symbol. That's true for Microsoft and similar linkers.

    The small note in the tutorial regarding linux says, you have to pass -rdynamic to the linux linker. I am guessing here, but since exporting symbols from a .EXE is unusual (standard is .DLL), the linux tools might ignore all dllexport tags and explicitly hide exported symbols from the runtime - unless you explicitly tell them to include them, which is what -rdynamic could be for.


    BTW, the way this is usually done is like this:

    #if defined(MYLIB_EXPORTS)
    #define MYLIB_API __declspec(dllexport)
    #else if defined(MYLIB_IMPORTS)
    #define MYLIB_API __declspec(dllimport)
    #else
    #define MYLIB_API /* */
    #endif
    
    void MYLIB_API B_get(void*);