1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdio.h> int
a[50]; int
main( void ) { int
i; for
(i = 0; i < 300; i++) a[i]=i; return
0; } |
上面程序中聲明瞭 1 個全局數組變量,且有 50 個元素。但在 main() 中,故意使它訪問越界,而且越出不少,但在程序運行時並沒有出錯。然而當我們將 for 裏的 i 循環次數增大到 700 時,會產生段錯誤。
這是因爲每個進程都有自己的頁表,一般情況下每個頁爲 4K,操作系統分配頁表時,每次至少分配一個頁。像上面程序中,爲什麼在小範圍內對數組越界訪問並不會造成錯誤,這正是由於沒有越過頁邊界。但是當增大越界的範圍時,終會導致缺頁,從而發生段錯誤,因爲再往下的線性地址沒有進行映射。
再看一下局部數組變量越界的情況:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdio.h> int
main( void ) { int
a[50]; int
i; for
(i = 0; i < 52; i++) a[i]=i; return
0; } |
上面程序運行起來也沒有出錯,但是如果將 for 裏的 i 的範圍改爲 53 時,就會看到段錯誤。這是因爲,數組 a 和整數 i 都是局部變量,它們在運行時所進行的讀寫操作均在棧中。觀察反彙編代碼:
08048394 <main>:
8048394: 55 push %ebp
8048395: 89 e5 mov %esp,%ebp
8048397: 81 ec d0 00 00 00 sub $0xd0,%esp
804839d: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp)
80483a4: eb 11 jmp 80483b7 <main+0x23>
80483a6: 8b 45 fc mov -0x4(%ebp),%eax
80483a9: 8b 55 fc mov -0x4(%ebp),%edx
80483ac: 89 94 85 34 ff ff ff mov %edx,-0xcc(%ebp,%eax,4)
80483b3: 83 45 fc 01 addl $0x1,-0x4(%ebp)
80483b7: 83 7d fc 33 cmpl $0x33,-0x4(%ebp)
80483bb: 7e e9 jle 80483a6 <main+0x12>
x86 的棧指針類型爲滿遞減,上面的 0xd0 分配了 52x4 個字節棧空間。當我們寫超出數組 1 個元素空間大小(4 個字節)時,那麼將覆蓋的是 i 變量,這種情況可以修改上面程序證明:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdio.h> int
main( void ) { int
a[50]; int
i; for
( i = 0; i < 51; i++) a[i] = (i+5); printf
( "%d\n" , i); return
0; } |
上面程序輸出 i 的值爲 56,可見 i 確實被覆蓋了。
當我們寫超出數組 2 個元素空間大小(8 個字節)時,保存在棧中的 EBP 寄存器也被覆蓋了;
當我們寫超出數組 3 個元素空間大小(3 個字節)時,main() 函數的返回地址被沖掉了,此時造成了緩衝區溢出。