--- tags: DYKC, CLANG, C LANGUAGE, linking --- # [你所不知道的 C 語言](https://hackmd.io/@sysprog/c-prog/):連結器和執行檔資訊 Copyright (**慣C**) 2019 [宅色夫](http://wiki.csie.ncku.edu.tw/User/jserv) ==[直播錄影](https://youtu.be/7Zraf5487YA)== ## 簡介 連結器 (linker) 是很多 C 程式開發者會忽略的議題,但在 Linux 核心和 Android Open Source Project (AOSP) 這樣包含大量 C 程式的專案中,不難見到連結器的身影,像是客製化的 linker script, 針對 gnu ld / gold linker 的最佳化,甚至是搭配編譯器和連結器選項,提供可掛載的核心模組 (Linux kernel module) 功能。 本講座先回顧在 1970 年 UNIX 剛被創造出來時,系統提供的 loader 是如何演化為 ld (現在的 UNIX 世界的 linker),及其名稱暗示著程式載入器的作用,述及 gcc 和連結器相關的 GNU extension 可如何運用 (如嵌入一份圖片內容到執行檔中),搭配分析工具探討 ELF 執行檔和連結器的交叉運作機制。 另外,慶祝[高雄愛河正名為「金銀河」](https://www.facebook.com/JservFans/posts/1832557863537206),本講座要探討 gold (別懷疑,真的有一個 linker 名稱就叫做「金」) 如何讓 Linux 核心發揮 Link-Time Optimization (LTO) 效益,編譯出更精簡且更高效的 Linux 核心映像檔。 ## 嵌入一個二進位檔案到執行檔 假設有個二進位檔案名為 `blob`,可善用 `xxd` 工具: ```shell $ xxd --include blob ``` 為了解說方便,先製造一個檔案,紀錄長度: ```shell $ uname -a > blob $ uname -a | wc -c 105 ``` 將 `blob` 納入 ELF 檔案中: ```shell $ ld -s -r -b binary -o blob.o blob ``` 觀察產生的 `blob.o`: ```shell $ objdump -t blob.o blob.o: file format elf64-x86-64 SYMBOL TABLE: 0000000000000000 l d .data 0000000000000000 .data 0000000000000069 g .data 0000000000000000 _binary_blob_end 0000000000000000 g .data 0000000000000000 _binary_blob_start 0000000000000069 g *ABS* 0000000000000000 _binary_blob_size ``` 寫個測試程式 (`test.c`): ```clike #include <stdio.h> int main(void) { extern void *_binary_blob_start, *_binary_blob_end; void *start = &_binary_blob_start, *end = &_binary_blob_end; printf("Data: %p..%p (%zu bytes)\n", start, end, end - start); return 0; } ``` 編譯、連結,和執行: ```shell $ gcc test.c blob.o -o test $ ./test Data: 0x55ed5ed15010..0x55ed5ed15079 (105 bytes) ``` 對照上面的 `105` bytes,符合。 回頭看稍早產生的 `blob.o`: ```shell $ readelf -S blob.o There are 5 section headers, starting at offset 0x180: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .data PROGBITS 0000000000000000 00000040 0000000000000069 0000000000000000 WA 0 0 1 [ 2] .symtab SYMTAB 0000000000000000 000000b0 0000000000000078 0000000000000018 3 2 8 [ 3] .strtab STRTAB 0000000000000000 00000128 0000000000000037 0000000000000000 0 0 1 [ 4] .shstrtab STRTAB 0000000000000000 0000015f 0000000000000021 0000000000000000 0 0 1 ``` 另一個示範 [objcopy_to_carray](https://github.com/vogelchr/objcopy_to_carray) ## init script 示範 在 [F9 microkernel](https://github.com/f9micro/f9-kernel) 有個特徵 [Init hooks](https://github.com/f9micro/f9-kernel/blob/master/Documentation/init-hooks.txt),允許特定程式碼在核心啟動早期就執行。使用方式: ```clike #include <init_hook.h> #include <debug.h> void hook_test(void) { dbg_printf(DL_EMERG, "hook test\n"); } INIT_HOOK(hook_test, INIT_LEVEL_PLATFORM - 1) ``` 透過 GNU extension 去指定 ELF section: [include/init_hook.h](https://github.com/f9micro/f9-kernel/blob/master/include/init_hook.h): ```clike #define INIT_HOOK(_hook, _level) \ const init_struct _init_struct_##_hook \ __attribute__((section(".init_hook"))) = { \ .level = _level, \ .hook = _hook, \ .hook_name = #_hook, \ }; ``` 在 [platform/stm32f4/f9.ld](https://github.com/f9micro/f9-kernel/blob/master/platform/stm32f4/f9.ld) 配置了 `.init_hook` 的空間: ```clike init_hook_start = .; KEEP(*(.init_hook)) init_hook_end = .; ``` 最後在 [kernel/init.c](https://github.com/f9micro/f9-kernel/blob/master/kernel/init.c) 就清晰了: ```clike extern const init_struct init_hook_start[]; extern const init_struct init_hook_end[]; static unsigned int last_level = 0; int run_init_hook(unsigned int level) { unsigned int max_called_level = last_level; for (const init_struct *ptr = init_hook_start; ptr != init_hook_end; ++ptr) if ((ptr->level > last_level) && (ptr->level <= level)) { max_called_level = MAX(max_called_level, ptr->level); ptr->hook(); } last_level = max_called_level; return last_level; } ``` ## 連結器在軟體最佳化扮演重要角色 複習「你所不知道的 C 語言」: [編譯器和最佳化原理篇](https://hackmd.io/s/Hy72937Me) [動態連結器篇](https://hackmd.io/s/HkK7Uf4Ml) Ian Wienand 的電子書: [Chapter 7. The Toolchain](https://www.bottomupcs.com/linker.xhtml) 對照閱讀: * [10分鐘讀懂 linker scripts](https://blog.louie.lu/2016/11/06/10%E5%88%86%E9%90%98%E8%AE%80%E6%87%82-linker-scripts/) * [Linker Script 初探 - GNU Linker ld 手冊略讀](http://wen00072.github.io/blog/2014/03/14/study-on-the-linker-script/) [Optimizing large applications](http://www.ucw.cz/~hubicka/slides/labs2013.pdf) (2013) * 原本的執行路徑 ![](https://i.imgur.com/V8Uh8tA.png) * [elfhack](https://wiki.mozilla.org/Elfhack) 調整後 ![](https://i.imgur.com/9k8f43r.png) - 20% of Firefox libxul image are relocations - ELF relocations are not terribly size optimized * REL relocations on x86 take 8 bytes * RELA relocation on x86-64 take 24 bytes - Elfhack compress the relocations * ELFhack removes IP relative ELF relocations and store them in compact custom format. It handles well sequences of IP relative relocations in vtables. * After ELF linking, ELFhack linking completes the process. * ELFhack is general tool but not compatible with -z relro security feature. - `7.5` MB of relocations → `0.3` MB. - 搭配閱讀: [Improving libxul startup I/O by hacking the ELF format](https://glandium.org/blog/?p=1177) * Linktime optimization in GCC (2014) * [part 1 - brief history](http://hubicka.blogspot.com/2014/04/linktime-optimization-in-gcc-1-brief.html) * [part 2 - Firefox](http://hubicka.blogspot.com/2014/04/linktime-optimization-in-gcc-2-firefox.html) * [part 3 - LibreOffice](http://hubicka.blogspot.com/2014/09/linktime-optimization-in-gcc-part-3.html) [What makes LLD so fast?](https://archive.fosdem.org/2019/schedule/event/llvm_lld/attachments/slides/3423/export/events/attachments/llvm_lld/slides/3423/WhatMakesLLDSoFastPresenterNotes.) * [WebM 錄影](https://video.fosdem.org/2019/K.4.201/llvm_lld.webm) * Peter Smith 宣稱 [lld](https://lld.llvm.org/) 比 GNU gold linker 快 2 到 3 倍,又比標準的 `ld.bfd` 快 5 到 10 倍 [The missing link: explaining ELF static linking, semantically](http://dominic-mulligan.co.uk/wp-content/uploads/2011/08/oopsla-elf-linking-2016.pdf) > In the C programming language, a simple program such as 'hello, world!' exercises very few features of the language, and can be compiled even by a toy compiler. However, for a linker, even the smallest C program amounts to a complex job, since it links with the C library—one of the most complex libraries on the system, in terms of the linker features it exercises. ## 在 Linux 核心的應用案例 [Shrinking the kernel with link-time garbage collection](https://lwn.net/Articles/741494/) [Shrinking the kernel with link-time optimization](https://lwn.net/Articles/744507/) * Dead-code elimination * LTO and the kernel [Shrinking the kernel with an axe](https://lwn.net/Articles/746780/) ## 其他 * [tramp-test](https://github.com/ncultra/tramp-test) ```shell $ ./dis.sh $ cat tramp_test.o.asm $ cat trampoline.o.asm ``` * [libelfmaster](https://github.com/elfmaster/libelfmaster): Secure ELF parsing/loading library for forensics reconstruction of malware, and robust reverse engineering tools * [dt_infect](https://github.com/elfmaster/dt_infect): ELF Shared library injector using DT_NEEDED precedence infection. Acts as a permanent LD_PRELOAD * [How the GNU C Library handles backward compatibility](https://developers.redhat.com/blog/2019/08/01/how-the-gnu-c-library-handles-backward-compatibility/) * [Behind "Hello World" on Linux](https://jvns.ca/blog/2023/08/03/behind--hello-world/)