自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何保留 Go 程序崩潰現(xiàn)場

存儲 存儲軟件
那線上 Go 程序突然莫名崩潰后,當日志記錄沒有覆蓋到錯誤場景時,還有別的方法排查嗎?

[[432381]]

沒有消滅一切的銀彈,也沒有可以保證永不出錯的程序。我們應當如何捕捉 Go 程序錯誤?我想同學們的第一反應是:打日志。

但錯誤日志的能力是有限的。第一,日志是開發(fā)者在代碼中定義的打印信息,我們沒法保證日志信息能包含所有的錯誤情況。第二,在 Go 程序中發(fā)生 panic 時,我們也并不總是能通過 recover 捕獲(沒法插入日志代碼)。

那線上 Go 程序突然莫名崩潰后,當日志記錄沒有覆蓋到錯誤場景時,還有別的方法排查嗎?

core dump

core dump 又即核心轉儲,簡單來說它就是程序意外終止時產(chǎn)生的內(nèi)存快照。我們可以通過 core dump 文件來調式程序,找出其崩潰原因。

在 linux 平臺上,可通過ulimit -c命令查看核心轉儲配置,系統(tǒng)默認為 0,表明未開啟 core dump 記錄功能。

  1. $ ulimit -c 

可以使用ulimit -c [size]命令指定記錄 core dump 文件的大小,即是開啟 core dump 記錄。當然,如果電腦資源足夠,避免 core dump 丟失或記錄不全,也可執(zhí)行ulimit -c unlimited而不限制 core dump 文件大小。

那在 Go 程序中,如何開啟 core dump 呢?

GOTRACEBACK

我們在你真的懂string與[]byte的轉換了嗎一文中探討過 string 轉 []byte 的黑魔法,如下例所示。

  1. package main 
  2.  
  3. import ( 
  4.  "reflect" 
  5.  "unsafe" 
  6.  
  7. func String2Bytes(s string) []byte { 
  8.  sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 
  9.  bh := reflect.SliceHeader{ 
  10.   Data: sh.Data, 
  11.   Len:  sh.Len, 
  12.   Cap:  sh.Len, 
  13.  } 
  14.  return *(*[]byte)(unsafe.Pointer(&bh)) 
  15.  
  16. func Modify() { 
  17.  a := "hello" 
  18.  b := String2Bytes(a) 
  19.  b[0] = 'H' 
  20.  
  21. func main() { 
  22.  Modify() 

string 是不可以被修改的,當我們將 string 類型通過黑魔法轉為 []byte 后,企圖修改其值,程序會發(fā)生一個不能被 recover 捕獲到的錯誤。

  1. $ go run main.go 
  2. unexpected fault address 0x106a6a4 
  3. fatal error: fault 
  4. [signal SIGBUS: bus error code=0x2 addr=0x106a6a4 pc=0x105b01a] 
  5.  
  6. goroutine 1 [running]: 
  7. runtime.throw({0x106a68b, 0x0}) 
  8.  /usr/local/go/src/runtime/panic.go:1198 +0x71 fp=0xc000092ee8 sp=0xc000092eb8 pc=0x102bad1 
  9. runtime.sigpanic() 
  10.  /usr/local/go/src/runtime/signal_unix.go:732 +0x1d6 fp=0xc000092f38 sp=0xc000092ee8 pc=0x103f2f6 
  11. main.Modify(...) 
  12.  /Users/slp/github/PostDemo/coreDemo/main.go:21 
  13. main.main() 
  14.  /Users/slp/github/PostDemo/coreDemo/main.go:25 +0x5a fp=0xc000092f80 sp=0xc000092f38 pc=0x105b01a 
  15. runtime.main() 
  16.  /usr/local/go/src/runtime/proc.go:255 +0x227 fp=0xc000092fe0 sp=0xc000092f80 pc=0x102e167 
  17. runtime.goexit() 
  18.  /usr/local/go/src/runtime/asm_amd64.s:1581 +0x1 fp=0xc000092fe8 sp=0xc000092fe0 pc=0x1052dc1 
  19. exit status 2 

這些堆棧信息是由 GOTRACEBACK 變量來控制打印粒度的,它有五種級別。

  • none,不顯示任何 goroutine 堆棧信息
  • single,默認級別,顯示當前 goroutine 堆棧信息
  • all,顯示所有 user (不包括 runtime)創(chuàng)建的 goroutine 堆棧信息
  • system,顯示所有 user + runtime 創(chuàng)建的 goroutine 堆棧信息
  • crash,和 system 打印一致,但會生成 core dump 文件(Unix 系統(tǒng)上,崩潰會引發(fā) SIGABRT 以觸發(fā)core dump)

如果我們將 GOTRACEBACK 設置為 system ,我們將看到程序崩潰時所有 goroutine 狀態(tài)信息

  1. $ GOTRACEBACK=system go run main.go 
  2. unexpected fault address 0x106a6a4 
  3. fatal error: fault 
  4. [signal SIGBUS: bus error code=0x2 addr=0x106a6a4 pc=0x105b01a] 
  5.  
  6. goroutine 1 [running]: 
  7. runtime.throw({0x106a68b, 0x0}) 
  8. ... 
  9.  
  10. goroutine 2 [force gc (idle)]: 
  11. runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0) 
  12. ... 
  13. created by runtime.init.7 
  14.  /usr/local/go/src/runtime/proc.go:294 +0x25 
  15.  
  16. goroutine 3 [GC sweep wait]: 
  17. runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0) 
  18. ... 
  19. created by runtime.gcenable 
  20.  /usr/local/go/src/runtime/mgc.go:181 +0x55 
  21.  
  22. goroutine 4 [GC scavenge wait]: 
  23. runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0) 
  24. ... 
  25. created by runtime.gcenable 
  26.  /usr/local/go/src/runtime/mgc.go:182 +0x65 
  27. exit status 2 

如果想獲取 core dump 文件,那么就應該把 GOTRACEBACK 的值設置為 crash 。當然,我們還可以通過 runtime/debug 包中的 SetTraceback 方法來設置堆棧打印級別。

delve 調試

delve 是 Go 語言編寫的 Go 程序調試器,我們可以通過 dlv core 命令來調試 core dump。

首先,通過以下命令安裝 delve

  1. go get -u github.com/go-delve/delve/cmd/dlv 

還是以上文中的例子為例,我們通過設置 GOTRACEBACK 為 crash 級別來獲取 core dump 文件

  1. $ tree 
  2. └── main.go 
  3. $ ulimit -c unlimited 
  4. $ go build main.go 
  5. $ GOTRACEBACK=crash ./main 
  6. ... 
  7. Aborted (core dumped) 
  8. $ tree 
  9. ├── core 
  10. ├── main 
  11. └── main.go 
  12. $ ls -alh core 
  13. -rw------- 1 slp slp 41M Oct 31 22:15 core 

此時,在同級目錄得到了 core dump 文件 core(文件名、存儲路徑、是否加上進程號都可以配置修改)。

通過 dlv 調試器來調試 core 文件,執(zhí)行命令格式 dlv core 可執(zhí)行文件名 core文件

  1. $ dlv core main core 
  2. Type 'help' for list of commands. 
  3. (dlv) 

命令 goroutines 獲取所有 goroutine 相關信息

  1. (dlv) goroutines 
  2. * Goroutine 1 - User: ./main.go:21 main.main (0x45b81a) (thread 18061) 
  3.   Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [force gc (idle)] 
  4.   Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [GC sweep wait] 
  5.   Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [GC scavenge wait] 
  6. [4 goroutines] 
  7. (dlv) 

Goroutine 1 是出問題的 goroutine (帶有 * 代表當前幀),通過命令 goroutine 1 切換到其棧幀

  1. (dlv) goroutine 1 
  2. Switched from 1 to 1 (thread 18061) 
  3. (dlv) 

執(zhí)行命令 bt(breakpoints trace) 查看當前的棧幀詳細信息

  1. (dlv) bt 
  2. 0  0x0000000000454bc1 in runtime.raise 
  3.    at /usr/local/go/src/runtime/sys_linux_amd64.s:165 
  4. 1  0x0000000000452f60 in runtime.systemstack_switch 
  5.    at /usr/local/go/src/runtime/asm_amd64.s:350 
  6. 2  0x000000000042c530 in runtime.fatalthrow 
  7.    at /usr/local/go/src/runtime/panic.go:1250 
  8. 3  0x000000000042c2f1 in runtime.throw 
  9.    at /usr/local/go/src/runtime/panic.go:1198 
  10. 4  0x000000000043fa76 in runtime.sigpanic 
  11.    at /usr/local/go/src/runtime/signal_unix.go:742 
  12. 5  0x000000000045b81a in main.Modify 
  13.    at ./main.go:21 
  14. 6  0x000000000045b81a in main.main 
  15.    at ./main.go:25 
  16. 7  0x000000000042e9c7 in runtime.main 
  17.    at /usr/local/go/src/runtime/proc.go:255 
  18. 8  0x0000000000453361 in runtime.goexit 
  19.    at /usr/local/go/src/runtime/asm_amd64.s:1581 
  20. (dlv) 

通過 5 0x000000000045b81a in main.Modify 發(fā)現(xiàn)了錯誤代碼所在函數(shù),執(zhí)行命令 frame 5 進入函數(shù)具體代碼

  1. (dlv) frame 5 
  2. > runtime.raise() /usr/local/go/src/runtime/sys_linux_amd64.s:165 (PC: 0x454bc1) 
  3. Warning: debugging optimized function 
  4. Frame 5: ./main.go:21 (PC: 45b81a) 
  5.     16: } 
  6.     17: 
  7.     18: func Modify() { 
  8.     19:  a := "hello" 
  9.     20:  b := String2Bytes(a) 
  10. =>  21:  b[0] = 'H' 
  11.     22: } 
  12.     23: 
  13.     24: func main() { 
  14.     25:  Modify() 
  15.     26: } 
  16. (dlv) 

自此,破案了,問題就出在了擅自修改 string 底層值。

Mac 不能使用

有一點需要注意,上文 core dump 生成的例子,我是在 linux 系統(tǒng)下完成的,mac amd64 系統(tǒng)沒法弄(很氣,害我折騰了兩個晚上)。

這是由于 mac 系統(tǒng)下的 Go 限制了生成 core dump 文件,這個在 Go 源碼 src/runtime/signal_unix.go 中有相關說明。

  1. //go:nosplit 
  2. func crash() { 
  3.  // OS X core dumps are linear dumps of the mapped memory, 
  4.  // from the first virtual byte to the lastwith zeros in the gaps. 
  5.  // Because of the way we arrange the address space on 64-bit systems, 
  6.  // this means the OS X core file will be >128 GB and even on a zippy 
  7.  // workstation can take OS X well over an hour to write (uninterruptible). 
  8.  // Save users from making that mistake. 
  9.  if GOOS == "darwin" && GOARCH == "amd64" { 
  10.   return 
  11.  } 
  12.  
  13.  dieFromSignal(_SIGABRT) 

總結

core dump 文件是操作系統(tǒng)提供給我們的一把利器,它是程序意外終止時產(chǎn)生的內(nèi)存快照。利用 core dump,我們可以在程序崩潰后更好地恢復事故現(xiàn)場,為故障排查保駕護航。

當然,core dump 文件的生成也是有弊端的。core dump 文件較大,如果線上服務本身內(nèi)存占用就很高,那在生成 core dump 文件上的內(nèi)存與時間開銷都會很大。另外,我們往往會布置服務守護進程,如果我們的程序頻繁崩潰和重啟,那會生成大量的 core dump 文件(設定了core+pid 命名規(guī)則),產(chǎn)生磁盤打滿的風險(如果放開了內(nèi)核限制 ulimit -c unlimited)。

 

最后,如果擔心錯誤日志不能幫助我們定位 Go 代碼問題,我們可以為它開啟 core dump 功能,在 hotfix 上增加奇兵。對于有守護進程的服務,建議設置好 ulimt -c 大小限制。

 

責任編輯:武曉燕 來源: Golang技術分享
相關推薦

2024-03-19 14:15:48

Go程序os.Exit()

2021-02-25 22:17:19

開發(fā)技術編程

2013-06-03 09:34:14

崩潰程序程序算法

2023-12-26 15:06:00

Linux內(nèi)核轉儲

2019-08-28 07:28:13

React應用程序代碼

2022-06-27 11:20:13

工具內(nèi)存GO

2024-01-26 10:56:47

go程序進程

2022-02-07 08:55:57

Go程序代碼

2021-01-20 05:37:14

大括號Python

2013-05-02 14:39:11

iOS開發(fā)iOS應用崩潰解決

2021-08-30 15:23:03

prometheus局限性cortex

2024-06-28 10:29:18

異常處理Python

2022-09-28 12:57:13

USBLinux系統(tǒng)

2022-10-09 12:00:36

開發(fā)參數(shù)

2021-11-24 16:51:03

gRPCGoPython

2022-05-13 23:46:52

GO編程內(nèi)存

2012-03-20 09:20:40

Go語言

2021-10-09 07:52:01

Go程序重命名

2021-04-01 11:28:44

C++ LinuxWindows

2024-02-26 00:02:00

開發(fā)Go
點贊
收藏

51CTO技術棧公眾號