Search code examples
cdockerlinkeralpine-linux

What could be causing linking errors when compiling in an Alpine Docker?


I am trying to compile a program within a docker container built from the Alpine 3.7 base image. The program uses argp.h, and includes it as #include <argp.h>. I have installed argp-standalone and verified that it is making it onto the image. The file argp.h is located in usr/include, however when I compile my program using the following commands:

gcc -W -Wall -Wextra -I/usr/include   -c -o progname.o progname.c
gcc -largp -o progname progname.o

I get the following error:

progname.o: In function `parse_opt':
progname.c:(.text+0x4c9): undefined reference to `argp_failure'
progname.c:(.text+0x50f): undefined reference to `argp_failure'
progname.c:(.text+0x555): undefined reference to `argp_failure'
progname.c:(.text+0x59b): undefined reference to `argp_failure'
progname.c:(.text+0x5ce): undefined reference to `argp_error'
progname.c:(.text+0x5f4): undefined reference to `argp_error'
progname.o: In function `main':
progname.c:(.text+0x1397): undefined reference to `argp_parse'
collect2: error: ld returned 1 exit status
make: *** [Makefile:9: progname] Error 1

I have:

  • Ensured that the version of argp.h which is on the image does in fact include the argp_failure, argp_parse, and argp_error functions.
  • Tried moving argp.h into different locations on the machine (e.g. into the same directory where compilation is taking place, into /usr/lib)
  • Tried compiling with -l and -L.

The relevant packages also installed in the image are build-base, make, and gcc. When compiling on an Ubuntu image these same commands work fine, even without the -largp and -I/usr/include flags. What could be happening differently within an Alpine image which would cause this not to work?

Edit

As per @Pablo's comment, I'm now compiling it as follows:

gcc -W -Wall -Wextra -I/usr/include -L/usr/lib -c -o progname.o progname.c
gcc -largp -o progname progname.o

After having verified that the static library, libargp.a, is located in /usr/lib. However, the same problem still persists.

Edit 2

Compiling as follows (and once again as per @Pablo's suggestion) has resolved the error I was having:

gcc -W -Wall -Wextra -I/usr/include -L/usr/lib -c -o progname.o progname.c
gcc -o progname progname.o /usr/lib/libargp.a

However, I am still curious why, using the exact same library and instructions, this would fail to compile in an Alpine image while compiling without issue in an Ubuntu image.


Solution

  • I am still curious why, using the exact same library and instructions, this would fail to compile in an Alpine image while compiling without issue in an Ubuntu image.

    The reason for the linking error on Alpine may be kind of surprising, and is actually not specific to Alpine.

    While this fails to link:

    gcc -largp -o progname progname.o
    

    This works:

    gcc -o progname progname.o -largp
    

    The reason is the order of parameters passed to the linker, and it related to the linking algorithm. Typically, in the linking command line objects are specified first (and possibly user's static libraries in any), then libraries using -l. The standard linker algorithm is explained perfectly in Eli Bendersky's article, Library order in static linking:

    Object files and libraries are provided in a certain order on the command-line, from left to right. This is the linking order. Here's what the linker does:

    • The linker maintains a symbol table. This symbol table does a bunch of things, but among them is keeping two lists:
      • A list of symbols exported by all the objects and libraries encountered so far.
      • A list of undefined symbols that the encountered objects and libraries requested to import and were not found yet.
    • When the linker encounters a new object file, it looks at:
      • The symbols it exports: these are added to the list of exported symbols mentioned above. If any symbol is in the undefined list, it's removed from there because it has now been found. If any symbol has already been in the exported list, we get a "multiple definition" error: two different objects export the same symbol and the linker is confused.
      • The symbols it imports: these are added to the list of undefined symbols, unless they can be found in the list of exported symbols.
    • When the linker encounters a new library, things are a bit more interesting. The linker goes over all the objects in the library. For each one, it first looks at the symbols it exports.
      • If any of the symbols it exports are on the undefined list, the object is added to the link and the next step is executed. Otherwise, the next step is skipped.
      • If the object has been added to the link, it's treated as described above - its undefined and exported symbols get added to the symbol table.
      • Finally, if any of the objects in the library has been included in the link, the library is rescanned again - it's possible that symbols imported by the included object can be found in other objects within the same library.

    When -largp appears first, the linker does not include any of its objects in the linking procedure, since it doesn't have any undefined symbols yet. If the static library is provided by path, and not with -l, then all of its objects are added to the linking procedure.