Go程式崩潰現場應該如何保留?
導讀 | Go 程式突然莫名崩潰後,當日志記錄沒有覆蓋到錯誤場景時,還有別的方法排查嗎? |
沒有消滅一切的銀彈,也沒有可以保證永不出錯的程式。我們應當如何捕捉 Go 程式錯誤?我想同學們的第一反應是:打日誌。
但錯誤日誌的能力是有限的。第一,日誌是開發者在程式碼中定義的列印資訊,我們沒法保證日誌資訊能包含所有的錯誤情況。第二,在 Go 程式中發生 panic 時,我們也並不總是能通過 recover 捕獲(沒法插入日誌程式碼)。
那線上 Go 程式突然莫名崩潰後,當日志記錄沒有覆蓋到錯誤場景時,還有別的方法排查嗎?
core dump 又即核心轉儲,簡單來說它就是程式意外終止時產生的記憶體快照。我們可以通過 core dump 檔案來調式程式,找出其崩潰原因。
在 平臺上,可通過ulimit -c 檢視核心轉儲配置,系統預設為 0,表明未開啟 core dump 記錄功能。
$ ulimit -c 0
可以使用ulimit -c [size] 指定記錄 core dump 檔案的大小,即是開啟 core dump 記錄。當然,如果電腦資源足夠,避免 core dump 丟失或記錄不全,也可執行ulimit -c unlimited而不限制 core dump 檔案大小。
那在 Go 程式中,如何開啟 core dump 呢?
我們在你真的懂string與[]byte的轉換了嗎一文中探討過 string 轉 []byte 的黑魔法,如下例所示。
package main import ( "reflect" "unsafe" ) func String2Bytes(s string) []byte { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := reflect.SliceHeader{ Data: sh.Data, Len: sh.Len, Cap: sh.Len, } return *(*[]byte)(unsafe.Pointer(&bh)) } func Modify() { a := "hello" b := String2Bytes(a) b[0] = 'H' } func main() { Modify() }
string 是不可以被修改的,當我們將 string 型別通過黑魔法轉為 []byte 後,企圖修改其值,程式會發生一個不能被 recover 捕獲到的錯誤。
$ go run main.go unexpected fault address 0x106a6a4 fatal error: fault [signal SIGBUS: bus error code=0x2 addr=0x106a6a4 pc=0x105b01a] goroutine 1 [running]: runtime.throw({0x106a68b, 0x0}) /usr/local/go/src/runtime/panic.go:1198 +0x71 fp=0xc000092ee8 sp=0xc000092eb8 pc=0x102bad1 runtime.sigpanic() /usr/local/go/src/runtime/signal_unix.go:732 +0x1d6 fp=0xc000092f38 sp=0xc000092ee8 pc=0x103f2f6 main.Modify(...) /Users/slp/github/PostDemo/coreDemo/main.go:21 main.main() /Users/slp/github/PostDemo/coreDemo/main.go:25 +0x5a fp=0xc000092f80 sp=0xc000092f38 pc=0x105b01a runtime.main() /usr/local/go/src/runtime/proc.go:255 +0x227 fp=0xc000092fe0 sp=0xc000092f80 pc=0x102e167 runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1581 +0x1 fp=0xc000092fe8 sp=0xc000092fe0 pc=0x1052dc1 exit status 2
這些堆疊資訊是由 GOTRACEBACK 變數來控制列印粒度的,它有五種級別。
- none,不顯示任何 goroutine 堆疊資訊
- single,預設級別,顯示當前 goroutine 堆疊資訊
- all,顯示所有 user (不包括 runtime)建立的 goroutine 堆疊資訊
- system,顯示所有 user + runtime 建立的 goroutine 堆疊資訊
- crash,和 system 列印一致,但會生成 core dump 檔案(Unix 系統上,崩潰會引發 SIGABRT 以觸發core dump)
如果我們將 GOTRACEBACK 設定為 system ,我們將看到程式崩潰時所有 goroutine 狀態資訊
$ GOTRACEBACK=system go run main.go unexpected fault address 0x106a6a4 fatal error: fault [signal SIGBUS: bus error code=0x2 addr=0x106a6a4 pc=0x105b01a] goroutine 1 [running]: runtime.throw({0x106a68b, 0x0}) ... goroutine 2 [force gc (idle)]: runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0) ... created by runtime.init.7 /usr/local/go/src/runtime/proc.go:294 +0x25 goroutine 3 [GC sweep wait]: runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0) ... created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:181 +0x55 goroutine 4 [GC scavenge wait]: runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0) ... created by runtime.gcenable /usr/local/go/src/runtime/mgc.go:182 +0x65 exit status 2
如果想獲取 core dump 檔案,那麼就應該把 GOTRACEBACK 的值設定為 crash 。當然,我們還可以通過 runtime/debug 包中的 SetTraceback 方法來設定堆疊列印級別。
delve 是 Go 語言編寫的 Go 程式偵錯程式,我們可以通過 dlv core 命令來除錯 core dump。
首先,通過以下命令安裝 delve
go get -u github.com/go-delve/delve/cmd/dlv
還是以上文中的例子為例,我們通過設定 GOTRACEBACK 為 crash 級別來獲取 core dump 檔案
$ tree . └── main.go $ ulimit -c unlimited $ go build main.go $ GOTRACEBACK=crash ./main ... Aborted (core dumped) $ tree . ├── core ├── main └── main.go $ ls -alh core -rw------- 1 slp slp 41M Oct 31 22:15 core
此時,在同級目錄得到了 core dump 檔案 core(檔名、儲存路徑、是否加上程式號都可以配置修改)。
通過 dlv 偵錯程式來除錯 core 檔案,執行命令格式 dlv core 可執行檔名 core檔案
$ dlv core main core Type 'help' for list of commands. (dlv)
命令 goroutines 獲取所有 goroutine 相關資訊
(dlv) goroutines * Goroutine 1 - User: ./main.go:21 main.main (0x45b81a) (thread 18061) Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [force gc (idle)] Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [GC sweep wait] Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [GC scavenge wait] [4 goroutines] (dlv)
Goroutine 1 是出問題的 goroutine (帶有 * 代表當前幀),通過命令 goroutine 1 切換到其棧幀
(dlv) goroutine 1 Switched from 1 to 1 (thread 18061) (dlv)
執行命令 bt(breakpoints trace) 檢視當前的棧幀詳細資訊
(dlv) bt 0 0x0000000000454bc1 in runtime.raise at /usr/local/go/src/runtime/sys_linux_amd64.s:165 1 0x0000000000452f60 in runtime.systemstack_switch at /usr/local/go/src/runtime/asm_amd64.s:350 2 0x000000000042c530 in runtime.fatalthrow at /usr/local/go/src/runtime/panic.go:1250 3 0x000000000042c2f1 in runtime.throw at /usr/local/go/src/runtime/panic.go:1198 4 0x000000000043fa76 in runtime.sigpanic at /usr/local/go/src/runtime/signal_unix.go:742 5 0x000000000045b81a in main.Modify at ./main.go:21 6 0x000000000045b81a in main.main at ./main.go:25 7 0x000000000042e9c7 in runtime.main at /usr/local/go/src/runtime/proc.go:255 8 0x0000000000453361 in runtime.goexit at /usr/local/go/src/runtime/asm_amd64.s:1581 (dlv)
通過 5 0x000000000045b81a in main.Modify 發現了錯誤程式碼所在函式,執行命令 frame 5 進入函式具體程式碼
(dlv) frame 5 > runtime.raise() /usr/local/go/src/runtime/sys_linux_amd64.s:165 (PC: 0x454bc1) Warning: debugging optimized function Frame 5: ./main.go:21 (PC: 45b81a) 16: } 17: 18: func Modify() { 19: a := "hello" 20: b := String2Bytes(a) => 21: b[0] = 'H' 22: } 23: 24: func main() { 25: Modify() 26: } (dlv)
自此,破案了,問題就出在了擅自修改 string 底層值。
有一點需要注意,上文 core dump 生成的例子,我是在 linux 系統下完成的,mac amd64 系統沒法弄(很氣,害我折騰了兩個晚上)。
這是由於 mac 系統下的 Go 限制了生成 core dump 檔案,這個在 Go 原始碼 src/runtime/signal_unix.go 中有相關說明。
//go:nosplit func crash() { // OS X core dumps are linear dumps of the mapped memory, // from the first virtual byte to the last, with zeros in the gaps. // Because of the way we arrange the address space on 64-bit systems, // this means the OS X core file will be >128 GB and even on a zippy // workstation can take OS X well over an hour to write (uninterruptible). // Save users from making that mistake. if GOOS == "darwin" && GOARCH == "amd64" { return } dieFromSignal(_SIGABRT) }
core dump 檔案是作業系統提供給我們的一把利器,它是程式意外終止時產生的記憶體快照。利用 core dump,我們可以在程式崩潰後更好地恢復事故現場,為故障排查保駕護航。
當然,core dump 檔案的生成也是有弊端的。core dump 檔案較大,如果線上服務本身記憶體佔用就很高,那在生成 core dump 檔案上的記憶體與時間開銷都會很大。另外,我們往往會佈置服務守護程式,如果我們的程式頻繁崩潰和重啟,那會生成大量的 core dump 檔案(設定了core+pid 命名規則),產生磁碟打滿的風險(如果放開了核心限制 ulimit -c unlimited)。
最後,如果擔心錯誤日誌不能幫助我們定位 Go 程式碼問題,我們可以為它開啟 core dump 功能,在 hotfix 上增加奇兵。對於有守護程式的服務,建議設定好 ulimt -c 大小限制。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2842165/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 網站經常崩潰,企業應該如何做好監控?網站
- 「Go框架」gin框架是如何做崩潰處理的?Go框架
- iOS應用崩潰了,如何透過崩潰手機連線電腦查詢日誌方法iOS應用崩潰
- WWDC 2018:理解崩潰以及崩潰日誌
- GodBlessYou: 讓你的應用不再崩潰Go
- Android 收集程式崩潰異常資訊Android
- APP防崩潰APP
- 崩潰日記
- win10系統崩潰藍屏什麼原因 win10系統出現崩潰藍屏如何修復Win10
- C++記錄程式崩潰時的dumpfileC++
- iOS Crash不崩潰iOS
- app 崩潰的原因APP
- 歷史技術棧體系即將崩潰,我們如何應對?
- 執行緒崩潰為什麼不會導致 JVM 崩潰執行緒JVM
- .net ocre 程式崩潰自動dump在多平臺中的實現
- iOS | 零程式碼快速整合AGC崩潰服務iOSGC
- Android | 零程式碼快速整合AGC崩潰服務AndroidGC
- 讓 Chrome 崩潰的一行 CSS 程式碼ChromeCSS
- Qt程式繼承QApplication發生崩潰的原因QT繼承APP
- win10經常網頁崩潰怎麼解決 win10網頁崩潰如何修復Win10網頁
- win10系統崩潰藍色畫面什麼原因 win10系統出現崩潰藍色畫面如何修復Win10
- AutoEx應用崩潰自動匹配Stack Overflow的解答應用崩潰
- ios12升級, App應用崩潰閃退iOSAPP應用崩潰
- IOS 崩潰日誌分析iOS
- MySQL 8.0.11 無故崩潰MySql
- 我們分析了100個移動應用程式,發現了App崩潰的6個常見原因!APP
- 模態對話方塊可能導致程式崩潰
- Linux中程式崩潰及重啟的原因詳解!Linux
- 記一次 .NET 某工控MES程式 崩潰分析
- React Native | 零程式碼快速整合AGC崩潰服務React NativeGC
- win10輸入法lol崩潰怎麼修復_win10輸入法lol崩潰如何解決Win10
- 應用崩潰了?Android vitals 幫您精確診斷應用崩潰Android
- 如何定位瀏覽器頁面崩潰的問題瀏覽器
- Go語言程式設計有哪些利與弊?程式設計時如何判斷是否應該用Go?Go程式設計
- 移動App測試崩潰常見的測試場景APP
- InnoDB 崩潰恢復機制
- 【除錯技巧】Dialog dismiss 崩潰除錯
- iOS 避免常見崩潰(二)iOS