最近在寫專題時遇到麻煩的 segmentation fault,簡單檢查過程式碼找不到問題,後來才想到可以用 core dump 來定位問題。

但是在程式需要的 container 環境內產生 core dump 時遇到了一些問題,在此做個記錄。

設定 core dump 檔案大小

先用 ulimit -a 查看當前 shell 的資源設定:

$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 127281
max locked memory           (kbytes, -l) 4082572
max memory size             (kbytes, -m) unlimited
open files                          (-n) 1024
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 0
stack size                  (kbytes, -s) 8192
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 127281
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited

core file size 的值為 0 表示不會產生 core dump 檔案,因此要使用 ulimit -c [limit] 調整 core dump 檔案的大小上限。ulimit -c unlimited 可以把上限調成無限大,若擔心檔案太大可自行調整 limit 的數值。

ulimit -c unlimited

使用 ulimit 設定 core dump 大小只會在此次 shell session 生效,下次登入就會恢復。

若要自動設定,可以將 ulimit 指令寫到 ~/.bashrc 或直接修改 /etc/security/limits.conf

設定 core dump 檔名

先查看 /proc/sys/kernel/core_pattern 的內容:

$ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E

Ubuntu 預設會使用 apport 這個程式來管理 crash 並存在它自己的目錄。我們可以改將 core dump 放在 /tmp(此資料夾的檔案一段時間沒更改會被清理),並修改其名稱格式:

$ echo "core.%e.%p.%t" | sudo tee /proc/sys/kernel/core_pattern
core.%e.%p.%t

由於我在 container 內已經是 root user,省略 sudo,執行後卻出現以下錯誤:

$ echo "core.%e.%p.%t" | tee /proc/sys/kernel/core_pattern
tee: /proc/sys/kernel/core_pattern: Read-only file system
core.%e.%p.%t

這是因為系統的核心檔案是以 read only 的形式 mount 到 container 裡面共用的,而 rootless container 沒有權限去更改它。因此需要先在 container 外面設定好。

分析 core dump 檔案

設定完成後,當程式發生 core dump 時就會在 /tmp 資料夾產生對應的檔案。

我們可以用 gdb 對這個檔案進行分析:

gdb [program] [core dump file]
$ ll /tmp/
total 124
drwxrwxrwt 1 root root   4096 May 31 02:50 ./
dr-xr-xr-x 1 root root   4096 May 31 02:50 ../
-rw------- 1 root root 253952 May 31 02:50 core.test.58.1717095059
$ gdb test /tmp/core.test.58.1717095059
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...
(No debugging symbols found in test)
[New LWP 58]
Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000566136e40135 in func () at test.cpp:4
4           *a = 1;
(gdb)

除了可以看到發生 segmentation fault 的那行以外,也能輸入 bt (backtrace 的縮寫) 查看完整的函式呼叫的 stack:

Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000566136e40135 in func () at test.cpp:4
4           *a = 1;
(gdb) bt
#0  0x0000566136e40135 in func () at test.cpp:4
#1  0x0000566136e4014b in main () at test.cpp:8
(gdb)

這樣就能比較準確的知道是哪個地方導致 segmantation fault。

若要獲得完整的除錯資訊(行號及具體程式碼等),需要在編譯的指令加上 -g 這個 flag。

參考連結