首页
社区
课程
招聘
关于Delphi中参数的传递和函数值的返回
发表于: 2004-8-26 14:41 11187

关于Delphi中参数的传递和函数值的返回

RoBa 活跃值
16
2004-8-26 14:41
11187
关于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	;看它能坚持到几时:D
	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中返回,而是在保存一个隐藏的参数中,等需要时再复制过来。(这一点是猜想而已,如果哪位高人知道的话还请指点。反正我以前都是糊里糊涂地跟,结果出来就算了。其实仔细一分析还有点意思。)

[课程]Linux pwn 探索篇!

收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
2
恩.不错不错
还真没注意过
不过Delphi可以下hmemcpy
在有messageboxa的时候下hmemcpy,然后回到程序领空,F12七次就会弹出对话框.而VC3次就够
这是我判断VC和DElphi的方法,还没碰到过不对的时候
2004-8-26 17:51
0
雪    币: 414
活跃值: (531)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
心很细啊。Roba值得鼓励!
2004-8-27 07:18
0
雪    币: 212
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学了东西:D 当然要顶
2004-8-27 08:30
0
雪    币: 205
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
Roba老大
    小的不才,斗胆给你解释一下最后一段的疑惑


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



以下的解释只是我个人的理解,并没有看到有关的官方资料,有待确认。

Delphi除了利用寄存器、栈(Stack)来传递参数和存放局部变量外,还利用了很重要的一种数据结构:堆(Heap)。堆通常放在比较高的地址段,感觉象malloc这样用于动态分配内存的区域。

上面代码中,edx,eax都是GetText的参数,GetText会将字符串内容复制一份到堆中,并将堆指针赋给edx。因此,局部变量[ebp-$04]是用来存放字符串指针的。(当然,我们也可以就把它看成字符串)

eax存放的是Tedit的对象ID(不知道Borland怎么称呼它),只用通过它,GetText例程才知道从哪个对象中获取字符串。这就象我们司空见惯的窗口句柄、进程ID一样。

另外值得注意的一个地方是所有的对象ID都存放在一个地方,这就象GDT,LDT一样,有这么一个ID表。因此在Delphi的编译代码中,对对象的引用总是以[ebp+offset]的方式。从另一个角度来说,知道这个基址ebp对逆向分析来说是很有帮助的。

希望我的解释能给大家一些提示。
2004-8-28 09:20
0
雪    币: 204
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
不错, 学习!
2004-8-28 09:39
0
雪    币: 519
活跃值: (1223)
能力值: ( LV12,RANK:650 )
在线值:
发帖
回帖
粉丝
7
谢谢指点,不知道有没有这方面的资料:)
2004-8-28 09:42
0
雪    币: 228
活跃值: (85)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
李维的 "Inside VCL"
这里有李维的版面
http://forum.vclxx.org

详细说明了 VCL 的生与死、和内部机制
2004-8-28 10:32
0
雪    币: 392
活跃值: (909)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
9
学习!
nbw兄还在98上呀:D
2004-8-28 10:37
0
雪    币: 258
活跃值: (230)
能力值: ( LV12,RANK:770 )
在线值:
发帖
回帖
粉丝
10
不错・・・・
学习ing・・
2004-9-1 15:47
0
游客
登录 | 注册 方可回帖
返回
//