关于Delphi中参数的传递和函数值的返回
前言:
高手们应该早知道了,不屑于写出来而已。真正的高手一个比一个潜的深,只剩下偶这样的小菜写些菜文给更小的菜。高手看时还请捂好大牙,多多指点。
不知各位小菜同胞对破解DELPHI程序有什么看法,反正我的感觉就一个字:怪。各位最先遇到的问题恐怕都是:我下了GetDlgItemInt、GetDlgItemText、GetWindowText....怎么什么也断不下来,甚至连Hmemcpy都不起作用?呵呵,从这里就能看出宝蓝的那批人成心想跟M$对着干,非搞出些新鲜的东东不可。
这回我们就来看看DLEPHI中对函数(过程)参数的传递是如何进行的。
我们知道WinAPI采用的调用约定是StdCall,也就是调用一个函数Func(arg1,agr2,agr3,arg4),你需要push arg4,push arg3,push arg2,push arg1,call Func 。在VC++里也是这种形式,所以一个函数有几个参数,可以非常直观地看出来。可是在DELPHI中就很奇怪了,在一个CALL前面你可能一个PUSH也看不到,怎么回事呢?听我慢慢道来。
DELPHI中的调用约定有StdCall,Cdecl,Safecall,Pascal和Register等几种方式,而DELPHI的默认方式是Register(为什么不是Pascal?)Register方式就是尽可能地使用寄存器来传递参数,减少堆栈的操作来提高速度。具体情况是怎样呢,看个例子先:
在FORM上放一个BUTTON,双击写代码如下:
代码:
function add1(a:Integer):Integer; //一个参数
begin
add1:=a+a;
end;
function add2(a,b:Integer):Integer; //两个参数
begin
add2:=a+b;
end;
function add3(a,b,c:Integer):Integer; //三个参数
begin
add3:=a+b+c;
end;
function add4(a,b,c,d:Integer):Integer; //四个参数
begin
add4:=a+b+c+d;
end;
function add5(a,b,c,d,e:Integer):Integer; //五个参数
begin
add5:=a+b+c+d+e;
end;
function add6:Integer; //加入一些局部变量
var local1,local2,local3,local4,local5:Integer;
begin
local1:=1;
local2:=2;
local3:=3;
local4:=4;
local5:=5;
add6:=local1+local2+local3+local4+local5;
end;
function add7(a,b,c,d,e:Integer):Integer; //利用result来返回
begin
result:=a+b+c+d+e;
end;
function add8(a,b,c,d,e:Integer):Integer;StdCall;//StdCall调用方式
begin
add8:=a+b+c+d+e;
end;
procedure TForm1.Button1Click(Sender: TObject);
var a,b,c,d,e:Integer;
s1,s2,s3,s4,s5,s6,s7,s8,s:Integer;
begin
a:=1; b:=2; c:=3; d:=4; e:=5;
s1:=add1(a);
s2:=add2(a,b);
s3:=add3(a,b,c);
s4:=add4(a,b,c,d);
s5:=add5(a,b,c,d,e);
s6:=add6;
s7:=add7(a,b,c,d,e);
s8:=add8(a,b,c,d,e);
s:=s1+s2+s3+s4+s5+s6+s7+s8; //必须要有这么几句
MessageDlg(IntToStr(s),mtConfirmation,[mbOK],0); //不然编译器根本不去处理返回值
end;
用DEDE反一下看看,这个Button1Click的内容:
代码:
004403EC 55 push ebp
004403ED 8BEC mov ebp, esp
004403EF 83C4D8 add esp, -$28 ;空出地方放局部变量
004403F2 53 push ebx
004403F3 56 push esi
004403F4 57 push edi
004403F5 33C9 xor ecx, ecx
004403F7 894DD8 mov [ebp-$28], ecx
004403FA 33C0 xor eax, eax
004403FC 55 push ebp
* Possible String Reference to: '関-?腽_^[嬪]?
|
004403FD 68E9044400 push $004404E9
***** TRY
|
00440402 64FF30 push dword ptr fs:[eax] ;这是DELPHI的例行公事
00440405 648920 mov fs:[eax], esp ;据我观察只要调用VCL库的都要SEH
00440408 BB01000000 mov ebx, $00000001 ;a:=1
0044040D BE02000000 mov esi, $00000002 ;b:=2
00440412 BF03000000 mov edi, $00000003 ;c:=3
00440417 C745FC04000000 mov dword ptr [ebp-$04], $00000004 ;d:=4
0044041E C745F805000000 mov dword ptr [ebp-$08], $00000005 ;e:=5
可以看出DELPHI的确不一样,把EBX,ESI,EDI能用的寄存器全都用上了,实在不行了才用[ebp-xx],
从下面的分析中也能看出这一点,DELPHI在能用寄存器时决不用堆栈。
00440425 8BC3 mov eax, ebx ;这是add1的参数啦,不用PUSH的
* Reference to : TForm1.Proc_00440360()
|
00440427 E834FFFFFF call 00440360 ;CALL add1
{
00440360 03C0 add eax, eax
00440362 C3 ret ;这样的确很快哟
}
0044042C 8945F4 mov [ebp-$0C], eax ;s1:=add1(a)
0044042F 8BD6 mov edx, esi ;add2的参数EDX=2
00440431 8BC3 mov eax, ebx ;add2的参数EAX=1
* Reference to : TForm1.Proc_00440364()
|
00440433 E82CFFFFFF call 00440364 ;CALL add2
{
00440364 03D0 add edx, eax
00440366 8BC2 mov eax, edx
00440368 C3 ret
}
00440438 8945F0 mov [ebp-$10], eax ;s2:=add2(a,b)
0044043B 8BCF mov ecx, edi ;add3的参数ECX=3
0044043D 8BD6 mov edx, esi ;EDX=2
0044043F 8BC3 mov eax, ebx ;EAX=1
* Reference to : TForm1.Proc_0044036C()
|
00440441 E826FFFFFF call 0044036C ;CALL add3
{
0044036C 03D0 add edx, eax
0044036E 03CA add ecx, edx
00440370 8BC1 mov eax, ecx
00440372 C3 ret
}
00440446 8945EC mov [ebp-$14], eax ;s3:=add3(a,b,c)
00440449 8B45FC mov eax, [ebp-$04] ;[EBP-4]=4
0044044C 50 push eax ;终于看见PUSH了噢
0044044D 8BCF mov ecx, edi ;ECX=3
0044044F 8BD6 mov edx, esi ;EDX=2
00440451 8BC3 mov eax, ebx ;EAX=1
* Reference to : TForm1.Proc_00440374()
|
00440453 E81CFFFFFF call 00440374 ;CALL add4
{
00440374 55 push ebp
00440375 8BEC mov ebp, esp ;这是C里面的方式啦
00440377 03D0 add edx, eax
00440379 03CA add ecx, edx
0044037B 034D08 add ecx, [ebp+$08];[EBP+8]本来是第一个参数的
0044037E 8BC1 mov eax, ecx ;这里[EBP+8]是第四个参数
00440380 5D pop ebp
00440381 C20400 ret $0004
}
00440458 8945E8 mov [ebp-$18], eax ;s4:=add4(a,b,c,d)
0044045B 8B45FC mov eax, [ebp-$04] ;[EBP-4]=4
0044045E 50 push eax ;注意:先压进去的是第四个参数
0044045F 8B45F8 mov eax, [ebp-$08] ;[EBP-8]=5
00440462 50 push eax ;再压进第五个参数,Pascal从左至右
00440463 8BCF mov ecx, edi ;ECX=3
00440465 8BD6 mov edx, esi ;EDX=2
00440467 8BC3 mov eax, ebx ;EAX=1
* Reference to : TForm1.Proc_00440384()
|
00440469 E816FFFFFF call 00440384 ;CALL add5(a,b,c,d,e)
0044046E 8945E4 mov [ebp-$1C], eax ;s5=add5(a,b,c,d,e)
* Reference to : TForm1.Proc_00440398()
|
00440471 E822FFFFFF call 00440398 ;add6 看看DLEPHI怎么处理局部变量
{
00440398 53 push ebx
00440399 56 push esi
0044039A B801000000 mov eax, $00000001
0044039F BA02000000 mov edx, $00000002
004403A4 B903000000 mov ecx, $00000003
004403A9 BB04000000 mov ebx, $00000004
004403AE BE05000000 mov esi, $00000005;哈哈,果然不出所料
004403B3 03D0 add edx, eax ;它用上了一切能用的寄存器
004403B5 03CA add ecx, edx ;各位可以试试加上十来个局部变量
004403B7 03D9 add ebx, ecx ;看它能坚持到几时
004403B9 03F3 add esi, ebx
004403BB 8BC6 mov eax, esi
004403BD 5E pop esi
004403BE 5B pop ebx
004403BF C3 ret
}
00440476 8945E0 mov [ebp-$20], eax
00440479 8B45FC mov eax, [ebp-$04]
0044047C 50 push eax
0044047D 8B45F8 mov eax, [ebp-$08]
00440480 50 push eax
00440481 8BCF mov ecx, edi
00440483 8BD6 mov edx, esi
00440485 8BC3 mov eax, ebx
* Reference to : TForm1.Proc_004403C0() ;我想看看用result是不是有不同
|
00440487 E834FFFFFF call 004403C0 ;其实和add5一样的,不写了
0044048C 8945DC mov [ebp-$24], eax
0044048F 8B45F8 mov eax, [ebp-$08]
00440492 50 push eax ;PUSH 5
00440493 8B45FC mov eax, [ebp-$04]
00440496 50 push eax ;PUSH 4
00440497 57 push edi ;PUSH 3
00440498 56 push esi ;PUSH 2
00440499 53 push ebx ;PUSH 1
* Reference to : TForm1.Proc_004403D4()
|
0044049A E835FFFFFF call 004403D4 ;这个眼熟的吧,从右至左的StdCall方式
{
004403D4 55 push ebp
004403D5 8BEC mov ebp, esp
004403D7 8B4508 mov eax, [ebp+$08]
004403DA 03450C add eax, [ebp+$0C]
004403DD 034510 add eax, [ebp+$10]
004403E0 034514 add eax, [ebp+$14]
004403E3 034518 add eax, [ebp+$18]
004403E6 5D pop ebp
004403E7 C21400 ret $0014 ;我还是觉得这样好看一些
}
* Reference to Form1
|
0044049F 8B5DF4 mov ebx, [ebp-$0C]
004404A2 035DF0 add ebx, [ebp-$10]
004404A5 035DEC add ebx, [ebp-$14]
004404A8 035DE8 add ebx, [ebp-$18]
004404AB 035DE4 add ebx, [ebp-$1C]
004404AE 035DE0 add ebx, [ebp-$20]
004404B1 035DDC add ebx, [ebp-$24]
004404B4 03D8 add ebx, eax ;加起来
........下面的不写了,还值得一提的是在最后DELPHI总要弄出两个RET来,跳来跳去的,也算是DELPHI的特色吧。
上面讲的是自己定义的函数,要是用VCL库的东东,有时候更加莫名其妙一些。看例子:
建一个FORM,放一个BUTTON,一个EDIT,代码如下:
代码:
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageDlg(edit1.text,mtConfirmation,[mbOK],0);
end;
呵呵太简单了是不是,用DEDE反下:(只写了关键部分)
代码:
004417BE 6A00 push $00 ;这是下面MessageDlg的第四个参数,找到没
004417C0 8D55FC lea edx, [ebp-$04];??这是什么??
* Reference to control TForm1.Edit1 : TEdit
|
004417C3 8B83C8020000 mov eax, [ebx+$02C8] ;这是下面GetText的参数TControl吧
;看上面的Reference
* Reference to: controls.TControl.GetText(TControl):TCaption;
|
004417C9 E8D619FEFF call 004231A4 ;得到EDIT的文本
004417CE 8B45FC mov eax, [ebp-$04];这是参数一要显示的字串放入EAX
004417D1 668B0D00184400 mov cx, word ptr [$00441800];这应该是参数二mtConfirmation
004417D8 B203 mov dl, $03 ;这是参数三[mbOK]
* Reference to: Dialogs.Proc_00441380
|
004417DA E8A1FBFFFF call 00441380 ;这个是MessageDlg
004417DF 33C0 xor eax, eax
如果按照上面的分析,看到GetText这里应该只有一个参数就是放入EAX的那个[ebx+02c8],从参考也可以看到这就是EDIT1,可是函数的返回值呢?刚执行完这个CALL后EAX中是没有的,mov eax,[ebp-04]后才出现了,返回值原来在[ebp-4]中。再向上找有一个莫名其妙的lea edx,[ebp-04],按照我上面的分析这应该表示GetText的第二个参数。可是GetText只有一个参数呀。 这种需要返回一个比较大的结构的函数,在VC中常用的方法是把一个指针当参数传递过去,而DLEPHI中我猜是不是做成一个隐藏的参数,像上面的GetText表面上看是返回一个TCaption,实际这个并不是放在EAX里返回来的。
总结一下:DELPHI对参数的传递是尽可能多地利用寄存器,一般第一个参数用EAX,第二个参数用EDX,第三个参数用ECX,多于三个参数的时候,对多出来的参数按照从左至右的PASCAL方式来压栈。 对于函数的返回值,有时尽管声明中说它的返回值是TCaption之类等,实际上并没有在EAX中返回,而是在保存一个隐藏的参数中,等需要时再复制过来。(这一点是猜想而已,如果哪位高人知道的话还请指点。反正我以前都是糊里糊涂地跟,结果出来就算了。其实仔细一分析还有点意思。)
|
请发表评论