Check if it has an INTERP type program header
At the lower level, the executable is static if it does not have a program header with the type:
Elf32_Phd.p_type == PT_INTERP
This is mentioned in the System V ABI specification .
Remember that program headers define ELF segments , including segments of type PT_LOAD which will be loaded into memory and run.
If this header is present, its contents exactly matches the path of the dynamic loader.
readelf checks
We can watch it with readelf . Dynamically compile C hello world first:
gcc -o main.out main.c
and then:
readelf --program-headers --wide main.out
outputs:
Elf file type is DYN (Shared object file) Entry point 0x1050 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x000268 0x000268 R 0x8 INTERP 0x0002a8 0x00000000000002a8 0x00000000000002a8 0x00001c 0x00001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000560 0x000560 R 0x1000 LOAD 0x001000 0x0000000000001000 0x0000000000001000 0x0001bd 0x0001bd RE 0x1000 LOAD 0x002000 0x0000000000002000 0x0000000000002000 0x000150 0x000150 R 0x1000 LOAD 0x002db8 0x0000000000003db8 0x0000000000003db8 0x000258 0x000260 RW 0x1000 DYNAMIC 0x002dc8 0x0000000000003dc8 0x0000000000003dc8 0x0001f0 0x0001f0 RW 0x8 NOTE 0x0002c4 0x00000000000002c4 0x00000000000002c4 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x00200c 0x000000000000200c 0x000000000000200c 0x00003c 0x00003c R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x002db8 0x0000000000003db8 0x0000000000003db8 0x000248 0x000248 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got
therefore, note that the INTERP header is INTERP , and it is very important that readelf even gives a quick preview of its short 28 (0x1c) byte content: /lib64/ld-linux-x86-64.so.2 , which is the path to the dynamic bootloader (27 bytes in length + 1 for \0 ).
Notice how this is next to other segments, including, for example, those that are actually loaded into memory, such as: .text .
Then we can extract these bytes more directly without previewing with:
readelf -x .interp main.out
which gives:
Hex dump of section '.interp': 0x000002a8 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux- 0x000002b8 7838362d 36342e73 6f2e3200 x86-64.so.2.
as explained on: How can I check the contents of the data section of an ELF file on Linux?
source code file
file 5.36 source code comments in src / readelf.c claim that it also checks PT_INTERP :
private int dophn_exec(struct magic_set *ms, int clazz, int swap, int fd, off_t off, int num, size_t size, off_t fsize, int sh_num, int *flags, uint16_t *notecount) { Elf32_Phdr ph32; Elf64_Phdr ph64; const char *linking_style = "statically";
found with git grep statically from main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped .
However, this comment looks outdated compared to code that instead checks for PT_DYNAMIC :
case PT_DYNAMIC: linking_style = "dynamically"; doread = 1; break;
I'm not sure why this is done, and I'm too lazy to dig into git log . In particular, it confused me a bit when I tried to make a statically linked PIE executable with --no-dynamic-linker as shown in: How to create a statically linked position-independent ELF executable on Linux? which does not have PT_INTERP but has PT_DYNAMIC , and which I do not expect to use a dynamic loader.
Linux kernel source code
The Linux 5.0 kernel reads the ELF file during the exec system call at fs / binfmt_elf.c, as described in the section : How does the kernel get an executable binary file that runs on Linux?
The kernel load_elf_binary over the program headers in load_elf_binary
for (i = 0; i < loc->elf_ex.e_phnum; i++) { if (elf_ppnt->p_type == PT_INTERP) {
I did not read the code completely, but I would expect it to use a dynamic loader only if INTERP , otherwise which path should it use?
PT_DYNAMIC not used in this file.
Bonus: check if -pie
I explained this in detail at: Why does GCC create a shared object instead of an executable binary file according to the file?