Search code examples
c++g++dlopendlsym

Using dlopen/dlsym to open C++ shared library - dlsym returns NULL


I have not yet dealt with shared libraries in C++, and am having some trouble. I want to create a shared library and then have a C function pick up on that library. So here is my shared library file:

extern int nothing();
//sym.cpp
int nothing() {
    return 0;
}

Below is my dlopen/dlsym script:

//symtest.c
#include <stdio.h>
#include <dlfcn.h>
int main(){
    void *handle;
    handle = dlopen("/path/to/lib/sym.so",RTLD_NOW);
    int (*onload)(void *, void **, int);
    onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle,"nothing");
    if(onload==NULL) {
        printf("NULL");
    }
    return 0;
}

Compile and ran as following:

$ g++ -shared -fPIC -o sym.so sym.cpp
$ gcc symtest.c -ldl -o symtest
$ ./symtest
NULL

Why am I getting NULL? I am pretty sure this symbol is getting exported, at least by observing the output of the following commands.

nm:

$ nm -CD sym.so | grep " T "                        
0000000000000670 T nothing()
000000000000067c T _fini
0000000000000518 T _init

objdump:

$ objdump -T sym.so 

sym.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000518 l    d  .init  0000000000000000              .init
0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
0000000000000000  w   D  *UND*  0000000000000000              _Jv_RegisterClasses
0000000000000000  w   D  *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000  w   D  *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000  w   DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_finalize
0000000000200970 g    D  .bss   0000000000000000  Base        _end
0000000000200968 g    D  .got.plt   0000000000000000  Base        _edata
0000000000200968 g    D  .bss   0000000000000000  Base        __bss_start
0000000000000518 g    DF .init  0000000000000000  Base        _init
000000000000067c g    DF .fini  0000000000000000  Base        _fini
0000000000000670 g    DF .text  000000000000000b  Base        _Z7nothingv

There is a bigger picture here (Creating C++ Redis Module - "does not export RedisModule_OnLoad() symbol") but I looked through some Redis source code to produce a minimalistic example. Anyone have any idea what I am doing wrong here?

As requested, nm without -C option:

$ nm -D sym.so
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
0000000000000670 T _Z7nothingv
0000000000200968 B __bss_start
                 w __cxa_finalize
                 w __gmon_start__
0000000000200968 D _edata
0000000000200970 B _end
000000000000067c T _fini
0000000000000518 T _init

Solution

  • C++ has function overloading so there can be multiple functions with the same name and just different parameters.

    Since object files store only names, C++ applies what is called name mangling. It adds extra symbols representing the parameters to the name to differentiate between different overloads.

    When using dlsym, one must use the mangled name to get at the function address.

    Now since name mangling is platform specific it is often better to use C linkage (no name mangling)

    This can be done with the extern "C" declaration.