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