-ENOENT, but believe me, it's there

Posted by Marcus Folkesson on Thursday, July 13, 2017

-ENOENT, but believe me, it's there

Almost every ELF-file in a Linux environment is dynamically linked, and the operating system has to locate all dynamic libraries in order to execute the file. To its help, it has the runtime dynamic linker, whose only job is to interpret the ELF file format, load the shared objects with unresolved references, and, at last, execute and pass over the control to the ELF file.

The expected path to the dynamic linker is hardcoded into the ELF itself, so if the linker is missing, it leads to an obvious but maybe confusing error.

If I want to run an executable that has its path to a dynamic linker that simple does not exists, we got this

1root@cgtqmx6:/# ./01_SimpleTriangle -sh:
2./01_SimpleTriangle: No such file or directory

Hue? But the file does exist! OK, lets find out which system calls is made.

1root@cgtqmx6:/# strace ./01_SimpleTriangle 
2
3execve("./01_SimpleTriangle", ["./01_SimpleTriangle"], [/* 15 vars */]) = -1 ENOENT (No such file or directory)
4write(2, "strace: exec: No such file or directory) = 40
5exit_group(1) = ?+++ exited with 1 +++

Not much valuable information. Strace calls execve that says the file does not exist. We are talking about the file... but which file is it?

As told before, the runtime dynamic linker is always executed first to load libraries for unresolved symbols. So one qualified guess is that it's the dynamic linker that is missing.

Readelf is a handy tool to read out information from an ELF file such as sections, which architecture it's compiled for and so on. It also shows us the expected path to the dynamic linker.

 1root@cgtqmx6:/# readelf -l 01_SimpleTriangle
 2
 3Elf file type is EXEC (Executable file)
 4Entry point 0x9288There are 8 program headers, starting at offset 52
 5Program Headers:  Type           Offset   VirtAddr   PhysAddr   
 6FileSiz MemSiz  Flg Align  
 7EXIDX          0x0022ec 0x0000a2ec 0x0000a2ec 0x00088 0x00088 R   0x4  
 8PHDR           0x000034 0x00008034 0x00008034 0x00100 0x00100 R E 0x4  
 9INTERP         0x000134 0x00008134 0x00008134 0x00013 0x00013 R   0x1    
10>>>>>>  [Requesting program interpreter: /lib/ld-linux.so.3] <<<<<<<  
11LOAD           0x000000 0x00008000 0x00008000 0x02378 0x02378 R E 0x8000         
120x002378 0x00012378 0x00012378 0x002a0 0x00358 RW  0x8000  
13DYNAMIC        0x002384 0x00012384 0x00012384 0x00118 0x00118 RW  0x4  
14NOTE           0x000148 0x00008148 0x00008148 0x00020 0x00020 R   0x4  
15GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
16
17Section to Segment mapping:  
18Segment Sections...   
1900     .ARM.exidx    
2001        
2102     .interp    
2203     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.extab .ARM.exidx .eh_frame    
2304     .init_array .fini_array .jcr .dynamic .got .data .bss    
2405     .dynamic    
2506     .note.ABI-tag    
2607     

Here we go! The expected path is /lib/ld-linux.so.3. Our system is currently using /lib/ld-linux-armhf.so.3, so lets create a symbolic link for now.

1ln -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3

And finally, the program is now executing!

1root@cgtqmx6:/# ./01_SimpleTriangle
2root@cgtqmx6:/#

Anyway, who decide which runtime dynamic linker to use? It's decided in the linker-stage, usually by ld or gcc and may vary between different toolchains,