在 Linux 上如何得到一個段錯誤的核心轉儲
本週工作中,我花了整整一週的時間來嘗試除錯一個段錯誤。我以前從來沒有這樣做過,我花了很長時間才弄清楚其中涉及的一些基本事情(獲得核心轉儲、找到導致段錯誤的行號)。於是便有了這篇部落格來解釋如何做那些事情!
在看完這篇部落格後,你應該知道如何從“哦,我的程式出現段錯誤,但我不知道正在發生什麼”到“我知道它出現段錯誤時的堆疊、行號了! ”。
什麼是段錯誤?
“段錯誤”是指你的程式嘗試訪問不允許訪問的記憶體地址的情況。這可能是由於:
- 試圖解引用空指標(你不被允許訪問記憶體地址
0
); - 試圖解引用其他一些不在你記憶體(LCTT 譯註:指不在合法的記憶體地址區間內)中的指標;
- 一個已被破壞並且指向錯誤的地方的 C++ 虛表指標,這導致程式嘗試執行沒有執行許可權的記憶體中的指令;
- 其他一些我不明白的事情,比如我認為訪問未對齊的記憶體地址也可能會導致段錯誤(LCTT 譯註:在要求自然邊界對齊的體系結構,如 MIPS、ARM 中更容易因非對齊訪問產生段錯誤)。
這個“C++ 虛表指標”是我的程式發生段錯誤的情況。我可能會在未來的部落格中解釋這個,因為我最初並不知道任何關於 C++ 的知識,並且這種虛表查詢導致程式段錯誤的情況也是我所不瞭解的。
但是!這篇部落格後不是關於 C++ 問題的。讓我們談論的基本的東西,比如,我們如何得到一個核心轉儲?
步驟1:執行 valgrind
我發現找出為什麼我的程式出現段錯誤的最簡單的方式是使用 valgrind
:我執行
valgrind -v your-program
這給了我一個故障時的堆疊呼叫序列。 簡潔!
但我想也希望做一個更深入調查,並找出些 valgrind
沒告訴我的資訊! 所以我想獲得一個核心轉儲並探索它。
如何獲得一個核心轉儲
核心轉儲是您的程式記憶體的一個副本,並且當您試圖除錯您的有問題的程式哪裡出錯的時候它非常有用。
當您的程式出現段錯誤,Linux 的核心有時會把一個核心轉儲寫到磁碟。 當我最初試圖獲得一個核心轉儲時,我很長一段時間非常沮喪,因為 - Linux 沒有生成核心轉儲!我的核心轉儲在哪裡?
這就是我最終做的事情:
- 在啟動我的程式之前執行
ulimit -c unlimited
- 執行
sudo sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t
ulimit:設定核心轉儲的最大尺寸
ulimit -c
設定核心轉儲的最大尺寸。 它往往設定為 0,這意味著核心根本不會寫核心轉儲。 它以千位元組為單位。 ulimit
是按每個程序分別設定的 —— 你可以透過執行 cat /proc/PID/limit
看到一個程序的各種資源限制。
例如這些是我的系統上一個隨便一個 Firefox 程序的資源限制:
$ cat /proc/6309/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 30571 30571 processes
Max open files 1024 1048576 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 30571 30571 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
核心在決定寫入多大的核心轉儲檔案時使用軟限制(在這種情況下,max core file size = 0
)。 您可以使用 shell 內建命令 ulimit
(ulimit -c unlimited
) 將軟限制增加到硬限制。
kernel.core_pattern:核心轉儲儲存在哪裡
kernel.core_pattern
是一個核心引數,或者叫 “sysctl 設定”,它控制 Linux 核心將核心轉儲檔案寫到磁碟的哪裡。
核心引數是一種設定您的系統全域性設定的方法。您可以透過執行 sysctl -a
得到一個包含每個核心引數的列表,或使用 sysctl kernel.core_pattern
來專門檢視 kernel.core_pattern
設定。
所以 sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t
將核心轉儲儲存到目錄 /tmp
下,並以 core
加上一系列能夠標識(出故障的)程序的引數構成的字尾為檔名。
如果你想知道這些形如 %e
、%p
的引數都表示什麼,請參考 man core。
有一點很重要,kernel.core_pattern
是一個全域性設定 —— 修改它的時候最好小心一點,因為有可能其它系統功能依賴於把它被設定為一個特定的方式(才能正常工作)。
kernel.core_pattern 和 Ubuntu
預設情況下在 ubuntu 系統中,kernel.core_pattern
被設定為下面的值:
$ sysctl kernel.core_pattern
kernel.core_pattern = |/usr/share/apport/apport %p %s %c %d %P
這引起了我的迷惑(這 apport 是幹什麼的,它對我的核心轉儲做了什麼?)。以下關於這個我瞭解到的:
- Ubuntu 使用一種叫做 apport 的系統來報告 apt 包有關的崩潰資訊。
- 設定
kernel.core_pattern=|/usr/share/apport/apport %p %s %c %d %P
意味著核心轉儲將被透過管道送給apport
程式。 - apport 的日誌儲存在檔案
/var/log/apport.log
中。 - apport 預設會忽略來自不屬於 Ubuntu 軟體包一部分的二進位制檔案的崩潰資訊
我最終只是跳過了 apport,並把 kernel.core_pattern
重新設定為 sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t
,因為我在一臺開發機上,我不在乎 apport 是否工作,我也不想嘗試讓 apport 把我的核心轉儲留在磁碟上。
現在你有了核心轉儲,接下來幹什麼?
好的,現在我們瞭解了 ulimit
和 kernel.core_pattern
,並且實際上在磁碟的 /tmp
目錄中有了一個核心轉儲檔案。太好了!接下來幹什麼?我們仍然不知道該程式為什麼會出現段錯誤!
下一步將使用 gdb
開啟核心轉儲檔案並獲取堆疊呼叫序列。
從 gdb 中得到堆疊呼叫序列
你可以像這樣用 gdb
開啟一個核心轉儲檔案:
$ gdb -c my_core_file
接下來,我們想知道程式崩潰時的堆疊是什麼樣的。在 gdb
提示符下執行 bt
會給你一個呼叫序列。在我的例子裡,gdb
沒有為二進位制檔案載入符號資訊,所以這些函式名就像 “??????”。幸運的是,(我們透過)載入符號修復了它。
下面是如何載入除錯符號。
symbol-file /path/to/my/binary
sharedlibrary
這從二進位制檔案及其引用的任何共享庫中載入符號。一旦我這樣做了,當我執行 bt
時,gdb 給了我一個帶有行號的漂亮的堆疊跟蹤!
如果你想它能工作,二進位制檔案應該以帶有除錯符號資訊的方式被編譯。在試圖找出程式崩潰的原因時,堆疊跟蹤中的行號非常有幫助。:)
檢視每個執行緒的堆疊
透過以下方式在 gdb
中獲取每個執行緒的呼叫棧!
thread apply all bt full
gdb + 核心轉儲 = 驚喜
如果你有一個帶除錯符號的核心轉儲以及 gdb
,那太棒了!您可以上下檢視呼叫堆疊(LCTT 譯註:指跳進呼叫序列不同的函式中以便於檢視區域性變數),列印變數,並檢視記憶體來得知發生了什麼。這是最好的。
如果您仍然正在基於 gdb 嚮導來工作上,只列印出棧跟蹤與bt也可以。 :)
ASAN
另一種搞清楚您的段錯誤的方法是使用 AddressSanitizer 選項編譯程式(“ASAN”,即 $CC -fsanitize=address
)然後執行它。 本文中我不準備討論那個,因為本文已經相當長了,並且在我的例子中開啟 ASAN 後段錯誤消失了,可能是因為 ASAN 使用了一個不同的記憶體分配器(系統記憶體分配器,而不是 tcmalloc)。
在未來如果我能讓 ASAN 工作,我可能會多寫點有關它的東西。(LCTT 譯註:這裡指使用 ASAN 也能復現段錯誤)
從一個核心轉儲得到一個堆疊跟蹤真的很親切!
這個部落格聽起來很多,當我做這些的時候很困惑,但說真的,從一個段錯誤的程式中獲得一個堆疊呼叫序列不需要那麼多步驟:
- 試試用
valgrind
如果那沒用,或者你想要拿到一個核心轉儲來調查:
- 確保二進位制檔案編譯時帶有除錯符號資訊;
- 正確的設定
ulimit
和kernel.core_pattern
; - 執行程式;
- 一旦你用
gdb
除錯核心轉儲了,載入符號並執行bt
; - 嘗試找出發生了什麼!
我可以使用 gdb
弄清楚有個 C++ 的虛表條目指向一些被破壞的記憶體,這有點幫助,並且使我感覺好像更懂了 C++ 一點。也許有一天我們會更多地討論如何使用 gdb
來查詢問題!
via: https://jvns.ca/blog/2018/04/28/debugging-a-segfault-on-linux/
作者:Julia Evans 譯者:stephenxs 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出
相關文章
- Ubuntu20.04出現段錯誤核心已轉儲問題解決方案Ubuntu
- ORA-07445: 出現異常錯誤: 核心轉儲 [kkqstcrf()+1355]CRF
- C++ 在模板三個階段檢查錯誤C++
- linux故障集合:架構階段備份服務-儲存服務錯誤Linux架構
- 除錯Go語言的核心轉儲(Core Dumps)除錯Go
- C中的匯流排錯誤和段錯誤
- backtrace() 段錯誤定位
- 企業選型作業上常犯的一個錯誤
- 第一個錯誤的版本
- [譯] 我在程式設計初級階段常犯的錯誤程式設計
- 2024.11.1 一個錯誤
- 如何得到一個隨機密碼隨機密碼
- 在Linux中,linux核心引數如何修改?Linux
- xsos:一個在 Linux 上閱讀 SOSReport 的工具Linux
- 如何簡單的在TF卡上做一個Linux的檔案系統Linux
- 教你在Ubuntu上安裝Linux核心6.1UbuntuLinux
- 在Linux中, 如何建立一個快照?Linux
- Linux 上如何禁用 USB 儲存Linux
- 轉:在Linux上執行WinFormLinuxORM
- 在 Linux 上如何安裝 SoundConverter及轉換音訊Linux音訊
- Java 工程師如何得到一個好 OfferJava工程師
- 準則2.1-效能、執行Wi-Fi在iPad上一個或多個錯誤問題iPad
- 在 Linux 上配置一個 syslog 伺服器Linux伺服器
- 如何解決ORA-04031 錯誤(轉)
- linux下gdb如何處理coredump錯誤Linux
- 在vscode上寫Makefile出現格式錯誤VSCode
- leedcode-第一個錯誤的版本
- 分享一個有意思的錯誤
- 在Linux中,如何建立一個分割槽?Linux
- 在Linux中,如何獲取CPU的總核心數?Linux
- WPF如何得到一個在使用者控制元件內部的元素的座標位置控制元件
- 在vue生命週期裡呼叫函式時犯的一個錯誤Vue函式
- 【轉載】Linux核心除錯之使用模組引數Linux除錯
- 如何使用cgdb + qemu除錯linux核心模組除錯Linux
- 【熱點】數字化轉型最致命的4個誤區和3個錯誤
- PHP 核心特性 - 錯誤處理PHP
- Linux 時間錯誤的修正Linux
- eclipse在使用中彈出這個錯誤框,該如何處理?Eclipse