关于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中返回,而是在保存一个隐藏的参数中,等需要时再复制过来。(这一点是猜想而已,如果哪位高人知道的话还请指点。反正我以前都是糊里糊涂地跟,结果出来就算了。其实仔细一分析还有点意思。)