這篇文章主要介紹了淺析C語(yǔ)言編程中的數(shù)組越界問題,通過(guò)內(nèi)存空間來(lái)討論其導(dǎo)致的程序崩潰問題,需要的朋友可以參考下
因?yàn)镃語(yǔ)言不檢查數(shù)組越界,而數(shù)組又是我們經(jīng)常用的數(shù)據(jù)結(jié)構(gòu)之一,所以程序中經(jīng)常會(huì)遇到數(shù)組越界的情況,并且后果輕者讀寫數(shù)據(jù)不對(duì),重者程序crash。下面我們來(lái)分析一下數(shù)組越界的情況:
1) 堆中的數(shù)組越界
因?yàn)槎咽俏覀冏约悍峙涞模绻浇纾敲磿?huì)把堆中其他空間的數(shù)據(jù)給寫掉,或讀取了其他空間的數(shù)據(jù),這樣就會(huì)導(dǎo)致其他變量的數(shù)據(jù)變得不對(duì),如果是一個(gè)指針的話,那么有可能會(huì)引起crash
2) 棧中的數(shù)組越界
因?yàn)闂J窍蛳略鲩L(zhǎng)的,在進(jìn)入一個(gè)函數(shù)之前,會(huì)先把參數(shù)和下一步要執(zhí)行的指令地址(通過(guò)call實(shí)現(xiàn))壓棧,在函數(shù)的入口會(huì)把ebp壓棧,并把esp賦值給ebp,在函數(shù)返回的時(shí)候,將ebp值賦給esp,pop先前棧內(nèi)的上級(jí)函數(shù)棧的基地址給ebp,恢復(fù)原棧基址,然后把調(diào)用函數(shù)之前的壓入棧的指令地址pop出來(lái)(通過(guò)ret實(shí)現(xiàn))。
棧是由高往低增長(zhǎng)的,而數(shù)組的存儲(chǔ)是由低位往高位存的 ,如果越界的話,會(huì)把當(dāng)前函數(shù)的ebp和下一跳的指令地址覆蓋掉,如果覆蓋了當(dāng)前函數(shù)的ebp,那么在恢復(fù)的時(shí)候esp就不能指向正確的地方,從而導(dǎo)致未可知的情況,如果下一跳的地址也被覆蓋掉,那么肯定會(huì)導(dǎo)致crash。
-------------------------
壓入的參數(shù)和函數(shù)指針
-------------------------
aa[4]
aa[3]
合法的數(shù)組空間 aa[2]
aa[1]
aa[0]
-------------------------
###sta.c###
- #include <stdio.h>
- void f(int ai)
- {
- int aa[5]={1,2,3};
- int i = 1;
- for (i=0;i<10;i++)
- aa[i]=i;
- printf("f()/n");
- }
- void main()
- {
- f(3);
- printf("ok/n");
- }
- ###sta.s###
- .file "sta.c" ;說(shuō)明匯編的源程序
- .section .rodata ;說(shuō)明以下是只讀數(shù)據(jù)區(qū)
- .LC0:
- .string "f()" ;"f()" 的類型是string,地址為L(zhǎng)C0
- .text ;代碼段開始
- .globl f ;f為全局可訪問
- .type f, @function ; f是函數(shù)
- f:
- pushl %ebp
- movl %esp, %ebp
- subl $40, %esp
- movl $0, -24(%ebp)
- movl $0, -20(%ebp)
- movl $0, -16(%ebp)
- movl $0, -12(%ebp)
- movl $0, -8(%ebp)
- movl $1, -24(%ebp)
- movl $2, -20(%ebp)
- movl $3, -16(%ebp)
- movl $1, -4(%ebp)
- movl $0, -4(%ebp)
- jmp .L2
- .L3:
- movl -4(%ebp), %edx
- movl -4(%ebp), %eax
- movl %eax, -24(%ebp,%edx,4)
- addl $1, -4(%ebp)
- .L2:
- cmpl $9, -4(%ebp)
- jle .L3
- movl $.LC0, (%esp)
- call puts
- leave
- ret
- .size f, .-f ;用以計(jì)算函數(shù)f的大小
- .section .rodata
- .LC1:
- .string "ok"
- .text
- .globl main
- .type main, @function
- main:
- leal 4(%esp), %ecx
- andl $-16, %esp
- pushl -4(%ecx)
- pushl %ebp
- movl %esp, %ebp
- pushl %ecx
- subl $4, %esp
- movl $3, (%esp)
- call f
- movl $.LC1, (%esp)
- call puts
- addl $4, %esp
- popl %ecx
- popl %ebp
- leal -4(%ecx), %esp
- ret
- .size main, .-main
- .ident "GCC: (GNU) 4.1.2 20070115 (SUSE Linux)" ;說(shuō)明是用什么工具編譯的
- .section .note.GNU-stack,"",@progbits
從main函數(shù)開始?jí)喝雈函數(shù)的參數(shù)開始,堆棧的調(diào)用情況如下
圖1 壓入?yún)?shù)
圖二 通過(guò)call 命令壓入下一跳地址 IP
圖三 函數(shù)f 通過(guò)pushl %ebp 把 ebp保存起來(lái)
圖四 函數(shù) f 通過(guò)movl %esp, %ebp讓ebp指向esp,這樣esp就可以進(jìn)行修改,在函數(shù)返回的時(shí)候用ebp的值對(duì)esp進(jìn)行恢復(fù)
圖五 函數(shù) f 通過(guò)subl $40, %esp 給函數(shù)的局部變量預(yù)留空間
圖六 int數(shù)組 aa[5]占用了20個(gè)字節(jié)的空間,然后 int i占用了4個(gè)字節(jié)的空間(緊鄰著之前壓入棧的%ebp)
故,如果aa[5]進(jìn)行賦值,則會(huì)把 i 的值覆蓋掉,
如果對(duì)aa[6]進(jìn)行賦值,則會(huì)把 棧中的 %ebp 覆蓋掉,那么在函數(shù) f 返回的時(shí)候則不能對(duì)ebp進(jìn)行恢復(fù),即main函數(shù)的ebp變成了我們覆蓋掉的值,程序不知道會(huì)發(fā)生什么事情,但因?yàn)槲覀兊某绦蚪酉聛?lái)沒有調(diào)用棧中的內(nèi)容,故還是可以運(yùn)行的。
如果對(duì)aa[7]進(jìn)行賦值,則會(huì)把棧中的 %IP 覆蓋掉,在函數(shù) f 返回的時(shí)候就不能正確地找到下一跳的地址,會(huì)crash;
新聞熱點(diǎn)
疑難解答