Is there an ELF file that could be both executed or dynamically loaded? An answer is the default dynamic loader in Linux: ld.so
1, it usually points to /lib/ld-*.so
or /lib64/ld-*.so
where *
represents the version of glibc in your system.
When you execute a normal program like ls
, the dynamic load is responsible for loading the dependent libraries the target program needs (you can see the needed libraries and how they are resolved via ldd
). When you execute the dynamic loader itself, the output would be:
Dynamic Loader
For example, when you loss the executable permission of chmod
, you can use /lib64/ld-2.17.so
to restore it back:
But the chmod
does not have executable permission, how does it could be executed?
When a executable is invoked, the system will decide which loader to load it from the .interp
section of the ELF executable. You can see it with objdump
like this:
So when you type chmod
, the system kernel equivalently runs /lib64/ld-2.17.so /bin/chmod
as you do manually. Then the shell will fork a process and use the execve
system call to load the program. Since the first argument is lib64/ld-2.17.so
which has the executable permission, the execve
permission check does not panic and /bin/chmod
is just the argument passed to the loader. It works the same way as bash a.sh
and ./a.sh
.
So when a dynamically-linked program is called, the system firstly load its dynamic linker into the virtual memory space of that process, then start to executing code of the linker to load necessary libraries into memory space and resolve undefined symbols to the correct definitions in those libraries. After the link process, the interpreter can start the origin program itself, at the address recorded at the executable’s ELF header. 2
Executable Shared Object
Since ld.so
could be executed, it should be an ELF executable. But it’s also a shared object:
This answer 3 gives how to compile such target: add -pie -fPIC -wl,-E
flags to the compiler so the target is compiled as shared library (-fPIC
) but also a regular executable (-pie
), and it’s symbol table is exportable (-Wl,-E
) such that it can be usefully linked against. Unless only those symbols that are referenced would be exported.
But why ld.so
needs to be both shared library and executable? If it is only an executable, then it must be loaded at the address it is linked at, which may be conflict with the origin target a.out
is linked to 4.
Moreover, there is another approach named dynamic loading which is commonly used like plugin. It allows a program to load and resolve symbols from dynamic shared libraries at runtime. Therefore it needs ld.so
already loaded to help load other dependencies for the libraries to be opened.
How does ld.so
Load Itself?
The ldd
command gives a conflict result with the one that file
gives (as shown above):
One says the loader is dynamic linked and the other says it’s statically linked. Why?
In fact, file
and ldd
report different things for the dynamic linker because they have different definitions of what constitutes a statically-linked binary. For ldd
, a binary is statically linked if it has no DT_NEEDED
symbols, i.e. no undefined symbols. For file
, an ELF binary is statically linked if it doesn’t have a PT_DYNAMIC
section 5. ldd
is indeed just a script, for example, ldd /usr/bin/ls
is equivalent to LD_TRACE_LOADED_OBJECTS=1 /lib64/ld-linux-x86-64.so.2 /usr/bin/ls
. The environment variable set is used to inspect the program’s dynamic dependencies.
So ld-2.17.so
itself it statically compiled and does not rely on any other libraries for it does not contain the .interp
section. The code executes bootstraps and carefully relocates ld.so
itself.