重新版本系統調用學習宏定義的用法

    這段時間又碰到系統調用這個傢伙,結果在我當前用的3.0.x內核裏全變樣了。爲了將這個問題弄明白,還得本身動手才行,這裏很是感謝CSDN的"海風林影"兄弟提供的博文和相關參考文獻,仍是那句話「成果和榮耀歸於前輩」。

   
不少人也都發現,在 2.6.28 及其以後的內核源碼裏,系統調用的寫法發生了比較大的變化,出現了大量宏定義的代碼。在源代碼裏,之前的諸如 open() 系統調用的 sys_open() 函數,如今僅僅能找到其聲明,而其定義卻「找不到」了。若是你把系統調用的宏展開後就會發現,之前的 sys_open() 依然安然無恙地躺在哪裏。
   
這裏咱們仍然以 open 系統調用爲例。 在內核源碼(固然這裏的內核版本要大於等於2.6.28)fs目錄下的open.c源文件裏,咱們會看到下面這樣的代碼:

點擊(此處)摺疊或打開 css

  1. SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
  2. {
  3.     long ret;

  4.     if (force_o_largefile())
  5.         flags |= O_LARGEFILE;

  6.     ret = do_sys_open(AT_FDCWD, filename, flags, mode);
  7.     /* avoid REGPARM breakage on x86: */
  8.     asmlinkage_protect(3, ret, filename, flags, mode);
  9.     return ret;
  10. }
   當咱們用gcc -E參數把該定義所涉及的全部宏就地擴展以後,在個人x86_32平臺上就會獲得下面這樣的結果:

點擊(此處)摺疊或打開 linux

  1. asmlinkage long sys_open(const char __user * filename, int flags, int mode)
  2. {
  3.  long ret;

  4.  if (force_o_largefile())
  5.   flags |= O_LARGEFILE;

  6.  ret = do_sys_open(AT_FDCWD, filename, flags, mode);
  7.  asmlinkage_protect(3, ret, filename, flags, mode);
  8.  return ret;
  9. }

   那麼SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)是如何轉換成asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode)形式的呢?其實就是宏的一些基本用法而已,咱們來簡單分析一下。在include/linux/syscall.h裏有下面一組宏: 程序員

點擊(此處)摺疊或打開 web

  1. #define SYSCALL_DEFINE0(name)     asmlinkage long sys_##name(void)
  2. #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
  3. #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
  4. #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
  5. #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
  6. #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
  7. #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
    這裏每一個SYSCALL_DEFINE後面的數字說明了,名爲name的系統調用接收幾個輸入參數。這些宏都由一個基礎宏SYSCALL_DEFINEx來實現:

點擊(此處)摺疊或打開 ide

  1. #define SYSCALL_DEFINEx(x, sname, ...) \
  2.    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
   繼續找__SYSCALL_DEFINEx宏的定義:

點擊(此處)摺疊或打開 函數

  1. #define __SYSCALL_DEFINEx(x, name, ...) \
  2.    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
    關於__SC_DECL##x最後也被展開,其定義以下:

點擊(此處)摺疊或打開 spa

  1. #define __SC_DECL1(t1, a1)    t1 a1
  2. #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
  3. #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)
  4. #define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)
  5. #define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)
  6. #define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)
    __SC_DECLx宏實現了系統調用輸入參數類型和參數名的結合,由於宏定義時是沒法肯定參數類型的。咱們來逐步分解一下open系統調用的實現:
步驟 宏擴展內容
1 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
2 SYSCALL_DEFINEx(3, _open, __VA_ARGS__)
3 __SYSCALL_DEFINEx(3, _open, __VA_ARGS__)
4 asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__))
5 asmlinkage long sys_open(const char __user *filename, __SC_DECL2(__VA_ARGS__))
6 asmlinkage long sys_open(const char __user *filename, int flags, __SC_DECL1(__VA_ARGS__))
7 asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode)
   
至於新版內核系統調用爲何要這麼改,這裏咱們先按下這個問題不表,待會兒回答,咱們先看看這裏面宏定義擴展的用法。

 

    1、宏定義之「雙井」運算符## .net

    在宏定義中能夠用##運算符把運算符先後兩個預處理符號鏈接成一個預處理符號。例如:
debug

點擊(此處)摺疊或打開 code

  1. #define VAR(n) x##n


  假如咱們代碼裏有用到諸如VAR(1)VAR(2)VAR(3)或者VAR(4)的語句時,它們在編譯預處理階段就分別被替換成了x1x2x3x4了。就像:

點擊(此處)摺疊或打開

  1. #define __SYSCALL_DEFINEx(x, name, ...) \
  2.    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

   中的__SC_DECL##x同樣,當x分別爲12345或者6時該宏就被替換成了__SC_DECL1__SC_DECL2__SC_DECL3__SC_DECL4__SC_DECL5或者__SC_DECL6


   2
、與「雙井」運算符比較形似的還有「單井」運算符#

    該運算符主要用於建立字符串,運算符的後面必須跟至少一個形參。例如,兩個形參的狀況:

點擊(此處)摺疊或打開

  1. #define tempfile(path) #path "/%s"
  2. char *bin=tempfile(/bin/ls);


    那麼最後bin=」 /bin/ls/%s」

    在tempfile宏定義裏,不管#後面的path」/%s」字符串之間有多少空格,或者多少Tab。在執行宏展開時#後面輸入參數之間的全部空格、Tab都將被忽略。但要注意若是空格或者Tab出如今雙引號」」以內,例如:

點擊(此處)摺疊或打開

  1. #define tempfile(path) #path " /%s"


    那麼這些空格或者Tab會被原封不動的保存下來並應用到最終字符串裏。

    因此,在#運算符後面多個形參之間的空格和Tab符號在宏展開時都會被刪掉,而每一個形參內部的空格或Tab則會被原封不動的保存下來。再來看個稍微複雜一點的例子:

點擊(此處)摺疊或打開

  1. #define STR(s) #s
  2. fputs(STR(strncmp("ab\"c\0d", "abc", '\4"')== 0) STR(: @\n), s);
    展開結果爲: fputs("strncmp(\"ab\\\"c\\0d\", \"abc\", '\\4\"') == 0" ": @\n", s);


    這個例子說明:若是實參中包含字符常量或字符串,則宏展開以後字符串的界定符"要替換成\",字符常量或字符串中的\和"字符要替換成\\和\"。注意,STR(: @\n)裏的\n裏的\是不會被替換的。

 

    3、可變參數表

    在函數定時咱們已經見過這種形式,例如C庫的printf函數的聲明:

點擊(此處)摺疊或打開

  1. int printf(const char *format, ...);


    最開始時可變參數列表的形式只能用在函數定義裏,在C99規範裏GCC用一個名爲__VA_ARGS__保留關鍵字實現了宏定義也可以使用可變數目參數的目的。若是有以下定義:

點擊(此處)摺疊或打開

  1. #define debug(...) printf(__VA_ARGS__)
  2. debug(「result=%d\n」,result);

    那麼在編譯預處理階段將被展開成printf(「result=%d\n」,result);

    上述宏定義中「…」爲一個可變長的參數列表,而__VA_ARGS__則會將宏定義中的省略號「…」所表示的內容原封不動的抄到__VA_ARGS__所在的位置上,即__VA_ARGS__自己就是一個可變長參數的宏,是C99標準規範裏新增的。

 

    而後咱們來回頭看一下上面的例子。SYSCALL_DEFINE3的原型以下:

點擊(此處)摺疊或打開

  1. #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

   open系統調用被修飾成:

點擊(此處)摺疊或打開

  1. SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)


    因此,在進一步展開SYSCALL_DEFINEx時(原型以下):

點擊(此處)摺疊或打開

  1. #define SYSCALL_DEFINEx(x, sname, ...) \
  2.    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)


    其中,宏__VA_ARGS__所表明的參數列表就是「const char __user *, filename, int, flags, umode_t, mode」。一樣地狀況,最後在替換__SC_DECL3(__VA_ARGS__)__VA_ARGS__的變化過程是:

  1. __SC_DECL3:__VA_ARGS__ <=>const char __user *, filename, int, flags, umode_t, mode
  2. __SC_DECL2:__VA_ARGS__ <=> int, flags, umode_t, mode
  3. __SC_DECL1:__VA_ARGS__ <=> umode_t, mode


    最終__SC_DECL3(__VA_ARGS__)就變成了:const char __user *filename, int flags, umode_t, mode,即open系統調用的三個輸入參數。

    咱們能夠看到宏定義在內核大牛們的手裏是被玩得多麼服帖。那麼有些朋友可能會問了:一個系統調用至於弄這麼複雜麼?難道大牛們在show他們的技能?固然絕對不是醬紫滴。

 

    2009年曾爆出關於Linux系統調用在某些64平臺發現有漏洞CVE-2009-0029,爲了修復該問題,自2.6.28以後系統調用才變成了如今這個樣子。該問題基本描述是:在Linux 2.6.28及之前版本內核中,IBM/S390PowerPCSparc64以及MIPS 64位平臺的ABI要求在系統調用時,用戶空間程序將系統調用中32位的參數存放在64位的寄存器中要作到正確的符號擴展,可是用戶空間程序卻不能保證作到這點,這樣就會能夠經過向有漏洞的系統調用傳送特製參數即可以致使系統崩潰或得到權限提高。

    既然問題已經很明確了,接下來就是如何解決的問題了。一般狀況咱們都會這樣作:既然在執行系統調用時用戶空間沒有進行寄存器的符號擴展,那麼咱們就在系統調用函數前加入一些彙編代碼,將寄存器進行符號擴展不就OK了麼。但問題是:系統調用前的代碼都是公共的,所以並不能將某個寄存器必定符號擴展。

 

    在Linux內核中,解決這個問題的辦法很巧妙,它先將系統調用的全部輸入參數都當成long類型(64位),而後再強制轉化到相應的類型,這樣就能解決問題了。若是去每一個系統調用中一一這麼作,這是通常程序員選擇的作法,但寫內核的大牛們不只要完成功能,並且完成得有藝術!因此在上述IBM/S390、PowerPC、Sparc64以及MIPS 64位平臺上,這就出現瞭如今的作法,定義了下面的宏:

點擊(此處)摺疊或打開

  1. #define __SYSCALL_DEFINEx(x, name, ...) \
  2. asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \
  3. static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \
  4. asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \
  5. { \
  6.       __SC_TEST##x(__VA_ARGS__); \
  7.       return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
  8. } \
  9. SYSCALL_ALIAS(sys##name, SyS##name); \
  10. static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
    若是是這些平臺上,open系統調用的擴展狀況就變成以下的樣子了:

點擊(此處)摺疊或打開

  1. asmlinkage long sys_open(const char __user * filename, int flags, umode_t mode);
  2. static inline long SYSC_open(const char __user * filename, int flags, umode_t mode);
  3. asmlinkage long SyS_open((long)filename, (long)flags, (long)mode)
  4. {
  5.   __SC_TEST3(const, char __user * filename, int, flags, umode_t, mode);
  6.   return (long)SYSC_open(const char __user * filename, int flags, umode_t mode);
  7. }

  8. SYSCALL_ALIAS(sys_open, SyS_open);
  9. static inline long SYSC_open(const char __user * filename, int flags, umode_t mode)
  10. {
  11.   …
  12. }

   __SC_TEST3 宏沒繼續展開,由於它是編譯時檢查類型是否錯誤的代碼,和咱們這裏討論的關係不大。另外,SYSCALL_ALIAS 宏也沒展開,意思即 sys_open 函數的別名是 SyS_open,這樣一來當執行系統調用sys_open時因爲別名宏的修飾,其實就是在調用SyS_open,而該函數的全部輸入參數皆爲long類型,該函數又直接調用 SYSC_open,而 SYSC_open 函數的參數又轉化爲 sys_open 原來正確的類型。這樣一來就消除了用戶空間不保證參數符號擴展的問題了,由於此時實際上系統調用函數由 SyS_open 函數完成了,它來保證 32 位寄存器參數正確的符號擴展。

    因爲某些體系結構是不存在此類問題的,如x86_64等,Linux內核定義了一個配置選項CONFIG_HAVE_SYSCALL_WRAPPERS,一開始介紹的擴展的宏定義是在沒有配置該選項擴展的結果,若是是S390PowerPCSparc 64等平臺就須要配置該選項。和CVE-2009-0029漏洞相似的還有X86_64位系統爆出的CVE-2010-3301漏洞,感興趣的朋友能夠去了解一下。


參考文獻:
http://blog.csdn.net/hazir/article/details/11835025
相關文章
相關標籤/搜索