本帖最后由 gclome 于 2020-3-9 14:17 编辑
汇编学习--数组 数组是相同数据类型的元素的集合,它们的内存中按顺序连续存放在一起。在汇编状态下访问数组一般是通过基址加变址寻址实现的。
先找一个数组访问的C语言代码: - #include<stdio.h>
- int main()
- {
- static int a[3]={0x11,0x22,0x33};
- int i,s=0,b[3];
- for(i=0;i<3;i++)
- {
- s=s+a[i];
- b[i]=s;
- }
- for(i=0;i<3;i++)
- {
- printf("%d\n",b[i]);
- }
- return 0;
- }
复制代码
查看对应的反汇编
第一个for循环将a数字的值逐个加上s,并赋值给了b数组;第二个for循环就是将b数组里的元素逐个输出。
接下来通过反汇编对操作数组的过程简要分析: - 8: s=s+a[i];
- 00401047 8B 4D FC mov ecx,dword ptr [ebp-4] //ebp-4这里存的i的值,并放到ecx寄存器里
- 0040104A 8B 55 F8 mov edx,dword ptr [ebp-8] //ebp-8存的是s的值,并放到edx寄存器里
- 0040104D 03 14 8D 30 4A 42 00 add edx,dword ptr [ecx*4+424A30h] // s+a[i] ,看下面解释1
- 00401054 89 55 F8 mov dword ptr [ebp-8],edx //把相加后的值在放到ebp-8处
- 9: b[i]=s;
- 00401057 8B 45 FC mov eax,dword ptr [ebp-4] //这里还是相当于i的值赋值到eax寄存器
- 0040105A 8B 4D F8 mov ecx,dword ptr [ebp-8] //把相加后的值放到ecx寄存器
- 0040105D 89 4C 85 EC mov dword ptr [ebp+eax*4-14h],ecx //将ecx中的值放到内存地址为ebp+eax*4-14h处,看下面解释2
- 10: }
复制代码 解释1:dword ptr [ecx*4+424A30h],这里对a数组的寻址是通过基址加变址实现的,首先我们在内存中看一下424A30h处的值,可以看到404A30h恰好就是数组的首地址,说明当我们对数组初始化时,就会分配一段内存空间出来用于存放数组中的值,那么对整个数组的取值就可以变成[ecx*4+424A30h],其中当ecx的值取0,1,2,就可以遍历整个数组,由于我们把i赋值给了ecx寄存器,所以ecx的取值会通过i的值的变化而变化。
解释2:mov dword ptr [ebp+eax*4-14h],这是对b数组赋值,ebp指向0019FF30,ebp-14h是0019FF1C处,也是b数组的首地址, 在for循环中,i=0,也就是当eax为0的时候,此时s的值为0,ebp-8处的值为11,然后通过ecx再把11放到0019FF1C, 第二次循环,i=1,eax也为1,此时s的值为11,ebp-8处的值是22,于是相加赋值给了[ebp+1*4-14h]处,也就是0019FF20, 第三次循环,i=2,eax也为2,此时s的值是33,ebp-8处的值是33,于是相加赋值给了[ebp+2*4-14h]处,也就是0019FF24. 这里值得注意的是:我们之前定义的数组b[3] , 内存就会自动分配3个字节的内存空间用于存放数组中的元素。
对b数组的赋值就结束了,接下来看一下如何输出b数组
- 12: {
- 13: printf("%d\n",b[i]);
- 0040107B 8B 45 FC mov eax,dword ptr [ebp-4] //eax作为数组的下标
- 0040107E 8B 4C 85 EC mov ecx,dword ptr [ebp+eax*4-14h] //还是通过基址加变址的方式对数组进行寻址,并把数组中的值通过循环逐个的赋值给ecx寄存器
- 00401082 51 push ecx //ecx进栈
- 00401083 68 1C 20 42 00 push offset string "%d\n" (0042201c) //“%的\n”进栈
- 00401088 E8 43 00 00 00 call printf (004010d0) //调用输出函数
- 0040108D 83 C4 08 add esp,8 //平衡堆栈
- 14: }
复制代码
接下来写出这个程序的汇编代码:
写这个程序的汇编代码时,我发现其实完全一个for语句就可以完成上面对b数组的赋值和输出,所以在汇编的时候,我只用了一个for语句。 第一版:
这个是第一次写出来的汇编,结果只输出了第一个值,于是我加断点,调试发现,当执行完第一遍输出语句之后,eax的值就变成了3,再回去执行add eax,1时,eax就变成4,cmp eax,3之后,由于大于3就直接跳出循环了,这样的话,肯定只能输出一个值。
可是eax寄存器的值为什么会变呢?发现printf语句执行过程中会用到eax,所以改变了eax的值
第二版 另外,我还有一个发现就是在第一版的基础上用ebx计数,那么就会输出三个值,说明ebx可以用作数组的索引,只是后两个数并不正确,那说明我不能把s的值存入寄存器,而是使用绝对地址!
有了这个发现,我在对代码进行了修改现在就可以成功的输出来了!汇编代码如下: - #include<stdio.h>
- int main()
- {
- static int a[3]={0x11,0x22,0x33};
- char *str="%d\n";
- _asm{
- mov dword ptr [ebp-8],0 //s=0
- mov ebx,0 //i=0
- jmp judge
- round: add ebx,1 ebx进行计数
- judge: cmp ebx,3
- jge end //大于等于3则跳到end
- mov edx,dword ptr [ebp-8]
- add edx,dword ptr [ebx*4+424A30h] //s=s+a[i]
- mov dword ptr [ebp-8],edx //将相加后的值放到[ebp-8]处
- mov dword ptr [ebp+ebx*4-14h],edx //b[i]=s
- mov ecx,dword ptr[ebp+ebx*4-14h]
- push ecx
- push str
- call printf
- add esp,8
- jmp round
- end:
- xor eax,eax //return 0
- }
- }
复制代码 运行结果:
小结: 1.在内存中,数组可存在于栈,数据段及动态内存中,其寻址用“基址+偏移量”实现。这种间接寻址一般出现在给一些数组或是结构赋值的情况下.基址可以是常量,也可以是寄存器,为定值。根据偏移量的不同,可以对结构中相应单元赋值。
2.b[]数组放在栈中,这些栈在编译时分配。数组在声明时可以直接计算偏移地址,针对数组成员寻址是采用实际的偏移量完成的。
3.寄存器ebx可以用作数组的索引,非常类似于高级语言中的变量i
|