22

In strace outputs, the paths to the libraries that executables call are in calls to open(). Is this the system call used by executables that are dynamically linked? What about dlopen()? open() isn't a call I'd have guessed would play a role in the execution of programs.

32

dlopen isn't a system call, it's a library function in the libdl library. Only system calls show up in strace.

On Linux and on many other platforms (especially those that use the ELF format for executables), dlopen is implemented by opening the target library with open() and mapping it into memory with mmap(). mmap() is really the critical part here, it's what incorporates the library into the process' address space, so the CPU can execute its code. But you have to open() the file before you can mmap() it!

  • 2
    "mmap() is really the critical part": And then the dynamic linker has to do the relocations, initialization and so one (but this is not seen at the system call level). – ysdx Aug 31 '15 at 13:06
  • 1
    Since loading of libraries is done by a library function, I think it is relevant to add that the executable itself and ld-linux are mapped by the kernel as part of the execve system call. – kasperd Aug 31 '15 at 17:55
  • mmap as per this answer. Note also that after "open"-ing each library, some (832) bytes are read prior to the mmap call, I presume to check that the library is valid. – Johan Sep 2 '15 at 18:33
  • @kasperd So is the Linux kernel aware of the dynamic loader? Does it call it when application is run? Or does the application itself do that? If the latter, how does another executable have access to the application's memory? – Melab Feb 28 '16 at 5:41
  • @Melab Yes, the kernel is aware of the dynamic linker. The kernel will read the path to the dynamic linker from the header of the executable. And the kernel will map both into memory. I don't know whether the entry point which the kernel transfer control to at first is within linker or executable. If I was implementing it, I would probably have the kernel transfer control to an entry point in the linker with a return address on the stack pointing to the entry point of the executable. – kasperd Feb 28 '16 at 7:59
5

dlopen has nothing to do with shared libraries as you think of them. There are two methods of loading a shared object:

  1. You tell the compile-time linker (ld, though usually it's called through the compiler) that you want to use functions from a particular shared library. With this approach, you must know what the name of the library will be at when the compile-time linker is run, but you can call the library's functions as if they were statically linked into your program. When the application is run, the dynamic, run-time linker (ld.so) will be called just before the main function is called, and set up the application's process space so that the application will find the functions of the library. This involves open()ing the lubrary, and then mmap()ing it, followed by setting up some lookup tables.
  2. You tell the compile-time linker that you want to link with libdl, from which you then (using the first method) can call the dlopen() and dlsym() functions. With dlopen you get a handle to the library, which you can then use with dlsym to receive a function pointer to a particular function. This method is much more complicated for the programmer than the first method (since you have to do the setup manually, rather than have the linker do it automatically for you), and it also is more fragile (since you don't get the compile-time checks that you're calling functions with the correct argument types as you get in the first method), but the advantage is that you can decide which shared object to load at runtime (or even whether to load it at all), making this an interface meant for plugin type functionality. Finally, the dlopen interface is also less portable than the other way, since its mechanics depend on the exact implementation of the dynamic linker (hence libtool's libltdl, which tries to abstract away these differences).
  • interesting; so dynamically loaded library are better called dynamically linked libraries, since loading binary files into memory isn't the hard part, making the addresses used in it make sense is. When I ask to load a dynamic library, I am actually asking to link(or unlink) the library into(or out of) my address space. – Dmitry Jan 22 '17 at 6:50
4

Today, most operating systems use the method for shared libraries introduced in late 1987 by SunOS-4.0. This method is based on mapping memory via mmap().

Given the fact that in the early 1990s, Sun did even donate the old a.out based code (Solaris at that time was already ELF based) to the FreeBSD people and that this code later was handed over to many other systems (including Linux), you may understand why there is no big difference between platforms.

3

ltrace -S analysis of a minimal example shows that mmap is used in glibc 2.23

In glibc 2.23, Ubuntu 16.04, running latrace -S on a minimal program that uses dlopen with:

ltrace -S ./dlopen.out

shows:

dlopen("libcirosantilli_ab.so", 1 <unfinished ...>
SYS_open("./x86_64/libcirosantilli_ab.so", 524288, 06267650550)      = -2
SYS_open("./libcirosantilli_ab.so", 524288, 06267650550)             = 3
SYS_read(3, "\177ELF\002\001\001", 832)                              = 832
SYS_brk(0)                                                           = 0x244c000
SYS_brk(0x246d000)                                                   = 0x246d000
SYS_fstat(3, 0x7fff42f9ce30)                                         = 0
SYS_getcwd("/home/ciro/bak/git/cpp-cheat"..., 128)                   = 54
SYS_mmap(0, 0x201028, 5, 2050)                                       = 0x7f1c323fe000
SYS_mprotect(0x7f1c323ff000, 2093056, 0)                             = 0
SYS_mmap(0x7f1c325fe000, 8192, 3, 2066)                              = 0x7f1c325fe000
SYS_close(3)                                                         = 0
SYS_mprotect(0x7f1c325fe000, 4096, 1)                                = 0

so we see immediately that dlopen calls open + mmap.

The awesome ltrace tool traces both library calls and system calls, and is therefore perfect to examine what is going on in this case.

A closer analysis shows that open returns the file descriptor 3 (next free one after stdin, out and err).

read then uses that file descriptor, but TODO why mmap's arguments are limited to four, and we can't see which fd was used there since that is the 5th argument. strace confirms as expected that 3 is the one, and the order of the universe is restored.

Brave souls can also venture into glibc code, but I could not find the mmap after a quick grep and I'm lazy.

Tested with this minimal example with build boilerplate on GitHub.

2

strace reports on system calls (i.e. functions implemented directly by the kernel). Dynamic libraries aren't a kernel function; dlopen is part of the C library, not the kernel. The implementation of dlopen will call open (which is a system call) to open the library file so it can be read.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.