Loading... # 运行环境 <div class="tip inlineBlock success"> 这里使用的 **Go 1.14.12** </div> 为确保结果与本文一致,使用以下 docker 环境进行操作 ```bash FROM centos RUN yum install golang -y \ && yum install dlv -y \ && yum install binutils -y \ && yum install vim -y \ && yum install gdb -y ``` 或者 pull 上传好的镜像 ```bash docker pull xargin/go1.14.12-dev ```  # panic: close of nil channel ```go package main func main() { var ch chan int close(ch) } ``` 进入容器后使用 vim 编写上述代码并使用 dlv debug 我们的程序 `dlv debug test_chan.go`,先在`close(ch)`处打断点 。 ```bash b main.go:5 ``` 好吧,我们的答案其实已经出来了,这里就不继续 debug 了,就是`closechan()`函数的第一个判断,如果传进来的 `channel`为`nil`就直接`panic`了 ```bash [root@b740f2207308 go_src]# dlv debug test_chan.go Type 'help' for list of commands. (dlv) break runtime.closechan Breakpoint 1 set at 0x404b03 for runtime.closechan() /usr/lib/golang/src/runtime/chan.go:340 (dlv) c > runtime.closechan() /usr/lib/golang/src/runtime/chan.go:340 (hits goroutine(1):1 total:1) (PC: 0x404b03) Warning: debugging optimized function 335: src := sg.elem 336: typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size) 337: memmove(dst, src, t.size) 338: } 339: => 340: func closechan(c *hchan) { 341: if c == nil { 342: panic(plainError("close of nil channel")) 343: } 344: 345: lock(&c.lock) (dlv) ``` # panic: send on closed channel ```go package main func main() { ch := make(chan int) close(ch) ch <- 1 } ``` 跟上面的操作一样,先在`ch <- 1`打断点,然后多按几次`si`(单步单核调试),等看到`chansend1()`的时候就可以用`s`或`n`调试了 ```bash [root@94a57e6a9e5b ~]# vi send_close.go [root@94a57e6a9e5b ~]# dlv debug send_close.go Type 'help' for list of commands. (dlv) b send_close.go:6 Breakpoint 1 (enabled) set at 0x4601d9 for main.main() ./send_close.go:6 (dlv) c > main.main() ./send_close.go:6 (hits goroutine(1):1 total:1) (PC: 0x4601d9) 1: package main 2: 3: func main() { 4: ch := make(chan int) 5: close(ch) => 6: ch <- 1 7: } (dlv) si > runtime.chansend1() /usr/local/go/src/runtime/chan.go:126 (PC: 0x404300) Warning: debugging optimized function 121: return add(c.buf, uintptr(i)*uintptr(c.elemsize)) 122: } 123: 124: // entry point for c <- x from compiled code 125: //go:nosplit => 126: func chansend1(c *hchan, elem unsafe.Pointer) { 127: chansend(c, elem, true, getcallerpc()) 128: } 129: 130: /* 131: * generic single channel send/recv ``` 重复几次,我们就能看到进入这个`c.closed != 0`的条件了 ```go (dlv) s > runtime.chansend() /usr/local/go/src/runtime/chan.go:186 (PC: 0x40483b) Warning: debugging optimized function 181: } 182: 183: lock(&c.lock) 184: 185: if c.closed != 0 { => 186: unlock(&c.lock) 187: panic(plainError("send on closed channel")) 188: } 189: 190: if sg := c.recvq.dequeue(); sg != nil { 191: // Found a waiting receiver. We pass the value we want to send ``` > 其实这样是比较蠢的操作。。 至于为啥要一直`si`而`s`不行,我猜是因为 `dlv`工具不认识这个 `<-`语法糖...所以我们干脆直接用`go tool compile`编译一下,直接看汇编里是`CALL`的那个方法,就不用考虑语法糖的问题了。 ```bash [root@b740f2207308 go_src]# go tool compile -S test_chan.go |grep "test_chan.go:7" 0x002a 00042 (test_chan.go:7) MOVQ $0, (SP) 0x0032 00050 (test_chan.go:7) PCDATA $0, $1 0x0032 00050 (test_chan.go:7) LEAQ ""..stmp_0(SB), AX 0x0039 00057 (test_chan.go:7) PCDATA $0, $0 0x0039 00057 (test_chan.go:7) MOVQ AX, 8(SP) 0x003e 00062 (test_chan.go:7) CALL runtime.chansend1(SB) ``` 这里我们直接定位到第七行,就可以看到 `CALL`的是`runtime.chansend1()`这个函数,然后我们就可以直接用`dlv`工具在这个函数打断点看了。 当然我们也可以先 `go tool compile -N -l test_chan.go`编译出`.o`对象文件,然后使用`go tool objdump test_chan.o` 反汇编出代码 (或者使用`go tool objdump -s xxx(函数名) test_chan.o`反汇编特定的函数) ```bash [root@b740f2207308 go_src]# go tool objdump test_chan.o TEXT %22%22.main(SB) gofile../root/go_src/test_chan.go test_chan.go:3 0x2d8 64488b0c2500000000 MOVQ FS:0, CX [5:9]R_TLS_LE test_chan.go:3 0x2e1 483b6110 CMPQ 0x10(CX), SP test_chan.go:3 0x2e5 763e JBE 0x325 test_chan.go:3 0x2e7 4883ec18 SUBQ $0x18, SP test_chan.go:3 0x2eb 48896c2410 MOVQ BP, 0x10(SP) test_chan.go:3 0x2f0 488d6c2410 LEAQ 0x10(SP), BP test_chan.go:6 0x2f5 48c7042400000000 MOVQ $0x0, 0(SP) test_chan.go:6 0x2fd e800000000 CALL 0x302 [1:5]R_CALL:runtime.closechan test_chan.go:7 0x302 48c7042400000000 MOVQ $0x0, 0(SP) test_chan.go:7 0x30a 488d0500000000 LEAQ 0(IP), AX [3:7]R_PCREL:%22%22..stmp_0 test_chan.go:7 0x311 4889442408 MOVQ AX, 0x8(SP) test_chan.go:7 0x316 e800000000 CALL 0x31b [1:5]R_CALL:runtime.chansend1 test_chan.go:8 0x31b 488b6c2410 MOVQ 0x10(SP), BP test_chan.go:8 0x320 4883c418 ADDQ $0x18, SP test_chan.go:8 0x324 c3 RET test_chan.go:3 0x325 e800000000 CALL 0x32a [1:5]R_CALL:runtime.morestack_noctxt test_chan.go:3 0x32a ebac JMP %22%22.main(SB) ``` # panic: close of closed channel ```go package main func main() { ch := make(chan int) close(ch) close(ch) } ``` 其实我们根据上面调试的经验,应该也能猜到触发 `close of closed channel panic` 肯定也在 `runtime.closechan()`函数里,所以我们直接在这个函数打断点。多调试几步就能看到这个 `panic`了 ```go (dlv) funcs closechan runtime.closechan (dlv) break runtime.closechan Breakpoint 1 (enabled) set at 0x404b03 for runtime.closechan() /usr/local/go/src/runtime/chan.go:340 (dlv) c > runtime.closechan() /usr/local/go/src/runtime/chan.go:340 (hits goroutine(1):1 total:1) (PC: 0x404b03) Warning: debugging optimized function 335: src := sg.elem 336: typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size) 337: memmove(dst, src, t.size) 338: } 339: => 340: func closechan(c *hchan) { 341: if c == nil { 342: panic(plainError("close of nil channel")) 343: } 344: 345: lock(&c.lock) (dlv) s > runtime.closechan() /usr/local/go/src/runtime/chan.go:341 (PC: 0x404b11) Warning: debugging optimized function 336: typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size) 337: memmove(dst, src, t.size) 338: } 339: 340: func closechan(c *hchan) { => 341: if c == nil { 342: panic(plainError("close of nil channel")) 343: } 344: 345: lock(&c.lock) 346: if c.closed != 0 { (dlv) n > runtime.closechan() /usr/local/go/src/runtime/chan.go:345 (PC: 0x404b1f) Warning: debugging optimized function 340: func closechan(c *hchan) { 341: if c == nil { 342: panic(plainError("close of nil channel")) 343: } 344: => 345: lock(&c.lock) 346: if c.closed != 0 { 347: unlock(&c.lock) 348: panic(plainError("close of closed channel")) 349: } ``` # Reference [使用 debugger 学习 golang](https://xargin.com/debugger/) [使用 Delve 工具调试 Golang 程序](https://gocn.vip/topics/12090) 最后修改:2021 年 07 月 09 日 08 : 22 PM © 允许规范转载 赞赏 如果觉得我的文章对你有用,请随意赞赏 赞赏作者 支付宝微信