1

I did a ldd on /bin/cat and I see that dynamic loader library /lib64/ld-linux-x86-64.so.2 is a part of it.

ldd /bin/cat
    linux-vdso.so.1 (0x00007ffe743f4000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fde4f0a1000)
    /lib64/ld-linux-x86-64.so.2 (0x000056057639c000)

However my problem started when I performed an strace over this binary (strace -o cat.trace /usr/bin/ls /etc/motd) and could not find it being loaded. I was assuming that it should be the first one to be loaded by kernel.

execve("/usr/bin/ls", ["/usr/bin/ls", "/etc/motd"], 0x7ffe9e6a8d38 /* 56 vars */) = 0
brk(NULL)                               = 0x5650331a2000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f39527b9000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=95294, ...}) = 0
mmap(NULL, 95294, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f39527a1000
close(3)                                = 0
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000c\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=153176, ...}) = 0
mmap(NULL, 2253688, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3952370000
mprotect(0x7f3952393000, 2097152, PROT_NONE) = 0
mmap(0x7f3952593000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x23000) = 0x7f3952593000
mmap(0x7f3952595000, 4984, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3952595000
close(3)                                = 0
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\25\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=18608, ...}) = 0
mmap(NULL, 2113840, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f395216b000
mprotect(0x7f395216f000, 2093056, PROT_NONE) = 0
mmap(0x7f395236e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f395236e000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\10\2\0\0\0\0\0"..., 832) = 832

As can be seen from ldd output, a supplementary question here is - how could ldd predict the load address of a dynamically linked/loaded library before loading of library? In theory as I studied, the memory is a shared resource and then multiple libraries should be able to be loaded at same memory locations (not simultaneously though).

1 Answer 1

3

A very similar question to this can be found on stackoverflow: How does execve call dynamic linker/loader (ld-linux.so.2)

The reason there is no strace output associated with the dynamic linker is that control is passed to the dynamic linker during process creation by the kernel. In other words, the dynamic linker is not invoked from userspace via a system call. No system call means no strace output.

From the generic SYSV ABI, Chapter 5: "Program Loading and Dynamic Linking"::

An executable file that participates in dynamic linking shall have one PT_INTERP program header element. During the function exec, the system retrieves a path name from the PT_INTERP segment and creates the initial process image from the interpreter file’s segments. That is, instead of using the original executable file’s segment images, the system composes a memory image for the interpreter. It then is the interpreter’s responsibility to receive control from the system and provide an environment for the application program.

As ‘‘Process Initialization’’ in Chapter 3 of the processor supplement mentions, the interpreter receives control in one of two ways. First, it may receive a file descriptor to read the executable file, positioned at the beginning. It can use this file descriptor to read and/or map the executable file’s segments into memory. Second, depending on the executable file format, the system may load the executable file into memory instead of giving the interpreter an open file descriptor. With the possible exception of the file descriptor, the interpreter’s initial process state matches what the executable file would have received. The interpreter itself may not require a second interpreter. An interpreter may be either a shared object or an executable file.

The term "system" used above refers to the kernel.

$ readelf -l /bin/cat

Elf file type is EXEC (Executable file)
Entry point 0x402602
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

It is significant that INTERP is a program header, as program headers are used by the kernel when mapping the binary into memory to create a running process. It can be inferred that the presence of the pathname of the dynamic linker in a program header has implications for process creation by the kernel.

When building an executable file that uses dynamic linking, the link editor adds a program header element of type PT _ INTERP to an executable file, telling the system to invoke the dynamic linker as the program interpreter.

exec and the dynamic linker cooperate to create the process image for the program, which entails the following actions:

  • Adding the executable file’s memory segments to the process image;
  • Adding shared object memory segments to the process image;
  • Performing relocations for the executable file and its shared objects;
  • Closing the file descriptor that was used to read the executable file, if one was given to the dynamic linker;
  • Transferring control to the program, making it look as if the program had received control directly from the function exec

Response to comment:

I tried the same stuff on NetBSD machine and there I see the mapping of ld.so as the first step. So this should essentially means that this is implementation dependent whether we will see the dynamic linker in strace or not. Right?

Wrong, at least according to the NetBSD process startup documentation:

Note that when starting a dynamic ELF executable, the ELF loader (also known as the interpreter: /usr/libexec/ld.elf_so) is loaded with the executable by the kernel. The ELF loader is started by the kernel and is responsible for starting the executable itself afterwards.

For a better answer, you will have to ask a separate question about this specifically, and with the strace output included.

how does ldd mentions the base address of a library even without loading it?

ldd calls the dynamic linker, which loads a dynamically-linked program's dynamic dependencies. From the ldd man page:

In the usual case, ldd invokes the standard dynamic linker (see ld.so(8)) with the LD_TRACE_LOADED_OBJECTS environment variable set to 1. This causes the dynamic linker to inspect the program's dynamic dependencies, and find (according to the rules described in ld.so(8)) and load the objects that satisfy those dependencies. For each dependency, ldd displays the location of the matching object and the (hexadecimal) address at which it is loaded. (The linux-vdso and ld-linux shared dependencies are special; see vdso(7) and ld.so(8).)

In some cases ldd will even execute the binary:

Be aware that in some circumstances (e.g., where the program specifies an ELF interpreter other than ld-linux.so), some versions of ldd may attempt to obtain the dependency information by attempting to directly execute the program (which may lead to the execution of whatever code is defined in the program's ELF interpreter, and perhaps to execution of the program itself). Thus, you should never employ ldd on an untrusted executable, since this may result in the execution of arbitrary code.

ldd does indeed result in a program's dynamic dependencies being loaded into virtual memory.

3
  • This makes sense. Thanks. A followup question is - I tried the same stuff on NetBSD machine and there I see the mapping of ld.so as the first step. So this should essentially means that this is implementation dependent whether we will see the dynamic linker in strace or not. Right? A second part of my question is - how does ldd mentions the base address of a library even without loading it? Please read last part of my question. Commented Jul 5, 2017 at 6:22
  • 1
    @RIPUNJAYTRIPATHI A response to your comment has been added
    – julian
    Commented Jul 5, 2017 at 14:05
  • thanks for your effort. I was under assumption that ldd does not executes or loads the binaries. However regarding NetBSD, I did an strace on version 7.1 and I see that ld.so was mmap'ed as first step. Let me know if I could send you my strace proof as personal message. Commented Jul 5, 2017 at 17:56

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