VA_ARG IN EFI

來源:互聯網
上載者:User

http://blog.csdn.net/hgf1011/article/details/4713371

這貨果然是大牛啊,,講的很好,留下來免得以後又找不到

VA_ARG IN EFI

1. Introduction

 

可變參數其實是標準C語言一個內建的功能,它和EFI本身並沒有太多關係。但是它在EDK中有重新實現和使用,而且我們家的code base使用頻繁,很多oem callback都使用了可變參數以此獲得函數格式的統一以及參數傳遞的靈活性。所以我就提一下可變參數的實現,希望對Legacy BIOS轉過來的以及對C不是很熟悉的朋友有所協助。

 

2. Function Call Impl

 

      要想搞明白可變參數的實現,那就肯定不能不提C的運行所必需的一個核心組件stack。離開stack,C是沒法活J,這也是為什麼EFI
code 只能99%而無法100%用C實現的其中一個原因(sec階段需要準備好stack然後才可以交棒給C的code)。先看一看函數調用過程中stack的變化狀況:

 

void testq(int a,int b)

{

     int tmp;

     a = a;

     b = b;

     testr();

}

 

void testp(void)

{

     testq(1,2);

}

testp調用testq,這時stack的狀況如1所示

 

通常情況下stack由高地址向低地址增長,壓進去一個參數esp就會減小,彈出當然就會增加而且通常會以機器字對齊。一個函數儲存局部變數以及調用下一級函數所需要的stack空間被稱作一個frame。如1所示以ebp所指向的地址為界,ebp上方的為一個frame,下方包括儲存的testp的ebp為另一個frame。ebp的存在也方便了函數參數,和局部變數的存取。ebp+n即可取出參數,ebp-n取出局部變數。函數調用參數進棧的順序與平台和編譯器有關,但通常都是從右向左進棧,所以testp會將b先進棧,然後是a接下來儲存返回地址(從testq返回時繼續執行的位置)。瞭解了這些知識,就足以揭開可變參數的面紗了,下面就來看看可變參數的實現。

 

3. VA_START, VA_ARG,VA_END

 

      這三個宏就是可變參數的所有秘密所在了,所有的代碼一共不超過十行,可是如果不清楚前面所提到到stack的布局,想搞明白這幾行代碼也不是很容易哦。翠花上codeJ,edk中的實現如下所示:

 

#define _EFI_INT_SIZE_OF(n) ((sizeof (n) + sizeof (UINTN) - 1) &~(sizeof (UINTN) - 1))

//

// Also support coding convention rules for var arg macros

//

#ifndef VA_START

 

typedef CHAR8 *VA_LIST;

#define VA_START(ap, v) (ap = (VA_LIST) & (v) + _EFI_INT_SIZE_OF (v))

#define VA_ARG(ap, t)   (*(t *) ((ap += _EFI_INT_SIZE_OF (t)) - _EFI_INT_SIZE_OF (t)))

#define VA_END(ap)      (ap = (VA_LIST) 0)

 

#endif

用一段測試sample code,示範和講解一下可變參數的使用和原理

void OemCallBack(

  IN OEMCALLBACK              *this,

  IN UINT32                   NumOfArgs,

  ...

  )

{

  VA_LIST                               Marker;

  UINT32                                  Tmp;

  UINT32                                  Cont;

 

  VA_START (Marker, NumOfArgs);

 

  for(Cont = 0x00; Cont < NumOfArgs; ++Cont)

  {

     Tmp = VA_ARG (Marker, UINT32);

     printf("The value is :%d,",Tmp);

  }

 

  printf("/n");

 

  VA_END (Marker);

 

  return EFI_SUCCESS;

}

 

int main (int argc,char** argv)

{

     OemCallBack(NULL,3,5,10,33);

     return 0;

}

先來看調用棧長的什麼模樣,再來分析實現原理吧,調用棧如2所示:

 

VA_START展開以後就是(Marker = (VA_LIST) & (NumOfArgs) + _EFI_INT_SIZE_OF (NumOfArgs))也即求出NumOfArgs之後的參數的地址,圖中紅色部分,也就是可變參數列的首地址。VA_ARG展開以後就有點意思了:(*(UINT32 *) ((Marker += _EFI_INT_SIZE_OF (UINT32)) - _EFI_INT_SIZE_OF (UINT32)))這裡就是defrence出當前Marker指向的地址的t類型的值,並且移動Marker指標為下一輪做準備,這就是“Marker
+= _EFI_INT_SIZE_OF (UINT32)”奧妙所在。這樣逐次移動Marker指標就可以遍曆出所有的可變參數了。VA_END就沒什麼好說的了,防止出現野指標:Marker = (VA_LIST) 0。最後一個_EFI_INT_SIZE_OF它是為了特定平台的記憶體對齊的需要,因為這個UINTN在不同的平台下大小不同,所以使用這個宏會將記憶體對齊到一個機器字。關於可變參數還有要特彆強調的地方就是:一定要有結束標識,否則程式無法識別參數的個數,OemCallBack中的NumOfArgs就給出了參數的個數,另外就是至少要有一個不變的參數J,否則無法獲得可變參數的首地址。

 

      以上就是可變參數的所有內容了,希望有人能夠從中獲得協助,也不枉我一番辛苦。再寫要吐血了。

 

Peter

2009-10-22

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.