你的哪些騷操作會導致Segmentation Fault😂
你的哪些騷操作會導致Segmentation Fault😂
前言
如果你是一個寫過一些C程式的同學,那麼很大可能你會遇到魔幻的segmentation fault,可能一時間抓耳撓腮,本篇文章主要介紹一些常見的導致segmentation fault的程式碼問題,希望能夠幫助大家快速定位問題!
出現Segmentation Fault的常見操作
寫只讀數據
#include <stdio.h>
char* str= "hello world";
int main() {
printf("%s\n", str);
*str = '1';
return 0;
}
在上面的程式當中,str
是一個全局變數,一個指向只讀數據hello world
的指針,因為指向的數據存放在只讀數據區,如下圖所示(rodata區域):
數組下標越界
#include <stdio.h>
int main() {
int arr[10];
arr[1 << 20] = 100; // 會導致 segmentation fault
printf("arr[n] = %d\n", arr[1 << 20]); // 會導致 segmentation fault
return 0;
}
棧溢出 stakc_overflow
我們可以使用ulimit -a
命令查看,系統的一些參數設置,比如說棧的最大大小:
➜ code git:(main) ✗ ulimit -a
-t: cpu time (seconds) unlimited
-f: file size (blocks) unlimited
-d: data seg size (kbytes) unlimited
-s: stack size (kbytes) 8192
-c: core file size (blocks) 0
-m: resident set size (kbytes) unlimited
-u: processes 2061578
-n: file descriptors 1048576
-l: locked-in-memory size (kbytes) 65536
-v: address space (kbytes) unlimited
-x: file locks unlimited
-i: pending signals 2061578
-q: bytes in POSIX msg queues 819200
-e: max nice 0
-r: max rt priority 0
-N 15: unlimited
上面的參數你可以通過重新編譯linux進行更改。在上面的參數當中我們的棧能夠申請的最大空間等於8192kb = 8M
,我們現在寫一個程式來測試一下:
#include <stdio.h>
void stakc_overflow(int times) {
printf("times = %d\n", times);
char data[1 << 20]; // 每次申請 1 Mega 數據
stakc_overflow(++times);
}
int main() {
stakc_overflow(1);
return 0;
}
上面的程式輸出結果如下所示:
當我們低8次調用stakc_overflow
函數的時候,程式崩潰了,因為這個時候我們再申請數組的時候,就一定會超過8M,因為在前面的 7 次調用當中已經申請的 7M 的空間,除此之外還有其他的數據需要使用一定的棧空間,因此會有棧溢出,然後報 segmentation failt 錯誤。
解引用空指針或者野指針
#include <stdio.h>
int main() {
int* p;
printf("%d\n", *p);
return 0;
}
當我們去解引用一個空指針或者一個野指針的時候就彙報segmentation fault,其實本質上還是解引用訪問的頁面沒有分配或者沒有許可權訪問,比如下面程式碼我們可以解引用一個已經被釋放的空間。
#include <stdio.h>
#include <stdint.h>
uint64_t find_rbp() {
// 這個函數主要是得到暫存器 rbp 的值
uint64_t rbp;
asm(
"movq %%rbp, %0;"
:"=m"(rbp)::
);
return rbp;
}
int main() {
uint64_t rbp = find_rbp();
printf("rbp = %lx\n", rbp);
// long* p = 0x7ffd4ea724a0;
printf("%ld\n", *(long*)rbp);
return 0;
}
上面的程式碼當中我們調用函數 find_rbp
,得到這個函數對應的暫存器 rbp 的值,當這個函數調用返回的時候,這個函數的棧幀會被摧毀,也就是說 rbp 指向的位置程式已經沒有使用了,但是上面的程式不會產生 segmentation fault ,其中最主要的原因就是解引用的位置的頁面我們已經分配了,而且我們有讀許可權,而且我們也有寫許可權,我們甚至可以給 rbp 指向的位置賦值,像下面那樣,程式也不會崩潰。
#include <stdio.h>
#include <stdint.h>
uint64_t find_rbp() {
uint64_t rbp;
asm(
"movq %%rbp, %0;"
:"=m"(rbp)::
);
return rbp;
}
int main() {
uint64_t rbp = find_rbp();
printf("rbp = %lx\n", rbp);
// long* p = 0x7ffd4ea724a0;
printf("%ld\n", *(long*)rbp);
*(long*)rbp = 100; // 給指向的位置進行複製操作
return 0;
}
解引用已經釋放的記憶體
#include <stdlib.h>
int main() {
int* ptr = (int*)malloc(sizeof(int));
free(ptr);
*ptr = 10;
return 0;
}
其實上面的程式碼不見得一定會產生 sementation fault 因為我們使用的libc給我們提供的 free 和 malloc 函數,當我們使用 free 函數釋放我們的記憶體的時候,這部分記憶體不一定就馬上還給作業系統了,因此在地址空間當中這部分記憶體還是存在的,因此是可以解引用的。
其他方式
我相信你肯定還有很多其他的方式去引起 sementation fault 的,嗯相信你!!!😂
造成 sementation failt 的原因主要有以下兩大類:
- 讀寫沒有許可權的位置:
- 比如說前面對只讀數據區的寫操作,或者讀寫內核數據等等。
- 使用沒有分配的頁面:
- 比如數組越界,就是訪問一個沒有分配的頁面。
- 解引用空指針或者野指針或者沒有初始化的指針,因為空指針或者野指針指向的地址沒有分配。
- 不正確的使用解引用和取地址符號,比如你在使用scanf的時候沒有使用取地址符號,也可能造成segmentation fault。
- 棧溢出,這個是作業系統規定的棧的最大空間。
總結
在本篇文章當中主要給大家介紹了一些常見的造成段錯誤的原因,下篇我們仔細分析 segmentation fauilt 的本質,以及我們應該如何應對和處理 segmentation fauilt 。希望大家有所收穫~~~
以上就是本篇文章的所有內容了,我是LeHung,我們下期再見!!!更多精彩內容合集可訪問項目://github.com/Chang-LeHung/CSCore
關注公眾號:一無是處的研究僧,了解更多電腦(Java、Python、電腦系統基礎、演算法與數據結構)知識。