C语言中的数组、字符串、指针反汇编学习笔记

数组

数组是C语言中的一个常见的数据结构,那么数组在汇编语言中是如何表示的呢?

一个简单的一维数组

#include <stdio.h>

int main(void)
{
	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };

	return 0;
}

反汇编如下

5: 	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
00A343A2 C7 45 D0 01 00 00 00 mov         dword ptr [array],1  
00A343A9 C7 45 D4 02 00 00 00 mov         dword ptr [ebp-2Ch],2  
00A343B0 C7 45 D8 03 00 00 00 mov         dword ptr [ebp-28h],3  
00A343B7 C7 45 DC 04 00 00 00 mov         dword ptr [ebp-24h],4  
00A343BE C7 45 E0 05 00 00 00 mov         dword ptr [ebp-20h],5  
00A343C5 C7 45 E4 06 00 00 00 mov         dword ptr [ebp-1Ch],6  
00A343CC C7 45 E8 07 00 00 00 mov         dword ptr [ebp-18h],7  
00A343D3 C7 45 EC 08 00 00 00 mov         dword ptr [ebp-14h],8  
00A343DA C7 45 F0 09 00 00 00 mov         dword ptr [ebp-10h],9  
00A343E1 C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah  
     6: 
     7: 	return 0;
00A343E8 33 C0                xor         eax,eax

为了更清晰的表示,我将符号名去掉

5: 	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
00A343A2 C7 45 D0 01 00 00 00 mov         dword ptr [ebp-30h],1  
00A343A9 C7 45 D4 02 00 00 00 mov         dword ptr [ebp-2Ch],2  
00A343B0 C7 45 D8 03 00 00 00 mov         dword ptr [ebp-28h],3  
00A343B7 C7 45 DC 04 00 00 00 mov         dword ptr [ebp-24h],4  
00A343BE C7 45 E0 05 00 00 00 mov         dword ptr [ebp-20h],5  
00A343C5 C7 45 E4 06 00 00 00 mov         dword ptr [ebp-1Ch],6  
00A343CC C7 45 E8 07 00 00 00 mov         dword ptr [ebp-18h],7  
00A343D3 C7 45 EC 08 00 00 00 mov         dword ptr [ebp-14h],8  
00A343DA C7 45 F0 09 00 00 00 mov         dword ptr [ebp-10h],9  
00A343E1 C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah  
     6: 
     7: 	return 0;
00A343E8 33 C0                xor         eax,eax

可以看的出来数组中的元素地址最低的是1,最高的是10。数组实际上就是将函数中将栈帧中的一部分作为了数组,而且这部分空间的地址是连续的


C语言中的数组、字符串、指针反汇编学习笔记


在C语言中经常会出现数组越界的情况,那么来看看在汇编中这种情况是怎么表示的

#include <stdio.h>

int main(void)
{
	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };

	for (int i = 0; i <= 10; i++)
		printf("%d ", array[i]);

	putchar('\n');

	return 0;
}

这段代码执行后会出现这种情况


C语言中的数组、字符串、指针反汇编学习笔记

最后一个负数实际上就是在main函数反汇编讲解中出现那个用来填充栈帧的CCCCCCCC,用计算器将它用十六进制表示就能发现。


C语言中的数组、字符串、指针反汇编学习笔记

我们随便把栈帧中一部分空间找出来,看看里面的值是不是CCCCCCCC

#include <stdio.h>

int main(void)
{
	int x;

	__asm			/*__asm是C语言的关键字,可以在__asm中写汇编代码*/
	{
		mov eax, dword ptr [ebp-4]
		mov x, eax
	}

	printf("%d\n", x);

	return 0;
}

运行结果如下:

C语言中的数组、字符串、指针反汇编学习笔记

值是一模一样的

结论:

从上述结果可知,数组越界实际上是将栈帧中一部分未赋值的空间进行了读取,从而会出现数组越界的情况


C语言中有时还会出现二维数组、三维数组等,我们来看看它们在汇编中是如何表示的

二维数组

#include <stdio.h>

int main(void)
{
   int array[3][3] =
   {
   	{0,1,2},
   	{3,4,5},
   	{6,7,8}
   };

   return 0;
}

反汇编后如下:

5: 	int array[3][3] =
009843A2 C7 45 D4 00 00 00 00 mov         dword ptr [ebp-2Ch],0  
009843A9 C7 45 D8 01 00 00 00 mov         dword ptr [ebp-28h],1  
009843B0 C7 45 DC 02 00 00 00 mov         dword ptr [ebp-24h],2  
009843B7 C7 45 E0 03 00 00 00 mov         dword ptr [ebp-20h],3  
009843BE C7 45 E4 04 00 00 00 mov         dword ptr [ebp-1Ch],4  
009843C5 C7 45 E8 05 00 00 00 mov         dword ptr [ebp-18h],5  
009843CC C7 45 EC 06 00 00 00 mov         dword ptr [ebp-14h],6  
009843D3 C7 45 F0 07 00 00 00 mov         dword ptr [ebp-10h],7  
009843DA C7 45 F4 08 00 00 00 mov         dword ptr [ebp-0Ch],8  
     6: 	{
     7: 		{0,1,2},
     8: 		{3,4,5},
     9: 		{6,7,8}
    10: 	};
    11: 
    12: 	return 0;
009843E1 33 C0                xor         eax,eax

这时会发现二维数组的表示和一维数组的表示几乎是一样的

三维数组会如何呢?

5: 	int array[2][2][2] =
00335202 C7 45 D8 00 00 00 00 mov         dword ptr [ebp-28h],0  
00335209 C7 45 DC 01 00 00 00 mov         dword ptr [ebp-24h],1  
00335210 C7 45 E0 02 00 00 00 mov         dword ptr [ebp-20h],2  
00335217 C7 45 E4 03 00 00 00 mov         dword ptr [ebp-1Ch],3  
0033521E C7 45 E8 04 00 00 00 mov         dword ptr [ebp-18h],4  
00335225 C7 45 EC 05 00 00 00 mov         dword ptr [ebp-14h],5  
0033522C C7 45 F0 06 00 00 00 mov         dword ptr [ebp-10h],6  
00335233 C7 45 F4 07 00 00 00 mov         dword ptr [ebp-0Ch],7  
     6: 	{
     7: 		{
     8: 			{0,1},
     9: 			{2,3},
    10: 		},
    11: 		{
    12: 			{4,5},
    13: 			{6,7},
    14: 		},
    15: 
    16: 	};
    17: 
    18: 	return 0;
0033523A 33 C0                xor         eax,eax

同上

结论:

无论是几维数组,在栈帧中总是一块连续的空间,且每相邻两个元素之间的距离视数组的数据类型决定


字符串

在看字符串之前先看看字符数组在汇编中是如何表示的

一个简单的字符数组

#include <stdio.h>

int main(void)
{
	char str[12] = { 'H','e','l','l','o',' ','W','o','r','l','d','!' };

	return 0;
}

反汇编如下

5: 	char str[12] = { 'H','e','l','l','o',' ','W','o','r','l','d','!' };
001A5202 C6 45 EC 48          mov         byte ptr [ebp-14h],48h  
001A5206 C6 45 ED 65          mov         byte ptr [ebp-13h],65h  
001A520A C6 45 EE 6C          mov         byte ptr [ebp-12h],6Ch  
001A520E C6 45 EF 6C          mov         byte ptr [ebp-11h],6Ch  
001A5212 C6 45 F0 6F          mov         byte ptr [ebp-10h],6Fh  
001A5216 C6 45 F1 20          mov         byte ptr [ebp-0Fh],20h  
001A521A C6 45 F2 57          mov         byte ptr [ebp-0Eh],57h  
001A521E C6 45 F3 6F          mov         byte ptr [ebp-0Dh],6Fh  
001A5222 C6 45 F4 72          mov         byte ptr [ebp-0Ch],72h  
001A5226 C6 45 F5 6C          mov         byte ptr [ebp-0Bh],6Ch  
001A522A C6 45 F6 64          mov         byte ptr [ebp-0Ah],64h  
001A522E C6 45 F7 21          mov         byte ptr [ebp-9],21h  
     6: 
     7: 	return 0;
001A5232 33 C0                xor         eax,eax

在汇编中,每个字符都用ASCII码表示,而且每个字符大小都为一个字节,即byte

了解了这些就可以看看C语言中的字符串在汇编中是如何表示的了

字符串

#include <stdio.h>

int main(void)
{
	char str[13] = "Hello,World!";

	return 0;
}

这里多出来的一个数组空间是为了给空白符'\0'准备的。这段代码反汇编如下

5: 	char str[13] = "Hello,World!";
00625202 A1 CC 7B 62 00       mov         eax,dword ptr ds:[00627BCCh]  
00625207 89 45 E8             mov         dword ptr [ebp-18h],eax  
0062520A 8B 0D D0 7B 62 00    mov         ecx,dword ptr ds:[00627BD0h]  
00625210 89 4D EC             mov         dword ptr [ebp-14h],ecx  
00625213 8B 15 D4 7B 62 00    mov         edx,dword ptr ds:[00627BD4h]  
00625219 89 55 F0             mov         dword ptr [ebp-10h],edx  
0062521C A0 D8 7B 62 00       mov         al,byte ptr ds:[00627BD8h]  
00625221 88 45 F4             mov         byte ptr [ebp-0Ch],al  
     6: 
     7: 	return 0;
00625224 33 C0                xor         eax,eax

因为字符串也是一种特殊的字符数组,它与字符数组最大的区别就在于末尾会有一个空白符'\0'。在之前讲过的栈中我们知道,汇编中的栈一个空间单位大小为4个字节,因此每次入栈都是4个字符

先将H、e、l、l入栈,然后是o、,、W、o入栈,最后是r、l、d、!入栈,全部入栈完毕后再入栈空白符'\0'


指针

C语言中最头疼的就是指针,可以说掌握了指针就相当于掌握了C语言,现在让我们看看指针在汇编中的表现形式

一个简单的指针

#include <stdio.h>

int main(void)
{
	int* p = NULL;

	return 0;
}

反汇编如下

5: 	int* p = NULL;
00F151F8 C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0  
     6: 
     7: 	return 0;
00F151FF 33 C0                xor         eax,eax

可以看到在汇编下指针与其他普通变量并无大的区别

现在来看看下面这个代码,看看指针怎么操作的

指针的简单操作

#include <stdio.h>

int main(void)
{
	int* p = NULL;
	int x = 5;
	int y;

	p = &x;
	y = *p;

	return 0;
}

反汇编如下

5: 	int* p = NULL;
00F851F8 C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0  
     6: 	int x = 5;
00F851FF C7 45 EC 05 00 00 00 mov         dword ptr [ebp-14h],5  
     7: 	int y;
     8: 
     9: 	p = &x;
00F85206 8D 45 EC             lea         eax,[ebp-14h]  
00F85209 89 45 F8             mov         dword ptr [ebp-8],eax  
    10: 	y = *p;
00F8520C 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00F8520F 8B 08                mov         ecx,dword ptr [eax]  
00F85211 89 4D E0             mov         dword ptr [ebp-20h],ecx  
    11: 
    12: 	return 0;
00F85214 33 C0                xor         eax,eax

我们一步一步看,先从p = &x开始看起

9: 	p = &x;
00F85206 8D 45 EC             lea         eax,[ebp-14h]  
00F85209 89 45 F8             mov         dword ptr [ebp-8],eax

在之前讲过,lea操作符是将变量的地址赋值给寄存器,这里就是将x的地址复制给了指针p。

10: 	y = *p;
00F8520C 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00F8520F 8B 08                mov         ecx,dword ptr [eax]  
00F85211 89 4D E0             mov         dword ptr [ebp-20h],ecx

这里p将地址赋给了eax,然后eax通过解引用地址后,将x赋值给了y。指针操作很明显是间接寻址,即通过一个地址媒介来操作一个对象;与之相反的是直接寻址,即直接操作对象。


在C语言中,我们访问数组的方式有两种,一种是下标法,一种是指针法。下面看看用指针法访问数组的汇编表示

指针法访问数组

#include <stdio.h>

int main(void)
{
	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int x;

	x = *array;
	x = *(array + 1);
	x = *(array + 2);

	return 0;
}

反汇编如下

5: 	int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
00E95202 C7 45 D0 01 00 00 00 mov         dword ptr [ebp-30h],1  
00E95209 C7 45 D4 02 00 00 00 mov         dword ptr [ebp-2Ch],2  
00E95210 C7 45 D8 03 00 00 00 mov         dword ptr [ebp-28h],3  
00E95217 C7 45 DC 04 00 00 00 mov         dword ptr [ebp-24h],4  
00E9521E C7 45 E0 05 00 00 00 mov         dword ptr [ebp-20h],5  
00E95225 C7 45 E4 06 00 00 00 mov         dword ptr [ebp-1Ch],6  
00E9522C C7 45 E8 07 00 00 00 mov         dword ptr [ebp-18h],7  
00E95233 C7 45 EC 08 00 00 00 mov         dword ptr [ebp-14h],8  
00E9523A C7 45 F0 09 00 00 00 mov         dword ptr [ebp-10h],9  
00E95241 C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah  
     6: 	int x;
     7: 
     8: 	x = *array;
00E95248 B8 04 00 00 00       mov         eax,4  
00E9524D 6B C8 00             imul        ecx,eax,0  
00E95250 8B 54 0D D0          mov         edx,dword ptr [ebp+ecx-30h]  
00E95254 89 55 C4             mov         dword ptr [ebp-3Ch],edx  
     9: 	x = *(array + 1);
00E95257 8B 45 D4             mov         eax,dword ptr [ebp-2Ch]  
00E9525A 89 45 C4             mov         dword ptr [ebp-3Ch],eax  
    10: 	x = *(array + 2);
00E9525D 8B 45 D8             mov         eax,dword ptr [ebp-28h]  
00E95260 89 45 C4             mov         dword ptr [ebp-3Ch],eax  
    11: 
    12: 	return 0;
00E95263 33 C0                xor         eax,eax

可以看到指针法的汇编表示与下标法的差不多,那么二维的呢?

指针法访问二维数组

#include <stdio.h>

int main(void)
{
	int array[3][3] =
	{
		{0,1,2},
		{3,4,5},
		{6,7,8}
	};

	return 0;
}

反汇编如下

5: 	int array[3][3] =
00E75202 C7 45 D4 00 00 00 00 mov         dword ptr [ebp-2Ch],0  
00E75209 C7 45 D8 01 00 00 00 mov         dword ptr [ebp-28h],1  
00E75210 C7 45 DC 02 00 00 00 mov         dword ptr [ebp-24h],2  
00E75217 C7 45 E0 03 00 00 00 mov         dword ptr [ebp-20h],3  
00E7521E C7 45 E4 04 00 00 00 mov         dword ptr [ebp-1Ch],4  
00E75225 C7 45 E8 05 00 00 00 mov         dword ptr [ebp-18h],5  
00E7522C C7 45 EC 06 00 00 00 mov         dword ptr [ebp-14h],6  
00E75233 C7 45 F0 07 00 00 00 mov         dword ptr [ebp-10h],7  
00E7523A C7 45 F4 08 00 00 00 mov         dword ptr [ebp-0Ch],8  
     6: 	{
     7: 		{0,1,2},
     8: 		{3,4,5},
     9: 		{6,7,8}
    10: 	};
    11: 
    12: 	int x;
    13: 
    14: 	x = *(*(array));
00E75241 B8 0C 00 00 00       mov         eax,0Ch  
00E75246 6B C8 00             imul        ecx,eax,0  
00E75249 8D 54 0D D4          lea         edx,[ebp+ecx-2Ch]  
00E7524D B8 04 00 00 00       mov         eax,4  
00E75252 6B C8 00             imul        ecx,eax,0  
00E75255 8B 14 0A             mov         edx,dword ptr [edx+ecx]  
00E75258 89 55 C8             mov         dword ptr [ebp-38h],edx  
    15: 	x = *(*(array)+2);
00E7525B B8 0C 00 00 00       mov         eax,0Ch  
00E75260 6B C8 00             imul        ecx,eax,0  
00E75263 8B 54 0D DC          mov         edx,dword ptr [ebp+ecx-24h]  
00E75267 89 55 C8             mov         dword ptr [ebp-38h],edx  
    16: 	x = *(*(array+2)+2);
00E7526A 8B 45 F4             mov         eax,dword ptr [ebp-0Ch]  
00E7526D 89 45 C8             mov         dword ptr [ebp-38h],eax  
    17: 
    18: 	return 0;
00E75270 33 C0                xor         eax,eax

同上


总结

  1. 数组无论是几维,在栈中都是一块连续的空间,且每相邻两个数组单元之间的距离视数组的数据类型决定(int对应dword、long int对应qword、char对应byte等)

  1. 数组访问越界实际上是因为访问到了一块没有赋值或初始化的空间,在C语言中无论什么函数,执行操作之前都会将栈帧中的区域进行初始化(如CCCCCCCC)

  1. 字符串是一种特殊的字符数组,又因为字符都用ASCII编码,每一个字符占一个字节,所以对字符串进行初始化时一次填入4个字符,并在最后加入空白符'\0'

  1. 指针在汇编中与其他一般的变量没有太大的区别,只不过多了一个lea操作

  1. 指针操作是间接寻址,即通过一个地址媒介来操作一个对象;与之相反的是直接寻址,即直接操作对象。
上一篇:反汇编分析C++代码


下一篇:硬核知识大全 作为程序员不得不了解