Skip to content

Instantly share code, notes, and snippets.

@cl0ne
Last active November 27, 2019 21:56
Show Gist options
  • Save cl0ne/05a0a056a087b82039d0c7d89ad66753 to your computer and use it in GitHub Desktop.
Save cl0ne/05a0a056a087b82039d0c7d89ad66753 to your computer and use it in GitHub Desktop.

TL;DR: Use ldd -r libHello.so to find unresolved symbols in your libHello.so. Another option is to make your linker complain about theese symbols (with --no-undefined for ld). Or look for linking/loading errors later.

For example, you have:

// a.cpp
namespace X {
int someExternValue = 0;
}

// b.cpp

namespace x {
    extern int someExternValue;
}

extern "C" { // for dlsym example
void setExternValue(int v)
{
    x::someExternValue = v;
}

int getExternValue()
{
    return x::someExternValue;
}
}

This will successfully build and link into .so. Those undefined symbols are left to be resolved later "by someone else".

user@host:~$ ldd -r libHello.so 
        linux-vdso.so.1 (0x00007ffc99b15000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f6bfe767000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007f6bfe621000)
        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f6bfe607000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f6bfe440000)
        /usr/lib64/ld-linux-x86-64.so.2 (0x00007f6bfe9c0000)
undefined symbol: _ZN1x15someExternValueE       (./libHello.so)

Of course, you'll get complains about that when linking some executable to libHello:

// hello_main.cpp
#include <iostream>

extern "C" {
void setExternValue(int);
int getExternValue();
}

int main(int, char *[])
{
    std::cout << getExternValue() << std::endl;
    setExternValue(10);
    std::cout << getExternValue() << std::endl;
    return 0;
}

Oops: /usr/bin/ld: libHello.so: undefined reference to ``x::someExternValue'

Mostly the same goes for dynamic loading:

// hello_dlsym.cpp
#include <iostream>
#include <dlfcn.h>

typedef void (*setValueFunc)(int);
typedef int (*getValueFunc)();

int main(int, char *[])
{
    auto libHandle = dlopen("./libHello.so", RTLD_LAZY);
    if (!libHandle) {
        std::cerr << "dlopen failure: " << dlerror() << std::endl;
        return EXIT_FAILURE;
    }

    getValueFunc getV = reinterpret_cast<getValueFunc>(dlsym(libHandle, "getExternValue"));
    if (!getV) {
        std::cerr << "failed to get getValueFunc: " << dlerror() << std::endl;
        return EXIT_FAILURE;
    }

    setValueFunc setV = reinterpret_cast<setValueFunc>(dlsym(libHandle, "setExternValue"));
    if (!setV) {
        std::cerr << "failed to get setValueFunc: " << dlerror() << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << getV() << std::endl;
    setV(10);
    std::cout << getV() << std::endl;

    dlclose(libHandle);
    return EXIT_SUCCESS;
}

The same oops: dlopen failure: ./libHello.so: undefined symbol: _ZN1x15someExternValueE

The worst thing is when libHello is a plugin to some app and that app doesn't give any info why it couldn't load your plugin or you don't know where to look for logs.

BTW Unity editor logs are located at ~/.config/unity3d/Editor[-prev].log on Linux and undefined symbol errors in your native plugins will be listed there.

All files for the example are added below.

Relevant blog post.

namespace X {
int someExternValue = 0;
}
namespace x {
extern int someExternValue;
}
extern "C" { // for dlsym example
void setExternValue(int v)
{
x::someExternValue = v;
}
int getExternValue()
{
return x::someExternValue;
}
}
cmake_minimum_required(VERSION 3.13)
project(UndefinedSymbols VERSION 1.0)
add_library(HelloLib SHARED a.cpp b.cpp)
set_target_properties(HelloLib PROPERTIES LIBRARY_OUTPUT_NAME "Hello")
# or use ld's option `--no-undefined`
# target_link_options(HelloLib PRIVATE "LINKER:--no-undefined")
# this will make linking phase fail when undefined symbols are found
add_executable(Hello hello_main.cpp)
target_link_libraries(Hello HelloLib)
add_executable(HelloDlsym hello_dlsym.cpp)
target_link_libraries(HelloDlsym ${CMAKE_DL_LIBS})
#include <iostream>
#include <dlfcn.h>
typedef void (*setValueFunc)(int);
typedef int (*getValueFunc)();
int main(int, char *[])
{
auto libHandle = dlopen("./libHello.so", RTLD_LAZY);
if (!libHandle) {
std::cerr << "dlopen failure: " << dlerror() << std::endl;
return EXIT_FAILURE;
}
getValueFunc getV = reinterpret_cast<getValueFunc>(dlsym(libHandle, "getExternValue"));
if (!getV) {
std::cerr << "failed to get getValueFunc: " << dlerror() << std::endl;
return EXIT_FAILURE;
}
setValueFunc setV = reinterpret_cast<setValueFunc>(dlsym(libHandle, "setExternValue"));
if (!setV) {
std::cerr << "failed to get setValueFunc: " << dlerror() << std::endl;
return EXIT_FAILURE;
}
std::cout << getV() << std::endl;
setV(10);
std::cout << getV() << std::endl;
dlclose(libHandle);
return EXIT_SUCCESS;
}
#include <iostream>
extern "C" {
void setExternValue(int);
int getExternValue();
}
int main(int, char *[])
{
std::cout << getExternValue() << std::endl;
setExternValue(10);
std::cout << getExternValue() << std::endl;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment