理解 Go 語言中的 panic 輸出

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。My Code有一個 bug。?```panic: runtime error: invalid memory address or nil pointer dereference[signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x751ba4]goroutine 58 [running]:github.com/joeshaw/example.UpdateResponse(0xad3c60, 0xc420257300, 0xc4201f4200, 0x16, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, ...) /go/src/github.com/joeshaw/example/resp.go:108 +0x144github.com/joeshaw/example.PrefetchLoop(0xacfd60, 0xc420395480, 0x13a52453c000, 0xad3c60, 0xc420257300) /go/src/github.com/joeshaw/example/resp.go:82 +0xc00created by main.runServer /go/src/github.com/joeshaw/example/cmd/server/server.go:100 +0x7e0 ```這個 panic 錯誤正如輸出的第一行所指示那樣,是由解引用一個 nil 指標造成的。由於 Go 在錯誤處理中的文法,相比於其它的語言,比如 C 或者 Java,這些類型的錯誤在 Go 中是不太常見的。這種類型的錯誤在 Go 中比在其他語言 (如 C 或 Java) 中要少得多, 這得益於 Go 的錯誤處理方式。如果一個函數執行失敗,那麼這個函數一定會返回一個 `error` 作為它的最後一個傳回值。調用者應該立即檢查該函數返回的錯誤。```go// val is a pointer, err is an error interface valueval, err := somethingThatCouldFail()if err != nil { // Deal with the error, probably pushing it up the call stack return err}// By convention, nearly all the time, val is guaranteed to not be// nil here.```然而,這裡一定某處有一個 bug(譯註:指開頭的 panic),違反了這個隱式 API 的約定。在我深入介紹之前,這裡有個附加說明:這(上述代碼)是與體繫結構和作業系統有關的,我僅僅在 amd64 Linux 系統和 macOS 系統上運行。其它的系統運行結果應該會有所不同。panic 錯誤輸出的第二行給出有關觸發這個 panic 的 UNIX 訊號的資訊:```[signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x751ba4] ```因為一個 nil 指標的解引用而發生了段錯誤(SIGSEGV)。`code` 區映射到 UNIX 的 `siginfo.si_cod` 區,並且在 Linux 的 `siginfo.h` 中 `0x1` 值是 `SEGV_MAPERR`(地址未映射到對象)。`addr` 映射到 `siginfo.si_addr`,其值是 `0x30`,這並不是一個有效記憶體位址。`pc` 是程式計數器,我們可以使用它來找出程式崩潰的地方,但是我們通常沒必要這麼做,因為一個 goroutine 跟蹤有如下資訊。```goroutine 58 [running]:github.com/joeshaw/example.UpdateResponse(0xad3c60, 0xc420257300, 0xc4201f4200, 0x16, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, ...) /go/src/github.com/joeshaw/example/resp.go:108 +0x144github.com/joeshaw/example.PrefetchLoop(0xacfd60, 0xc420395480, 0x13a52453c000, 0xad3c60, 0xc420257300) /go/src/github.com/joeshaw/example/resp.go:82 +0xc00created by main.runServer /go/src/github.com/joeshaw/example/cmd/server/server.go:100 +0x7e0```在這個深層次的棧幀中,第一個導致 panic 發生的(檔案)會先列出。在這個例子中,是 `resp.go` 檔案的 108 行。在這個 goroutine 回溯資訊裡,吸引我眼球的東西是函數 `UpdateResponse` 和 `PrefetchLoop` 的參數, 因為該數字與函數簽名不匹配。```gofunc UpdateResponse(c Client, id string, version int, resp *Response, data []byte) errorfunc PrefetchLoop(ctx context.Context, interval time.Duration, c Client) ````UpdateResponse` 需要 5 個參數,但是 panic 顯示它攜帶超過 10 個參數。 `PrefetchLoop` 需要 3 個參數,但 panic 顯示它帶有 5 個參數。這樣會發生什麼呢?為了理解參數值,我們必須要瞭解一些關於 Go 底層類型的資料結構。RussCox 有兩篇很棒的部落格,一篇關於 [基本類型,結構體和指標,字串和切片](https://research.swtch.com/godata) ,另一篇關於 [介面](https://research.swtch.com/interfaces) ,它描述了這些在記憶體中是怎樣分布的。對於 Go 程式員,這兩篇文章是必備讀物,但是概括起來是:- 字串有兩個域 (一個指向字串資料的指標和一個長度)- 切片有三個域 (一個指向底層數組的指標,一個長度,一個容量)- 介面有兩個域 (一個指向類型的指標和一個指向值的指標)當 panic 發生時,我們看到在輸出中的參數值包括字串、切片和介面的匯出值。另外,函數的傳回值會被添加到參數列表的末尾。 回到我們的 `UpdateResponse` 函數,`Client` 類型是一個介面,它帶有 2 個值。 `id` 是一個字串,它有 2 個值(共 4 個)。`version` 是一個整型,帶有 1 個值(共 5 個值)。`resp` 是一個指標,帶有 1 個值(共 6 個)。`data` 是一個切片,帶有 3 個值(共 9 個值)。`error` 傳回值是一個介面,所以又多 2 個值,從而總數到達 11 個。panic 輸出數目限制為 10 個, 所以最後一個值在輸出中被截斷。這是一個帶有注釋的 `UpdateResponse` 棧幀:```github.com/joeshaw/example.UpdateResponse( 0xad3c60, // c Client interface, type pointer 0xc420257300, // c Client interface, value pointer 0xc4201f4200, // id string, data pointer 0x16, // id string, length (0x16 = 22) 0x1, // version int (1) 0x0, // resp pointer (nil!) 0x0, // data slice, backing array pointer (nil) 0x0, // data slice, length (0) 0x0, // data slice, capacity (0) 0x0, // error interface (return value), type pointer ... // truncated; would have been error interface value pointer)```這有助於確認該訊息來源的建議, 即 `resp` 是 `nil`,它被解引用了。上移一個棧幀到 `PrefetchLoop`: `ctx context.Context` 是一個介面值, `interval` 是一個 `time.Duration` (它僅僅是一個 `int64`), `Client` 也是一個介面。`PrefetchLoop` 注釋為:``` github.com/joeshaw/example.PrefetchLoop( 0xacfd60, // ctx context.Context interface, type pointer 0xc420395480, // ctx context.Context interface, value pointer 0x13a52453c000, // interval time.Duration (6h0m) 0xad3c60, // c Client interface, type pointer 0xc420257300, // c Client interface, value pointer)```正如我之前提過的,`resp` 本不應該是 `nil`,因為這種情況只有在當返回 error 不是 `nil` 時才會發生。罪魁禍首是在代碼中錯誤的使用了 `github.com/pkg/errors` 的 `Wrapf()` 函數而不是 `Errorf()`。```go// Function returns (*Response, []byte, error)if resp.StatusCode != http.StatusOK { return nil, nil, errors.Wrapf(err, "got status code %d fetching response %s", resp.StatusCode, url)}```如果 `Wrapf()` 的第一個參數傳入為 `nil`, 則它的傳回值為 `nil`。當這個 HTTP 狀態代碼不是 `http.StatusOK`,這個函數將錯誤的返回 `nil,nil,nil`,因為一個非 200 的狀態代碼不是一個錯誤,因此 `err` 的值為 `nil`。將 `errors.Wrapf()` 調用換成`errors.Errorf()` 可以修複這個 bug。理解並且結合上下文語境中看 panic 輸出可以更容易的追蹤到錯誤!希望這些資訊日後對你有用。感謝 Peter Teichman, Damian Gryski, 和 Travis Bischel,他們協助我分析 panic 的輸出參數列表。

via: https://joeshaw.org/understanding-go-panic-output/

作者:Joe Shaw 譯者:liuxinyu123 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

3752 次點擊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.