--- tags: 你所不知道的 C 語言, 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統 --- # 動態連結器、連結器和執行檔資訊、執行階段程式庫 (CRT) contributed by <`RusselCK` > ###### tags: `RusselCK` ## [動態連結器篇](https://hackmd.io/@sysprog/c-dynamic-linkage?type=view#你所不知道的-C-語言:動態連結器篇) “lingua franca” (IPA 音標 [ˌlɪŋgwə ˈfræŋkə]) 一詞源自 17 世紀義大利語稱呼「法蘭克語/口音」,後來引申為橋接用的語言 * 現代英語就扮演這樣的角色,讓世界各國、不同文化背景的人,得以透過共通的英語來交流。 * [How is English Used as a Lingua Franca Today?](https://www.altalang.com/beyond-words/how-is-english-used-as-a-lingua-franca-today/) * 北京普通話 $\leftrightarrow$ 台灣國語 $\leftrightarrow$ 台語 * 而對近代程式語言來說,就是指 C 語言。 以 Java 程式語言來說,儘管有 Java 虛擬機器,甚至能用 Java 開發 Java 虛擬機器 (如 [Jikes RVM](http://www.jikesrvm.org/), [Maxine VM](https://en.wikipedia.org/wiki/Maxine_Virtual_Machine), [Graal VM](http://openjdk.java.net/projects/graal/)),但和作業系統相關的操作仍需要透過 C 語言 (或 C++),連同呼叫原本用 C/C++ 開發的函式庫在內。 :::warning * [P-code 與 Kenneth Bowles 教授](https://www.facebook.com/JservFans/posts/1711808435612150) * [UCSD Pascal pioneer Ken Bowles has died](https://news.ycombinator.com/item?id=18161217) - 若 UCSD Pascal 裡頭的解譯 P-code 的直譯器,是用 C 語言開發的話,這個直譯器應該會用到 C 語言的函式庫,那麼,是否可與其他用 C 語言所開發的程式做連結,使用彼此的功能 ? * 可以,但要先解決動態連結的問題 ::: ### 用 `LD_PRELOAD` 做壞壞的事 #### 如何得知 malloc/free 的呼叫次數? ##### 簡單的作法 (使用 巨集) ```c int malloc_count = 0, free_count = 0; #define MALLOC(x) do { if (malloc(x)) malloc_count++; } while (0) ``` :::danger 1. 要改寫原始程式碼,將 `malloc` 換成 `MALLOC` 巨集 2. 對 C++ 不適用 (`new` 和 `delete`) * 即便底層 [libstdc++](https://gcc.gnu.org/onlinedocs/libstdc++/) 也用 malloc()/free() 來實做 new 和 delete 3. 若使用到的函式庫 (靜態和動態) 裡頭若呼叫到 malloc()/free(),也無法追蹤到 ::: ##### Interpositioning (使用 動態連結器 (dynamic linker)) * 以 GNU/Linux 搭配 [glibc](https://www.gnu.org/software/libc/) 為例,建立檔案 **malloc_count.c** ```clike #include <stddef.h> #include <string.h> #include <stdio.h> #include <dlfcn.h> void *malloc(size_t size) { char buf[32]; static void *(*real_malloc)(size_t) = NULL; if (real_malloc == NULL) { real_malloc = dlsym(RTLD_NEXT, "malloc"); } sprintf(buf, "malloc called, size = %zu\n", size); write(2, buf, strlen(buf)); return real_malloc(size); } ``` 想要以新的 `malloc` 取代 原有的 `malloc` :::info * **`void *dlsym(void *handle, const char *symbol);`** * obtain **address of a symbol** in a shared object or executable * shared object : dll 檔, ... * executable : 執行檔 * RTLD_NEXT * 告知動態連結器,我們想從下一個載入的動態函式庫載入 malloc 的程式碼位址 ::: :::warning 為什麼 **malloc_count.c** 不需要 `void *p = dlopen(“libc.so.6”, RTLD_LAZY);` ? * 因為 早就打開了 * **malloc_count.c** 是用 C 語言開發的,會需要 C 語言函式庫 同理,也不用主動關掉,因為是共用 `libc` 的 ::: * 編譯和執行: ```shell $ gcc -D_GNU_SOURCE -shared -ldl -fPIC -o /tmp/libmcount.so malloc_count.c $ LD_PRELOAD=/tmp/libmcount.so ls ``` 即可得知每次 `malloc()` 呼叫對應的參數,甚至可以統計記憶體配置,完全不需要變更原始程式碼 :::info * -shared * -fPIC * LD_PRELOAD ::: :::success 【流程解析】 * 透過設定 LD_PRELOAD 環境變數,glibc 的 dynamic linker ([ld.so](http://man7.org/linux/man-pages/man8/ld.so.8.html)) 會在載入和重定位 (relocation) `libc.so` **之前**,載入我們撰寫的 `/tmp/libmcount.so` 動態連結函式庫 * 如此一來,我們實做的 malloc 就會在 `libc.so` 提供的 malloc 函式之前被載入。 * 當然,我們還是需要「真正的」 malloc,否則無法發揮作用,所以透過 dlsym 去從 `libc.so` 載入 malloc 程式碼 ::: * [glibc-abi](https://pagure.io/glibc-abi) * [application binary interface, ABI](https://zh.wikipedia.org/wiki/应用二进制接口) #### Unrandomize (暫時不「亂」的亂數) - [ ] [Dynamic linker tricks: Using LD_PRELOAD to cheat, inject features and investigate programs](https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/) ```c= #include <stdlib.h> #include <time.h> int main(){ srand(time(NULL)); int i = 10; while(i--) printf("%d\n",rand()%100); return 0; } ``` * 建立檔案 **unrandom.c** ```c int rand(){ return 42; //the most random number in the universe } ``` * 編譯和執行: ```shell gcc -shared -fPIC unrandom.c -o unrandom.so LD_PRELOAD=$PWD/unrandom.so ./random_nums ``` ### Interpositioning 的其他應用 - 遊戲破解 - 執行時期追蹤 - sandboxing / software fault isolation (SFI) - profiling - 效能最佳化的函式庫 (如 [TCMalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html))。 延伸閱讀: * [Tutorial: Function Interposition in Linux](http://jayconrod.com/posts/23/tutorial-function-interposition-in-linux) * [List of resources related to LD_PRELOAD](https://github.com/gaul/awesome-ld-preload) 也可使用 `_ld --wrap=symbol` 的方式,詳見_ [How to wrap a system call (libc function) in Linux](http://samanbarghi.com/blog/2014/09/05/how-to-wrap-a-system-call-libc-function-in-linux/)。 ### Symbolism (what does `-Bsymbolic` do?) - GNU ld 有個選項 `-Bsymbolic-functions` 會影響 LD_PRELOAD 的行為 >When creating a shared library, bind references to global function symbols to the definition within the shared library, if any. This option is only meaningful on ELF platforms which support shared libraries. - [ ] [Symbolism and ELF files (or, what does -Bsymbolic do?)](https://blog.flameeyes.eu/2012/10/symbolism-and-elf-files-or-what-does-bsymbolic-do) 自己看 ### ELF files ("No such file or directory" 可能跟你猜想的不一樣) - [ ] [where is ELF interpreter](https://github.com/imay/imay.github.io/blob/master/_posts/2014-11-02-linker-loader.md) - [ ] [PatchELF](https://nixos.org/patchelf.html) ==在動態連結的環境中,ELF interpreter 其實就是 dynamic linker!== > Main binary specifies which loader to use * linux 核心的程式碼 [fs/binfmt_elf.c](http://lxr.free-electrons.com/source/fs/binfmt_elf.c) ```c=2 /* * linux/fs/binfmt_elf.c * * These are the functions used to load ELF format executables as used * on SVr4 machines. Information on the format may be found in the book * "UNIX SYSTEM V RELEASE 4 Programmers Guide: Ansi C and Programming Support * Tools". * * Copyright 1993, 1994: Eric Youngdale (ericy@cais.com). */ ``` ```c=690 static int load_elf_binary(struct linux_binprm *bprm) ``` ```clike=751 if (elf_ppnt->p_type == PT_INTERP) { /* This is the program interpreter used for * shared libraries - for now assume that this * is an a.out format binary */ ... elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL); if (!elf_interpreter) goto out_free_ph; retval = kernel_read(bprm->file, elf_ppnt->p_offset, elf_interpreter, elf_ppnt->p_filesz); ``` program interpreter 找尋程式需要哪些共享的 libraries :::info * [UNIX System V](https://zh.wikipedia.org/wiki/UNIX_System_V) * System V是AT&T的第一個**商業**UNIX版本的加強 ::: 延伸閱讀: - [ ] [Hacking Your ELF For Fun And Profit](http://mgalgs.github.io/2013/05/10/hacking-your-ELF-for-fun-and-profit.html) - [ ] 《[Binary Hacks](https://ncku365-my.sharepoint.com/:b:/g/personal/p76091624_ncku_edu_tw/Ef-hwrKrKG5MvCwSIf688HUBiP3Ot2qzjzne0K1ttGH7BQ?e=o35u4p)》 * [ELF Hacks](https://maskray.me/blog/2015-03-26-elf-hacks) ### 複習 編譯器最佳化原理 :dart: [編譯器最佳化原理 筆記](https://hackmd.io/FspGCG57QfO5u6oaIYj0uQ?both#編譯器與最佳化原理、案例分析) * Compilation units * LTO (Link Time Optimization) 影片 1:16:00 ~ 1:37:00 ### 《[Modern C](https://modernc.gforge.inria.fr/)》 * [C/C++ 中的 static, extern 的變數](https://medium.com/@alan81920/c-c-中的-static-extern-的變數-9b42d000688f) Rule 摘錄 [ **Rule 4.22.2.1** ] File scope static const objects may be replicated in all compilation units that use them. (Page 169) >英語中 replicate 有「複製」或「重複」的意思 ==一旦物件宣告為 `static const`,那麼編譯器就可以施加更多樣的最佳化策略== ```c static const x = 42; // 編譯器可將程式裡的 `x` 全部換成 `42` ``` [ **Rule 4.22.2.2** ] File scope static const objects cannot be used inside inline functions with external linkage. Another way is to declare them ```clike extern listElem const singleton; ``` and to define them in one of the compilation units: ```clike listElem const singleton = { 0 }; ``` This second method has the big **disadvantage** that **the value of the object is not available in the other units that include the declaration**. Therefore we may miss some opportunities for the compiler to optimize our code. 考慮以下程式碼: ```clike inline listElem *listElem_init(listElem *el) { if (el) *el = singleton; return el; } ``` 如果編譯器已經得知 `singleton` 的內含值,那麼原本指定數值的操作就不用重複自記憶體載入,而且呼叫 `listElem_init()` 的地方就能更緊湊,對效能和程式追蹤有助益。 ### Symbol Visibility 預設情況下,所有「不是 static」的 symbol (函式/變數) 都可會開放給其他 compilation unit 去存取,這樣的行為我們稱為 "**export**"。 * 一個 symbol 一旦 export,就可能遇到前述的 interpositioning,這很可能會導致非預期的行為。 解決方法是,妥善地設定 symbol visibility。 :::info ==gcc 和 clang 都支援 [visibility](https://gcc.gnu.org/wiki/Visibility) 屬性和 **`-fvisibility`** 編譯命令==,以便對每個 object file 來進行全域設定: * **default** : 不修改 visibility * **hidden** : 對 visibility 的影響與 **static** 這個 qualifier 相同。此 symbol 不會被放入 dynamic symbol table,其他動態連結函式庫或執行檔看不到此 symbol ::: > [Standard Template Library, STL](https://zh.wikipedia.org/wiki/标准模板库) > 《[The Annotated STL Source](http://read.pudn.com/downloads190/ebook/894697/STL-InsightByFU.pdf)》侯傑 範例 : 新酷音 [ [source](https://github.com/chewing/libchewing/blob/master/include/global.h) ] ```clike if (__GNUC__ > 3) && (defined(__ELF__) || defined(__PIC__)) # define CHEWING_API __attribute__((__visibility__("default"))) # define CHEWING_PRIVATE __attribute__((__visibility__("hidden"))) #else # define CHEWING_API # define CHEWING_PRIVATE #endif ``` * [C 语言中__attribute__的作用](https://winddoing.github.io/post/12087.html) 實驗: - [ ] [Why symbol visibility is good](https://www.technovelty.org/code/why-symbol-visibility-is-good.html) * 慮以下程式碼: (syms.c) ```clike static int local(void) { } int global(void) { } int __attribute__((weak)) weak(void) { } ``` :::info * **`attribute((weak))`** * 若 linker 發先別人也有相同名稱的 Symbol,則會先用別人的 ::: * 編譯和分析: (Symbol table) ```shell $ gcc -o syms.o -c syms.c $ LC_ALL=C readelf --syms syms.so Symbol table '.symtab' contains 11 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS syms.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 4: 0000000000000000 0 SECTION LOCAL DEFAULT 3 5: 0000000000000000 7 FUNC LOCAL DEFAULT 1 local 6: 0000000000000000 0 SECTION LOCAL DEFAULT 5 7: 0000000000000000 0 SECTION LOCAL DEFAULT 6 8: 0000000000000000 0 SECTION LOCAL DEFAULT 4 9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 global 10: 000000000000000e 7 FUNC WEAK DEFAULT 1 ``` 對照看之前的 malloc_count: ```shell $ readelf --syms /tmp/libmcount.so | grep malloc 15: 00000000000007c0 163 FUNC GLOBAL DEFAULT 12 malloc 35: 0000000000000000 0 FILE LOCAL DEFAULT ABS malloc_count.c 36: 0000000000201050 8 OBJECT LOCAL DEFAULT 24 real_malloc.3854 61: 00000000000007c0 163 FUNC GLOBAL DEFAULT 12 malloc ``` * 修改 malloc_count.c,讓定義的程式碼變更為以下: ```clike __attribute__((visibility("hidden"))) void *malloc(size_t size) { ... 其餘不變 ... } ``` 就會發現 `LD_PRELOAD=/tmp/libmcount.so ls` 沒有效果。 換言之,我們定義的 `malloc` 已經變成 local,不會影響到其他動態連結函式庫和執行檔。 * 重新觀察: ```shell $ readelf --syms /tmp/libmcount.so | grep malloc 35: 0000000000000000 0 FILE LOCAL DEFAULT ABS malloc_count.c 36: 0000000000201050 8 OBJECT LOCAL DEFAULT 24 real_malloc.3854 46: 00000000000007a0 163 FUNC LOCAL DEFAULT 12 malloc ``` 可見到 visibility 從原本的 GLOBAL 變更為 LOCAL。 ### 動態連結支援 (Dynamic Linking) - [ ] [Linking](http://www.scs.stanford.edu/15wi-cs140/notes/linkers.pdf) (老師影片用的) - [ ] [Linking](http://www.scs.stanford.edu/18wi-cs140/notes/linking.pdf) (new) ![](https://i.imgur.com/kL6uJkE.png) * `f.o` 和 `c.o` 在 linking 會互相參照 ![](https://i.imgur.com/SZmL379.png) * 在動態連結,我們關注在 Compile time 之後的 Load/Run time ![](https://i.imgur.com/aJevea4.png) * 此時並不知道 `printf` 的真實位址 * 因為尚未參照與 `printf` 有關的函式庫 ![](https://i.imgur.com/mYBYuGD.png) > 此時還是不知道 `printf` 的真實位址 ![](https://i.imgur.com/pM84yO3.png) * 連結參照到 動態連結函式庫、glibc 等之後,便可知道確切位置 (Relocation) ### Shared Libraries * Make upgrading, bug fixing, and security patches easier * Reduces total code size installed * Plugins ![](https://i.imgur.com/8vmvH1X.png) * `libc.a` 出現好多次 :::info * [ar](https://linux.die.net/man/1/ar) * create, modify, and extract from **archives** * 裡面放 **relocatable object** > `printf.o` 、`scanf.o` ... ::: ![](https://i.imgur.com/xBCjMtv.png) :::info * -fpic * 允許在連結時期調整位址 ![](https://i.imgur.com/GAZ515M.png) ::: ### 如何做到動態載入 Symbol ? (PLT、GOT) ![](https://i.imgur.com/nLzi2FZ.png) * 查表 (PTL) * 表裡面還有表 (GOT) :::info * **procedure Linkage table, PLT** * entry for the function so that the dynamic loader can re-direct the function call. * **global offset table, GOT** * The PLT is a trampoline which gets the correct address of the function being called (from the _global offset table_, GOT) and bounces the function call to the right place. ![](https://i.imgur.com/lfKuo8e.png) ::: ![](https://i.imgur.com/59c2exc.png) * 這裡的 `&dlfixup` 是假的位址 (Lazy Dynamic Linking) * Program 載入後,**有需要才去找尋**真正的位址並更新 ![](https://i.imgur.com/sGmj8uf.png) 後面還有 資安 的部分 ~ ### 其他必看 - [ ] [Computer Science from the Bottom Up](https://www.bottomupcs.com/) ([Ian Wienand](https://github.com/ianw) 的電子書) * [Chapter 9\. Dynamic Linking](https://www.bottomupcs.com/chapter08.xhtml) - [ ] [C語言編程透視](https://github.com/tinyclub/open-c-book) (電子書) * [Chap 4](https://github.com/tinyclub/open-c-book/blob/master/zh/chapters/02-chapter4.markdown) - [ ] [Anatomy of Linux dynamic libraries](http://www.ibm.com/developerworks/library/l-dynamic-libraries/) 原共筆還有其他延伸閱讀及相關實際應用 ~ ## [連結器和執行檔資訊](https://hackmd.io/@sysprog/c-linker-loader?type=view#你所不知道的-C-語言:連結器和執行檔資訊) 探討 gold (別懷疑,真的有一個 linker 名稱就叫做「金」) 如何讓 Linux 核心發揮 Link-Time Optimization (LTO) 效益,編譯出更精簡且更高效的 Linux 核心映像檔。 ### 嵌入一個二進位檔案到執行檔 #### 範例 I : 假設有個二進位檔案名為 `blob`,可善用 `xxd` 工具: ```shell $ xxd --include blob ``` :::info * $ [xxd](https://linux.die.net/man/1/xxd) * make a hexdump or do the reverse * -i * include * output in C include file style ::: 為了解說方便,先製造一個檔案,紀錄長度: ```shell $ uname -a > blob $ uname -a | wc -c // 檢查字串長度 (bytes) 105 ``` :::info * $ [uname](https://linux.die.net/man/1/uname) * print system information * -a * print all information ::: * 將 `blob` 納入 ELF 檔案中: ```shell $ ld -s -r -b binary -o blob.o blob ``` :::info - [ ] [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/) * $ [ld](https://linux.die.net/man/1/ld) * The GNU linker * ld combines a number of object and archive files, **relocates** their data and ties up symbol references. * Usually the linker is invoked with at least one object file, but you can specify other forms of binary input files * -b * input-format * 此例指定 二進位檔案 (binary) 為 input * -o * output ::: 觀察產生的 `blob.o` 有哪些 Symbol: ```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; } ``` :::warning * `#2` : `_binary_blob_start` 、 `_binary_blob_end` 是 `blob.o` 裏頭的 Symbol,並不存在於 `test.c`,因此需要使用 `extern` 來宣告 ::: * 編譯、連結,和執行: ```shell $ gcc test.c blob.o -o test $ ./test Data: 0x55ed5ed15010..0x55ed5ed15079 (105 bytes) ``` 對照上面的 `105` bytes,符合。 :::info * $ [strings](https://linux.die.net/man/1/strings) * print the strings of printable characters in files. ::: 回頭看稍早產生的 `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 ``` :::info * $ [readelf](https://linux.die.net/man/1/readelf) * Displays information about ELF files. * -S * sections、section-headers * Displays the information contained in the file's section headers * 檔名前面有 `.` 代表 section ::: #### 範例 II : ( [objcopy_to_carray](https://github.com/vogelchr/objcopy_to_carray) ) * 觀察 **Makefile** ```shell=20 passwd.o : /etc/passwd objcopy -I binary $(OBJCOPY_ARCH) \ --rename-section .data=.rodata,alloc,load,readonly,data,contents \ --add-section ".note.GNU-stack"=/dev/null \ --set-section-flags ".note.GNU-stack"=contents,readonly \ $< $@ || (rm -f $@ ; exit 1) ``` 複製 /etc/passwd 裡的內容,產生 **passwd.o** :::info * $ [objcopy](https://linux.die.net/man/1/objcopy) * **copy** and translate **object files** * objcopy uses the **GNU BFD** Library to read and write the object files. ::: - [ ] [`readelf` vs. `objdump`: why are both needed](https://stackoverflow.com/questions/8979664/readelf-vs-objdump-why-are-both-needed) * **testme.c** ```c= #include <unistd.h> #include <stdlib.h> #include <stdio.h> extern void _binary__etc_passwd_size; extern char _binary__etc_passwd_start; extern char _binary__etc_passwd_end; int main(int argc, char **argv) { (void)argc; (void)argv; /* try one of the following two lines */ // size_t size = &_binary__etc_passwd_size; size_t size = &_binary__etc_passwd_end - &_binary__etc_passwd_start; printf("Dumping /etc/passwd, in memory @%p, size is %zu.\n", &_binary__etc_passwd_start, size); write(1,&_binary__etc_passwd_start,size); // 輸出內容 exit(0); } ``` ### Init hooks/script (允許特定程式碼在核心啟動早期就執行) - [ ] [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/) 在 [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, \ }; ``` 將有用到 `INIT_HOOK` 的 Symbol (不論在何處使用),全部丟到 `.init_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 = .; ``` 用 `init_hook_start`/`end` 包住`.init_hook` 的空間 * 最後在 [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; } ``` ### 連結器在軟體最佳化扮演重要角色 在**雲端運算** (伺服器超級多台) 中,每一個(小)程式都很重要,找不太到效能瓶頸 * Linker 的最佳化是個很好的切入點 * 程式最佳化程式 * 只改善 1% 就有很大的幫助 :dart: [Linker 筆記](https://hackmd.io/FspGCG57QfO5u6oaIYj0uQ#Linker) * Linker 命令列 本身就是一種語言 * GNU ld linker * Google gold linker ### 教材 : Computer Science from the Bottom Up - [ ] [Computer Science from the Bottom Up](https://www.bottomupcs.com/) ([Ian Wienand](https://github.com/ianw) 的電子書) * [Chapter 7. The Toolchain](https://www.bottomupcs.com/linker.xhtml) 術語解說 * [name decoration](https://zh.wikipedia.org/wiki/名字修饰) 符號 (Symbol) 處理 * [compilation example](https://www.bottomupcs.com/compilation_example.xhtml) * UND : undefined **hello.c** ```c #include <stdio.h> /* We need a prototype so the compiler knows what types function() takes */ int function(char *input); /* Since this is static, we can define it in both hello.c and function.c */ static int i = 100; /* This is a global variable */ int global = 10; int main(void) { /* function() should return the value of global */ int ret = function("Hello, World!"); exit(ret); } ``` **function.c** ```c #include <stdio.h> static int i = 100; /* Declard as extern since defined in hello.c */ extern int global; int function(char *input) { printf("%s\n", input); return global; } ``` Compiling ```shell $ gcc -S hello.c $ gcc -S function.c ``` Assemply ```shell $ as -o function.o function.s $ as -o hello.o hello.s $ ls function.c function.o function.s hello.c hello.o hello.s ``` ```shell $ readelf --symbols ./hello.o Symbol table '.symtab' contains 15 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 4 OBJECT LOCAL DEFAULT 5 i 7: 0000000000000000 0 SECTION LOCAL DEFAULT 6 8: 0000000000000000 0 SECTION LOCAL DEFAULT 7 9: 0000000000000000 0 SECTION LOCAL DEFAULT 8 10: 0000000000000000 0 SECTION LOCAL DEFAULT 10 11: 0000000000000004 4 OBJECT GLOBAL DEFAULT 5 global 12: 0000000000000000 96 FUNC GLOBAL DEFAULT 1 main 13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND function 14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit ``` * 在 `hello.o` 中 `function` 為 undefined ```shell $ readelf --symbols ./function.o Symbol table '.symtab' contains 14 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS function.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 4 OBJECT LOCAL DEFAULT 5 i 7: 0000000000000000 0 SECTION LOCAL DEFAULT 6 8: 0000000000000000 0 SECTION LOCAL DEFAULT 7 9: 0000000000000000 0 SECTION LOCAL DEFAULT 8 10: 0000000000000000 0 SECTION LOCAL DEFAULT 10 11: 0000000000000000 128 FUNC GLOBAL DEFAULT 1 function 12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf 13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND global ``` * 在 `function.o` 中 `function` 為 defined Linking :::info we can spy on what gcc is doing under the hood with the `-v` (verbose) flag. ::: ```shell /usr/lib/gcc-lib/ia64-linux/3.3.5/collect2 -static /usr/lib/gcc-lib/ia64-linux/3.3.5/../../../crt1.o /usr/lib/gcc-lib/ia64-linux/3.3.5/../../../crti.o /usr/lib/gcc-lib/ia64-linux/3.3.5/crtbegin.o -L/usr/lib/gcc-lib/ia64-linux/3.3.5 -L/usr/lib/gcc-lib/ia64-linux/3.3.5/../../.. hello.o function.o --start-group -lgcc -lgcc_eh -lunwind -lc --end-group /usr/lib/gcc-lib/ia64-linux/3.3.5/crtend.o /usr/lib/gcc-lib/ia64-linux/3.3.5/../../../crtn.o ``` * crtbegin.o / crtend.o * -lgcc / -gcc_eh (exception handling) The first thing you notice is that a program called `collect2` is being called. This is a simple wrapper around `ld` that is used internally by gcc. ### Startup Time Issue - [ ] [Optimizing large applications](http://www.ucw.cz/~hubicka/slides/labs2013.pdf) (2013) Firefox - Mozilla - gecko - [XUL:Lib XUL](https://wiki.mozilla.org/XUL:Lib_XUL) - [ ] [使用 Prelink 加速程序啓動](https://www.twblogs.net/a/5c7d7b8ebd9eee114211f91d) (2004) - [ ] .gnu.hash (2006) * 原本的執行路徑 ![](https://i.imgur.com/nueVA8l.png) ![](https://i.imgur.com/gjXPT07.png) * 程式在執行之初,都在動態連結相關的 section 之間忙碌 * `.data.rel.ro` 和 `.rela.dyn` - [ ] [elfhack](https://wiki.mozilla.org/Elfhack) (2010) 調整後 - [ ] [Improving libxul startup I/O by hacking the ELF format](https://glandium.org/blog/?p=1177) ![](https://i.imgur.com/9k8f43r.png) * 程式執行之初,有機會執行程式內容相關的 section * `.text` - 20% of Firefox libxul image are relocations - **relocation 耗時** - ELF relocations are not terribly size optimized - **relocation 占空間** * 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. - [ ] Feedback directed reordering (2013) - [ ] [valgrind](https://zh.wikipedia.org/wiki/Valgrind) - [ ] GCC FDO ![](https://i.imgur.com/JOGlITd.png) - [ ] [AutoFDO](https://gcc.gnu.org/wiki/AutoFDO/Tutorial) - [ ] [AutoFDO: Automatic feedback-directed optimization for warehouse-scale applications](https://static.googleusercontent.com/media/research.google.com/zh-TW//pubs/archive/45290.pdf) ### Linktime optimization (LTO) * First released in GCC 4.5. * 需要更多的 CPU 及 記憶體 資源 - 有機會產生更有效率的程式 ![](https://i.imgur.com/qtxShw3.png) - [ ] Linktime optimization in GCC (2014) - [ ] [part 1 - brief history](http://hubicka.blogspot.com/2014/04/linktime-optimization-in-gcc-1-brief.html) * [Tree-SSA](http://gcc.gnu.org/projects/tree-ssa/) - [ ] [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) * 裡面有各種最佳化組合的比較圖表 ### [LLD - The LLVM Linker](http://lld.llvm.org) * LLD is a drop-in replacement for the GNU linkers that accepts the same command line arguments and linker scripts as GNU. - [ ] [What makes LLD so fast?](https://ncku365-my.sharepoint.com/:b:/g/personal/p76091624_ncku_edu_tw/EYeH1p1K7stPq_LixdYyH1MB_aWvA0ru9dBU_FIR3bVf8g?e=cAEXmk) / [WebM 錄影](https://video.fosdem.org/2019/K.4.201/llvm_lld.webm) - [ ] [Linkers and Loaders](https://wh0rd.org/books/linkers-and-loaders/linkers_and_loaders.pdf) * Peter Smith 宣稱 [lld](https://lld.llvm.org/) 比 GNU gold linker 快 2 到 3 倍,又比標準的 `ld.bfd` 快 5 到 10 倍 ![](https://i.imgur.com/577GpRA.png) ### 延伸閱讀 - [ ] [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. * formal proof ### 在 Linux 核心的應用案例 - [ ] [Shrinking the kernel with link-time garbage collection](https://lwn.net/Articles/741494/) * 將沒有用到的 Symbol 去除 - [ ] [Shrinking the kernel with link-time optimization](https://lwn.net/Articles/744507/) * [STM32](https://en.wikipedia.org/wiki/STM32) * no MMU - [ ] [STM32F429](http://wiki.csie.ncku.edu.tw/embedded/STM32F429) * 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/) ## [執行階段程式庫 (CRT)](https://hackmd.io/@sysprog/c-runtime?type=view#你所不知道的-C-語言-執行階段程式庫-CRT) 你想過 C 程式 `int main(int argc, char *argv[])` 背後的運作原理嗎? 想過 C 程式既然「從 main() 開始執行」,那從哪裡獲取 argv[] 的內容呢? 以及最後 main 函式 return 數值時,又做了什麼處理,才能讓作業系統得知程式執行結果呢? `atexit()` 一類的函式如何確保在 C 程式執行終止階段,得以執行註冊的函式? :::info - [ ] [使用 atexit() 讓程式被關閉時做對應的動作](https://kheresy.wordpress.com/2013/11/28/c-atexit/) * [`int atexit(void (*function)(void));`](https://linux.die.net/man/3/atexit) * register a function to be called at **normal process termination** * i.e. `return 0;` * Functions so registered are called in the reverse order of their registration; no arguments are passed. ::: - [ ] [The Development of the C Language](https://www.bell-labs.com/usr/dmr/www/chist.html) C 語言標準化的緩慢過程 - [ ] [Re: [問卦] 寫程式語言的語言是怎麼來的?](https://disp.cc/b/163-aR41) - [ ] [Re: [新聞] 她會個屁程式設計! 維密超模驚人簡歷](https://pttweb.tw/s/38ewP) ### 先看 Microsoft 的文件怎麼說 - [ ] [DLL 和 Visual C++ 執行階段程式庫行為](https://docs.microsoft.com/zh-tw/cpp/build/run-time-library-behavior?view=vs-2017) * 指定使用動態連結程式庫 (DLL) 時,預設連結器就會包含 Visual C++ 執行階段程式庫 (**VCRuntime**) * VCRuntime 包含初始化及終止 C/C++ 可執行檔所需的程式碼。 * VCRuntime 程式碼會提供內部 ==DLL 進入點函式呼叫== * `_DllMainCRTStartup` 函式會執行基本工作,例如堆疊緩衝區安全性設定,C 執行階段程式庫 (CRT) 初始化及終止,而且會呼叫==建構函式和解構函式== * `_DllMainCRTStartup` 也呼叫攔截函式的其他程式庫,例如 WinRT、 MFC 和 ATL 來執行他們自己的初始化及終止。而不需要這項初始化、 CRT 和其他程式庫,以及靜態變數,就會處於未初始化的狀態。 * `VCRuntime` 內部初始化和終止常式會呼叫是否以**靜態方式連結的 CRT** 或**動態連結的 CRTDLL**,會使用您的 DLL。 :::info * [Constructors and Destructors in C++](https://www.studytonight.com/cpp/constructors-and-destructors-in-cpp.php) * 在定義類別時,您可以使用建構函式(Constructor)來進行物件的初始化,而在物件釋放資源之前,您也可以使用「解構函式」 (Destructor)來進行一些善後的工作,例如清除動態配置的記憶體,或像是檔案的儲存、記錄檔的撰寫等等。 ::: - [ ] [How To Use the C Run-Time](https://support.microsoft.com/en-us/help/94248/how-to-use-the-c-run-time) :::info * [**`perror()`**](https://man7.org/linux/man-pages/man3/perror.3.html) * print a system error message * produces a message on standard error describing the last error encountered during a call to a system or library function. ::: - [ ] [MSVC與CRT的恩怨情仇](https://www.cnblogs.com/shijingjing07/p/5509640.html) - [ ] [深入淺出 MFC](https://wizardforcel.gitbooks.io/jjhou-mfc/content/0.html) -侯傑 ### 複習編譯流程 當你執行 `$ gcc -o hello hello.c` 時,會經歷以下步驟: 1. 前置處理 (pre-process) 給定的程式碼,移除程式內的註解,和其他技巧, 像是 expanding (展開) C 的 marco 2. 確認你的程式語法是否確實遵照 C/C++ 的規定,如果沒有符合的話,編譯器會出現警告 3. 將原始碼轉成組合語言 —— 它跟機器語言(machine code)非常相近,但仍在人類可理解的範圍內 4. 把組合語言轉成機器語言 —— 是的,這裡說的機器語言就是常提到的 bit 和 byte,也就是特定 0 和 1 的序列 5. 確認程式中用到的函式呼叫、全域變數是否正確,舉例來說:如若呼叫了不存在的函式,編譯器會顯示警告 6. 如果程式是由程式碼檔案來編譯,編譯器會整合起來 7. 編譯器會負責產生東西,讓系統上的 run-time loader 可以把程式載入記憶體內執行 8. 最後會把編譯完的執行檔存在指定的儲存空間 通常「編譯」(compilation) 是指上述第 1 到 4 個步驟,其他則稱為「連結」(linking),有時步驟 1 也指「前置處理」(pre-processing),而步驟 3 到步驟 4 則是「組譯」(assemble)。 ![](https://i.imgur.com/Ic9vGte.png) 思考以下程式的執行: (**`hello.c`**) ```clike int main() { return 1; } ``` * 編譯和執行 ```shell $ gcc -o hello hello.c $ ./hello ``` 看起來啥屁都沒發生 (或者說,看起來沒有 I/O) * 利用 `echo $?` 來取得程式返回值 ```shell $ echo $? 1 ``` 這個 **`hello`** 已經執行完了,這個回傳值是如何被保留下來的? 程式結束後, CRT 會自動執行 `exit()`,`return` 的 `1` 即為 **exit status** :::info * [**`void _exit(int status);`**](https://man7.org/linux/man-pages/man2/exit.2.html) * terminate the calling process * These functions do not return. * The value `status & 0xFF` is returned to the parent process as the **process's exit status**, ... ::: :::warning * 程式出錯時,我們可以利用 `atexit()` 來檢查問題是否出在所使用的函式上 ::: ### 為何需要 C runtime ? C 語言和一般的程式語言有個很重要的差異,就是 C 語言設計來滿足系統程式的需求 * 作業系統核心 * 一系列的工具程式,像是 ls, find, grep 等等 * 如果忽略指標操作這類幾乎可以直接對應於組合語言的指令的特色 C 語言之所以需要 runtime,有以下幾個地方: 1. `int main() { return 1; }` 也就是 `main()` 結束後,**要將 exit code 傳遞給作業系統的程式**,這部份會放在 `crt0` 2. **exception handling**,不要懷疑,C 語言當然有這個特徵,只是透過 `setjmp` 和 `longjmp` 函式來存取,這需要額外的函式庫 (如 libgcc) 來協助處理 stack 3. **算術操作**,特別在硬體沒有 **FPU** 時,需要 [libgcc](https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html) 來提供浮點運算協助 > - [ ] [What's the difference between hard and soft floating point numbers?](https://stackoverflow.com/questions/3321468/whats-the-difference-between-hard-and-soft-floating-point-numbers) > - [ ] [ArmHardFloatPort](https://wiki.debian.org/ArmHardFloatPort/VfpComparison) 指出在 gcc 針對 Arm 平台上有三種浮點數處理機制,也就是參數 `-mfloat-abi` 的選項有: > * **`soft`** - this is pure software > * **`softfp`** - this supports a hardware FPU, but the ABI is soft compatible. > * **`hard`** - the ABI uses float or VFP registers. ### 驗證執行檔 * 在核心裡的「驗證執行檔」步驟,是要驗證什麼? * UNIX 的「執行檔」有很多種可能,一個是依據特定格式保存的機械碼,也可能是透過額外程式去解析的 shell script,作業系統核心必須得事先解析並確認這個合法的執行檔,才能著手去執行 * 近來還有對執行檔進行簽章的機制,請見: [ELF executable signing and verification ](https://lwn.net/Articles/532710/) * secureboot mode * signelf * opensll ### `int main(int argc, char *argv[])` 背後的學問 :::danger 有些書上使用 `void main()` 的函式宣告,這是錯誤的。 ::: C++ 之父 Bjarne Stroustrup 在他的 [C++ Style and Technique FAQ](http://www.stroustrup.com/bs_faq2.html#void-main) 中明確地寫著 > "The definition void main( ) { /* … */ } is not and never has been C++, nor has it even been C." C 語言規範 5.1.2.2.1 : > It shall be defined with **a return type of int** and with **no parameters** or with **two parameters (argc and argv)** 複習名詞: ![](http://i.imgur.com/K2XsAUi.png) * "argument" 的重點是「傳遞給函式的形式」 > expressions passed into a function * 稱 `argc` 是 argument count * 稱 `argv` 是 argument vector :::info * [**`write()`**](https://www.man7.org/linux/man-pages/man2/write.2.html) * [**`writev()`**](https://linux.die.net/man/2/writev) * write data into multiple buffers ::: - "parameter" 的重點是「接受到的數值」 > values received by the function 比方說 C++ 有 [parameterized type](https://isocpp.org/wiki/faq/templates#param-types),就是說某個型態可以當作另外一個型態的「參數」,換個角度說,「型態」變成像是數值一樣的參數。 * Caller > 呼叫者(通常就是在其他函式呼叫的function) - Callee > 被呼叫者(被呼叫的 function) - [ ] [Parameter (computer programming)](https://en.wikipedia.org/wiki/Parameter_(computer_programming)) ### 程式使用函式呼叫在組合語言的實作 我們看到包含 **`envp`** 的宣告: ```cpp int main(int argc, char *argv[], char *envp[]) { ... } ``` - [ ] [(六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制](https://www.cnblogs.com/0xcafebabe/p/4434218.html) * 故意改寫為以下: (**`x.c`**) ```cpp #include <stdio.h> int main(int argc, char (*argv)[0]) { puts(((char **) argv)[0]); return 0; } ``` * 使用 gdb 觀察: ```shell $ gcc -o x x.c -g $ gdb -q x ``` ```shell (gdb) b main (gdb) r (gdb) print *((char **) argv) $1 = 0x7fffffffe7c9 "/tmp/x" ``` 這裡符合預期,但接下來: ```shell (gdb) x/4s (char **) argv 0x7fffffffe558: "\311\347\377\377\377\177" 0x7fffffffe55f: "" 0x7fffffffe560: "" 0x7fffffffe561: "" ``` ```shell (gdb) x/4s (argv) 0x7fffffffe558: "\311\347\377\377\377\177" 0x7fffffffe55f: "" 0x7fffffffe560: "" 0x7fffffffe561: "" ``` 看不懂了,要換個方式: ```shell (gdb) x/4s ((char **) argv)[0] 0x7fffffffe7c9: "/tmp/x" 0x7fffffffe7d0: "LC_PAPER=zh_TW" 0x7fffffffe7df: "XDG_SESSION_ID=91" 0x7fffffffe7f1: "LC_ADDRESS=zh_TW" ``` ![](https://i.imgur.com/QndglVq.png) 原來後 3 項是 envp (environment variables),在 C run-time 傳遞進來的內容和 `printenv` 輸出一致 ```shell (gdb) shell printenv ``` **下面從影片 1:35:00 開始~** 假設 PID = 31114,那麼我們可觀察: ```shell cat /proc/31114/cmdline cat /proc/31114/environ ``` :::info 讀取 `argv[0]` 和 `cmdline` 來判斷執行的程式名稱,有個非常巧妙的應用: - [ ] [BusyBox - The Swiss Army Knife of Embedded Linux](https://busybox.net/downloads/BusyBox.html) ::: - [ ] [深度剖析 C 語言 main 函式](https://blog.csdn.net/z_ryan/article/details/80985101) * `_start()` * `__attribute__()` ### GNU Toolchain ![](https://i.imgur.com/jDdgIaH.png) * gcc : GNU compiler **collection** * as : GNU assembler * ld : GNU linker * gdb : GNU debugger (過度精簡的) 編譯的流程還有格式 ![](http://i.imgur.com/9c0k1v0.png) .coff 和 .elf 分別表示以下: * [COFF (common object file format)](https://en.wikipedia.org/wiki/COFF) : 是種用於執行檔、目的碼、共享函式庫 (shared library) 的檔案格式 * [ELF (extended linker format)](https://en.wikipedia.org/wiki/Elf) : GNU/Linux 和 *BSD 上最常用的執行檔格式,用於執行檔、目的碼、共享函式庫和核心的標準檔案格式,**用來取代 COFF** - [ ] [Computer Science from the Bottom Up](https://www.bottomupcs.com/) - [ ] [Starting a process](https://www.bottomupcs.com/starting_a_process.xhtml) * `__libc_csu_init` / `__libc_csu_fini` ### 隱藏的 `crt0` [crt0](https://en.wikipedia.org/wiki/Crt0) (也稱為 `c0`) ```= .text .globl _start _start: # _start is the entry point known to the linker mov %rsp, %rbp # setup a new stack frame mov 0(%rbp), %rdi # get argc from the stack lea 8(%rbp), %rsi # get argv from the stack call main # %rdi, %rsi are the first two args to main mov %rax, %rdi # mov the return of main to the first argument call exit # terminate the program ``` * `#6` : 將 `argc`、`argv[]`存入 stack * `#11` : 將 `main()` 的回傳值放入 `%rax` * `#12` : call `exit` [Newlib](https://en.wikipedia.org/wiki/Newlib) [newlib/i386 的實作程式碼](https://github.com/eblot/newlib/blob/master/newlib/libc/sys/linux/machine/i386/crt0.c) ```C extern char **environ; extern int main(int argc, char **argv, char **envp); extern char _end; extern char __bss_start; void _start(int args) { /* * The argument block begins above the current stack frame, because we * have no return address. The calculation assumes that sizeof(int) == * sizeof(void *). This is okay for i386 user space, but may be invalid in * other cases. */ int *params = &args - 1; int argc = *params; char **argv = (char **) (params + 1); environ = argv + argc + 1; /* Note: do not clear the .bss section. When running with shared * libraries, certain data items such __mb_cur_max or environ * may get placed in the .bss, even though they are initialized * to non-zero values. Clearing the .bss will end up zeroing * out their initial values. */ tzset(); /* initialize timezone info */ exit(main(argc, argv, environ)); } ``` 延伸閱讀: * [Creating a C Library](https://wiki.osdev.org/Creating_a_C_Library)