93

I am writing a fairly large C++ shared-object library, and have run into a small issue that makes debugging a pain:

If I define a function/method in a header file, and forget to create a stub for it (during development), since I am building as a shared object library rather than an executable, no errors appear at compile-time telling me I have forgotten to implement that function. The only way I find out something is wrong is at runtime, when eventually an application linking against this library falls over with an 'undefined symbol' error.

I am looking for an easy way to check if I have all the symbols I need at compile time, perhaps something I can add to my Makefile.

One solution I did come up with is to run the compiled library through nm -C -U to get a demangled list of all undefined references. The problem is this also comes up with the list of all references that are in other libraries, such as GLibC, which of course will be linked against along with this library when the final application is put together. It would be possible to use the output of nm to grep through all my header files and see if any of the names corresponding.. but this seems insane. Surely this is not an uncommon issue and there is a better way of solving it?

Braiam
  • 1
  • 11
  • 47
  • 78
David Claridge
  • 6,159
  • 2
  • 27
  • 25
  • 4
    `nm -C -u` has saved me multiple times! (note the lowercase `-u` on my system.) Leaving this comment here so that I can find it the next time that I need it. – dpritch Jul 31 '17 at 03:24

4 Answers4

101

Check out the linker option -z defs / --no-undefined. When creating a shared object, it will cause the link to fail if there are unresolved symbols.

If you are using gcc to invoke the linker, you'll use the compiler -Wl option to pass the option to the linker:

gcc -shared ... -Wl,-z,defs

As an example, consider the following file:

#include <stdio.h>

void forgot_to_define(FILE *fp);

void doit(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp != NULL)
    {
        forgot_to_define(fp);
        fclose(fp);
    }
}

Now, if you build that into a shared object, it will succeed:

> gcc -shared -fPIC -o libsilly.so silly.c && echo succeeded || echo failed
succeeded

But if you add -z defs, the link will fail and tell you about your missing symbol:

> gcc -shared -fPIC -o libsilly.so silly.c -Wl,-z,defs && echo succeeded || echo failed
/tmp/cccIwwbn.o: In function `doit':
silly.c:(.text+0x2c): undefined reference to `forgot_to_define'
collect2: ld returned 1 exit status
failed
Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
  • 4
    +1 this answer could assist guys who came from Windows background in which *DLLs* external symbols must be resolved during compile time. Way to go for the nice code snippet ! – Shmil The Cat Jan 02 '14 at 17:08
  • @ShmilTheCat: Hi, I have tried putting -Wl,-z,defs in my make file, still I'm getting undefined symbol error while running but not during compiling. What can I do?. In my scenario, I have two folders one inside another, (say, A/B, i,e B is inside A), and I can see few symbols or B, in "libA.so". I'm not sure if every symbol in B is available in "libA.so". How do I make sure that? – Vinay Jul 13 '16 at 06:31
  • @ShmilTheCat To make things even more like a Windows DLL, you can add `-Bsymbolic` to the linker command line. Even if `forgot_to_define` now exists in the library thanks to the `-z` check, the executable can still override it with its own definition and the library's own definitions will go to that override; `-Bsymbolic` forces things so that the library's own definitions to its functions go to its functions. – Kaz Oct 03 '16 at 23:09
  • Works only for actually used functions. If I do `// forgot_to_define(fp);` it does _not_ report an error – agentsmith May 05 '17 at 15:17
  • in gcc 6.3 it says : unrecognized command line option ‘-W1,-z,-defs’ – Sandeep Feb 16 '21 at 00:53
  • @mk.. The flag is `-Wl` (a lower case L), not `-W1` (the digit one). – R Samuel Klatchko Feb 17 '21 at 01:14
26

On Linux (which you appear to be using) ldd -r a.out should give you exactly the answer you are looking for.

UPDATE: a trivial way to create a.out against which to check:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out
Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • 4
    That does almost exactly the same thing as nm -C -U, which I suggested in the original post. The problem is there's no 'a.out' application to speak of, it's a shared library, so ldd -r mylibrary.so gives a whole heap of output, because it uses symbols from various other dynamic libraries.. I'm particularly interested in symbols missing that are defined in *my* header files, rather than external libraries. – David Claridge Oct 27 '09 at 22:17
  • 3
    this one should be accepted, the question is what is the easy way to check undefined symbols, not how to avoid undefined symbols – http8086 May 29 '18 at 08:52
10

What about a testsuite ? You create mock executables that link to the symbols you need. If the linking fails, it means that your library interface is incomplete.

Stefano Borini
  • 138,652
  • 96
  • 297
  • 431
4

I had the same problem once. I was developing a component model in C++, and, of course, components should load at runtime dynamically. Three solutions come to mind, that were the ones I applied:

  1. Take some time to define a build system that is able to compile statically. You'll lose some time engineering it, but it will save you much time catching these annoying runtime errors.
  2. Group your functions in well-known and well-understood sections, so that you can group of functions/stubs to be sure that each corresponding function has its stub. If you take the time on documenting it well, you can write perhaps a script that checks the definitions (via, for example, its doxygen comments) and check the corresponding .cpp file for it.
  3. Do several test executables that load the same set of libraries and specify the RTLD_NOW flag to dlopen (if you're under *NIX). They will signal the missing symbols.

Hope that helps.

Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
  • on the lines of RTLD_NOW, does an env var exist to force immediate (non lazy) binding ? – Stefano Borini Oct 24 '09 at 12:03
  • 1
    yup. here it is LD_BIND_NOW (libc5; glibc since 2.1.1) If set to non-empty string, causes the dynamic linker to resolve all symbols at program startup instead of deferring function call resolval to the point when they are first referenced. This is useful when using a debugger. – Stefano Borini Oct 24 '09 at 12:06
  • This can also be useful for the purpose of the OP: LD_WARN (ELF only)(glibc since 2.1.3) If set to non-empty string, warn about unresolved symbols. – Stefano Borini Oct 24 '09 at 12:08
  • Stefano: Yes, that's right. Those variables exist. However, if you are deferring the dynamic loading till later (say when the user loads a module) these variables are not useful unless you actually write the testing program that loads the intended (future) libraries. – Diego Sevilla Oct 24 '09 at 16:33