ldd on the binary not showing my shared library
By using the gcc option -c
you're telling it to create only object from foo.c
, so the only product you're getting from this gcc command is an object file. The fact that its suffix is .so
is only because you forced it using -o
option. If you run this command without -o
you'd see that the output is just foo.o
- an object file.
If you omit the -c
from the gcc command you'd get the shared object you wanted.
Running file
on the output file shows the difference (note that I'm not setting the output name using -o
and letting gcc use its default names):
With -c
:
> gcc -c foo.c -shared
> file foo.o
foo.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Without -c
:
> gcc foo.c -shared
> file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=a63581bfc45f845c501ffb6635, not stripped
^^^
|||
What's the difference between statically linked and not a dynamic executable from Linux ldd?
There are two separate things here:
- Requesting an ELF interpreter (ld.so) or not.
Like#!/bin/sh
but for binaries, runs before your_start
.
This is the difference between a static vs. dynamic executable. - The list of dynamically linked libraries for ld.so to load happens to be empty.
This is apparently whatldd
calls "statically linked", i.e. that any libraries you might have linked at build time were static libraries.
Other tools like file
and readelf
give more information and use terminology that matches what you'd expect.
Your GCC is configured so -pie
is the default, and gcc doesn't make a static-pie for the special case of no dynamic libraries.
gcc -nostdlib
just makes a PIE that happens not to link to any libraries but is otherwise identical to a normal PIE, specifying an ELF interpreter.ldd
confusingly calls this "statically linked".file
:ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2 ...
gcc -nostdlib -static
overrides the-pie
default and makes a true static executable.file
:ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked ...
gcc -nostdlib -no-pie
also chooses to make a static executable as an optimization for the case where there are no dynamic libraries at all. Since a non-PIE executable couldn't have been ASLRed anyway, this makes sense. Byte-for-byte identical to the-static
case.gcc -nostdlib -static-pie
makes an ASLRable executable that doesn't need an ELF interpreter. GCC doesn't do this by default forgcc -pie -nostdlib
, unlike the no-pie case where it chooses to sidestepld.so
when no dynamically-linked libraries are involved.file
:ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked ...
-static-pie
is obscure, rarely used, and olderfile
doesn't identify it as statically linked.
-nostdlib
doesn't imply -no-pie
or -static
, and -static-pie
has to be explicitly specified to get that.
gcc -static-pie
invokes ld -static -pie
, so ld
has to know what that means. Unlike with the non-PIE case where you don't have to ask for a dynamic executable explicitly, you just get one if you pass ld
any .so
libraries. I think that's why you happen to get a static executable from gcc -nostdlib -no-pie
- GCC doesn't have to do anything special, it's just ld
doing that optimization.
But ld
doesn't enable -static
implicitly when -pie
is specified, even when there are no shared libraries to link.
Details
Examples generated with gcc --version
gcc (Arch Linux 9.3.0-1) 9.3.0ld --version
GNU ld (GNU Binutils) 2.34 (also readelf is binutils)ldd --version
ldd (GNU libc) 2.31file --version
file-5.38 - note that static-pie detection has changed in recent patches, with Ubuntu cherry-picking an unreleased patch. (Thanks @Joseph for the detective work) - this in 2019 detected dynamic = having a PT_INTERP to handle static-pie, but it was reverted to detect based on PT_DYNAMIC so shared libraries count as dynamic
. debian bug #948269. static-pie
is an obscure rarely-used feature.
GCC ends up running ld -pie exit.o
with a dynamic linker path specified, and no libraries. (And a boatload of other options to support possible LTO link-time optimization, but the keys here are -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie
. collect2
is just a wrapper around ld
.)
$ gcc -nostdlib exit.s -v # output manually line wrapped with \ for readability
...
COLLECT_GCC_OPTIONS='-nostdlib' '-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/collect2 \
-plugin /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/liblto_plugin.so \
-plugin-opt=/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/lto-wrapper \
-plugin-opt=-fresolution=/tmp/ccoNx1IR.res \
--build-id --eh-frame-hdr --hash-style=gnu \
-m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie \
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0 \
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/../../../../lib -L/lib/../lib \
-L/usr/lib/../lib \
-L/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/../../.. \
/tmp/cctm2fSS.o
You get a dynamic PIE with no dependencies on other libraries. Running it still invokes the "ELF interpreter" /lib64/ld-linux-x86-64.so.2
on it which runs before jumping to your _start
. (Although the kernel has already mapped the executable's ELF segments to ASLRed virtual addresses, along with ld.so's text / data / bss).
file
and readelf are more descriptive.
PIE non-static executable from gcc -nostdlib
$ gcc -nostdlib exit.s -o exit-default
$ ls -l exit-default
-rwxr-xr-x 1 peter peter 13536 May 2 02:15 exit-default
$ ldd exit-default
statically linked
$ file exit-default
exit-default: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=05a4d1bdbc94d6f91cca1c9c26314e1aa227a3a5, not stripped
$ readelf -a exit-default
...
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1000
...
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000002b1 0x00000000000002b1 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000009 0x0000000000000009 R E 0x1000
... (the Read+Exec segment to be mapped at virt addr 0x1000 is where your text section was linked.)
If you strace it you can also see the differences:
$ gcc -nostdlib exit.s -o exit-default
$ strace ./exit-default
execve("./exit-default", ["./exit-default"], 0x7ffe1f526040 /* 51 vars */) = 0
brk(NULL) = 0x5617eb1e4000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffcea703380) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9ff5b3e000
arch_prctl(ARCH_SET_FS, 0x7f9ff5b3ea80) = 0
mprotect(0x5617eabac000, 4096, PROT_READ) = 0
exit(0) = ?
+++ exited with 0 +++
vs. -static
and -static-pie
the first instruction executed in user-space is your _start
(which you can also check with GDB using starti
).
$ strace ./exit-static-pie
execve("./exit-static-pie", ["./exit-static-pie"], 0x7ffcdac96dd0 /* 51 vars */) = 0
exit(0) = ?
+++ exited with 0 +++
gcc -nostdlib -static-pie
$ gcc -nostdlib -static-pie exit.s -o exit-static-pie
$ ls -l exit-static-pie
-rwxr-xr-x 1 peter peter 13440 May 2 02:18 exit-static-pie
peter@volta:/tmp$ ldd exit-static-pie
statically linked
peter@volta:/tmp$ file exit-static-pie
exit-static-pie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=daeb4a8f11bec1bb1aaa13cd48d24b5795af638e, not stripped
$ readelf -a exit-static-pie
...
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1000
...
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000229 0x0000000000000229 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000009 0x0000000000000009 R E 0x1000
... (no Interp header, but still a read+exec text segment)
Notice that the addresses are still relative to the image base, leaving ASLR up to the kernel.
Surprisingly, ldd
doesn't say that it's not a dynamic executable. That might be a bug, or a side effect of some implementation detail.
gcc -nostdlib -static
traditional non-PIE old-school static executable
$ gcc -nostdlib -static exit.s -o exit-static
$ ls -l exit-static
-rwxr-xr-x 1 peter peter 4744 May 2 02:26 exit-static
peter@volta:/tmp$ ldd exit-static
not a dynamic executable
peter@volta:/tmp$ file exit-static
exit-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=1b03e3d05709b7288fe3006b4696fd0c11fb1cb2, not stripped
peter@volta:/tmp$ readelf -a exit-static
ELF Header:
...
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401000
... (Note the absolute entry-point address nailed down at link time)
(And that the ELF type is EXEC, not DYN)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000010c 0x000000000000010c R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000009 0x0000000000000009 R E 0x1000
NOTE 0x00000000000000e8 0x00000000004000e8 0x00000000004000e8
0x0000000000000024 0x0000000000000024 R 0x4
Section to Segment mapping:
Segment Sections...
00 .note.gnu.build-id
01 .text
02 .note.gnu.build-id
...
Those are all the program headers; unlike pie / static-pie I'm not leaving any out, just other whole parts of the readelf -a
output.
Also note the absolute virtual addresses in the program headers that don't give the kernel a choice where in virtual address space to map the file. This is the difference between EXEC and DYN types of ELF objects. PIE executables are shared objects with an entry point, allowing us to get ASLR for the main executable. Actual EXEC executables have a link-time-chosen memory layout.
ldd
apparently only reports "not a dynamic executable" when both:
- no ELF interpreter (dynamic linker) path
- ELF type = EXEC
Does dynamically-linked binaries use crt in linux?
It doesn't matter if the binary links with no other libraries or does link. The crt is always "statically" included inside the executable.
Let's do real life, consider the following setup:
==> main.c <==
#include <stdio.h>
int main(int argc, char **argv) {
puts(argv[0]);
}
==> compile.sh <==
#!/bin/bash
set -x
gcc -static main.c -o static.out
gcc main.c -o dynamic.out
ldd static.out
ldd dynamic.out
./static.out
./dynamic.out
objdump -D static.out | grep -C10 '<_start>:'
objdump -D dynamic.out | grep -C10 '<_start>:'
The ./compile.sh
script execution outputs:
+ gcc -static main.c -o static.out
+ gcc main.c -o dynamic.out
+ ldd static.out
not a dynamic executable
+ ldd dynamic.out
linux-vdso.so.1 (0x00007ffc226e7000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f079878e000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f07989cf000)
+ ./static.out
./static.out
+ ./dynamic.out
./dynamic.out
+ objdump -D static.out
+ grep -C10 '<_start>:'
0000000000401508 <read_encoded_value_with_base.cold>:
401508: 50 push %rax
401509: 67 e8 cb fb ff ff addr32 call 4010da <abort>
000000000040150f <__gcc_personality_v0.cold>:
40150f: 67 e8 c5 fb ff ff addr32 call 4010da <abort>
401515: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
40151c: 00 00 00
40151f: 90 nop
0000000000401520 <_start>:
401520: f3 0f 1e fa endbr64
401524: 31 ed xor %ebp,%ebp
401526: 49 89 d1 mov %rdx,%r9
401529: 5e pop %rsi
40152a: 48 89 e2 mov %rsp,%rdx
40152d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
401531: 50 push %rax
401532: 54 push %rsp
401533: 45 31 c0 xor %r8d,%r8d
401536: 31 c9 xor %ecx,%ecx
+ objdump -D dynamic.out
+ grep -C10 '<_start>:'
1026: ff 25 e4 2f 00 00 jmp *0x2fe4(%rip) # 4010 <_GLOBAL_OFFSET_TABLE_+0x10>
102c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000001030 <puts@plt>:
1030: ff 25 e2 2f 00 00 jmp *0x2fe2(%rip) # 4018 <puts@GLIBC_2.2.5>
1036: 68 00 00 00 00 push $0x0
103b: e9 e0 ff ff ff jmp 1020 <_init+0x20>
Disassembly of section .text:
0000000000001040 <_start>:
1040: f3 0f 1e fa endbr64
1044: 31 ed xor %ebp,%ebp
1046: 49 89 d1 mov %rdx,%r9
1049: 5e pop %rsi
104a: 48 89 e2 mov %rsp,%rdx
104d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1051: 50 push %rax
1052: 54 push %rsp
1053: 45 31 c0 xor %r8d,%r8d
1056: 31 c9 xor %ecx,%ecx
We can learn that:
- static.out is static executable
- dynamic.out is dynamic executable
- both executables include code for
_start
symbol _start
symbol comes from crt
Ergo, both executables include crt inside them.
MacOSX: which dynamic libraries linked by binary?
Try setting these environment variables before running matlab:
export DYLD_PRINT_LIBRARIES=1
export DYLD_PRINT_LIBRARIES_POST_LAUNCH=1
export DYLD_PRINT_RPATHS=1
Run man dyld
for more possibilities.
You can also set the variables for just the matlab command like this:
DYLD_PRINT_LIBRARIES=1 DYLD_PRINT_LIBRARIES_POST_LAUNCH=1 DYLD_PRINT_RPATHS=1 matlab
API for ldd (or objdump)?
I need to programmatically inspect the library dependencies of a given executable.
I am going to assume that you are using an ELF system (probably Linux).
Dynamic library dependencies of an executable or a shared library are encoded as a table on Elf{32_,64}_Dyn
entries in the PT_DYNAMIC
segment of the library or executable. The ldd
(indirectly, but that's an implementation detail) interprets these entries and then uses various details of system configuration and/or LD_LIBRARY_PATH
environment variable to locate the needed libraries.
You can print the contents of PT_DYNAMIC
with readelf -d a.out
. For example:
$ readelf -d /bin/date
Dynamic section at offset 0x19df8 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x3000
0x000000000000000d (FINI) 0x12780
0x0000000000000019 (INIT_ARRAY) 0x1a250
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x1a258
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x308
0x0000000000000005 (STRTAB) 0xb38
0x0000000000000006 (SYMTAB) 0x358
0x000000000000000a (STRSZ) 946 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x1b000
0x0000000000000002 (PLTRELSZ) 1656 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x2118
0x0000000000000007 (RELA) 0x1008
0x0000000000000008 (RELASZ) 4368 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffb (FLAGS_1) Flags: PIE
0x000000006ffffffe (VERNEED) 0xf98
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0xeea
0x000000006ffffff9 (RELACOUNT) 170
0x0000000000000000 (NULL) 0x0
This tells you that the only library needed for this binary is libc.so.6
(the NEEDED
entry).
If your real question is "what other libraries does this ELF binary require", then that is pretty easy to obtain: just look for DT_NEEDED
entries in the dynamic symbol table. Doing this programmatically is rather easy:
- Locate the table of program headers (the ELF file header
.e_phoff
tells you where it starts). - Iterate over them to find the one with
PT_DYNAMIC
.p_type
. - That segment contains a set of fixed sized
Elf{32,64}_Dyn
records. - Iterate over them, looking for ones with
.d_tag == DT_NEEDED
.
Voila.
P.S. There is a bit of a complication: the strings, such as libc.so.6
are not part of the PT_DYNAMIC
. But there is a pointer to where they are in the .d_tag == DT_STRTAB
entry. See this answer for example code.
Related Topics
How to Diff Top Lines of Two Files Without Intermediate File
Avrdude: Ser_Open(): Can't Open Device "/Dev/Ttyacm0": Device or Resource Busy
Shell Script Working Fine Without Shebang Line? Why
Grep String Inside Double Quotes
Install.Packages("Devtools") on R 3.0.2 Fails in Ubuntu 14.04
Changing the PHPmyadmin Default Url
How to Disable Editing My History in Bash
How Does Ancillary Data in Sendmsg() Work
I Cannot Get a Result from a Single Line Put into the Erlang Shell
Pass Private Key Password to Openvpn Command Directly in Ubuntu 10.10
Get the Characters After the Last Index of a Substring from a String
How to Calculate the Total Size of Certain Files Only, Recursive, in Linux
How to Change the Soname of a Binary Directly
How to Add a String to the Beginning of Each File in a Folder in Bash