c语言变长数组与数组越界保护


在c99协议标准中,增加了变长数组(VLA)这一特性,本文旨在从汇编的角度来理解其原理,并且简单阐述下数组越界保护的内容。

在此顺带说一下自己对c语言学习的理解,关于常规的表达式、语法等不做阐述,主要对一些复杂的关键字或者特性的学习方式作以自己的感悟。如:c语言中对const、static、变长数组等的学习。
1.通过尝试式学习,即通过代码实现进行尝试说明。
2.通过的内存段工具binutils,可以通过工具查看分析代码的段、符号、汇编等。
3.通过汇编代码进行学习,汇编作为分析调用栈不可或缺的技能,当然需要我们予以重视。
4.通过地址进行学习,如果我们懂了地址与内存之间的关系,可以通过地址推断出一些c的特性原理,如两个变量地址间的关系等
利用以下代码进行说明,里面有定长和变长数组的代码,在此通过汇编进行说明,采用arm交叉编译链进行说明;

#include <stdio.h>
void array()
{
   int a[10];
}
void array_vla(int x)
{
	int a[x];
}
int main(int argc,char *argv[])
{
    array();
	array_vla(10);
	return 0;	
}

通过gcc编译后,使用objdump工具查看其汇编代码:

1.定长数组的使用

定长数组,应该都很熟悉了,是对同一种类型的描述,如int a[10];明显这是定义了长度为10的数组,类型为int类型。
定长数组使用函数array(),我们通过汇编进一步说明。

0001040c <array>:
   1040c:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
   10410:	e28db000 	add	fp, sp, #0
   10414:	e24dd02c 	sub	sp, sp, #44	; 0x2c
   10418:	e28bd000 	add	sp, fp, #0
   1041c:	e8bd0800 	ldmfd	sp!, {fp}
   10420:	e12fff1e 	bx	lr

2.定长数组的栈空间

一步一步汇编进行分析:
1.先将fp入栈;
2.fp与sp同时指向一个地址;即栈空间为0;
3.扩展栈空间,大小为44个字节;//此处因为我们有个局部的定长数组
4.然后将栈空间还原为0;
5.将fp出栈
6.通过lr寄存器返回main函数;
可以看到的是,除了函数的出口与入口,在函数的运行过程中栈空间不会改变;

3.变长数组的使用

定长数组使用函数array_vla(int x),我们通过汇编进一步说明。

00010424 <array_vla>:
————————————————————————————(1)——————————————————————————————————
   10424:	e92d08f0 	push	{r4, r5, r6, r7, fp}
   10428:	e28db010 	add	fp, sp, #16
   1042c:	e24dd014 	sub	sp, sp, #20
—————————————————————————————(2)—————————————————————————————————
   10430:	e50b0020 	str	r0, [fp, #-32]	; 0xffffffe0
   10434:	e1a0300d 	mov	r3, sp
   10438:	e1a0c003 	mov	ip, r3
   1043c:	e51b0020 	ldr	r0, [fp, #-32]	; 0xffffffe0
   10440:	e2403001 	sub	r3, r0, #1
   10444:	e50b301c 	str	r3, [fp, #-28]	; 0xffffffe4
   10448:	e1a07000 	mov	r7, r0
   1044c:	e1a03007 	mov	r3, r7
   10450:	e3a04000 	mov	r4, #0
   10454:	e1a06284 	lsl	r6, r4, #5
   10458:	e1866da3 	orr	r6, r6, r3, lsr #27
   1045c:	e1a05283 	lsl	r5, r3, #5
   10460:	e1a05000 	mov	r5, r0
   10464:	e1a03005 	mov	r3, r5
   10468:	e3a04000 	mov	r4, #0
   1046c:	e1a02284 	lsl	r2, r4, #5
   10470:	e1822da3 	orr	r2, r2, r3, lsr #27
   10474:	e1a01283 	lsl	r1, r3, #5
   10478:	e1a03000 	mov	r3, r0
   1047c:	e1a03103 	lsl	r3, r3, #2
   10480:	e2833003 	add	r3, r3, #3
   10484:	e2833007 	add	r3, r3, #7
   10488:	e1a031a3 	lsr	r3, r3, #3
   1048c:	e1a03183 	lsl	r3, r3, #3
   10490:	e04dd003 	sub	sp, sp, r3
 —————————————————————————————(3)—————————————————————————————————
   10494:	e1a0300d 	mov	r3, sp
   10498:	e2833003 	add	r3, r3, #3
   1049c:	e1a03123 	lsr	r3, r3, #2
   104a0:	e1a03103 	lsl	r3, r3, #2
   104a4:	e50b3018 	str	r3, [fp, #-24]	; 0xffffffe8
   104a8:	e1a0d00c 	mov	sp, ip
   104ac:	e24bd010 	sub	sp, fp, #16
   104b0:	e8bd08f0 	pop	{r4, r5, r6, r7, fp}
   104b4:	e12fff1e 	bx	lr

4.变长数组的栈空间

可以明显的看到,虽然仅仅是一个定长数组与变长数组的差异,可以汇编代码其实差异是很大的,至少从汇编的行数上也能说明这一点,
对比定长数组的汇编分析,其主要可以分为三个过程:
1.开辟固定的栈空间
2.然后通过传参x计算出需要扩展的栈空间;
最终通过计算后利用r3进行扩展栈空间,通过反复尝试,发现r3 = (x+2)*sizeof(int);即分配的空间大于原始的数组空间,后面两个字节其实是编译器做的数组越界保护;
3.还原栈空间;
主要的差异为第二阶段的扩展栈空间的过程,从以上分析可以看到,其实变长数组只是对之前的栈做了动态的扩充,以达到可以实现栈变长的目地。

5 数组越界保护

无论是定长数组或者变长数组,发现在数组栈的申请时,会比原始的数组大一点,其实这是编译器做的数组越界保护,我们知道了这个原理后,其实就可以做相应的程序来"欺骗"过执行行为。

//test1.c
#include<stdio.h>
int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	a[10]= 111;
	for(int i = 0; i < 11; ++i)
		printf("%d ", a[i]);
	printf("\n");	
	return 0;
}
//执行结果:
1 2 3 4 5 6 7 8 9 10 111
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
//test2.c
#include<stdio.h>
int x = 0;
int main()
{
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	x = a[10];
	a[10]= 111;
	for(int i = 0; i < 11; ++i)
		printf("%d ", a[i]);
	printf("\n");
	a[10] = x;
	return 0;
}
//执行结果:
1 2 3 4 5 6 7 8 9 10 111

对于test1.c,很明显可以发现我们对a[10]进行了写操作,然后执行时出现了异常,我们对test1.c进行简单的修改,将a[10]的参数进行写入后最后进行恢复,发现test2.c是可以执行的,说明我们已经完美的欺骗过了执行环境;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值