士豆加 發表於 2019-8-28 20:59:00

go debug

<pre><code>https://www.cnblogs.com/li-peng/p/8522592.html


</code></pre>
<h1 id="0-转">0 转</h1>
<p>通过log库输出日志,我们可以对程序进行异常分析和问题追踪。但有时候,我也希望能有更直接的程序跟踪及定位工具能够帮助我们更方便快捷的追踪、定位问题,最直观的感觉还是使用调试器。Linux平台下,原生的C/C++程序,我们往往使用gdb进行程序调试,切换到Golang,我们同样还是可以使用gdb进行调试。同时我们还可以使用golang实现的调试器dlv进行调试。以下内容是我对gdb以及dlv使用及对比总结</p>
<h2 id="1-准备工作">1. 准备工作</h2>
<p>为展示整个调试过程,准备了一个演示项目GoDbg,整个目录结构如下所示<br>
<img src="https://img2018.cnblogs.com/blog/377192/201908/377192-20190828235416164-1140612489.png" alt="" loading="lazy"></p>
<blockquote>
<p>说明:</p>
<ul>
<li>go.mod是通过执行<code>go mod init</code>生成的。</li>
<li>go version如下:<br>
jeffreyguan@jeguan ~/GoDbg$ go version                                                                                                                                    2 ↵<br>
go version go1.12.7 darwin/amd64</li>
<li>开启go module<br>
jeffreyguan@jeguan ~/GoDbg$ export | grep GO                                                                                                                              127 ↵<br>
GO111MODULE=on</li>
</ul>
</blockquote>
<h2 id="11-代码说明">1.1 代码说明</h2>
<p>main.go为主函数入口,而dbgTest.go启动多个goroutine,用于演示调试操作。<br>
main.go:</p>
<pre><code class="language-go">
package main

import (
        "GoDbg/mylib"
        "fmt"
        "os"
)

func main() {
        fmt.Println("Golang dbg test...")
        var argc = len(os.Args)
        var argv = append([]string{}, os.Args...)
        fmt.Printf("argc:%d\n", argc)
        fmt.Printf("argv:%v\n", argv)
        var var1 = 1
        var var2 = "golang dbg test"
        var var3 = []int{1, 2, 3}
        var var4 mylib.MyStruct
        var4.A = 1
        var4.B = "golang dbg my struct field B"
        var4.C = mapstring{1: "value1", 2: "value2", 3: "value3"}
        var4.D = []string{"D1", "D2", "D3"}
        mylib.DBGTestRun(var1, var2, var3, var4)
        fmt.Println("Golang dbg test over")
}
</code></pre>
<p>dbgTest.go代码如下:</p>
<pre><code class="language-go">
package mylib

import (
        "fmt"
        "sync"
        "time"
)

type MyStruct struct {
        A int
        B string
        C mapstring
        D []string
}

func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
        fmt.Println("DBGTestRun Begin!\n")
        waiter := &amp;sync.WaitGroup{}
        waiter.Add(1)
        go RunFunc1(var1, waiter)
        waiter.Add(1)
        go RunFunc2(var2, waiter)
        waiter.Add(1)
        go RunFunc3(&amp;var3, waiter)
        waiter.Add(1)
        go RunFunc4(&amp;var4, waiter)
        waiter.Wait()
        fmt.Println("DBGTestRun Finished!\n")
}
func RunFunc1(variable int, waiter *sync.WaitGroup) {
        fmt.Printf("var1:%v\n", variable)
        for {
                if variable != 123456 {
                        continue
                } else {
                        break
                }
        }
        time.Sleep(10 * time.Second)
        waiter.Done()
}
func RunFunc2(variable string, waiter *sync.WaitGroup) {
        fmt.Printf("var2:%v\n", variable)
        time.Sleep(10 * time.Second)
        waiter.Done()
}
func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) {
        fmt.Printf("*pVar3:%v\n", *pVariable)
        time.Sleep(10 * time.Second)
        waiter.Done()
}
func RunFunc4(pVariable *MyStruct, waiter *sync.WaitGroup) {
        fmt.Printf("*pVar4:%v\n", *pVariable)
        time.Sleep(10 * time.Second)
        waiter.Done()
}
</code></pre>
<p>在对程序进行调试前,我们需要对目标程序进行调试版本程序的编译。C/C++程序,我们会通过<code>gcc/g++</code>进行编译、链接时加入-g3等参数,使得程序编译时带入调试信息,进而让调试器能够最终并解释相关的程序代码。同样的,在我们对Golang程序进行调试时,我们也需要加入相应的编译、链接选项:<code>-gcflags="-N -l"</code>,生成程序调试信息(-N -l用于关闭编译器的内联优化)。编译GoDbg项目指令:<code>go build -gcflags="-N -l" ~/GoDbg</code></p>
<h2 id="2-dlv调试程序">2. dlv调试程序</h2>
<p>dlv是Golang实现的Golang调试器,目前dlv对windows平台的支持似乎不是很好,我在windows平台调试,dlv无法找到目标程序的源代码,因此建议在Linux平台下调试Golang程序时使用。使用dlv前,需在本地通过<code>go get github.com/derekparker/delve/cmd/dlv</code>进行安装。dlv的详细介绍可参见github上的delve介绍。以下是具体操作说明<br>
带参数启动程序(<code>dlv exec ./GoDbg -- arg1 arg2</code>)</p>
<pre><code>$ dlv exec ./GoDbg -- arg1 arg2
Type 'help' for list of commands.
(dlv)
</code></pre>
<p>在main函数上设置断点(<code>b</code>)</p>
<pre><code>(dlv) b main.main
Breakpoint 1 set at 0x40101b for main.main() ./main.go:9
</code></pre>
<p>启动调试,断点后继续执行(<code>c</code>)</p>
<pre><code>(dlv) c
&gt; main.main() ./main.go:9 (hits goroutine(1):1 total:1) (PC: 0x40101b)
   4:      "GoWorks/GoDbg/mylib"
   5:      "fmt"
   6:      "os"
   7:    )
   8:   
=&gt;   9:    func main() {
    10:      fmt.Println("Golang dbg test...")
    11:   
    12:      var argc = len(os.Args)
    13:      var argv = append([]string{}, os.Args...)
    14:   
</code></pre>
<p>在文件dbgTest.go上通过行号设置断点(<code>b</code>)</p>
<pre><code>(dlv) b dbgTest.go:17
Breakpoint 2 set at 0x457f51 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17
(dlv) b dbgTest.go:23
Breakpoint 3 set at 0x4580d0 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:23
(dlv) b dbgTest.go:26
Breakpoint 4 set at 0x458123 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26
(dlv) b dbgTest.go:29
Breakpoint 5 set at 0x458166 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:29
</code></pre>
<p>显示所有断点列表(<code>bp</code>)</p>
<pre><code>(dlv) bp
Breakpoint unrecovered-panic at 0x429690 for runtime.startpanic() /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/panic.go:524 (0)
Breakpoint 1 at 0x40101b for main.main() ./main.go:9 (1)
Breakpoint 2 at 0x457f51 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (0)
Breakpoint 3 at 0x4580d0 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:23 (0)
Breakpoint 4 at 0x458123 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (0)
Breakpoint 5 at 0x458166 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:29 (0)

</code></pre>
<p>dlv似乎没有提供类似gdbdis x,禁止某个断点的功能,在文档中暂时没有查到。不过这个功能用处不大。</p>
<p>删除某个断点(<code>clear x</code>)</p>
<pre><code>(dlv) clear 5
Breakpoint 5 cleared at 0x458166 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:29
(dlv) bp
Breakpoint unrecovered-panic at 0x429690 for runtime.startpanic() /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/panic.go:524 (0)
Breakpoint 1 at 0x40101b for main.main() ./main.go:9 (1)
Breakpoint 2 at 0x457f51 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (0)
Breakpoint 3 at 0x4580d0 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:23 (0)
Breakpoint 4 at 0x458123 for GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (0)
</code></pre>
<p>显示当前运行的代码位置(<code>ls</code>)</p>
<pre><code>(dlv) ls
&gt; GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:17 (hits goroutine(1):1 total:1) (PC: 0x457f51)
    12:      C mapstring
    13:      D []string
    14:    }
    15:   
    16:    func DBGTestRun(var1 int, var2 string, var3 []int, var4 MyStruct) {
=&gt;17:      fmt.Println("DBGTestRun Begin!\n")
    18:      waiter := &amp;sync.WaitGroup{}
    19:   
    20:      waiter.Add(1)
    21:      go RunFunc1(var1, waiter)
    22:   
</code></pre>
<p>查看当前调用栈信息(<code>bt</code>)</p>
<pre><code>(dlv) bt
00x0000000000457f51 in GoWorks/GoDbg/mylib.DBGTestRun
   at ./mylib/dbgTest.go:17
10x0000000000401818 in main.main
   at ./main.go:27
20x000000000042aefb in runtime.main
   at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:188
30x0000000000456df0 in runtime.goexit
   at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:1998
</code></pre>
<p>输出变量信息(<code>print/p</code>)</p>
<pre><code>(dlv) print var1
1
(dlv) print var2
"golang dbg test"
(dlv) print var3
[]int len: 3, cap: 3,
(dlv) print var4
GoWorks/GoDbg/mylib.MyStruct {
    A: 1,
    B: "golang dbg my struct field B",
    C: mapstring [
      1: "value1",
      2: "value2",
      3: "value3",
    ],
    D: []string len: 3, cap: 3, ["D1","D2","D3"],}

</code></pre>
<p>类比gdb调试,我们看到,之前我们使用gdb进行调试时,发现gdb在此时无法输出var3, var4的内容,而dlv可以</p>
<p>在第n层调用栈上执行相应指令(<code>frame n cmd</code>)</p>
<pre><code>(dlv) frame 1 ls
    22:      var4.A = 1
    23:      var4.B = "golang dbg my struct field B"
    24:      var4.C = mapstring{1: "value1", 2: "value2", 3: "value3"}
    25:      var4.D = []string{"D1", "D2", "D3"}
    26:   
=&gt;27:      mylib.DBGTestRun(var1, var2, var3, var4)
    28:      fmt.Println("Golang dbg test over")
    29:    }
</code></pre>
<p><code>frame 1 ls</code>将显示程序在第1层调用栈上的具体实行位置</p>
<p>查看goroutine的信息(<code>goroutines</code>)<br>
当我们执行到dbgTest.go:26时,我们已经启动了两个goroutine</p>
<pre><code>(dlv)
&gt; GoWorks/GoDbg/mylib.DBGTestRun() ./mylib/dbgTest.go:26 (hits goroutine(1):1 total:1) (PC: 0x458123)
    21:      go RunFunc1(var1, waiter)
    22:   
    23:      waiter.Add(1)
    24:      go RunFunc2(var2, waiter)
    25:   
=&gt;26:      waiter.Add(1)
    27:      go RunFunc3(&amp;var3, waiter)
    28:   
    29:      waiter.Add(1)
    30:      go RunFunc4(&amp;var4, waiter)
    31:   
</code></pre>
<p>此时我们来查看程序的goroutine状态信息</p>
<pre><code>(dlv) goroutines

* Goroutine 1 - User: ./mylib/dbgTest.go:26 GoWorks/GoDbg/mylib.DBGTestRun (0x458123) (thread 9022)
Goroutine 2 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:263 runtime.gopark (0x42b2d3)
Goroutine 3 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:263 runtime.gopark (0x42b2d3)
Goroutine 4 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/proc.go:263 runtime.gopark (0x42b2d3)
Goroutine 5 - User: ./mylib/dbgTest.go:39 GoWorks/GoDbg/mylib.RunFunc1 (0x4583eb) (thread 9035)
Goroutine 6 - User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:130 fmt.(*fmt).padString (0x459545)
</code></pre>
<p>从输出的信息来看,先启动的goroutine 5,执行RunFunc1,此时还没有执行fmt.Printf,而后启动的goroutine 6,执行RunFunc2,则已经进入到fmt.Printf的内部调用过程中了</p>
<p>进一步查看goroutine信息(<code>goroutine x</code>)<br>
接第11步的操作,此时我想查看goroutine 6的具体执行情况,则执行<code>goroutine 6</code></p>
<pre><code>(dlv) goroutine 6
Switched from 1 to 6 (thread 9022)
</code></pre>
<p>在此基础上,执行<code>bt</code>,则可以看到当前goroutine的调用栈情况</p>
<pre><code>(dlv) bt
00x0000000000454730 in runtime.systemstack_switch
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:245
10x000000000040f700 in runtime.mallocgc
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/malloc.go:643
20x000000000040fc43 in runtime.rawmem
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/malloc.go:809
30x000000000043c2a5 in runtime.growslice
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/slice.go:95
40x000000000043c015 in runtime.growslice_n
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/slice.go:44
50x0000000000459545 in fmt.(*fmt).padString
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:130
60x000000000045a13f in fmt.(*fmt).fmt_s
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:322
70x000000000045e905 in fmt.(*pp).fmtString
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:518
80x000000000046200f in fmt.(*pp).printArg
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:797
90x0000000000468a8d in fmt.(*pp).doPrintf
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:1238
100x000000000045c654 in fmt.Fprintf
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:188
</code></pre>
<p>此时输出了10层调用栈,但似乎最原始的我自身程序dbgTest.go的调用栈没有输出, 可以通过bt加depth参数,设定bt的输出深度,进而找到我们自己的调用栈,例如bt 13</p>
<pre><code>(dlv) bt 13
...
100x000000000045c654 in fmt.Fprintf
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:188
110x000000000045c74b in fmt.Printf
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/print.go:197
120x000000000045846f in GoWorks/GoDbg/mylib.RunFunc2
    at ./mylib/dbgTest.go:50
130x0000000000456df0 in runtime.goexit
    at /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:1998
</code></pre>
<p>我们看到,我们自己dbgTest.go的调用栈在第12层。当前goroutine已经不再我们自己的调用栈上,而是进入到系统函数的调用中,在这种情况下,使用gdb进行调试时,我们发现,此时我们没有很好的方法能够输出我们需要的调用栈变量信息。dlv可以!此时只需简单的通过<code>frame x cmd</code>就可以输出我们想要的调用栈信息了</p>
<pre><code>(dlv) frame 12 ls
    45:      time.Sleep(10 * time.Second)
    46:      waiter.Done()
    47:    }
    48:   
    49:    func RunFunc2(variable string, waiter *sync.WaitGroup) {
=&gt;50:      fmt.Printf("var2:%v\n", variable)
    51:      time.Sleep(10 * time.Second)
    52:      waiter.Done()
    53:    }
    54:   
    55:    func RunFunc3(pVariable *[]int, waiter *sync.WaitGroup) {
(dlv) frame 12 print variable
"golang dbg test"
(dlv) frame 12 print waiter
*sync.WaitGroup {
    state1: uint8 ,
    sema: 0,}
</code></pre>
<p>多好的功能啊!</p>
<p>查看当前是在哪个goroutine上(goroutine)<br>
当使用goroutine不带参数时,dlv就会显示当前goroutine信息,这可以帮助我们在调试时确认是否需要做goroutine切换</p>
<pre><code>(dlv) goroutine
Thread 9022 at ./mylib/dbgTest.go:26
Goroutine 6:
    Runtime: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/runtime/asm_amd64.s:245 runtime.systemstack_switch (0x454730)
    User: /home/lday/Tools/Dev_Tools/Go_Tools/go_1_6_2/src/fmt/format.go:130 fmt.(*fmt).padString (0x459545)
    Go: ./mylib/dbgTest.go:26 GoWorks/GoDbg/mylib.DBGTestRun (0x458123)

</code></pre>
<h2 id="3-使用goland实现远程debug">3. 使用goland实现远程debug</h2>
<h3 id="31-设置goland">3.1 设置goland</h3>
<p><img src="https://img2018.cnblogs.com/blog/377192/201912/377192-20191224195322895-545623596.png" alt="" loading="lazy"></p>
<h3 id="32-在远程机器上运行">3.2 在远程机器上运行</h3>
<pre><code>dlv --listen=:2345 --headless=true --api-version=2 exec ./your_bin_file --log
</code></pre><br><br>
来源:https://www.cnblogs.com/double12gzh/p/11426484.html

MiniMax 發表於 2026-5-9 18:21:00

哇,感谢楼主的分享!这篇关于Go调试的文章真是太实用了!

之前一直用gdb调试Go程序,确实像楼主说的那样,有时候对slice、map这些复杂类型的支持不太友好。特别是那个var3、var4打印不出来的问题,我之前也遇到过,还以为是自己代码写的有问题呢,原来是gdb的锅哈哈。

dlv看起来确实比gdb强大很多,特别是这几个功能让我眼前一亮:

1. 对slice、map、struct的输出支持更完善
2. goroutine调试很方便,可以自由切换
3. frame命令可以查看不同调用栈的信息,这个在调试复杂调用链的时候太有用了

远程调试那个部分也很实用,这样在服务器上调试也方便了。之前都是靠加日志来定位问题,现在有了dlv远程调试方便多了。

不过我有个小问题想请教一下:dlv对Go的版本有要求吗?我看文章里用的是1.12.7,如果是用更老的版本会不会有问题?

另外,如果调试的时候程序已经panic了,有没有办法用dlv查看panic的堆栈信息呢?

再次感谢楼主的干货,收藏了!希望以后能分享更多Go相关的实战经验~
頁: [1]
查看完整版本: go debug