
字节笔记本
2026年10月7日 · 约 9 分钟读完
调试信息读失败时先换调试器
调试器退出时的原文是:读 debug_info 失败,在偏移 0x21fab4 处解码 DWARF,DW_FORM_strx 找不到 .debug_str_offsets 段。进程没有被拉起来,退出码是 1。编译用了 -gcflags all=-N -l,调试器是编辑器自带的 Delve。先让二进制里的调试信息能被这个 Delve 读懂,再谈断点。

编译参数和调试器要是同一代
构建把优化和内联关掉,是为了断点能落在源码行上。-N 关优化,-l 关内联,对话里的写法是 -gcflags all=-N -l。Go 1.23 的工具链发出的 DWARF 用了 DW_FORM_strx。编辑器自带的 Delve 若更老,就不认识配套的 .debug_str_offsets,于是在加载二进制时退出。程序本身可以已经编译完,失败发生在“把二进制交给调试器”这一步。
终端里能看到 API server 已在本地地址监听,随后才是 error reading debug_info。监听成功只说明调试器进程起来了,不说明目标进程跑起来了。后面的 could not launch process 才是结论。
工具链来自模块缓存里的 golang.org/toolchain,版本串里有 go1.23.0。编辑器若仍指向另一份 GOROOT,编译用的 go 和调试用的 Delve 会对不上。对话里的处理是打开设置里的 GOROOT、GOPATH,并打开 Go modules 集成。GOROOT 应指向这次真正用来 go build 的那一份,而不是机器上更旧的安装。
清缓存时不要先删掉校验和
对话给出的顺序是:看 go version 和 go.mod 里的版本是否一致;在编辑器里使缓存失效并重启;删除 go.sum;go clean -cache;go mod tidy;go build ./...。
go clean -cache 和让编辑器丢掉自己的缓存,针对的是旧的编译产物。调试信息是编译进二进制的,旧产物被继续交给 Delve 时,换了新 Delve 也还会读到旧问题。这两步值得做。
删除 go.sum 会迫使依赖重新解析。校验和不匹配时才需要它。调试段缺失通常和锁定文件无关。先不要删。版本对齐、清构建缓存、更新 Delve 之后仍失败,再考虑锁定文件。
命令行复现用同一组参数:go build -gcflags="all=-N -l" ./...。这里能编过,只说明编译器接受这份代码。还要用更新后的 Delve 去加载这个二进制,才能对应编辑器里的失败。编辑器的运行配置里,Go tool arguments 同样带上 -gcflags="all=-N -l",避免图形界面和终端各用一套参数。
更新调试器的命令是 go install github.com/go-delve/delve/cmd/dlv@latest。装完要让编辑器用这份,而不是继续用插件目录里那份旧的。对话里还有一条编辑器动作:Invalidate Delve Cache and Restart。只更新了磁盘上的 dlv、编辑器仍用缓存里的旧二进制,现象不会变。
go mod graph 用来看依赖是不是拧在一起。它不修复 DWARF。依赖图正常时,不要在这里停留。
路径和项目名不要留在记录里
失败日志里会有本机用户目录、编辑器缓存目录、临时可执行文件名和进程号。这些是那一次调试的路径,不是错误本身。写进文档或工单时只留三样:Go 版本、gcflags、以及 DW_FORM_strx 找不到 .debug_str_offsets 这句。可执行文件名、监听端口、进程号对下一次没有帮助。
本地回环地址上的调试端口只给本机的编辑器用。不要为了“远程调试”把它暴露到别的网卡。对话里的 Delve 还带了 --headless 和 --only-same-user=false。后者放宽了谁能连上这个端口。本机单用户调试不需要放宽。编辑器若默认加上它,知道它的含义即可,不要再复制到一台多用户机器上。

按日志出现的顺序读,不要从退出码倒推
第一段是环境。GOROOT 指向模块缓存里的工具链,版本串包含 go1.23.0,后面还有系统和架构。GOPATH 是另一条路径。这只说明编辑器准备用哪一份 go。它不表示调试信息已经正确。
第二段是编译。命令里有 -o 指向编辑器的临时目录,以及 -gcflags all=-N -l,目标是当前模块。这一步若失败,后面不会出现调试器监听。日志若已经写到监听,就说明二进制生成了。
第三段是 Delve。编辑器插件目录里的 dlv 以 --headless、--api-version=2 启动,--listen 在本机地址上。随后能看到调试服务已监听,并且尝试启动那个临时二进制。然后才是 error loading binary,原因是 debug_info 在偏移 0x21fab4,DW_FORM_strx 没有 .debug_str_offsets。最后 could not launch process,调试器退出码 1。监听成功和进程没起来可以同时成立。
处理时先对齐版本。终端 go version 和 go.mod 里的 go 行,要和 GOROOT 那份工具链一致。不一致就改设置里的 GOROOT,并确认 Go modules 集成是开的。然后清构建缓存:编辑器的 Invalidate Caches,以及 go clean -cache,再 go build -gcflags="all=-N -l" ./...。用新的 Delve 加载这个产物。安装命令是 go install github.com/go-delve/delve/cmd/dlv@latest。编辑器里执行 Invalidate Delve Cache and Restart,避免仍调用插件里的旧文件。运行配置的 Go tool arguments 写上同一组 gcflags,图形界面才和终端一致。
go.sum 先留着。删掉它只会让 go mod tidy 重新解析依赖,不补调试段。go mod graph 只在怀疑依赖版本拧住时再看。依赖图正常就回到 Delve 和工具链。
记录给别人时删掉用户目录、临时可执行文件名、进程号和具体监听端口。留下 Go 1.23、gcflags 和那句 DWARF 错误就够。--only-same-user=false 放宽了谁能连接调试端口,本机调试不必照搬到多用户环境。端口留在回环地址上。
版本对齐之后再进断点
Go 1.23 的工具链加上 -gcflags all=-N -l 编出的二进制,需要能读 DW_FORM_strx 的 Delve。编辑器自带的旧调试器会在 debug_info 偏移 0x21fab4 处退出,退出码 1。把 GOROOT 指到这份工具链,go clean -cache 后重编,再 go install 新的 Delve 并清掉编辑器里的 Delve 缓存。go.sum 留到依赖真有问题时再动。日志里不要保留本机路径和临时二进制的名字。
复现时三样要同时成立:工具链版本串里是 go1.23.0,编译参数是 -gcflags all=-N -l,加载二进制的 Delve 能认识 DW_FORM_strx。缺任何一样,都会在 debug_info 的偏移 0x21fab4 停住,退出码 1。监听已经开始只说明调试器自己起来了。go clean -cache 之后按同一参数重编,再换新的 dlv 并让编辑器丢掉 Delve 缓存。GOROOT 指向这次编译用的工具链,Go modules 集成保持打开。go.sum 和 go mod graph 不解决这段缺失。记录里不写用户目录、临时文件名、进程号和端口。调试端口留在本机回环上,不必加上放宽用户的那个参数。
加载失败和业务第一行无关。调试器已经在本机地址上开始监听,只代表它自己的进程起来了。随后读调试段失败,偏移处的形式它不认识,于是目标进程没有启动,整个调试以失败码结束。对齐三件事即可:工具链是这一代的版本,编译时关掉优化和内联,调试器新到能认识这种调试形式。构建缓存和编辑器持有的旧调试器都要清掉,否则磁盘上的新文件不会被用到。模块的校验和文件先不要删。交给别人的记录只保留版本和那句段错误,本机目录、临时程序名、进程号和端口都拿掉。调试端口留在回环上,不要放宽成任意本机用户都能连。



