C语言再学习5-数组与优化

什么是数组?为什么要用数组?

通俗来讲,在内存中一块连续存储数据的叫数组,数组的每个子元素的宽度都一样,并且只能为通用的数据类型做单位(char,short,int等等)
让我们先定义一个数组,然后赋值:

char arr1[2] = { 0 };
arr1[0] = 1;
arr1[1] = 2;
arr1[2] = 3;

让我们先见识一下反汇编

	char arr1[3] = { 0 };
013D4DC2 33 C0                xor         eax,eax  
013D4DC4 66 89 45 F4          mov         word ptr [arr1],ax  
013D4DC8 88 45 F6             mov         byte ptr [ebp-0Ah],al  
	arr1[0] = 10;
013D4DCB B8 01 00 00 00       mov         eax,1  
013D4DD0 6B C8 00             imul        ecx,eax,0  
013D4DD3 C6 44 0D F4 0A       mov         byte ptr arr1[ecx],0Ah  
	arr1[1] = 20;
013D4DD8 B8 01 00 00 00       mov         eax,1  
013D4DDD C1 E0 00             shl         eax,0  
013D4DE0 C6 44 05 F4 14       mov         byte ptr arr1[eax],14h  
	arr1[2] = 30;
013D4DE5 B8 01 00 00 00       mov         eax,1  
013D4DEA D1 E0                shl         eax,1  
013D4DEC C6 44 05 F4 1E       mov         byte ptr arr1[eax],1Eh  

一个小小的数组,为什么变成了这么多东西??(vs2017下)
先别慌,我们来一条一条的解析指令!

xor eax,eax             			 //清除eax寄存器的数据
	--eax-ax-al        0x00000000
mov word ptr [arr1],ax	    //根据数组的宽度来讲,我们要存储两个数组,所以第一次先使用ax寄存器(0x0000)
	-- cc cc cc         	 ->      	    00 00 cc
mov byte ptr [ebp-0Ah],al   //ax寄存器+al(0x00),这样我们就使用2+1的宽度填充了3 的宽度的0
	-- 00 00 cc        	 ->        	    00 00 00
	//第一次把cccccc变成0000cc,第二次把0000cc变成000000,完成填充0,初始化数组
mov         eax,1
imul        ecx,eax,0
mov         byte ptr arr1[ecx],0Ah

//这个指令看起来有点复杂,继续解析
//-把1放到eax寄存器里,imul是什么?我们先观察一下

影响 OF、CF 标志位
第一种指令格式:IMUL r/m ;单操作数;
如果参数是 r8/m8, 将把 AL 做乘数, 结果放在 AX;
如果参数是 r16/m16, 将把 AX 做乘数, 结果放在 EAX;
如果参数是 r32/m32, 将把 EAX 做乘数, 结果放在 EDX:EAX

看了半天也没看懂,反正imul ecx,eax,0,乘来乘去最后乘了0,就一直是0

再观察一下shl指令,逻辑左移指令,通过左移来计算偏移!
后面你就会知道为什么需要通过imul和shl指令来计算eax或者ecx的值

但是这条指令为什么看起来怪怪的?
mov         byte ptr arr1[ecx],0Ah          //ecx为0
不要紧,这是编译器的锅,反汇编的显示就是这样,让我们换一个软件来显示这些opcode
C6 44 0D   F4    0A             mov byte ptr ss:[ebp+ecx-C],0a           //ebp+0-c,其实就是ebp-c的地方存了第一个
C6 44 05    F4   14              mov byte ptr ss:[ebp+ecx-C],14		//ebp+1-c
让我来展示一张堆栈结构图

在这里插入图片描述
比如我们的ebp是401020的时候,第一行操作数组的汇编代码的意思就是:

mov byte ptr ss:[ebp+ecx-C],0a 
401020的ebp+0-c。其实就是401014。在401014的地方存储10
401020的ebp+1-c。其实就是401015。在401015的地方存储20
...

在这里插入图片描述
我们的数据成功被写入堆栈!

传统的数组通过ss:[ebp-8], ss:[ebp-c]

通过对堆栈进行操作并且存储数组,而在vs2017里,使用ebp+准确偏移来存储数组,保证每个内存单元都不会白费

在这里插入图片描述



再看看全局数组



	arr1[0] = 10;
00204DB8 B8 01 00 00 00       mov         eax,1  
00204DBD 6B C8 00             imul        ecx,eax,0  
00204DC0 C6 81 48 A1 20 00 0A mov         byte ptr arr1 (020A148h)[ecx],0Ah  
	arr1[1] = 20;
00204DC7 B8 01 00 00 00       mov         eax,1  
00204DCC C1 E0 00             shl         eax,0  
00204DCF C6 80 48 A1 20 00 14 mov         byte ptr arr1 (020A148h)[eax],14h  
	arr1[2] = 30;
00204DD6 B8 01 00 00 00       mov         eax,1  
00204DDB D1 E0                shl         eax,1  
00204DDD C6 80 48 A1 20 00 1E mov         byte ptr arr1 (020A148h)[eax],1Eh  

我们的数组被存储在基址0x20A148h的位置,再看到imul和shl指令的时候,我们已经恍然大悟
mov word ptr ds:[0x20a148+eax],14//eax=0
mov word ptr ds:[0x20a148+eax],14//eax=1
和我们的堆栈操作何其相似?



最后对arr[10]等大的数组进行拆分发现:

ebp+eax(0)-14 ebp+eax(1)-14 ebp+eax(2)-14 ebp+eax(3)-14
ebp+eax(0)-c ebp+eax(1)-10 ebp+eax(2)-10 ebp+eax(3)-10
ebp+eax(0)-c ebp+eax(1)-c ebp+eax(2)-c ebp+eax(3)-c

在这里插入图片描述

结论:通过精确到位的操作,合理的分配堆栈空间来使用数组,达到优化的目的!



基于缓冲区溢出的函数调用:

先上代码再解析:

#include "pch.h"
#include <iostream>
#include <windows.h>
void HelloWord()
{
	printf("Hello World");

	getchar();
}
void Fun()
{
	int arr[5] = { 1,2,3,4,5 };

	arr[7] = (int)HelloWord;


}

int main()
{
	Fun();
	return 0;
}

	void Fun()
{
01334630 55                   push        ebp  
01334631 8B EC                mov         ebp,esp  
01334633 81 EC DC 00 00 00    sub         esp,0DCh  
01334639 53                   push        ebx  
0133463A 56                   push        esi  
0133463B 57                   push        edi  
0133463C 8D BD 24 FF FF FF    lea         edi,[ebp-0DCh]  
01334642 B9 37 00 00 00       mov         ecx,37h  
01334647 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
0133464C F3 AB                rep stos    dword ptr es:[edi]  
0133464E B9 08 C0 33 01       mov         ecx,offset _0340B6FD_consoleapplication3.cpp (0133C008h)  
01334653 E8 B0 CB FF FF       call        @__CheckForDebuggerJustMyCode@4 (01331208h)  
	int arr[5] = { 1,2,3,4,5 };
01334658 C7 45 E8 01 00 00 00 mov         dword ptr [arr],1  
0133465F C7 45 EC 02 00 00 00 mov         dword ptr [ebp-14h],2  
01334666 C7 45 F0 03 00 00 00 mov         dword ptr [ebp-10h],3  
0133466D C7 45 F4 04 00 00 00 mov         dword ptr [ebp-0Ch],4  
01334674 C7 45 F8 05 00 00 00 mov         dword ptr [ebp-8],5  

	arr[7] = (int)HelloWord;
0133467B B8 04 00 00 00       mov         eax,4  
01334680 6B C8 07             imul        ecx,eax,7  
01334683 C7 44 0D E8 A2 13 33 01 mov         dword ptr arr[ecx],offset HelloWord (013313A2h)  
}

让我们来看一看堆栈图:
在这里插入图片描述
当我们在使用arr[7]的时候,实际上就是写入了ebp+4,众所周知。ebp+4储存返回地址,而当我们使用arr[7]的时候,实际上就是修改了整个函数的返回地址!,如果我们使用arr[6]即可读取ebp栈底的值!

正向开发的有效数组为arr[0~x]

标题而我们使用的arr[x+1]为无效数据,arr[x+2]为ebp栈底,arr[x+3]为函数返回地址!

上运行结果图:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_35425243/article/details/82837866
今日推荐