Linux kernel用了很多GCC選項支持ftrace。
-pg-mfentry-mnop-mcount-mprofile-kernelpowerpc-mrecord-mcount-mhotpatch=pre-halfwords,post-halfwords-fpatchable-function-entry=N[,M]
在當前GCC git repo的“史前”時期(Initial
revision)就能看到-pg支持了。-pg在函數prologue後插入mcount()(Linux
x86),在其他OS或arch上可能叫不同名字,如_mcount、__mcount、.mcount。
trace信息可用於gprof和gcov。
1 | # gcc -S -pg -O3 -fno-asynchronous-unwind-tables |
-pg作用在inlining後。
- 鏈接時GCC會選擇一個不同的crt1文件
gcrt1.o - libc實現
gcrt1.o(glibcsysdeps/x86_64/_mcount.Sgmon/mcount.c, FreeBSDsys/amd64/amd64/prof_machdep.c)。 - musl不提供
gcrt1.ohttps://www.openwall.com/lists/musl/2014/11/05/2
glibc的用法:
gcrt1.o定義__gmon_start__。其他crt1.o沒有定義crti.o用undefined weak__gmon_start__檢測gcrt1.o,是則調用gcrt1.o的__gmon_start__調用__monstartup初始化。在程序運行前初始化完可以避免call-once的同步。
GCC
r21495 (1998)引入-finstrument-functions,
在函數prologue後插入__cyg_profile_func_enter(this_fn, call_site)、epilogue前插入__cyg_profile_func_enter(callee, call_site)。程序實現這兩個函數後可以記錄函數調用。
這兩個函數分別有兩個參數,對code
size有較大影響。另外,很多應用其實不需要call_site這個參數。
1 | void __cyg_profile_func_enter(void *this_fn, void *call_site); |
1 | # gcc -S -O3 -finstrument-functions -fno-asynchronous-unwind-tables |
-finstrument-functions默認作用在inlining前,能較好地體現控制流,但有很多的開銷。原因是inlining後,一個函數裏可能有多個__cyg_profile_func_enter()。
clang提供了-finstrument-functions-after-inlining在inlining後再trace。
GCC
x86的-pg -mfentry -minstrument-return=call可以在函數返回時插入call __return__,可以作爲-finstrument-functions -finstrument-functions-after-inlining的替代品。
Linux kernel 2008年最早的ftrace實現16444a8a40d使用-pg和mcount。
Linux定義了mcount,比較一個函數指針來檢查ftrace是否開啓,倘若沒有開啓,mcount則相當於一個空函數。
1
2
3
4
5
6
7
8
ENTRY(mcount)
cmpq $ftrace_stub, ftrace_trace_function
jnz trace
.globl ftrace_stub
ftrace_stub:
...
所有函數的prologue後都執行call mcount,會產生很大的開銷。因此,後來Linux
kernel在一個hash
table裏記錄mcount的caller的PC,用一個一秒運行一次的daemon檢查hash
table,把不需要trace的函數的call mcount修改成NOP。
之後,8da3821ba56把"JIT"改成了"AOT"。
構建時,一個Perl script
scripts/recordmcount.pl調用objdump記錄所有call mcount的地址,存儲在__mcount_loc
section裏。Kernel啓動時預先把所有call mcount修改成NOP,免去了daemon。
由於Perl+objdump太慢,2010年,16444a8a40d添加了一個C實現scripts/recordmcount.c。
mcount有一個弊端是stack frame
size難以確定,ftrace不能訪問tracee的參數。 GCC
r162651 (2010) (GCC
4.6)引入-mfentry,把prologue後的call mcount改成prologue前的call __fentry__。
2011年,d57c5d51a30添加了x86-64的-mfentry支持。
GCC
r206111 (2013)引入了SystemZ特有的-mhotpatch。
注意描述,function
entry後僅有一個NOP,對entry前的NOP類型進行了限定。這樣缺乏通用性,其他arch用不上。後來一般化爲-mhotpatch=pre-halfwords,post-halfwords。
GCC
b54214fe22107618e7dd7c6abd3bff9526fcb3e5
(2013-03)移植-mprofile-kernel到PowerPC64 ELFv2。
2016年powerpc/ftrace:
Add Kconfig & Make glue for
mprofile-kernel和之前的幾個commits用上了這個選項。
GCC
r215629
(2014)引入-mrecord-mcount、-mnop-mcount。
-mrecord-mcount用於代替linux/scripts/record_mcount.{pl,c}。-mnop-mcount不可用於PIC,把__fentry__替換成NOP。
設計時沒有考慮通用性,大多數RISC都用不上不帶參數的-mnop-mcount。截至今天,-mnop-mcount只有x86和SystemZ支持。
(2019年,Linux x86移除了mcount支持562e14f7229。)
GCC
r250521 (2017)引入-fpatchable-function-entry=N[,M]。
和SystemZ特有選項-mhotpatch=類似,在function
entry前插入M個NOP,在entry後插入N-M個NOP。現在被Linux
arm64和parisc採用。這個功能設計理念挺好的,可惜實現有諸多問題,僅能用於Linux
kernel。
2018年GCC
x86引入了-minstrument-return=call用於配合-pg -mfentry在函數返回時插入call __return__。
-minstrument-return=nop5則是插入一個5-byte nop。
1 | # gcc -fpatchable-function-entry=3,1 -S -O3 a.c -fno-asynchronous-unwind-tables |
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93197
__patchable_function_entries會被ld --gc-sections(linker section garbage collection)收集。導致GCC的實現無法用於大部分程序。这个问题最终在添加PowerPC ELFv2支持时被完全修复https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99899 (milestone: 13.0) - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93195
__patchable_function_entriesentry所屬的COMDAT section group被收集會產生鏈接錯誤。導致很多使用inline的C++程序無法使用。 - 錯誤信息寫錯選項名:
gcc -fpatchable-function-entry=a -c a.c=>cc1: error: invalid arguments for ‘-fpatchable_function_entry’ - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93194
__patchable_function_entries沒有指定section alignment。我的第二個GCC patch~ __patchable_function_entries的entries應用PC-relative relocations,而非absolute relocations,避免鏈接後生成R_*_RELATIVEdynamic relocations。這一點我一開始不能接受,因爲其他缺陷clang這邊修復後也能保持backward compatible,但relocation type是沒法改的。後來我認識到MIPS沒有提供R_MIPS_PC64……那麼選擇原諒GCC了。MIPS就是這樣,ISA缺陷->psABI“發明”聰明的ELF技巧繞過+引入新的問題。"mips is really the worst abi i've ever seen." "you mean worst dozen abis ;"- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92424 AArch64 Branch Target Identification開啓時,NOP sled應在BTI後
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93492 x86 Indirect Branch Tracking開啓時,NOP sled應在ENDBR32/ENDBR64後。在開始實現-fpatchable-function-entry=前,正巧給lld加-z force-ibt。因此在看到AArch64問題很自然地想到了x86也有類似問題。
- 沒有考慮和
-fasynchronous-unwind-tables的協作。再一次,Linux kernel使用-fno-asynchronous-unwind-tables。所以GCC實現時很自然地沒有思考這個問題 - Initial
.locdirective應在NOP sled前。會導致symbolize function address得不到文件名/行號信息
修復--gc-sections和COMDAT比較棘手,還需要binutils這邊的GNU
as和GNU ld的功能:
- https://sourceware.org/bugzilla/show_bug.cgi?id=25380支持unique section ID。2月2日GNU as添加了支持https://sourceware.org/ml/binutils/2020-02/msg00020.html
- https://sourceware.org/bugzilla/show_bug.cgi?id=25381支持
SHF_LINK_ORDER。HJ Lu發了patch:https://sourceware.org/ml/binutils/2020-02/msg00028.html - GNU ld --gc-sections semantics https://sourceware.org/ml/binutils/2019-11/msg00266.html
除AArch64 BTI外,其餘問題都是我報告的~
給clang添加-fpatchable-function-entry=的步驟如下:
- D72215 引入LLVM function attribute "patchable-function-entry",AArch64 AsmPrinter支持
- D72220 x86 AsmPrinter支持
- D72221
在clang裏實現function attribute
__attribute__((patchable_function_entry(0,0))) - D72222
給clang添加driver option
-fpatchable-function-entry=N[,0] - D73070 引入LLVM function attribute "patchable-function-prefix"
- 移動codegen passes,改變NOP sled與BTI/ENDBR的順序,順便修好了XRay、-mfentry與-fcf-protection=branch的協作。
- D73680 AArch64
BTI,處理M=0時,patch
label的位置:
bti c; .Lpatch0: nop而不是.Lpatch0: bti c; nop - x86 ENDBR32/ENDBR64,處理M=0時,patch
label的位置:
endbr64; .Lpatch0: nop而不是.Lpatch0: endbr64; nop
上述patches,除了x86 ENDBR的patch label位置调整,都会包含在clang 10.0.0里。
在-fpatchable-function-entry=之前,clang已經有多種在function entry插入代碼的方法了:
-fxray-instrument。XRay使用類似-finstrument-functions的方法trace,和Linux kernel類似,運行時修改代碼- Azul Systems引入了PatchableFunction用於JIT。我引入"patchable-function-entry"時就複用了這個pass
- IR feature: prologue data,在function entry後添加任意字節。用於function sanitizer
- IR feature: prefix data,在function entry前添加任意字節。用於GHC
TABLES_NEXT_TO_CODE。Info table放在entry code前。GHC的LLVM後端目前仍是年久失修狀態
PowerPC64 ELFv2
PowerPC ELFv2的實現見https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99888
-fpatchable-function-entry=5,2輸出如下:
1 | .globl foo |
NOPs在global entry後。Local entry前後分別有M、N-M個NOPs。 因爲global entry和local entry間距有限制,M只能取0 (2-2)、2 (4-2)、6 (8-2)、14 (16-2)等少數值。
在PR99888中,我在2022年就提出沒有必要讓NOP連續。2023年末的討論也說明了當前連續NOP不方便kernel和userspace live patching。 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112980 打算修改實現。