1. 程式人生 > >x86_64體系結構動態替換核心函式hotpatch之完結篇

x86_64體系結構動態替換核心函式hotpatch之完結篇

我的小小要是能用鋼琴彈出《二泉映月》,我就要努力用二胡拉出《卡農》!


最近寫了三篇和網路技術無關的三篇文章:
Linux核心如何替換核心函式並呼叫原始函式https://blog.csdn.net/dog250/article/details/84201114
x86_64執行時動態替換函式的hotpatch機制https://blog.csdn.net/dog250/article/details/84258601
x86_64動態替換核心函式的hotpatch模組解除安裝問題https://blog.csdn.net/dog250/article/details/84555025
主要目的是總結一下碰到的令人蛋疼的問題。

實在是有點噁心了,昨天我就說,這個到此為止了,不搞了,真的搞噁心了。然而昨天那篇文章在寫完後發現遺漏了幾個細節,我又不想修改原文將問題掩蓋掉讓我忘記曾經是遺忘過那些問題的,所以今天寫最後一篇總結性的文章,作為備忘給以後的自己看看,以便以後萬一再做這個時,能回憶起一些思考的過程。

  • 細節1:使用讀寫鎖的text poke機制依然存在問題
    參見《x86_64動態替換核心函式的hotpatch模組解除安裝問題》。
    非常簡單的case,如果我在成功註冊kprobe的時候,有CPU的thread進入了hook函式的中間,豈不是在該thread退出hook函式的時候,在沒有read_lock(因為進入函式的時候kprobe還沒有註冊
    )的前提下呼叫了read_unlock?這會搞亂rwlock的計數器的!
    所以說,還要引入複雜度。即定義一個percpu的全域性變數陣列,其元素為一個atomic型別,這個percpu變數是為了控制每個CPU上只有read lock呼叫過才能read unlock。該變數初始值為0,進入hold這個前處理函式時執行inc,在post後處理中只有該atomic非0時才呼叫unlock!
    static int hold(...)
    {
    	atomic_t var = per_cpu(hook_var, this_cpu);
    	inc(var);
    	read_lock(&hook_lock);
    	return
    0; } static int release(...) { atomic_t var = per_cpu(hook_var, this_cpu); if (var) { read_unlock(&hook_lock); } return 0; } static void hotpatch_poke_text(...) { register(&probe); // 借用kprobe的pre/post機制 // 大概等待一個“函式執行完畢的最長間隔”,以防止沒有read lock // 的thread在post後處理執行前被當前CPU搶掉讀寫鎖。 usleep(..); write_lock(&hook_lock); ...
  • 細節2:啟用了ftrace編譯選項的函式前5個位元組
    原理上,32bit相對跳轉指令序列只需要5個位元組,但是函式的前5個位元組可能並不是一個完整的指令序列,有可能某些函式的開頭是:
    xx xx xx
    xx xx xx
    
    這樣就需要覆蓋函式的前6個位元組,5個位元組為相對跳轉指令序列,外加一個0x90作為nop。因此我不得不在替換指令的時候,做一堆的if-else,switch-case的測試,以便決定到底替換多少指令。
    非常幸運,我的核心啟用了FTRACE相關的編譯選項,因此所有的函式開頭都是下面的序列:
    0x0f 0x1f 0x44 0x00 0x00
    
    或者:
    0x66 0x66 0x66 0x66 0x90
    
    這種指令序列是類似nop的變體,就是專門讓你做替換做32bit跳轉hook用的,直接替換它們為32bit跳轉序列即可。非常方便!要是每一條指令後面都有個這樣的序列而不僅僅在函式開頭,是不是就很容易實現單步了呢?當然,代價就是程式的大小翻倍!不過現在,記憶體貌似不值錢了。
    不過,還是老老實實用 while(!OK) {stop_machine} 的版本吧!
  • 細節3:nop指令的多樣性
    是的,nop不僅僅只有0x90,它有很多變體,比如1位元組的,2位元組的,3位元組的…滿足你各種長度的替換操作!
  • 細節4:最直接的安全解除安裝hook模組的方案
    折騰了這麼久,是不是把問題想複雜了呢?其實在模組的exit函式中,直接呼叫text_poke_smp來恢復函式的前幾個位元組,出現程式跑飛的概率非常低,那麼完全可以這麼玩。
    此外,如果擔心hook函式的text被free掉,那麼在恢復替換完成後,sleep一段足夠的時間,保證所有在替換當時的thread均已經出去該函式,就OK了,至於到底sleep多久,預估一個函式執行時間的經驗值乘以2應該是安全的!

這是最後一篇關於hotpatch的了。但今後也不一定會寫網路方面的。

所以,一個人到底要幹什麼其實是無法預知的,一切都只是概率。會有三個自己出現:

  • 別人眼中的你自己
  • 自己受到別人影響後認識的你自己
  • 不受任何外界影響的你自己

今天和同事討論關於“皮鞋溼,不會胖”的意義,其實沒有意義,我把它當作發語詞了。最後就聊到了“無”和“空”的區別,“無”就是“所有”,可以任意發揮,“空”就是“空”,確定性的“什麼都沒有”。

關於皮鞋,皮鞋只是瞬間情境的載體,與識別性以及語言無關,不能糾結於“皮鞋在句中的作用到底是什麼?”,皮鞋的意義就是“無”。類似“物哀”和“禪宗公案”,包括“尼采為什麼發瘋”的答案,其實都涵蓋了一個意思,即在解釋“什麼是意義”。

練過鬥雞眼的人都明白,開始的時候,你要用手指豎立在兩眼之間,然後兩眼盯著指尖,再往後不需要手指了,想象指尖的位置有一粒灰塵,就能成功對眼,但是到最後,那粒灰塵也不需要了,也能成功!為什麼需要那粒根本就不存在的灰塵呢?雖然它本來就不存在,但是確實幫助你成功對眼。

同事給出關於“無”和“空”的一個神例:

char *p1;
char *p2 = NULL;

萬惡的C指標!

有點繞了…

一縷月光照當頭,上下光而相湊,雞屎也不流,用手毆,淨肉球!

浙江溫州皮鞋溼,下雨進水不會胖。