Golang 程序卡死排查指南
我正在幫助某人設置我的 gokrazy/rsync 實現來同步 RPKI 數據(用於保護 BGP 路由基礎設施),當時我們發現,在特定調用方式下,我的 rsync 接收器會無限期掛起。
這個問題很快就解決了,但在這個過程中,我意識到我應該記錄下這些年來我所欣賞的一些 Go 調試技巧!
場景:Go 程序掛起
如果你想跟着實驗,可以構建 gokrazy/rsync 的一個較舊版本,就在修復 bug 的提交之前(你需要 Go 1.22 或更新版本):
git clone https://github.com/gokrazy/rsync
cd rsync
git reset --hard 6c89d4dda3be055f19684c0ed56d623da458194e^
go install ./cmd/...
現在我們可以嘗試同步代碼庫:
% gokr-rsync \
-rtO \
--delete \
rsync://rsync.paas.rpki.ripe.net/repository/ \
/tmp/rpki-repo
[…]
2025/02/08 09:35:10 Opening TCP connection to rsync.paas.rpki.ripe.net:873
2025/02/08 09:35:10 rsync module "repo", path "repo/"
2025/02/08 09:35:10 (Client) Protocol versions: remote=31, negotiated=27
2025/02/08 09:35:10 Client checksum: md4
2025/02/08 09:35:10 sending daemon args: [--server --sender -tr . repo/]
2025/02/08 09:35:10 exclusion list sent
2025/02/08 09:35:10 receiving file list
2025/02/08 09:35:11 [Receiver] i=0 ? . mode=40755 len=4096 uid=0 gid=0 flags=?
[…]
2025/02/08 09:35:11 [Receiver] i=89 ? clonoth/1/3139332e33322e3130302e302f32342d3234203d3e203537313936.roa mode=100644 len=1747 uid=0 gid=0 flags=?
... 然後程序就卡在那裏了。
提示 1: 按 Ctrl+\ (SIGQUIT) 打印堆棧跟蹤
查看 Go 程序卡住位置的最簡單方法是按下 Ctrl+\
(反斜槓),讓終端發送 SIGQUIT
信號。當 Go 運行時接收到 SIGQUIT
信號時,它會在退出進程前將堆棧跟蹤打印到終端。這個行爲默認啓用,可通過 GOTRACEBACK
環境變量自定義,詳見 runtime
包文檔。
以下是我們案例中的輸出示例。我將字體縮小,方便你識別輸出的形狀(具體細節不重要,請繼續閱讀下文):
^\SIGQUIT: quit
PC=0x47664e m=0 sigcode=128
goroutine 0 gp=0x6e6020 m=0 mp=0x6e6ec0 [idle]:
internal/runtime/syscall.Syscall6()
/home/michael/sdk/go1.23.0/src/internal/runtime/syscall/asm_linux_amd64.s:36 +0xe fp=0x7ffc58665090 sp=0x7ffc58665088 pc=0x47664e
internal/runtime/syscall.EpollWait(0x586651e0?, {0x7ffc5866511c?, 0x3000000018?, 0x7ffc586651f0?}, 0x58665110?, 0x7ffc?)
/home/michael/sdk/go1.23.0/src/internal/runtime/syscall/syscall_linux.go:32 +0x45 fp=0x7ffc586650e0 sp=0x7ffc58665090 pc=0x4765e5
runtime.netpoll(0xc0000000c0?)
/home/michael/sdk/go1.23.0/src/runtime/netpoll_epoll.go:116 +0xd2 fp=0x7ffc58665768 sp=0x7ffc586650e0 pc=0x432332
runtime.findRunnable()
/home/michael/sdk/go1.23.0/src/runtime/proc.go:3580 +0x8c5 fp=0x7ffc586658e0 sp=0x7ffc58665768 pc=0x43f045
runtime.schedule()
/home/michael/sdk/go1.23.0/src/runtime/proc.go:3995 +0xb1 fp=0x7ffc58665918 sp=0x7ffc586658e0 pc=0x4405b1
runtime.park_m(0xc0000061c0)
/home/michael/sdk/go1.23.0/src/runtime/proc.go:4102 +0x1eb fp=0x7ffc58665970 sp=0x7ffc58665918 pc=0x4409cb
runtime.mcall()
/home/michael/sdk/go1.23.0/src/runtime/asm_amd64.s:459 +0x4e fp=0x7ffc58665988 sp=0x7ffc58665970 pc=0x470e2e
goroutine 1 gp=0xc0000061c0 m=nil [IO wait]:
runtime.gopark(0x452658?, 0x0?, 0x98?, 0xb3?, 0xb?)
/home/michael/sdk/go1.23.0/src/runtime/proc.go:424 +0xce fp=0xc0000eb358 sp=0xc0000eb338 pc=0x46bc0e
runtime.netpollblock(0x4a01b8?, 0x4058e6?, 0x0?)
/home/michael/sdk/go1.23.0/src/runtime/netpoll.go:575 +0xf7 fp=0xc0000eb390 sp=0xc0000eb358 pc=0x4318f7
internal/poll.runtime_pollWait(0x7ef586628808, 0x72)
/home/michael/sdk/go1.23.0/src/runtime/netpoll.go:351 +0x85 fp=0xc0000eb3b0 sp=0xc0000eb390 pc=0x46af05
internal/poll.(*pollDesc).wait(0xc0000ce180?, 0xc00020e99c?, 0x0)
/home/michael/sdk/go1.23.0/src/internal/poll/fd_poll_runtime.go:84 +0x27 fp=0xc0000eb3d8 sp=0xc0000eb3b0 pc=0x4b0ce7
internal/poll.(*pollDesc).waitRead(...)
/home/michael/sdk/go1.23.0/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Read(0xc0000ce180, {0xc00020e99c, 0x4, 0x4})
/home/michael/sdk/go1.23.0/src/internal/poll/fd_unix.go:165 +0x27a fp=0xc0000eb470 sp=0xc0000eb3d8 pc=0x4b17da
net.(*netFD).Read(0xc0000ce180, {0xc00020e99c?, 0x6eeea0?, 0x1?})
/home/michael/sdk/go1.23.0/src/net/fd_posix.go:55 +0x25 fp=0xc0000eb4b8 sp=0xc0000eb470 pc=0x4f7e85
net.(*conn).Read(0xc000206000, {0xc00020e99c?, 0xc000212000?, 0x6e6ec0?})
/home/michael/sdk/go1.23.0/src/net/net.go:189 +0x45 fp=0xc0000eb500 sp=0xc0000eb4b8 pc=0x5001a5
net.(*TCPConn).Read(0x0?, {0xc00020e99c?, 0xc0000eb568?, 0x46d449?})
<autogenerated>:1 +0x25 fp=0xc0000eb530 sp=0xc0000eb500 pc=0x50bb25
io.ReadAtLeast({0x5d9640, 0xc000206000}, {0xc00020e99c, 0x4, 0x4}, 0x4)
/home/michael/sdk/go1.23.0/src/io/io.go:335 +0x90 fp=0xc0000eb578 sp=0xc0000eb530 pc=0x4957d0
io.ReadFull(...)
/home/michael/sdk/go1.23.0/src/io/io.go:354
encoding/binary.Read({0x5d9640, 0xc000206000}, {0x5da8b0, 0x7059a0}, {0x55e7c0, 0xc0000eb6a0})
/home/michael/sdk/go1.23.0/src/encoding/binary/binary.go:244 +0xa5 fp=0xc0000eb670 sp=0xc0000eb578 pc=0x5102a5
github.com/gokrazy/rsync/internal/rsyncwire.(*MultiplexReader).ReadMsg(0xc00020a100)
/home/michael/kr/rsync/internal/rsyncwire/wire.go:50 +0x48 fp=0xc0000eb6e8 sp=0xc0000eb670 pc=0x514428
github.com/gokrazy/rsync/internal/rsyncwire.(*MultiplexReader).Read(0x7ef5869b9a68?, {0xc000280000, 0x40000, 0x4dd4fb?})
/home/michael/kr/rsync/internal/rsyncwire/wire.go:72 +0x2f fp=0xc0000eb788 sp=0xc0000eb6e8 pc=0x5145af
bufio.(*Reader).Read(0xc0002020c0, {0xc00020e998, 0x4, 0x40ece5?})
/home/michael/sdk/go1.23.0/src/bufio/bufio.go:241 +0x197 fp=0xc0000eb7c0 sp=0xc0000eb788 pc=0x4d5a57
io.ReadAtLeast({0x5d93e0, 0xc0002020c0}, {0xc00020e998, 0x4, 0x4}, 0x4)
/home/michael/sdk/go1.23.0/src/io/io.go:335 +0x90 fp=0xc0000eb808 sp=0xc0000eb7c0 pc=0x4957d0
io.ReadFull(...)
/home/michael/sdk/go1.23.0/src/io/io.go:354
github.com/gokrazy/rsync/internal/rsyncwire.(*Conn).ReadInt32(0xc000208060)
/home/michael/kr/rsync/internal/rsyncwire/wire.go:163 +0x4a fp=0xc0000eb850 sp=0xc0000eb808 pc=0x51490a
github.com/gokrazy/rsync/internal/receiver.(*Transfer).recvIdMapping1(0xc000202120, 0x5a9b58)
/home/michael/kr/rsync/internal/receiver/uidlist.go:16 +0x3d fp=0xc0000eb8c0 sp=0xc0000eb850 pc=0x51fc7d
github.com/gokrazy/rsync/internal/receiver.(*Transfer).RecvIdList(0xc000202120)
/home/michael/kr/rsync/internal/receiver/uidlist.go:52 +0x1dd fp=0xc0000eba08 sp=0xc0000eb8c0 pc=0x51ffbd
github.com/gokrazy/rsync/internal/receiver.(*Transfer).ReceiveFileList(0xc000202120)
/home/michael/kr/rsync/internal/receiver/flist.go:229 +0x378 fp=0xc0000ebb10 sp=0xc0000eba08 pc=0x51c5b8
github.com/gokrazy/rsync/internal/receivermaincmd.clientRun({{0x5d9280, 0xc000078058}, {0x5d92a0, 0xc000078060}, {0x5d92a0, 0xc000078068}}, 0xc0000d0d90, {0x7ef53d47efc8, 0xc000206000}, {0x7ffc5866600e, ...}, ...)
/home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:341 +0x5cd fp=0xc0000ebc10 sp=0xc0000ebb10 pc=0x550c2d
github.com/gokrazy/rsync/internal/receivermaincmd.socketClient({{0x5d9280, 0xc000078058}, {0x5d92a0, 0xc000078060}, {0x5d92a0, 0xc000078068}}, 0xc0000d0d90, {0x7ffc58665ff4?, 0x1?}, {0x7ffc5866600e, ...})
/home/michael/kr/rsync/internal/receivermaincmd/clientserver.go:44 +0x425 fp=0xc0000ebcd0 sp=0xc0000ebc10 pc=0x54c205
github.com/gokrazy/rsync/internal/receivermaincmd.rsyncMain({{0x5d9280, 0xc000078058}, {0x5d92a0, 0xc000078060}, {0x5d92a0, 0xc000078068}}, 0xc0000d0d90, {0xc00007e440, 0x1, 0x2}, ...)
/home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:160 +0x5d7 fp=0xc0000ebdf0 sp=0xc0000ebcd0 pc=0x54f697
github.com/gokrazy/rsync/internal/receivermaincmd.Main({0xc0000160a0, 0x5, 0x5}, {0x5d9280?, 0xc000078058?}, {0x5d92a0?, 0xc000078060?}, {0x5d92a0?, 0xc000078068?})
/home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:394 +0x272 fp=0xc0000ebee8 sp=0xc0000ebdf0 pc=0x5510d2
main.main()
/home/michael/kr/rsync/cmd/gokr-rsync/rsync.go:12 +0x4e fp=0xc0000ebf50 sp=0xc0000ebee8 pc=0x5515ae
runtime.main()
/home/michael/sdk/go1.23.0/src/runtime/proc.go:272 +0x28b fp=0xc0000ebfe0 sp=0xc0000ebf50 pc=0x438d4b
runtime.goexit({})
/home/michael/sdk/go1.23.0/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc0000ebfe8 sp=0xc0000ebfe0 pc=0x472e61
goroutine 2 gp=0xc000006c40 m=nil [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
/home/michael/sdk/go1.23.0/src/runtime/proc.go:424 +0xce fp=0xc000074fa8 sp=0xc000074f88 pc=0x46bc0e
runtime.goparkunlock(...)
/home/michael/sdk/go1.23.0/src/runtime/proc.go:430
runtime.forcegchelper()
/home/michael/sdk/go1.23.0/src/runtime/proc.go:337 +0xb3 fp=0xc000074fe0 sp=0xc000074fa8 pc=0x439093
runtime.goexit({})
/home/michael/sdk/go1.23.0/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000074fe8 sp=0xc000074fe0 pc=0x472e61
created by runtime.init.7 in goroutine 1
/home/michael/sdk/go1.23.0/src/runtime/proc.go:325 +0x1a
呼!這輸出相當密集。 我們可以使用 https://github.com/maruel/panicparse 程序來展示這個堆棧跟蹤,使其更加多彩且大幅簡化:
紅色高亮的函數清晰地指出了問題所在:我的 rsync 接收器實現錯誤地期望服務器發送 uid/gid 列表,儘管 PreserveUid 和 PreserveGid 選項並未啓用。提交 6c89d4d
修復了這個問題。
提示 2:用 delve 調試器掛載進程
如果瞬時轉儲堆棧跟蹤不足以診斷問題,你可以更進一步使用交互式調試器。
Linux 上最知名的調試器可能是 GDB,但在處理 Go 程序時,我建議使用 delve 調試器,因爲它通常表現更好。如果你還沒安裝 delve,請先安裝:
%
go install github
.
com
/
go
-
delve
/
delve
/
cmd
/
dlv@latest
本文中,我使用的是 delve v1.24.0 版本。
雖然你可以在調試器中運行新的子進程(使用 dlvexec
)而不需要特殊權限,但由於安全原因,在 Linux 中默認禁止調試器附加到現有進程。我們可以通過以下命令啓用此功能(記得稍後關閉!):
% sudo sysctl -w kernel.yama.ptrace_scope=0
kernel.yama.ptrace_scope = 0
… 然後我們可以使用 dlv attach
來查看掛起的 gokr-rsync
進程:
% dlv attach $(pidof gokr-rsync)
Type 'help' for list of commands.
(dlv)
很好。但如果我們只是打印棧跟蹤,我們只能看到來自 runtime
包的函數:
(dlv) bt
0 0x000000000047bb83 in runtime.futex
at /home/michael/sdk/go1.23.6/src/runtime/sys_linux_amd64.s:558
1 0x00000000004374d0 in runtime.futexsleep
at /home/michael/sdk/go1.23.6/src/runtime/os_linux.go:69
2 0x000000000040d89d in runtime.notesleep
at /home/michael/sdk/go1.23.6/src/runtime/lock_futex.go:170
3 0x000000000044123e in runtime.mPark
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:1866
4 0x000000000044290d in runtime.stopm
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:2886
5 0x00000000004433d0 in runtime.findRunnable
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:3623
6 0x0000000000444e1d in runtime.schedule
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:3996
7 0x00000000004451cb in runtime.park_m
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:4103
8 0x0000000000477eee in runtime.mcall
at /home/michael/sdk/go1.23.6/src/runtime/asm_amd64.s:459
原因是沒有正在運行的 goroutine(程序在無限期等待從服務器接收數據),所以我們看到的是一個在 Go 調度器中等待的操作系統線程。
我們首先需要切換到我們感興趣的 goroutine( grs
打印所有 goroutine),然後棧跟蹤就會顯示我們期望的內容:
(dlv) gr 1
Switched from 0 to 1 (thread 414327)
(dlv) bt
0 0x0000000000474ebc in runtime.gopark
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:425
1 0x000000000043819e in runtime.netpollblock
at /home/michael/sdk/go1.23.6/src/runtime/netpoll.go:575
2 0x000000000047435c in internal/poll.runtime_pollWait
at /home/michael/sdk/go1.23.6/src/runtime/netpoll.go:351
3 0x00000000004ed15a in internal/poll.(*pollDesc).wait
at /home/michael/sdk/go1.23.6/src/internal/poll/fd_poll_runtime.go:84
4 0x00000000004ed1f1 in internal/poll.(*pollDesc).waitRead
at /home/michael/sdk/go1.23.6/src/internal/poll/fd_poll_runtime.go:89
5 0x00000000004ee351 in internal/poll.(*FD).Read
at /home/michael/sdk/go1.23.6/src/internal/poll/fd_unix.go:165
6 0x0000000000569bb3 in net.(*netFD).Read
at /home/michael/sdk/go1.23.6/src/net/fd_posix.go:55
7 0x000000000057a025 in net.(*conn).Read
at /home/michael/sdk/go1.23.6/src/net/net.go:189
8 0x000000000058fcc5 in net.(*TCPConn).Read
at <autogenerated>:1
9 0x00000000004b72e8 in io.ReadAtLeast
at /home/michael/sdk/go1.23.6/src/io/io.go:335
10 0x00000000004b74d3 in io.ReadFull
at /home/michael/sdk/go1.23.6/src/io/io.go:354
11 0x0000000000598d5f in encoding/binary.Read
at /home/michael/sdk/go1.23.6/src/encoding/binary/binary.go:244
12 0x00000000005a0b7a in github.com/gokrazy/rsync/internal/rsyncwire.(*MultiplexReader).ReadMsg
at /home/michael/kr/rsync/internal/rsyncwire/wire.go:50
13 0x00000000005a0f17 in github.com/gokrazy/rsync/internal/rsyncwire.(*MultiplexReader).Read
at /home/michael/kr/rsync/internal/rsyncwire/wire.go:72
14 0x0000000000528de8 in bufio.(*Reader).Read
at /home/michael/sdk/go1.23.6/src/bufio/bufio.go:241
15 0x00000000004b72e8 in io.ReadAtLeast
at /home/michael/sdk/go1.23.6/src/io/io.go:335
16 0x00000000004b74d3 in io.ReadFull
at /home/michael/sdk/go1.23.6/src/io/io.go:354
17 0x00000000005a19ef in github.com/gokrazy/rsync/internal/rsyncwire.(*Conn).ReadInt32
at /home/michael/kr/rsync/internal/rsyncwire/wire.go:163
18 0x00000000005b77d2 in github.com/gokrazy/rsync/internal/receiver.(*Transfer).recvIdMapping1
at /home/michael/kr/rsync/internal/receiver/uidlist.go:16
19 0x00000000005b7ea8 in github.com/gokrazy/rsync/internal/receiver.(*Transfer).RecvIdList
at /home/michael/kr/rsync/internal/receiver/uidlist.go:52
20 0x00000000005b18db in github.com/gokrazy/rsync/internal/receiver.(*Transfer).ReceiveFileList
at /home/michael/kr/rsync/internal/receiver/flist.go:229
21 0x0000000000605390 in github.com/gokrazy/rsync/internal/receivermaincmd.clientRun
at /home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:341
22 0x00000000005fe572 in github.com/gokrazy/rsync/internal/receivermaincmd.socketClient
at /home/michael/kr/rsync/internal/receivermaincmd/clientserver.go:44
23 0x0000000000602f10 in github.com/gokrazy/rsync/internal/receivermaincmd.rsyncMain
at /home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:160
24 0x0000000000605e7e in github.com/gokrazy/rsync/internal/receivermaincmd.Main
at /home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:394
25 0x0000000000606653 in main.main
at /home/michael/kr/rsync/cmd/gokr-rsync/rsync.go:12
26 0x000000000043fa47 in runtime.main
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:272
27 0x000000000047bd01 in runtime.goexit
at /home/michael/sdk/go1.23.6/src/runtime/asm_amd64.s:1700
技巧 3:保存核心轉儲以便後續分析
如果你現在沒有時間在調試器中探索,可以保存一個核心轉儲文件留待以後分析。
除了在 SIGQUIT
打印堆棧跟蹤外,我們還可以通過設置環境變量 GOTRACEBACK=crash
讓 Go 運行時崩潰程序,從而使 Linux 內核生成核心轉儲文件。
現代 Linux 系統通常包含 systemd-coredump(8)
(但你可能需要顯式安裝它,例如在 Ubuntu 上)來收集核心轉儲文件(並清理舊文件)。你可以使用 coredumpctl(1)
來列出並處理這些文件。在 macOS 上,收集核心轉儲文件會更復雜。關於 Windows,我不太瞭解。 如果您的 Linux 系統未使用 systemd-coredump
,可以使用 ulimit-c unlimited
並設置內核的 kernel.core_pattern
sysctl 設置。更多詳情和選項可參閱 Go wiki 的 CoreDumpDebugging 頁面。本文中,我們將使用 coredumpctl
:
% GOTRACEBACK=crash gokr-rsync -rtO --delete rsync://rsync.paas.rpki.ripe.net/repo/ /tmp/rpki-repo
[…]
^\SIGQUIT: quit
[…]
zsh: IOT instruction (core dumped) GOTRACEBACK=crash gokr-rsync -rtO […]
最後一行就是我們想看到的:它應該顯示 "core dumped"。
此 core 文件現在應該出現在 coredumpctl(1)
中:
% coredumpctl info
PID: 414607 (gokr-rsync)
UID: 1000 (michael)
GID: 1000 (michael)
Signal: 6 (ABRT)
Timestamp: Sat 2025-02-08 10:18:27 CET (12s ago)
Command Line: gokr-rsync -rtO --delete rsync://rsync.paas.rpki.ripe.net/repo/ /tmp/rpki-repo
Executable: /bin/gokr-rsync
Control Group: /user.slice/user-1000.slice/session-1.scope
Unit: session-1.scope
Slice: user-1000.slice
Session: 1
Owner UID: 1000 (michael)
Boot ID: 6158dd3b52af4b8384c103a8a336fc02
Machine ID: ecb5a44f1a5846ad871566e113bf8937
Hostname: midna
Storage: /var/lib/systemd/coredump/core.gokr-rsync.1000.6158dd3b52af4b8384c103a8a336fc02.414607.1739006307000000.zst (present)
Size on Disk: 158.3K
Message: Process 414607 (gokr-rsync) of user 1000 dumped core.
Module [dso] without build-id.
Module [dso]
Stack trace of thread 1604447:
#0 0x0000000000475a41 runtime.raise.abi0 (/bin/gokr-rsync + 0x75a41)
#1 0x0000000000451d85 runtime.dieFromSignal (/bin/gokr-rsync + 0x51d85)
#2 0x00000000004522e6 runtime.sigfwdgo (/bin/gokr-rsync + 0x522e6)
#3 0x0000000000450c45 runtime.sigtrampgo (/bin/gokr-rsync + 0x50c45)
#4 0x0000000000475d26 runtime.sigtramp.abi0 (/bin/gokr-rsync + 0x75d26)
#5 0x0000000000475e20 n/a (/bin/gokr-rsync + 0x75e20)
ELF object binary architecture: AMD x86-64
如果您只看到十六進制地址後跟 n/a(n/a+0x0)
,這意味着 systemd-coredump
無法符號化(即將地址解析爲函數名)您的核心轉儲文件。以下是缺少符號化的幾個可能原因:
- Linux 6.12 開始產生 elfutils 無法符號化的核心轉儲。
systemd-coredump
使用 elfutils 進行符號化,所以在 Linux 和 / 或 elfutils 修復此問題之前,你需要繼續使用 Linux <6.12 版本或回退觸發該問題的提交。 - 在 systemd v234-v256 中,
systemd-coredump
沒有權限查看/home
目錄中的程序(在 systemd v257+ 中通過提交4ac1755
修復)。 - 類似地,
systemd-coredump
以PrivateTmp=yes
運行,這意味着它無法訪問您放在/tmp
中的程序。 - Go 默認構建帶有調試符號,但也許您在構建時通過
-ldflags=-w
明確剝離了調試符號?
我們現在可以使用 coredumpctl(1)
爲該程序和核心轉儲啓動 delve:
% coredumpctl debug --debugger=dlv --debugger-arguments=core
[…]
Type 'help' for list of commands.
(dlv) gr 1
Switched from 0 to 1 (thread 414607)
(dlv) bt
[…]
16 0x00000000004b74d3 in io.ReadFull
at /home/michael/sdk/go1.23.6/src/io/io.go:354
17 0x00000000005a19ef in github.com/gokrazy/rsync/internal/rsyncwire.(*Conn).ReadInt32
at /home/michael/kr/rsync/internal/rsyncwire/wire.go:163
18 0x00000000005b77d2 in github.com/gokrazy/rsync/internal/receiver.(*Transfer).recvIdMapping1
at /home/michael/kr/rsync/internal/receiver/uidlist.go:16
19 0x00000000005b7ea8 in github.com/gokrazy/rsync/internal/receiver.(*Transfer).RecvIdList
at /home/michael/kr/rsync/internal/receiver/uidlist.go:52
20 0x00000000005b18db in github.com/gokrazy/rsync/internal/receiver.(*Transfer).ReceiveFileList
at /home/michael/kr/rsync/internal/receiver/flist.go:229
21 0x0000000000605390 in github.com/gokrazy/rsync/internal/receivermaincmd.clientRun
at /home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:341
22 0x00000000005fe572 in github.com/gokrazy/rsync/internal/receivermaincmd.socketClient
at /home/michael/kr/rsync/internal/receivermaincmd/clientserver.go:44
23 0x0000000000602f10 in github.com/gokrazy/rsync/internal/receivermaincmd.rsyncMain
at /home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:160
24 0x0000000000605e7e in github.com/gokrazy/rsync/internal/receivermaincmd.Main
at /home/michael/kr/rsync/internal/receivermaincmd/receivermaincmd.go:394
25 0x0000000000606653 in main.main
at /home/michael/kr/rsync/cmd/gokr-rsync/rsync.go:12
26 0x000000000043fa47 in runtime.main
at /home/michael/sdk/go1.23.6/src/runtime/proc.go:272
27 0x000000000047bd01 in runtime.goexit
at /home/michael/sdk/go1.23.6/src/runtime/asm_amd64.s:1700
結論
根據我的經驗,從中長期來看,配置一個便於調試程序的環境總是會帶來回報。我強烈建議每位程序員(甚至是用戶!)都花時間投資於開發和調試環境的搭建。
幸運的是,Go 語言默認提供了堆棧打印功能(只需按 Ctrl+\
),而且我們可以通過使用 GOTRACEBACK=crash
運行程序輕鬆獲取 Go 程序的核心轉儲文件——前提是系統配置爲收集核心轉儲。
結合 delve 調試器,這些工具爲我們提供了有效且高效診斷 Go 程序問題所需的一切。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vzzjdsDzNWQ73YTtNL_PRw