今天講講怎么讓golang程式生成coredump檔案,并且進行除錯的,
別看我寫了不少golang的博客,其實我平時寫c++的時間更多,所以也算和coredump是老相識了,core dump檔案實際上是行程在某個時間點時的記憶體映像,當時行程使用的記憶體是啥樣就會被原樣保存下來存在檔案系統的某個位置上,這個時間點一般是觸發了SIGSEGV或者SIGABRT這兩個信號的時候,當行程的記憶體映像保存完畢后行程就會例外終止,也就是大家喜聞樂見的“程式崩了”和“段錯誤:核心已轉儲”,
因此coredump就像是程式出錯崩潰后的“第一現場”,是用來排查錯誤的主要資源,
不過我很少在golang里除錯coredump檔案,通常來說可靠的日志和panic時列印的錯誤資訊加堆疊就足夠定位錯誤了,然而有時光靠這些資訊還不夠,不得不去求助老朋友coredump了,
下面我們主要針對這段代碼除錯,這只是個事例,所以你一眼看出問題在哪了也不要介意:
package main
import (
"fmt"
"math/rand"
)
func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for {
index := rand.Intn(11)
fmt.Println(arr[index])
}
}
編譯并運行這段代碼,運行上一小會兒就會看到程式panic了,假設報錯資訊沒能幫助我們定位問題,接下來我們看看如何用coredump除錯golang程式,
如何讓golang程式生成coredump
首先,如果你不做任何額外的設定,那么golang程式崩潰的時候只會列印崩潰資訊和簡單的呼叫堆疊資訊,并不會生成coredump檔案,
想改變這個行為有兩種方式:設定環境變數和在代碼里呼叫相關的標準庫介面,
在這之前先用ulimit命令檢測下系統當前能不能生成coredump:
$ ulimit -c
unlimited
如果是unlimited就表示可以,如果是0那就不會生成,需要修改ulimit的設定,
修改GOTRACEBACK環境變數
我們先看修改環境變數的辦法,
GOTRACEBACK是用來控制panic發生時golang程式行為的,值是字串,具體內容如下:
| 值 | 行為 |
|---|---|
| none | 不列印任何堆疊跟蹤資訊,不過崩潰的原因和哪行代碼觸發的panic還是會列印 |
| single | 只列印當前正在運行的觸發panic的goroutine的堆疊以及runtime的堆疊;如果panic是runtime里發出的,則列印所有goroutine的堆疊跟蹤資訊 |
| all | 列印所有用戶創建的goroutine的堆疊資訊(不包含runtime的) |
| system | 在前面all的基礎上把runtime相關的所有協程的堆疊資訊也一起列印出來 |
| crash | 列印的內容和前面system一樣,但還會額外生成對應作業系統上的coredump檔案 |
將這個環境變數設定成crash就可以獲得資訊最全面的coredump檔案,所以我們要做的就是像下面這樣:
go build main.go
GOTRACEBACK=crash ./main
或者你嫌麻煩,那就在服務器系統里做全域設定,一般是修改/etc/profile:
# 其他內容
# 全域設定,需要讓所有已登錄的用戶注銷會話重新登錄或者干脆重啟系統才會生效
export GOTRACEBACK=crash
上面的全域設定是針對Linux的,Windows就按正常設定環境變數那樣操作,然后重新登錄用戶即可,
這樣運行后就會生成coredump檔案了,一般會生成在當前的作業目錄里,
還有一點要注意:如果你正在使用較新的linux發行版,那么coredump檔案會被coredumpctl接管,并不會生成在當前目錄:

可以看到coredump檔案被集中管理了,使用info子命令可以看到存放這些檔案的路徑和崩潰的行程的資訊:

其中的present表示coredump的檔案還保存著,可以用來除錯,missing的哪些就代碼coredump檔案已經沒了,
想要用dlv來除錯的話得用這樣的命令:
coredumpctl debug <list那給出的崩潰的行程的id> --debugger=<除錯器程式的名字或路徑> -A <傳給除錯器的引數>
填一下空就是這樣:
coredumpctl debug 156814 --debugger=dlv -A core ./main
這樣就能正常進行除錯了,另外編譯main程式的時候記得把優化關了,以免代碼被優化得和寫的不一樣導致沒法除錯,
coredumpctl除了把coredump檔案壓縮了一下節約了一點硬碟空間之外沒有什么優勢,整個就體現了systemd家族的臭毛病:多管閑事,
使用標準庫介面
沒有標準庫函式可以主動觸發coredump生成,但有可以在代碼里設定panic時候的行為的,使用的值和GPTRACEBACK一模一樣:
debug.SetTraceback
這個函式優先級比環境變數高,但有個限制,它只能設定比環境變數的值列印更多資訊的值,也就是說如果環境變數是all,那么這個函式就只能設定system和crash,不能設定none和single,
代碼例子:
package main
import (
"fmt"
"math/rand"
+ "runtime/debug"
)
func main() {
+ debug.SetTraceback("crash")
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for {
index := rand.Intn(11)
fmt.Println(arr[index])
}
}
效果和設定環境變數一樣,這里就不展示了,
我該用哪個
沒什么特別的需求的話,我推薦你只用GOTRACEBACK環境變數,
環境變數可以在不修改代碼或者組態檔的情況下控制程式的行為,不需要花時間改代碼改配置然后再編譯運行,用標準庫的介面想達到類似效果就得寫不少代碼了,
還有個好處是方便在容器里管理,也符合云原生十二要素,
除錯coredump
coredump里保存了程式崩潰前的所有狀態,包括執行到哪行代碼了,各個變數的值是什么,還包含了runtime當前的狀態等等,
仔細檢查這些資訊就可以發現程式崩潰的原因,
還是用這條命令打開除錯器:
coredumpctl debug 156814 --debugger=dlv -A core ./main
然后按下面的步驟查看資訊:
bt,查看當前的呼叫堆疊,找到觸發panic的那行代碼在哪個frame(堆疊幀)里- 看到是編號為10的frame,使用
frame 10進入這個堆疊幀 - 使用
locals查看當前堆疊幀內變數的值 p <變數名/運算式>查看變數的具體內容,或者執行一些簡單的運算式- 還可以修改變數的值,設定斷點后再次運行查看結果,不過例子里的問題到第四步就已經明了了,

這里的問題很明顯:陣列長度是10,索引最大只有9,而index變數的值是10,所以索引訪問越界,導致了panic,
QA
Q: 上面只說了panic的時候生成coredump,如果我想要個程式正常運行時的快照該怎么做?
A: Linux上有不少行程記憶體快照生成工具,不過delve內置的互動式命令dump就可以滿足需求,
具體方法是dlv attach <pid>之后直接運行dump <輸出coredump的檔案名>命令,然后退出,或者還有全自動化的:
$ echo 'dump coredump'|dlv attach <pid> ./main --allow-non-terminal-interactive
$ ls -lh
總計 47M
-rw-r--r-- 1 a a 45M 7月 8日 00:34 coredump
-rw-r--r-- 1 a a 25 7月 8日 00:20 go.mod
-rwxr-xr-x 1 a a 1.8M 7月 8日 00:31 main
-rw-r--r-- 1 a a 141 7月 8日 00:30 main.go
可以看到當前目錄下生成了一個名為“coredump”的coredump檔案,
這個命令本身比較耗時,行程用的記憶體越多就越慢,請謹慎在生產環境使用,
Q: 這個例子里沒看出來有除錯coredump的必要,
A: 是這個例子的問題,它不夠好,我可以簡單舉一個以前遇到的真實情況:
以前有個處理用戶輸入的程式,用戶可以輸入任何utf8字符,程式會簡單處理這些字符然后存到一塊記憶體里,這東西上線后隔三差五就會panic,每次都是越界訪問,但越界的值和發生的時間都沒有規律可言,
最后實在沒辦法,抓了一次coredump,仔細檢查了用戶的輸入,發現是我們的代碼在處理某些特殊字符時想當然了,沒能正確處理資料的長度,如果光看代碼本身的話這個問題很難排查,
至于為什么不把用戶輸入打進日志,這涉及了隱私和權益問題,不能這么做,但除錯完coredump后洗掉勉強能規避這些問題,
Q: 我有必要總是開啟coredump嗎?
A: 沒有,正如我前面所說,一般日志和panic列印的資訊就夠用了,coredump本身會占據很多磁盤空間,而且在容器里dump下來的東西容器重啟后就沒了,除非單獨設定資料卷但這非常復雜,
Q: 一些web框架會用recover處理panic,請問這時候還能獲得coredump嗎?
A: 不能,被recover的panic不會觸發coredump,這時候你得想想其他辦法了,比如用第一個QA那的辦法生成個實時快照,
總結
coredump對于golang來說并不常用,但技多不壓身,了解一下對以后處理各種問題總是有幫助的,
參考
https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_attach.md
https://pkg.go.dev/runtime
https://linderud.dev/blog/coredumpctl-delve-and-debug-packages-for-go/
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/556832.html
標籤:其他
上一篇:C語言:資料結構之單鏈表(二)
下一篇:返回列表
