您的位置:首页 > 其它

函数调用 堆栈

2009-11-10 16:03 309 查看
这段代码反汇编后,代码是什么呢?

#include
<
stdio.h
>

long
test(
int
a,
int
b)

{

a
=
a
+

3
;

b
=
b
+

5
;

return
a
+
b;

}

int
main(
int
argc,
char
*
argv[])

{

printf(
"
%d
"
,test(
10
,
90
));

return

0
;

}

先来看一个概貌

16
:
int
main(
int
argc, char* argv[])

17
: {

00401070

push
ebp

00401071

mov
ebp,esp

00401073

sub
esp,40h

00401076

push
ebx

00401077

push
esi

00401078

push
edi

00401079

lea
edi,[ebp-40h]

0040107C

mov
ecx,10h

00401081

mov
eax,0CCCCCCCCh

00401086

rep
stos dword ptr [edi]

18
: printf(
"
%d
"
,
test
(
10
,
90
))
;

00401088

push
5Ah

0040108A

push
0Ah

0040108C

call
@ILT+
0
(
test
) (
00401005
)

00401091

add
esp,
8

00401094

push
eax

00401095

push
offset string
"
%d
"
(
0042201c
)

0040109A

call
printf (
004010d0
)

0040109F

add
esp,
8

19
: return
0
;

004010A2

xor
eax,eax

20
: }

下面来解释一下



开始进入Main函数 esp=0x12FF84 ebp=0x12FFC0

完成椭圆形框起来的部分

00401070 push ebp ebp的值入栈,保存现场(调用现场
,从test函数看,如红线所示,即保存的0x12FF80用于从test函数堆栈返回到main函数)

00401071 mov ebp,esp
此时ebp=0x12FF80 此时ebp就是“当前函数
堆栈”的基址 以便访问堆栈中的信息;还有就是从当前函数栈顶返回到栈底


00401073 sub esp,40h

函数使用的堆栈,默认64个字节,堆栈上就是16个横条(密集线部分)此时esp=0x12FF40

在上图中,上面密集线是test函数堆栈空间,下面是Main的堆栈空间 (补充,其实这个就叫做 Stack Frame
)

00401076 push ebx

00401077 push esi

00401078 push edi 入栈

00401079 lea edi,[ebp-40h]

0040107C mov ecx,10h

00401081 mov eax,0CCCCCCCCh

00401086 rep stos dword ptr [edi]

初始化用于该函数的栈空间为0XCCCCCCCC 即从0x12FF40~0x12FF80所有的值均为0xCCCCCCCC

18: printf("%d",test(10,90));

00401088 push 5Ah 参数入栈 从右至左 先90 后10

0040108A push 0Ah

0040108C call @ILT+0(test) (00401005)

函数调用,转向eip 00401005

注意,此时仍入栈,入栈的是call test 指令下一条指令的地址00401091 下一条指令是add esp,8

@ILT+0(?test@@YAJHH@Z):

00401005 jmp test (00401020)

即转向被调函数test

8

: long

test

(

int

a,

int

b)

9

: {

00401020

push

ebp

00401021

mov

ebp,esp

00401023

sub

esp,40h

00401026

push

ebx

00401027

push

esi

00401028

push

edi

00401029

lea

edi,[ebp-40h]

0040102C

mov

ecx,10h

00401031

mov

eax,0CCCCCCCCh

00401036

rep

stos dword ptr [edi] //这些和上面一样

10

: a = a +

3

;

00401038

mov

eax,dword ptr [ebp+

8

] //ebp=0x12FF24 加8 [0x12FF30]即取到了参数10

0040103B

add

eax,

3

0040103E

mov

dword ptr [ebp+

8

],eax

11

: b = b +

5

;

00401041

mov

ecx,dword ptr [ebp+0Ch]

00401044

add

ecx,

5

00401047

mov

dword ptr [ebp+0Ch],ecx

12

: return a + b

;

0040104A

mov

eax,dword ptr [ebp+

8

]

0040104D

add

eax,dword ptr [ebp+0Ch] //最后的结果保存在eax, 结果得以返回

13

: }

00401050

pop

edi

00401051

pop

esi

00401052

pop

ebx

00401053

mov

esp,ebp //esp指向0x12FF24, test函数的堆栈空间被放弃,从当前函数栈顶返回到栈底

00401055

pop

ebp //此时ebp=0x12FF80, 恢复现场 esp=0x12FF28

00401056

ret ret负责栈顶0x12FF28之值00401091弹出到指令寄存器中,

esp=0x12FF30

因为win32汇编一般用eax返回结果 所以如果最终结果不是在eax里面的话 还要把它放到eax

注意,从被调函数返回时,是弹出
EBP,恢复堆栈到函数调用前的地址,
弹出返回地址到
EIP以继续执行程序。

从test函数返回,执行

00401091 add esp,8

清栈,清除两个压栈的参数10 90 调用者main负责

(所谓__cdecl调用由调用者负责恢复栈,调用者负责清理的只是入栈的参数,test函数自己的堆栈空间自己返回时自己已经清除,靠!一直理解错)

00401094 push eax 入栈,计算结果108入栈,即printf函数的参数之一入栈

00401095 push offset string "%d" (0042201c) 入栈,参数 "%d" 当然其实是%d的地址

0040109A call printf (004010d0) 函数调用 printf("%d",108) 因为printf函数时

0040109F add esp,8 清栈,清除参数 ("%d", 108)

19: return 0;

004010A2 xor eax,eax eax清零

20: }

main函数执行完毕 此时esp=0x12FF34 ebp=0x12FF80

004010A4 pop edi

004010A5 pop esi

004010A6 pop ebx

004010A7 add esp,40h //为啥不用mov esp, ebp? 是为了下面的比较

004010AA cmp ebp,esp //比较,若不同则调用chkesp抛出异常

004010AC call __chkesp (00401150)

004010B1 mov esp,ebp

004010B3 pop ebp //ESP=0X12FF84 EBP=0x12FFC0 尘归尘 土归土 一切都恢复最初的平静了 :)

004010B4 ret



1. 如果函数调用方式是__stdcall 不同之处在于

main函数call 后面没有了 add esp, 8

test函数最后一句 是 ret 8 (由test函数清栈, ret 8意思是执行ret后,esp+8)

2. 运行过程中0x12FF28 保存了指令地址 00401091是怎么保存的?

栈每个空间保存4个字节(粒度4字节) 例如下一个栈空间0x12FF2C保存参数10

因此

0x12FF28 0x12FF29 0x12FF2A 0x12FF2B

91 10 40 00

little-endian 认为其读的第一个字节为最小的那位上的数

3. char a[] = "abcde"

对局部字符数组变量(栈变量)赋值,是利用寄存器从全局数据内存区把字符串“abcde”拷贝到栈内存中的

4. int szNum[5] = { 1, 2, 3, 4, 5 }; 栈中是如何分布的?

00401798 mov dword ptr [ebp-14h],1

0040179F mov dword ptr [ebp-10h],2

004017A6 mov dword ptr [ebp-0Ch],3

004017AD mov dword ptr [ebp-8],4

004017B4 mov dword ptr [ebp-4],5

可以看出来 是从右边开始入栈,所以是 5 4 3 2 1 入栈

int *ptrA = (int*)(&szNum+1);

int *ptrB = (int*)((int)szNum + 1);

std::cout<< ptrA[-1] << *ptrB << std::endl;

结果如何?

28: int *ptrA = (int*)(&szNum+1);

004017BB lea eax,[ebp]

004017BE mov dword ptr [ebp-18h],eax

&szNum是指向数组指针;加1是加一个数组宽度;&szNum+1指向移动5个int单位之后的那个地方, 就是把EBP的地址赋给指针

ptrA[-1]是回退一个int*宽度,即ebp-4

29: int *ptrB = (int*)((int)szNum + 1);

004017C1 lea ecx,[ebp-13h]

004017C4 mov dword ptr [ebp-1Ch],ecx

如果上面是指针算术,那这里就是地址算术,只是首地址+1个字节的offset,即ebp-13h给指针

实际保存是这样的

01 00 00 00 02
00 00 00

ebp-14h ebp-13h ebp-10h

注意是int*类型的,最后获得的是 00 00 00 02

由于Little-endian, 实际上逻辑数是02000000 转换为十进制数就为33554432

最后输出533554432

原文地址:http://www.cnblogs.com/dylanwind/archive/2008/12/08/1349822.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: