X86C++反汇编10.数组
数组改低版本有差异,高版本差异是从2013开始的### 什么是数组
数组是一组连续且一致的元素的集合
数组的特性: 连续 一致(业务功能)
结构体(成员都是整形)不是数组是因为成员的业务功能不一致
证明一致的方法:
1. 循环,在一个循环中,跑同一块代码的变量业务功能肯定一致
2. 比例因子寻址 reg的取值范围业务类型都一致
因此汇编中证明数组的方法
1. 比例因子寻址 因子寄存器的取值范围可以得到数组的界限
2. 循环结构 循环次数的取值范围可以得到数组的界限
无法确定范围说明可以通过数组越界读写
## 数组的优化
### 一维数组
##### 比例因子寻址
```
```
**ary的地址= (int)ary+n \* sizeof(type)**
**因此访问第n个元素 就是 [(int)ary+n \* sizeof(type) ]**
```
int main(int argc, char* argv[])
{
int ary={1,2,3,4,5};
ary = 20; //常量下标访问
ary = 30; //变量下标访问
ary = 50; //表达式下标访问
}
反汇编代码:
7: int ary={1,2,3,4,5};
0040D718 mov dword ptr ,1
0040D71F mov dword ptr ,2
0040D726 mov dword ptr ,3
0040D72D mov dword ptr ,4
0040D734 mov dword ptr ,5
;上面看起来跟反汇编差不多.因此无法构成 是数组的依据
8: ary = 20; //常量下标访问
0040D73B mov dword ptr ,14h
;这里产生了常量折叠 (ebp - 14h) + 2*4= ebp - 0Ch;
9: ary = 30; //变量下标访问
0040D742 mov eax,dword ptr
0040D745 mov dword ptr ,1Eh
; => [(ebp+var_14)+eax*4] ,正常情况 eax 肯定有取值范围,不然就是漏洞
10: ary = 50; //表达式下标访问
0040D74D mov eax,dword ptr
0040D750 cdq
0040D751 mov ecx,7
0040D756 idiv eax,ecx
0040D758 mov dword ptr ,32h
;先计算表达式,再把结果进行比例因子寻址
11: return 0;
0040D760 xor eax,eax
```
##### 无比例因子寻址有指针
在汇编中,判断是否指针 只需要看他是否保存了有效地址,保存了有效地址说明是指针
```
int main(int argc, char* argv[])
{
int ary={1,2,3,4,5};
int *p =ary;
for(int i= 0; i<5 ; i++)
{
printf("%d\r\n",*p);
p++;
}
return 0;
}
反汇编代码:
debug版
7: int ary={1,2,3,4,5};
0040D718 mov dword ptr ,1
0040D71F mov dword ptr ,2
0040D726 mov dword ptr ,3
0040D72D mov dword ptr ,4
0040D734 mov dword ptr ,5
8: int *p =ary;
0040D73B lea eax,
0040D73E mov dword ptr ,eax ;保存了地址,所以 ebp-18h 这个变量是指针
9: for(int i= 0; i<5 ; i++)
0040D741 mov dword ptr ,0 //循环起点
0040D748 jmp main+53h (0040d753)
0040D74A mov ecx,dword ptr
0040D74D add ecx,1 //循环步长
0040D750 mov dword ptr ,ecx
0040D753 cmp dword ptr ,5 //循环终点
0040D757 jge main+77h (0040d777)
10: {
11: printf("%d\r\n",*p);
0040D759 mov edx,dword ptr //从地址 ebp-18h 开始,循环5轮,说明连续且一致
0040D75C mov eax,dword ptr //指针取内容间接访问
0040D75E push eax
0040D75F push offset string "%d\r\n" (0042201c)
0040D764 call printf (00401070)
0040D769 add esp,8
12: p++;
0040D76C mov ecx,dword ptr
0040D76F add ecx,4
0040D772 mov dword ptr ,ecx
13: }
```
##### 无比例因子无指针
结构体数组不止四个字节,在比例因子寻址时,就会用到imul 获得地址,再寻址
```
int main(int argc, char* argv[])
{
int ary={1,2,3,4,5};
for(int i= 0; i<5 ; i++)
{
printf("%d\r\n",ary);
}
return 0;
}
反汇编代码:
DEBUG版
7: int ary={1,2,3,4,5};
0040D718 mov dword ptr ,1
0040D71F mov dword ptr ,2
0040D726 mov dword ptr ,3
0040D72D mov dword ptr ,4
0040D734 mov dword ptr ,5
8: for(int i= 0; i<5 ; i++)
;循环,可以判定数组
0040D73B mov dword ptr ,0
0040D742 jmp main+4Dh (0040d74d)
0040D744 mov eax,dword ptr
0040D747 add eax,1
0040D74A mov dword ptr ,eax
0040D74D cmp dword ptr ,5
0040D751 jge main+6Ah (0040d76a)
9: {
10: printf("%d\r\n",ary);
0040D753 mov ecx,dword ptr ;从上面可以看出 ebp-18h 的取值范围是 0 - 5
0040D756 mov edx,dword ptr ;比例因子寻址可以判定为数组
0040D75A push edx
0040D75B push offset string "%d\r\n" (0042201c)
0040D760 call printf (00401070)
0040D765 add esp,8
11:
12: }
RELEASE版
release版都变成了指针,没有用到比例因子寻址
.text:00401005 mov edi, 5
.text:0040100A mov , 1
.text:00401012 mov , 2
.text:0040101A mov , 3
.text:00401022 mov , 4
.text:0040102A mov , edi
.text:0040102E lea esi, ;说明这里是数组首地址
.text:00401032
.text:00401032 loc_401032: ;循环
.text:00401032 mov eax,
.text:00401034 push eax
.text:00401035 push offset aD ; "%d\r\n"
.text:0040103A call sub_401050
.text:0040103F add esp, 8
.text:00401042 add esi, 4 ;每次加4字节 说明是 dword 数组
.text:00401045 dec edi ;循环式比较的是edi-- 从5开始递减到0
.text:00401046 jnz short loc_401032
.text:00401048 pop edi
.text:00401049 xor eax, eax
.text:0040104B pop esi
.text:0040104C add esp, 14h
.text:0040104F retn
```
!(./notesimg/1658233020355-0b0a882b-2d1a-49f8-b275-649d3b8b4a6c.png)
!(./notesimg/1658233033246-92dfb893-b1c8-4c77-b4d1-7564f88945e5.png)
这样就可以很容看出地址加的偏移
!(./notesimg/1658233063563-9337779b-e53c-42a8-acba-c8de80cf4d93.png)
**高版本 RELEASE 版**
使用了多媒体指令集
.text:00401040 push ebp
.text:00401041 mov ebp, esp
.text:00401043 sub esp, 14h
.text:00401046 movapsxmm0, ds:__xmm@00000004000000030000000200000001
; xmm 是单位,表示128位 xmm0 相当于浮点型寄存中的eax 这里就相当把 1,2,3,4 16字节一次性给xmm0
.text:0040104D push esi
.text:0040104E movupsxmmword ptr , xmm0
;这里相当于 把 xmm0 中 的 1,2,3,4 给数组中的 首地址开始的 16字节
.text:00401052 mov , 5 ;前面已经给了4个dword ,所以这里直接是5
.text:00401059 xor esi, esi
.text:0040105B nop dword ptr
.text:00401060
.text:00401060 loc_401060:
.text:00401060 push [**ebp+esi\*4+ary] ;比例因子, 每个元素长度为4字节**
.text:00401064 push offset _Format; "%d\r\n"
.text:00401069 call _printf
.text:0040106E inc esi
.text:0040106F add esp, 8
.text:00401072 cmp esi, **5**
.text:00401075 jl short loc_401060
.text:00401077 xor eax, eax
.text:00401079 pop esi
.text:0040107A mov esp, ebp
.text:0040107C pop ebp
.text:0040107D retn
###### 多媒体指令集
多媒体指令集:
总共8个寄存器 (xmm0- xmm7),每个寄存器长度是 128 位 (16字节)除了能做多媒体指令外,还能用于浮点运算和数组的初始化,因此初始化一次就是 4个int,因此很容易把数组初始化完
##### 数组与数组相加
都转成了指针
```
int main(int argc, char* argv[])
{
int ary={1,2,3,4,5};
for(int i= 0; i<5 ; i++)
{
printf("%d\r\n",ary + ary );
}
return 0;
}
反汇编代码:
低版本 RELEASE
text:00401000 sub esp, 14h
.text:00401003 push ebx
.text:00401004 push esi
.text:00401005 mov ebx, 5
.text:0040100A push edi
.text:0040100B mov , 1
.text:00401013 mov , 2
.text:0040101B mov , 3
.text:00401023 mov , 4
.text:0040102B mov , ebx
.text:0040102F lea edi,
.text:00401033 lea esi,
.text:00401037
.text:00401037 loc_401037:
.text:00401037 mov eax,
.text:00401039 mov ecx,
;eax和 ecx 分别取内容
.text:0040103B add eax, ecx
.text:0040103D push eax
.text:0040103E push offset aD ; "%d\r\n"
.text:00401043 call sub_401060
.text:00401048 add esp, 8
.text:0040104B sub esi, 4
.text:0040104E add edi, 4
;一个取头,一个取尾
.text:00401051 dec ebx
.text:00401052 jnz short loc_401037
.text:00401054 pop edi
.text:00401055 pop esi
.text:00401056 xor eax, eax
.text:00401058 pop ebx
.text:00401059 add esp, 14h
.text:0040105C retn
高版本RELEASE
text:00401040 push ebp
.text:00401041 mov ebp, esp
.text:00401043 sub esp, 14h
.text:00401046 movapsxmm0, ds:__xmm@00000004000000030000000200000001
.text:0040104D push esi
.text:0040104E push edi
.text:0040104F movupsxmmword ptr , xmm0
.text:00401053 mov , 5
.text:0040105A lea edi, ;数组最后一个元素给edi
.text:0040105D xor esi, esi
.text:0040105F nop
.text:00401060
.text:00401060 loc_401060:
.text:00401060 mov eax, ;数组第一个数给 eax
.text:00401064 add eax, ;2者相加
.text:00401066 push eax
.text:00401067 push offset _Format; "%d\r\n"
.text:0040106C call _printf
.text:00401071 inc esi
.text:00401072 lea edi,
.text:00401075 add esp, 8
.text:00401078 cmp esi, 5
.text:0040107B jl short loc_401060
.text:0040107D pop edi
.text:0040107E xor eax, eax
.text:00401080 pop esi
.text:00401081 mov esp, ebp
.text:00401083 pop ebp
.text:00401084 retn
```
#### 字符数组
主要是识别常用的字符串函数,因为 release 版 会直接内联进汇编
```
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
char szHello[] = "Hello world!";
char szBuf;
strcpy(szBuf, szHello);
printf(szBuf);
return strlen(szHello);
}
高版本:
.text:00401050 mov eax, ds:dword_402108
.text:00401055 movq xmm0, qword ptr ds:aHelloWo ; "Hello wo"
.text:0040105D mov dword ptr , eax
.text:00401060 mov al, ds:byte_40210C
.text:00401065 mov , al
.text:00401068 xor eax, eax
.text:0040106A movq qword ptr , xmm0
.text:0040106F nop
.text:00401070
.text:00401070 loc_401070:
.text:00401070 mov cl, ;eax取数组首地址
.text:00401074 lea eax,
.text:00401077 mov , cl
.text:0040107B test cl, cl ;判断 \0
.text:0040107D jnz short loc_401070
.text:0040107F lea eax,
.text:00401082 push eax ; _Format
.text:00401083 call _printf
.text:00401088 lea eax,
.text:0040108B add esp, 4
.text:0040108E lea edx,
.text:00401091
.text:00401091 loc_401091:
.text:00401091 mov cl,
.text:00401093 inc eax
.text:00401094 test cl, cl ;判断 \0
.text:00401096 jnz short loc_401091
.text:00401098 mov ecx,
.text:0040109B sub eax, edx ;尾指针 - (头指针 + 1) ,求字符串长度 头+1 是去掉 \0
低版本:
.text:00401000 sub esp, 24h
.text:00401003 mov eax, dword_407030
.text:00401008 mov ecx, dword_407034
.text:0040100E mov edx, dword_407038
.text:00401014 mov , eax
.text:00401018 mov al, byte_40703C
.text:0040101D push esi
.text:0040101E push edi
.text:0040101F mov , ecx
.text:00401023 mov , al
.text:00401027 lea edi, ;源字符串首地址
.text:0040102B or ecx, 0FFFFFFFFh
.text:0040102E xor eax, eax
.text:00401030 mov , edx
.text:00401034 lea edx, ;目标字符串首地址
.text:00401038 repne scasb
.text:0040103A not ecx
;求长度strlen
.text:0040103C sub edi, ecx
.text:0040103E mov eax, ecx
.text:00401040 mov esi, edi
.text:00401042 mov edi, edx
.text:00401044 shr ecx, 2 ; /4 四字节拷贝总长度除以四,四字节拷贝次数
.text:00401047 rep movsd ;四字节拷贝
.text:00401049 mov ecx, eax
.text:0040104B and ecx, 3 ; %4 ,四字节拷贝完,剩下的字节,不足四节一个字节的拷贝
.text:0040104E rep movsb ;一字节拷贝
.text:00401050 lea ecx,
.text:00401054 push ecx
.text:00401055 call sub_401080
注意:拷贝前面没有求长度 ,那么 有可能是 memcpy, 有求长度则是 strcpy
.text:0040105A lea edi,
.text:0040105E or ecx, 0FFFFFFFFh
.text:00401061 xor eax, eax
.text:00401063 add esp, 4
.text:00401066 repne scasb
;repne 不等于就重复al = 0,所以是找 \0 scasb:edi指向的空间是否等于al,如果不等于 ecx-1,edi+1;
.text:00401068 not ecx
.text:0040106A dec ecx ;如果是strlen(szBuf) + 1 ,则此条指令没有
.text:0040106B pop edi
.text:0040106C mov eax, ecx
.text:0040106E pop esi
.text:0040106F add esp, 24h
.text:00401072 retn
分析: ecx = -1
=> -1-Length-1=ecx
=>- Length= ecx +2
=> Length = -ecx - 2
=> Length=neg(ecx ) - 2
=> Length=not (ecx) + 1- 2
=> Length=not (ecx) -1
```
!(./notesimg/1658237735543-1f62f8e7-c6d1-427c-b6e9-ec39a65a3c47.png)
对于memcpy如果长度 太小,那么就会直接move 实现, 如果很长 ,在低版本就是 **movsd**和**movsw** ,**scasb** 搭配高版本种可能还有 多媒体指令集
### 二维数组
二维数组在低版本才有优化,高版本无优化(看循环)
二维数组的寻址公式
type ary = {.....}
intx ,y
ary 的地址为:
=(int) ary+ sizeof ( type ) *x //第一次下标运算得到一维数组ary
```
```
\+ sizeof ( type ) *y //第二次下标运算得到维维数组ary
= **(int) ary+ sizeof ( type ) \* x + sizeof ( type ) \*y** //调试版寻址
= (int) ary+ sizeof ( type) *M * x + sizeof ( type ) *y
= **(int) ary+ sizeof ( type ) \*(M\* x+ y)** //release版寻址
三维数组的寻址公式
type ary = {.....}
intx ,y,z
ary 的地址为:
=(int) ary+ sizeof ( type ) *x //第一次下标运算得到一维数组ary
```
```
\+ sizeof ( type ) *y //第二次下标运算得到维维数组ary
```
```
\+ sizeof ( type ) *z //第三次下标运算得到维维数组ary
= **(int)ary +sizeof(type ) \* x + sizeof ( type ) \* y+ sizeof ( type ) \*z** //调试版寻址
= (int) ary+ sizeof ( type) *M*L * x + sizeof ( type ) * L *y + sizeof ( type ) * z
= **(int) ary+ sizeof ( type ) \*(L \* ( M \* x + y) + z)** //release版寻址
这时候不管是多维数组还是一维数组,归根结底就是变成了运算符 和 表达式的还原
```
int main(int argc, char* argv[])
{
int ary ={
{10,20,30},
{40,50,60},
};
for(int i =0; i<2;i++)
{
for(int j =0; j<3;j++)
{
printf("%d\r\n",ary);
}
}
return 0;
}
反汇编代码:
调试版DEBUG
7: int ary ={
8: {10,20,30},
0040D718 mov dword ptr ,0Ah
0040D71F mov dword ptr ,14h
0040D726 mov dword ptr ,1Eh
9: {40,50,60},
0040D72D mov dword ptr ,28h
0040D734 mov dword ptr ,32h
0040D73B mov dword ptr ,3Ch
10: };
11:
12: for(int i =0; i<2;i++)
0040D742 mov dword ptr ,0
0040D749 jmp main+54h (0040d754)
0040D74B mov eax,dword ptr
0040D74E add eax,1
0040D751 mov dword ptr ,eax
0040D754 cmp dword ptr ,2
0040D758 jge main+94h (0040d794)
13: {
14: for(int j =0; j<3;j++)
0040D75A mov dword ptr ,0
0040D761 jmp main+6Ch (0040d76c)
0040D763 mov ecx,dword ptr
0040D766 add ecx,1
0040D769 mov dword ptr ,ecx
0040D76C cmp dword ptr ,3
0040D770 jge main+92h (0040d792)
15: {
16:
17: printf("%d\r\n",ary);
0040D772 mov edx,dword ptr
0040D775 imul edx,edx,0Ch
0040D778 lea eax,
;第一次下标运算
0040D77C mov ecx,dword ptr
0040D77F mov edx,dword ptr ;比例因子寻址
;第二次下标运算
0040D782 push edx
0040D783 push offset string "%d\r\n" (0042201c)
0040D788 call printf (00401070)
0040D78D add esp,8
18: }
0040D790 jmp main+63h (0040d763)
19: }
RELEASE版
int main(int argc, char* argv[])
{
int ary ={
{10,20,30},
{40,50,60},
};
int x,y;
scanf("%d%d",&x,&y);
ary=999;
return 0;
}
反汇编代码
.text:00401003 lea eax,
.text:00401007 lea ecx,
.text:0040100B push eax
.text:0040100C push ecx
.text:0040100D push offset Format ; "%d%d"
.text:00401012 call _scanf
.text:00401017 mov eax, ; x
.text:0040101B mov edx, ; y
.text:0040101F lea ecx, ; ecx = 2x + y
.text:00401022 add eax, ecx ;eax =3x+y
.text:00401024 mov , 3E7h
.text:0040102C xor eax, eax
.text:0040102E add esp, 2Ch
.text:00401031 retn
int main(int argc, char* argv[])
{
int ary = {
{10,20,30},
{40,50,60},
};
int x, y;
scanf_s("%d%d", &x, &y);
ary = 999;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d\r\n", ary);
}
}
return 0;
}
高版本汇编代码:
.text:004010A0 movapsxmm0, ds:__xmm@000000280000001e000000140000000a
.text:004010A7 lea eax,
.text:004010AA push ebx
.text:004010AB push esi
.text:004010AC push edi
.text:004010AD push eax
.text:004010AE lea eax,
.text:004010B1 mov , 32h
.text:004010B8 push eax
.text:004010B9 push offset _Format; "%d%d"
.text:004010BE movupsxmmword ptr , xmm0
.text:004010C2 mov , 3Ch
.text:004010C9 call _scanf_s
.text:004010CE mov ecx,
.text:004010D1 lea esi,
.text:004010D4 mov eax,
.text:004010D7 add esp, 0Ch
.text:004010DA mov ebx, 2
.text:004010DF lea eax,
.text:004010E2 add eax, ecx
.text:004010E4 mov , 3E7h ;比例因子寻址
.text:004010EC nop dword ptr
.text:004010F0
.text:004010F0 loc_4010F0:
.text:004010F0 mov edi, 3
.text:004010F5 db 66h, 66h
.text:004010F5 nop word ptr ;对齐到0地址
.text:00401100
.text:00401100 loc_401100:
.text:00401100 push dword ptr
.text:00401102 push offset aD ; "%d\r\n"
.text:00401107 call _printf
.text:0040110C add esp, 8
.text:0040110F add esi, 4
.text:00401112 sub edi, 1
.text:00401115 jnz short loc_401100
.text:00401117 sub ebx, 1
.text:0040111A jnz short loc_4010F0
```
编译器经常为了对齐或者其他考虑,或者合并指令的考虑,不一定是从数组首地址寻址 ,而是从数组首地址 +1, -1 的地方开始寻址,这样可以省一条指令,如果不在首地址,寻址的时候,对应的偏移量 页跟着 加或减对应的值就可以了,如果首地址不在第一个元素的位置而在后面元素的地址调整sizeof (type) * n 就可以了
到了数组,会结合前面的很多优化手段,因为存在借指令的情况,即前面的指令,后面直接拿来用
### 借指令
前面的指令,后面直接拿来用,一般是为了节省指令或者减少指令长度
```
int main(int argc, char* argv[])
{
int ary={1,2,3,4,5};
for(int i= 0; i<5 ; i++)
{
printf("%d\r\n",ary);
}
return 0;
}
反汇编代码:
.text:00401005 mov edi, 5
.text:0040100A mov , 1
.text:00401012 mov , 2
.text:0040101A mov , 3
.text:00401022 mov , 4
.text:0040102A mov , edi ;这本来应该是 5 ,但是为了节省字节用了前面的edi
.text:0040102E lea esi,
.text:00401032
.text:00401032 loc_401032: ; CODE XREF: _main+46↓j
.text:00401032 mov eax,
.text:00401034 push eax
.text:00401035 push offset aD ; "%d\r\n"
.text:0040103A call sub_401050
.text:0040103F add esp, 8
.text:00401042 add esi, 4
.text:00401045 dec edi
.text:00401046 jnz short loc_401032
.text:00401048 pop edi
.text:00401049 xor eax, eax
.text:0040104B pop esi
.text:0040104C add esp, 14h
```
!(./notesimg/1658243113654-d04341ca-b8d3-40cd-8288-931404d69c40.png)
可以看到用寄存器的指令长度要小于 立即数
### 作业
分析 strcmp 的返回结果
```
int main(int argc, char* argv[])
{
return (int)strcmp(argv,argv);
}
反汇编代码:
.text:00401000 mov eax,
.text:00401004 push ebx
.text:00401005 push esi
.text:00401006 mov esi,
.text:00401009 mov eax,
.text:0040100B
.text:0040100B LOOP_BEGIN:
.text:0040100B mov dl,
.text:0040100D mov bl,
.text:0040100F mov cl, dl
.text:00401011 cmp dl, bl
.text:00401013 jnz short LOOP_END
.text:00401015 test cl, cl
.text:00401017 jz short loc_40102F
.text:00401019 mov dl,
.text:0040101C mov bl,
.text:0040101F mov cl, dl
.text:00401021 cmp dl, bl
.text:00401023 jnz short LOOP_END
.text:00401025 add eax, 2
.text:00401028 add esi, 2
.text:0040102B test cl, cl
.text:0040102D jnz short LOOP_BEGIN
.text:0040102F
.text:0040102F loc_40102F:
.text:0040102F pop esi
.text:00401030 xor eax, eax
.text:00401032 pop ebx
.text:00401033 retn
.text:00401034 ; ---------------------------------------------------------------------------
.text:00401034
.text:00401034 LOOP_END:
.text:00401034
.text:00401034 sbb eax, eax
cmp dl, bl,当 dl != bl时跳转
当dl >bl 即 str1>str2,cf=0
sbb =>eax = eax - eax-cf =>=0
当dl < bl 即 str1< str2, cf = 1
sbb =>eax = eax - eax-cf =>= -1
.text:00401036 pop esi
.text:00401037 sbb eax, 0FFFFFFFFh
=>eax = eax - ( -1)-cf
=>eax =0时 cf =0=> eax = eax - ( -1)-cf = 1
=>eax =-1时 cf =1 => eax = eax - ( -1)-cf = -1
=>当dl >bl 即 str1> str2 返回值eax =1
=>当dl <bl 即 str1< str2 返回值eax = - 1
.text:0040103A pop ebx
.text:0040103B retn
```
谢谢分享 谢谢大佬 是个宝藏资料,谢谢了,
页:
[1]