Fixing the ‘Undefined Reference’ Errors for C/C++ Projects

This tutorial shows how to various problems related to missing symbols in C/C++ projects. We will create a basic project calling the png_create_read_struct() function from the libpng library and will go through common missing setup steps, explaining the errors that will arise.

Before you begin, install VisualGDB 5.4 or later.

  1. Start Visual Studio and locate the VisualGDB Linux Project Wizard:
  2. Pick a name and location for your project:
  3. Press “Create” to launch the VisualGDB-specific part of the wizard. On the first page, pick Create a new project -> Application -> MSBuild:
  4. In this tutorial we will use a cross-toolchain that runs on Windows and builds code for a Linux target and will demonstrate points of failure caused by this configuration. However, most of the steps shown below will work for projects built directly on Linux as well:
  5. Press “Finish” to generate the project. Now we will try calling a function from the libpng library and will go through most possible missing steps that could cause a build-time or run-time error. Add the following code to the main() function and try building the project:
    png_create_read_struct(NULL, NULL, NULL, NULL);
  6. The build will now fail with the following error:
    MissingSymbolDemo.cpp: In function 'int main(int, char**)':
    MissingSymbolDemo.cpp:7:5: error: 'png_create_read_struct' was not declared in this scope
         png_create_read_struct(NULL, NULL, NULL, NULL);
         ^~~~~~~~~~~~~~~~~~~~~~

  7. You can find out the exact output from GCC by right-clicking on the error message and selecting “Go to Build Log”:
  8. The “‘png_create_read_struct’ was not declared in this scope” error means that the C/C++ compiler does not know the png_create_read_struct symbol. It cannot tell whether it is a function, global function pointer or a preprocessor macro. To resolve this error, the png_create_read_struct() function needs to be declared. A declaration for it would look as follows:
    png_structp png_create_read_struct(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn);
  9. As it is not practical to copy each function declaration into every source file, most C/C++ libraries provide header files that list all the relevant declarations. You can usually find out the header file name in the documentation and include it into your program as shown below:
    #include <png.h>

    This will be equivalent to copying and pasting the header file contents into your source file.

  10. Including a header file will not search the entire build machine for it. Instead, GCC will only search a few common places (e.g. /usr/include) and the header search directories specified in your project’s settings. In our example, the png.h file is located in toolchain’s /usr/include/libpng directory and won’t be found initially, producing the following error:
    MissingSymbolDemo.cpp:2:10: fatal error: png.h: No such file or directory
     #include <png.h>
              ^~~~~~~

  11. VisualGDB’s will normally search for the missing headers in the nearby directories and will suggest fixing the settings automatically. If you are configuring the project manually, simply locate the header file on your build machine and add its directory to the Include Directories field:Note that if you are using a cross-toolchain, the header must be present in its sysroot directory (e.g. /usr/include/libpng/png.h corresponds to E:\sysgcc\raspberry\arm-linux-gnueabihf\sysroot\usr\include\libpng\png.h). If you are using a cross-toolchain that runs on Windows, you would need to specify the path to the file on the Windows machine (i.e. $(ToolchainDir)\arm-linux-gnueabihf\sysroot\usr\include\libpng\png.h). For convenience, GCC automtaically replaces ‘=’ at the beginning of the path with the sysroot directory, so the correct include directory in this example would be =/usr/include/libpng/png.h (note the ‘=’ at the beginning).
  12. If you build the project now, it will fail with another error:
    C:\projects\temp\MissingSymbolDemo/MissingSymbolDemo.cpp:9: undefined reference to `png_create_read_struct'
    Build failed: arm-linux-gnueabihf-g++.exe exited with code 1
    collect2.exe: error: ld returned 1 exit status

  13. This happens because the png.h file only contains a declaration for theĀ png_create_read_struct() function. I.e. it defines its return type and argument types (letting GCC check them at compile time), but it does not provide an implementation for it. The function implementations are typically located in the static libraries (.a files) or shared libraries (.so files). You can find the correct library name by searching for all .a and .so files that contain the png_create_read_struct text. If you have found several libraries, use the objdump -T <.so file> or objdump -t <.a file> command to list all symbols exported by that library:
    0000e8fc g DF .text 00000024 PNG16_0 png_create_read_struct

    If the symbol you are looking for is listed with a non-zero address (first column), you have found the correct library. Otherwise, that library is importing that symbol, not exporting it.

  14. In this example, the library defining png_create_read_struct() is called libpng.so. You can link it into your project by adding its name (without the lib prefix) to the “Library Names” field, as long as it is located in a standard library directory. If not, you would also need to add the library’s directory to the Library Directories field:
  15. Now you can build the project successfully:If the build still fails, you might be missing an extern “C” clause in the headers. See this tutorial for a detailed step-by-step explanation.
  16. Finally, we will demonstrate a runtime error. Deploy the built program to your target and run “ld <Binary Name>”:
  17. The LDD command will show the shared libraries required by that file. In this tutorial we will move all the libpng* libraries to /usr/lib/arm-linux-gnueabihf/png subdirectory and will show what will happen. Once you have moved the library, start debugging the project with VisualGDB and observe the following error message in the Debug Output:
    /tmp/MissingSymbolDemo: error while loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory

    VisualGDB will automatically suggest locating the file on the target:

  18. Locate the libpng16.so library in the directory where you moved it and press “OK”:
  19. This will automatically add the directory of the library to the project’s LD_LIBRARY_PATH variable, so now you will be able to launch it successfully:
  20. If you are running the project manually, simply add the directory of the missing .so file to the LD_LIBRARY_PATH variable as shown below and you will be able to launc it as well:As we have passed NULL to png_create_read_struct() instead of the libpng version number, it will show a warning message. You can eliminate it by passing PNG_LIBPNG_VER_STRING as the first argument to png_create_read_struct().