4

Been fighting with this on and off for 48 hours now; I'm still getting undefined reference errors when attempting to link a dynamic library with its dependency - despite all exports existing, and the library being found successfully.

Scenario:

  • libmemory (C++) - exports functions with extern "C"
  • libstring (C) - exports functions, imports from libmemory

libmemory builds successfully:

$ g++ -shared -fPIC -o ./builds/libmemory.so ...$(OBJECTS)...

libstring compiles successfully, but fails to link:

$ gcc -shared -fPIC -o ./builds/libstring.so ...$(OBJECTS)... -L./builds -lmemory
./temp/libstring/string.o: In function `STR_duplicate':
string.c:(.text+0x1cb): undefined reference to `MEM_priv_alloc'
./temp/libstring/string.o: In function `STR_duplicate_replace':
string.c:(.text+0x2a0): undefined reference to `MEM_priv_free'
string.c:(.text+0x2bf): undefined reference to `MEM_priv_alloc'
/usr/bin/ld: ./builds/libstring.so: hidden symbol `MEM_priv_free' isn't defined
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status

Verifying libmemory exports its symbols, and the library itself is found by using -v to gcc:

...
attempt to open ./builds/libmemory.so succeeded
-lmemory (./builds/libmemory.so)
...

$ nm -gC ./builds/libmemory.so | grep MEM_
0000000000009178 T MEM_exit
0000000000009343 T MEM_init
00000000000093e9 T MEM_print_leaks
00000000000095be T MEM_priv_alloc
000000000000971d T MEM_priv_free
00000000000099c1 T MEM_priv_realloc
0000000000009d26 T MEM_set_callback_leak
0000000000009d3f T MEM_set_callback_noleak

$ objdump -T ./builds/libmemory.so | grep MEM_
0000000000009d3f g    DF .text  0000000000000019  Base        MEM_set_callback_noleak
00000000000093e9 g    DF .text  00000000000001d5  Base        MEM_print_leaks
0000000000009d26 g    DF .text  0000000000000019  Base        MEM_set_callback_leak
00000000000099c1 g    DF .text  0000000000000365  Base        MEM_priv_realloc
0000000000009343 g    DF .text  00000000000000a6  Base        MEM_init
00000000000095be g    DF .text  000000000000015f  Base        MEM_priv_alloc
000000000000971d g    DF .text  00000000000002a4  Base        MEM_priv_free
0000000000009178 g    DF .text  00000000000000a7  Base        MEM_exit

$ readelf -Ws ./builds/libmemory.so | grep MEM_
    49: 0000000000009d3f    25 FUNC    GLOBAL DEFAULT   11 MEM_set_callback_noleak
    95: 00000000000093e9   469 FUNC    GLOBAL DEFAULT   11 MEM_print_leaks
    99: 0000000000009d26    25 FUNC    GLOBAL DEFAULT   11 MEM_set_callback_leak
   118: 00000000000099c1   869 FUNC    GLOBAL DEFAULT   11 MEM_priv_realloc
   126: 0000000000009343   166 FUNC    GLOBAL DEFAULT   11 MEM_init
   145: 00000000000095be   351 FUNC    GLOBAL DEFAULT   11 MEM_priv_alloc
   192: 000000000000971d   676 FUNC    GLOBAL DEFAULT   11 MEM_priv_free
   272: 0000000000009178   167 FUNC    GLOBAL DEFAULT   11 MEM_exit
   103: 0000000000009343   166 FUNC    GLOBAL DEFAULT   11 MEM_init
   108: 0000000000009178   167 FUNC    GLOBAL DEFAULT   11 MEM_exit
   148: 0000000000009d3f    25 FUNC    GLOBAL DEFAULT   11 MEM_set_callback_noleak
   202: 00000000000095be   351 FUNC    GLOBAL DEFAULT   11 MEM_priv_alloc
   267: 000000000000971d   676 FUNC    GLOBAL DEFAULT   11 MEM_priv_free
   342: 0000000000009d26    25 FUNC    GLOBAL DEFAULT   11 MEM_set_callback_leak
   346: 00000000000099c1   869 FUNC    GLOBAL DEFAULT   11 MEM_priv_realloc
   366: 00000000000093e9   469 FUNC    GLOBAL DEFAULT   11 MEM_print_leaks

Is there something horribly simple I'm missing? All the other related questions to this have simple answers such as link library order, and the paths used - but I've already verified they're in place and working as expected.

Tinkering with -fvisibility led to no changes either.

The same result exists whether using clang or gcc.

Linux 3.16.0-38-generic gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)

ZXcvbnM
  • 611
  • 7
  • 16

2 Answers2

0

You should mark MEM_priv_alloc() function as extern "C" or wrap function body into extern "C" { /* function implementation */ } (already done as I can see from description). And use combination of __cplusplus and extern "C" in header

#ifdef __cplusplus
   #define EXTERNC extern "C"
#else
   #define EXTERNC
#endif

EXTERNC int MEM_priv_alloc (void);

Please also double check how MEM_priv_alloc prototype is done. For example, MEM_priv_alloc should not be inline (unfortunately I do not know phisics of this, however inline example failed for me)


Below is simplified example that works for me:

files:

$ ls
main.c  Makefile  mem.cpp  mem.h  strings.c  strings.h

mem.cpp

#include <stdio.h>
#include "mem.h"
extern "C" int MEM_priv_alloc (void)
{
   return 0;
}

mem.h

#ifdef __cplusplus
   #define EXTERNC extern "C"
#else
   #define EXTERNC
#endif
EXTERNC int MEM_priv_alloc (void);

strings.c:

#include <stdio.h>
#include "mem.h"
int STR_duplicate_replace (void)
{
   MEM_priv_alloc();
}

strings.h:

int STR_duplicate_replace (void);

main.c

#include <stdio.h>
#include "string.h"
int main (void)
{
   STR_duplicate_replace ();
   return 0;
}

Makefile:

prog: libmemory.so libstrings.so
        gcc -o $@ -L. -lmemory -lstrings main.c

libmemory.so: mem.cpp
        g++ -shared -fPIC -o $@ $^

libstrings.so: strings.c
        gcc -shared -fPIC -o $@ $^

and the build log:

g++ -shared -fPIC -o libmemory.so mem.cpp
gcc -shared -fPIC -o libstrings.so strings.c
gcc -o prog -L. -lmemory -lstrings main.c

please also see more details in Using C++ library in C code and wiki How to mix C and C++

Community
  • 1
  • 1
Peter K.
  • 134
  • 7
  • Yeah, I suspect something more complex is at fault. This code (and the other libraries I haven't gotten to yet) already fully build in msvc2015 for Windows, and it does work if I disable my memory debugging (so the MEM_priv* functions are never used or exported). I'll strip it down into minimal and see what I find later this evening. – ZXcvbnM Oct 05 '16 at 08:16
0

So, I was stripping out the final parts of the amalgamation, and uncovered the issue.

My import/export is modelled off of this: https://gcc.gnu.org/wiki/Visibility

My equivalent implementation ends up looking like this:

#     if GCC_IS_V4_OR_LATER
#             define DLLEXPORT        __attribute__((visibility("default")))
#             define DLLIMPORT        __attribute__((visibility("hidden")))
#     else
#             define DLLEXPORT
#             define DLLIMPORT
#     endif

The DLLIMPORT (visibility hidden) is causing the problem; I replace it with a blank definition and it's all ok. Yes, I did also have the equivalent for clang, which is why that also failed in the same way.

My takeaway from this is that the C code only ever saw these would-be-symbols as hidden, and therefore couldn't import them no matter how hard it tried, and however much they actually existed!

ZXcvbnM
  • 611
  • 7
  • 16