I know that statically-linked binaries use crt (C Runtime) when linking, as it passes cmd arguments to main
, deals with TLS storage, etc. However, inside a dynamically-linked binary, there is no such codes, so crt is not linked to it when linking.
But after searching keyword "crt" in source codes of ld
, glibc
and even linux kernel, I can't find any clue that dynamically-linked binaries use crt.
So how does those dynamically-linked binaries handle cmd arguments passing, TLS initialization, etc. without crt?
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:
_start
symbol_start
symbol comes from crtErgo, both executables include crt inside them.