翻譯自:Understanding ld-linux.so.2
前言
ld-linux.so.2是linux的動態加載器(dynamic loader)。本文試圖就ld-linux.so.2如何與Linux交互,如何與正在調用的應用程序進行交互 給出一個概述。
什么是ld-linux.so
現在,大多數程序都是動態鏈接的。 當操作系統加載一個動態鏈接的應用程序時,它必須找到並加載它執行該應用程序所依賴的動態庫。 在linux系統上,這份工作由ld-linux.so.2處理。 你可以對一個應用程序 或 動態庫使用ldd命令查看他依賴哪些庫。

root@ubuntu:/lib# ldd `which ls` linux-vdso.so.1 => (0x00007ffdb075f000) libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fb9e3650000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb9e3286000) libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fb9e3016000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb9e2e11000) /lib64/ld-linux-x86-64.so.2 (0x00005607fd069000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb9e2bf4000)
當應用程序ls被加載到內存時,OS將控制權傳遞給ld-linux.so.2,而不是應用程序ls的正常入口點。 ld-linux.so.2搜索並加載未解析的庫,然后將控制權傳遞給應用程序的起始點。
ld-linux.so.2的man手冊頁給了動態鏈接器(dynamic linker)一個高層次的概述。 ld-linux.so.2是鏈接器(linker)(ld)的運行時組件,它定位應用程序使用的動態庫並將其加載到內存中。 通常,在鏈接期間隱式指定動態鏈接器。 ELF規范說GCC包含一個名為INTERP的特殊ELF程序頭,它的p_type為PT_INTERP。 此程序頭指定解釋器(interpreter)的路徑。 您可以使用readelf命令檢查給定程序的程序頭:

root@ubuntu:/lib# readelf -l `which ls` Elf file type is EXEC (Executable file) Entry point 0x4049a0 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R E 8 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000001da64 0x000000000001da64 R E 200000 LOAD 0x000000000001de00 0x000000000061de00 0x000000000061de00 0x0000000000000800 0x0000000000001568 RW 200000 DYNAMIC 0x000000000001de18 0x000000000061de18 0x000000000061de18 0x00000000000001e0 0x00000000000001e0 RW 8 NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R 4 GNU_EH_FRAME 0x000000000001a5f4 0x000000000041a5f4 0x000000000041a5f4 0x0000000000000804 0x0000000000000804 R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 GNU_RELRO 0x000000000001de00 0x000000000061de00 0x000000000061de00 0x0000000000000200 0x0000000000000200 R 1 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 .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got
ELF規范要求如果存在PT_INTERP部分,則操作系統必須創建解釋器文件段(interpreter's file segments)的進程映像,而不是應用程序的過程映像。 然后控制權轉到解釋器,解釋器負責加載動態庫。 ELF規范在如何給出控制方面提供了一定程度的靈活性。 對於x86 / Linux,傳遞給動態加載程序的參數是指向mmap'd節的指針。
編譯細節
Glibc負責創建ld-linux.so.2。在glibc 2.3.2版中,使用以下命令創建文件:

gcc -nostdlib -nostartfiles -shared \ -o /home/dww4s/packages/glibc-build/elf/ld.so \ -Wl,-z,combreloc -Wl,-z,defs \ /home/dww4s/packages/glibc-build/elf/librtld.os \ -Wl,--version-script=/home/dww4s/packages/glibc-build/ld.map \ -Wl,-soname=ld-linux.so.2 \ -T /home/dww4s/packages/glibc-build/elf/ld.so.lds
通過-shared標志,將構建一個名為ld.so的共享庫。ld.so的符號鏈接是ld-linux.so.2。唯一的輸入是librtld.os,它在make過程的早期通過命令創建了幾行:

gcc -nostdlib -nostartfiles -r \ -o /home/dww4s/packages/glibc-build/elf/librtld.os \ '-Wl,-(' \ /home/dww4s/packages/glibc-build/elf/dl-allobjs.os \ /home/dww4s/packages/glibc-build/elf/rtld-libc.a \ -lgcc \ '-Wl,-)'
因此,作為librtld.os包含的文件dl-allobjs.os,rtld-libc.a以及libgcc.so。 請注意使用-Wl前綴發送到鏈接器的參數 - (和 - )。 這些參數告訴鏈接器迭代存檔直到達到穩定狀態。 dl-allobjs.os和rtld-libc.a包含共享庫的目標文件以及入口點_start。 _start在rtld.c中定義,來自頭文件dl-machine.h中包含的宏(RTLD_START)。 宏擴展為內聯程序集,用於定義_start符號,並調用函數_dl_start。 由於ld-linux.so.2有一個_start符號,因此可以直接運行。