skip to Main Content

The rules regarding linking are getting jumbled in my head.

I have this little C file that I want to turn into a shared library. For the purpose of illustration, I’m calling a libuv function, but it could just as well have been calling a function from any library.

// func.c
#include <uv.h>

int func(void) {
  uv_close(NULL, NULL);

  return 0;
}

I compile it to a shared library, like so; note that I do not link with libuv by providing a -luv flag to cc:

$ cc -fpic -g -c func.c
$ cc -shared -o libfunc.so func.o
$ ldd ./libfunc.so
        statically linked
$

I don’t understand what that "statically linked" output actually means — I thought I’d created a shared lib (not a static lib) by specifying the -shared flag?

Anyhow, trying to make sense of things, I next tried creating my shared lib explicitly linking libuv:

$ cc -fpic -g -c func.c
$ cc -shared -o libfunc.so func.o -luv
$ ldd ./libfunc.so
        linux-vdso.so.1 (0x00007ffe2b39a000)
        libuv.so.1 => /lib/x86_64-linux-gnu/libuv.so.1 (0x00007f012d6d0000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f012d4a7000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f012d710000)
$

Can someone please explain what is happening between the two different "methods" of creating the libfunc.so shared library? Why did adding -luv to the cc flags result in those dependencies being created/recognized by ldd? Also: why did the first version (without -luv) even work? I thought the fact that I was using a function from another library obliged me to provide a correct -l argument during linking, meaning I thought the attempt at creating the shared library without specifying -luv would have failed(?)


Update: Following up on @JohnBollinger’s comment, I repeated the -fpic flag for the link command, but it doesn’t appear to have affected the observed behavior:

$ cc -fpic -g -c func.c
$ cc -fpic -shared -o libfunc.so func.o
$ ldd ./libfunc.so
        statically linked
$
$ cc -fpic -g -c func.c
$ cc -fpic -shared -o libfunc.so func.o -luv
$ ldd ./libfunc.so
        linux-vdso.so.1 (0x00007fff043f0000)
        libuv.so.1 => /lib/x86_64-linux-gnu/libuv.so.1 (0x00007fea17963000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fea1773a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fea179a3000)
$

Update: my gcc/cc and ld versions:

$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cc --version
cc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.38
Copyright (C) 2022 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.
$

My Linux distro, fwiw, which is Ubuntu WSL, release jammy:

$ uname -a
Linux US193F98B0DB51 5.15.133.1-microsoft-standard-WSL2 #1 SMP Thu Oct 5 21:02:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$

I assumed the fact that I was running WSL was irrelevant, but the fact that @JohnBollinger cannot reproduce my result in RHEL/Centos 8 has me uncertain about everything. I do not have access to a physical Linux box to compare behavior there.


Following up on @DarkSoda’s inquiry, it appears that both versions of libfunc.so (with and without -luv during linking) are the same size:

$ cc -fpic -g -c func.c
$ cc -fpic -shared -o libfunc.so func.o
$ ls -l ./libfunc.so
-rwxr-xr-x 1 user user 20528 Jan 11 18:53 ./libfunc.so
$
$
$ rm -f ./func.o ./libfunc.so
$
$ cc -fpic -g -c func.c
$ cc -fpic -shared -o libfunc.so func.o -luv
$ ls -l ./libfunc.so
-rwxr-xr-x 1 user user 20528 Jan 11 18:53 ./libfunc.so
$

2

Answers


  1. I am curious whether the two versions of generated libfunc.so are different in size. It seems the first version (without -luv) somehow got uv statically linked into libfunc.so (which doesn’t make much sense since -static is not used)

    Login or Signup to reply.
  2. As I wrote in comments, I do not reproduce the behavior you describe, but it turns out that I can obtain a similar result by adding option -nostdlib to the link command:

    cc -shared -nostdlib -o libfunc.so func.o
    

    The result is an ELF shared object that ldd reports as "statically linked". Using readelf -a to examine the two files produced with and without -nostdlib shows that both are bona fide ELF shared objects (so not "static libraries", which have an altogether different form). There is a variety of differences between them, however, starting with the fact that the statically linked one’s dynamic section does not contain a NEEDED entry referring to libc.so.6 (nor any other NEEDED entries). This one is also missing other entries in its dynamic section,has considerably fewer sections overall, and has half as many entries in its symbol table.

    Some of that is likely related to not including Glibc startup files in the link, but some is related simply to not dynamically linking to any other shared libraries, and in particular, not to libc. This is what makes it "statically linked", even though it itself is a shared library. And given that the contents do not (directly) call any standard library functions or access any external objects provided by the standard library, that’s not such an unreasonable result, even if it’s not what my version of the toolchain produces by default.

    Why did adding -luv to the cc flags result in those dependencies being created/recognized by ldd?

    Adding -luv to the link command instructs the linker that the shared library should be linked against libuv. That will be via dynamic linking by default if a shared-library version is found. If linking is successful then the result is necessarily dynamically linked to libuv, whether or not it is (dynamically) linked to libc.

    why did the first version (without -luv) even work? I thought the fact that I was using a function from another library obliged me to provide a correct -l argument during linking, meaning I thought the attempt at creating the shared library without specifying -luv would have failed(?)

    This is a difference between linking a shared library and linking an executable. And even so, not all shared library systems support it. The resulting shared lib has an undefined symbol uv_close that is not resolved by any shared library dynamically linked to it. But at least with ELF and Linux’s dynamic linker, that symbol can still be resolved at runtime from another shared object included in the dynamic link (by virtue of being dynamically linked to a different shared object, such as the main executable, or being manually loaded).

    On some platforms you would indeed be obligated to include -luv to ensure that all symbols could be resolved. On Linux, that’s still almost always what you want, but it’s not required.


    In the end, it makes a certain amount of sense to avoid dynamically linking to libc when that doesn’t resolve any symbols or otherwise provide anything useful to the library. That’s a pretty unusual situation, but it applies to your example, and that is the threshold consideration that allows the result that surprised you.

    I guess you also did not appreciate the difference between a static library (a simple archive of object files) and a statically-linked shared library (a coherent shared object that does not depend on any other dynamically-linked libraries).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search