两年前写的,欢迎大家吐槽!
转载请注明出处。
1. 数组在函数内
先通过一个简单的例子来看一下数组和普通变量在初始化时的不同之处:
这是数组初始化:
42: int nArry[5] = {1, 2, 3, 4, 5};
0042B758 mov dword ptr [ebp-1Ch],1
0042B75F mov dword ptr[ebp-18h],2
0042B766 mov dword ptr [ebp-14h],3
0042B76D mov dword ptr [ebp-10h],4
0042B774 mov dword ptr [ebp-0Ch],5
下面是变量的初始化:
43: charcChar = 'A';
0042B77B mov byte ptr [ebp-25h],41h
44: floatfFloat = 1.0f;
0042B77F fld1
0042B781 fstp dword ptr [ebp-34h]
45: short sShort = 1;
0042B784 mov eax,1
0042B789 mov word ptr [ebp-40h],ax
46: intnInt = 2;
0042B78D mov dword ptr [ebp-4Ch],2
47: double dDouble = 2.0f;
0042B794 fld qword ptr [__real@4000000000000000 (47DC90h)]
0042B79A fstp qword ptr[ebp-5Ch]
数组中每个元素的数据类型是相同的,并且地址是连续的,通过这一点可以很容易的区别出在函数中的数组,而全局数组则在后面会讲到。Release版本的没有多大变化,和局部变量一样优化,但是数组不会因为被赋值常量而进行常量传递,代码如下:
.text:0042B758 mov [ebp+nArray], 1
.text:0042B75F mov [ebp+nArray+4], 2
.text:0042B766 mov [ebp+nArray+8], 3
.text:0042B76D mov [ebp+nArray+0Ch], 4
.text:0042B774 mov [ebp+nArray+10h], 5
现在再来看一下字符串,其实字符串就是字符数组,只是最后一个元素为‘\0’作为字符串结束标志而已。而字符串的初始化就是复制字节的过程,书上说VC++6.0编译器是通过寄存器来赋值的,因为每个寄存器拥有4字节,所以每次最多可以复制4字节的内容。但是我电脑上只有VS2010,它的编译器选择了直接将字符串常量写进文件然后将指针直接指向字符串常量装载入内存的地址。下面看这个例子使理解更加深刻一些:
55: char *szHello = "Hello world";
0042B74E mov dword ptr [szHello],offset string "Hello world" (47DD10h)
通过监视可以看到变量所在地址:
szHello的地址中存储的就是"Hello world"所在文件中的地址,再根据这个地址可以找到字符串:
2. 数组作为参数
首先来看个例子(Debug版本):
这是main函数里面数组定义、初始化以及作为参数调用函数的代码
57: charszHello[20] = {0};
0042B758 mov byte ptr [ebp-1Ch],0 ;这个字节是数组的首地址
0042B75C xor eax,eax
;下面则是数组的剩余19个元素的初始化,利用eax寄存器进行赋值
0042B75E mov dword ptr [ebp-1Bh],eax
0042B761 mov dword ptr [ebp-17h],eax
0042B764 mov dword ptr [ebp-13h],eax
0042B767 mov dword ptr [ebp-0Fh],eax
;当不足4字节时编译器会做相应处理,这里的3字节被拆成2字节和1字节
0042B76A mov word ptr [ebp-0Bh],ax
0042B76E mov byte ptr [ebp-9],al
58: Show(szHello);
0042B771 lea eax,[ebp-1Ch]
0042B774 push eax ;取出数组首地址并入栈作为参数传到函数里面
0042B775 call Show (429FA5h)
0042B77A add esp,4
下面是show函数的定义:
9: void Show(char szBuff[])
10: { ;先前代码略
11: strcpy(szBuff, "Hello World");
0042B66E push offset string "Hello World" (47DC6Ch) ;获取字符串常量的首地址
0042B673 mov eax,dword ptr [esp+8] ;获取参数
0042B676 push eax ;将其作为字符串复制函数的参数入栈
0042B677 call @ILT+1770(_strcpy) (4296EFh)
0042B67C add esp,8
12: printf(szBuff);
;printf函数代码略
13: };后续代码略
从以上的例子可以很清楚的看到,数组作为函数的参数时是将数组的首地址入栈作为参数,然后根据首地址找到数组的每一个元素。
字符串处理函数在Debug版本下非常容易识别,但是在Release版本中字符串处理函数内联到程序中,没有call指令对函数进行调用,因此识别这些函数有一定的困难,但是可以根据反汇编的代码确定其内联代码的功能,并非必须还原出原函数。下面就用一个例子来具体说明:
先是C源代码:
main()
{
charszHello[20] = {0};
Show(szHello);
}
void Show(charszBuff[])
{
strcpy(szBuff, "HelloWorld");
printf(szBuff);
}
首先对main函数中的代码用IDA P ro进行反汇编得到如下代码:
.text:00401030 aHello = byte ptr -18h ;这是一个数组,可以看到大小为0x14
.text:00401030 var_4 = dword ptr -4
.text:00401030
.text:00401030 push ebp
.text:00401031 mov ebp, esp
.text:00401033 sub esp, 18h
.text:00401036 mov eax, dword_40B014
.text:0040103B xor eax, ebp
.text:0040103D mov [ebp+var_4], eax
.text:00401040 xor eax, eax
;和Debug版本一样,下面是对数组的初始化
.text:00401042 mov [ebp+aHello], al
.text:00401045 mov dword ptr [ebp+aHello+1], eax
.text:00401048 mov dword ptr [ebp+aHello+5], eax
.text:0040104B mov dword ptr [ebp+aHello+9], eax
.text:0040104E mov dword ptr [ebp+aHello+0Dh], eax
.text:00401051 mov word ptr [ebp+aHello+11h], ax
.text:00401055 mov [ebp+aHello+13h], al
;取出数组首地址作为函数参数入栈