你的哪些騷操作會導致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、電腦系統基礎、演算法與數據結構)知識。