找回密码
 加入计匠网
搜索
热搜: BIOS ACPI CPU Windows
查看: 10906|回复: 4

[原创]VA_ARG IN EFI

[复制链接]
发表于 2009-10-22 20:19:17 | 显示全部楼层 |阅读模式
VA_ARG IN EFI
1. Introduction
1 @( X  u& g8 m2 D: o0 q  R2 C" u
  l/ ^/ p+ ~/ ]1 w可变参数其实是标准C语言一个内建的功能,它和EFI本身并没有太多关系。但是它在EDK中有重新实现和使用,而且我们家的code base使用频繁,很多oem callback都使用了可变参数以此获得函数格式的统一以及参数传递的灵活性。所以我就提一下可变参数的实现,希望对Legacy BIOS转过来的以及对C不是很熟悉的朋友有所帮助。/ @% U' \( X: e4 t

; z( L. b! s! Y5 Z2. Function Call Impl
- ?# w0 ]2 D0 p: `9 v8 l
" Q% \! D: P# {要想搞明白可变参数的实现,那就肯定不能不提C的运行所必需的一个核心部件stack。离开了stackC是没法活J,这也是为什么EFI code 只能99%而无法100%C实现的其中一个原因(sec阶段需要准备好stack然后才可以交棒给Ccode)。先看一看函数调用过程中stack的变化状况:
. f, m; t5 O. K8 Q$ L: B
void testq(int a,int b)
{
     int tmp;
) q  l/ }" }: O9 C/ W7 k- k
a = a;
% ]# X2 A% |; g& ?2 O
b = b;
0 t$ ^! L( q/ H  p& I- e
testr();
}
void testp(void)
{
, l/ q& p9 _$ i/ Q
testq(1,2);
}
testp调用testq,这时stack的状况如下图1所示
7 ^  e* T7 ]7 d  g% I! z+ p: @

: H& D9 }) \; m+ f  Z- C' Q
6 h4 J. H  A* E* ]% m4 z& u1 i通常情况下stack由高地址向低地址增长,压进去一个参数esp就会减小,弹出当然就会增加而且通常会以机器字对齐。一个函数保存局部变量以及调用下一级函数所需要的stack空间被称作一个frame。如上图1所示以ebp所指向的地址为界,ebp上方的为一个frame,下方包括保存的testpebp为另一个frameebp的存在也方便了函数参数,和局部变量的存取。ebp+n即可取出参数,ebp-n取出局部变量。函数调用参数进栈的顺序与平台和编译器有关,但通常都是从右向左进栈,所以testp会将b先进栈,然后是a接下来保存返回地址(testq返回时继续执行的位置)。了解了这些知识,就足以揭开可变参数的面纱了,下面就来看看可变参数的实现。# B/ \9 I( ?4 l# }; s5 L' @/ Q
" K* W5 t$ Q4 M# u  O; h
3. VA_START, VA_ARG,VA_END
+ S+ L! q' u! P4 C4 O, q' V9 U  n5 M: X% D' m
! q/ Y# I1 W9 \$ W8 }9 X
这三个宏就是可变参数的所有秘密所在了,所有的代码一共不超过十行,可是如果不清楚前面所提到到stack的布局,想搞明白这几行代码也不是很容易哦。翠花上codeJedk中的实现如下所示:
" _2 o1 E( M8 ?- N9 @# ^
#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). R5 g. P6 S4 V' A7 f+ F
(*(t *) ((ap += _EFI_INT_SIZE_OF (t)) - _EFI_INT_SIZE_OF (t)))
#define VA_END(ap)  X5 h5 U  r( Y% X, G
(ap = (VA_LIST) 0)
#endif
用一段测试sample code,演示和讲解一下可变参数的使用和原理
$ W( M6 l0 H. |
void OemCallBack(

) o4 \8 l/ X0 |1 a, L! G% k" |# g9 YIN OEMCALLBACK
- F  X1 a) G4 f9 k& b! K*
this,

4 e- _# c( R! m' iIN UINT32" A" _5 T$ w, v" m" {/ J
NumOfArgs,
0 {! [! k  p/ q, N7 e- y* _
...

2 ?1 w2 Y- l8 ^* C0 |' q9 v' {! }+ q- V)
{
/ H! V+ Z& o, K, n2 L$ A6 X
VA_LIST: n8 W* q& _- Z4 h
Marker;
% f3 I- i- G& P! R
UINT32& d% j, ], w" i) X
Tmp;
) n: q. F& l' d: c2 R
UINT32! {. q2 ^; J8 P4 o+ i1 |! C. Z! z
Cont;

9 w3 S# X5 Y5 t$ w* lVA_START (Marker, NumOfArgs);
6 `' J; M9 U' I7 l# Q
  for(Cont = 0x00; Cont < NumOfArgs; ++Cont)
4 D4 U5 e. H" Y$ V  Y) [
{

/ r. r( S  @, W' z" QTmp = VA_ARG (Marker, UINT32);

$ l6 N. I/ ]% `/ A/ yprintf(
"The value is :%d,",Tmp);

% H. W/ \8 X: I) a5 N' b. {}
0 {: ]% D: y1 v
0 [; R2 W+ l- i8 ^" e+ ~7 a6 L. d
printf(
"\n");

5 @/ A  _2 X5 t  s" j4 SVA_END (Marker);
  return EFI_SUCCESS;
}
int main (int argc,char** argv)
{

8 }( e" t: W: l% v  i) VOemCallBack(NULL,3,5,10,33);
     return 0;
}/ K& @' J! H0 k6 y/ J
先来看调用栈长的什么模样,再来分析实现原理吧,调用栈如下图2所示:' m6 q- u4 Y0 ^  |, V6 w4 m
( j  {, C& m0 y/ Q9 A9 R. P
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,否则无法获得可变参数的首地址。
$ L# z) {8 f! Z( @# h
9 |* M; `! G4 S) A
以上就是可变参数的所有内容了,希望有人能够从中获得帮助,也不枉我一番辛苦。再写要吐血了,闪!+ R; j( o) K  y5 @

9 w. Q8 |/ M2 O7 tPeter% g# N$ O+ S* p$ h
2009-10-22

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?加入计匠网

×
发表于 2009-10-23 15:36:59 | 显示全部楼层
沙发~~~!
2 H) S4 r, A1 {: p* oVA_ARG在code中是会经常遇到的。。。。
% K7 f4 B9 i; U6 M( k/ {楼主剖析了其基本原理及实现方式,可谓拨云见日,受益不小啊!: N1 I0 ]4 J5 r2 Z- v( z
& O% `* v. X& C7 [* }
[ 本帖最后由 adward 于 2009-10-23 15:54 编辑 ]
回复

使用道具 举报

发表于 2009-10-23 15:37:45 | 显示全部楼层

楼主辛苦

谢谢楼主总结此功能
回复

使用道具 举报

发表于 2010-1-25 19:14:11 | 显示全部楼层
楼主辛苦了!thanks!
回复

使用道具 举报

发表于 2010-1-27 18:32:29 | 显示全部楼层
相当清楚- Z; M1 j) g' m
只是还是不明白_EFI_INT_SIZE_OF这个的作用
( J4 \; B" b; f也就是我看不出来_EFI_INT_SIZE_OF(n)和sizeof(n)的区别6 X, h! h3 I) D; S( m
楼主能不能再指教一下?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

Archiver|手机版|小黑屋|计匠网

GMT+8, 2026-2-1 12:38 , Processed in 0.287530 second(s), 18 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表