如何查看GoLand调试器在程序中是否为运行?

How can I see if the GoLand debugger is running in the program?

在 C# 中,执行程序可以检测它是否在调试器中 运行 使用:

System.Diagnostics.Debugger.IsAttached

Go 中是否有等效项?我有一些超时,我想在单步执行代码时将其禁用。谢谢!

我正在使用 GoLand 调试器。

据我所知,没有内置方法可以按照您描述的方式执行此操作。但是您可以或多或少地使用构建标记来指示 delve 调试器正在 运行ning。您可以使用 --build-flags 参数将构建标签传递给 dlv。这与我在

中描述的技术基本相同

isdelve/delve.go

// +build delve

package isdelve

const Enabled = true

isdelve/nodelve.go:

// +build !delve

package isdelve

const Enabled = false

a.go:

package main

import (
    "isdelve"
    "fmt"
)

func main() {
    fmt.Println("delve", isdelve.Enabled)
}

在 Goland 中,您可以在 'Run/Debug Configurations' 下启用此功能,方法是将以下内容添加到 'Go tool arguments:'

-tags=delve


如果你在Goland之外,运行ning go run a.go会报告delve false,如果你想运行自己dlv,使用 dlv debug --build-flags='-tags=delve' a.go;这将报告 delve true.


或者,您可以使用 delve 的 set 命令在启动调试器后手动设置变量。

如果您假设使用的调试器是 Delve,您可以检查 Delve 进程。至少有两种情况需要考虑(也许更多)。

  1. Delve 启动了您的进程。在这种情况下,当您调用 os.Getppid() 获取父进程的 pid 时,该进程将为 Delve。
  2. Delve 没有启动您的进程,但稍后附加到它。在这种情况下,您需要查找所有 运行 Delve 进程,查看它们的命令行,并查看是否使用包含 "attach " 的命令行启动了任何进程,其中调用 os.Getpid()。这依赖于您没有找到旧的 Delve 的假设,运行 一个恰好与您的匹配的旧 PID。 (我忘记了 OS 重用 PID 的规则是什么)。

注意1和2使用的os函数是不一样的。一个获取父 PID,另一个获取您的 PID。

执行 1 的一些非常基本的代码如下所示:

func isLaunchedByDebugger() bool {
    // gops executable must be in the path. See https://github.com/google/gops
    gopsOut, err := exec.Command("gops", strconv.Itoa(os.Getppid())).Output()
    if err == nil && strings.Contains(string(gopsOut), "\dlv.exe") {
        // our parent process is (probably) the Delve debugger
        return true
    }
    return false
}

对于情况 2,我们可以将程序设置为等待某个信号 (SIGUSR1) 并在此等待期间附加调试器。
main.go的代码可以这样:

package main

import (
    "os"
    "os/signal"
    "syscall"
    "fmt"
    "github.com/my/repo/cmd"
)

const (
    waitForSignalEnv       = "WAIT_FOR_DEBUGGER"
    debuggerPort           = "4321"
)

func main() {
    // Waiting for debugger attach in case if waitForSignalEnv!=""
    if os.Getenv(waitForSignalEnv) != "" {
        sigs := make(chan os.Signal, 1)
        goOn := make(chan bool, 1)
        signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR1)

        go func() {
            sig := <-sigs
            if sig == syscall.SIGUSR1 {
                goOn <- true
            } else if (sig == syscall.SIGTERM || sig == syscall.SIGINT ){
                fmt.Printf("Exiting ...")
                os.Exit(0)
            }
        }()     
            
        fmt.Printf("%s env is set, waiting SIGUSR1.\nYou can run remote debug in vscode and attach dlv debugger:\n\n", waitForSignalEnv)
    
        pid := os.Getpid()
        fmt.Printf("dlv attach --continue --accept-multiclient --headless --listen=:%s %d\n", debuggerPort, pid)
        fmt.Printf("\nLaunch remote debugger in vscode to port %d and then give SIGUSR1 to the process\n", debuggerPort)
        fmt.Printf("kill -SIGUSR1 %d\n", pid)
        
        <-goOn
        fmt.Printf("Continue ...")
    }
    cmd.Execute()
}

launch.json 共 vscode:

{
    "name": "myprog-remote-debug",
    "type": "go",
    "request": "launch",
    "remotePath": "${env:GOPATH}/src/github.com/my/repo",
    "mode": "remote",
    "port": 4321,
    "host": "127.0.0.1",
    "program": "${env:GOPATH}/src/github.com/my/repo",   
    "showLog": true,
    "trace": "verbose" 

}

说明: 我们使用 env WAIT_FOR_DEBUGGER=true 启动程序,例如

export WAIT_FOR_DEBUGGER=true
./myprog -f values.yaml

会输出dlv attach ...命令和kill -SIGUSR <pid> :

WAIT_FOR_DEBUGGER env is set, waiting SIGUSR1.
You can run remote debug in vscode and attach dlv debugger:

dlv attach --continue --accept-multiclient --headless --listen=:4321 556127

Launch remote debugger in vscode to port 4321 and then give SIGUSR1 to the process
kill -SIGUSR1 556127

运行上面的dlv attach ...
然后转到 VS Code 和 运行 myprog-remote-debug。在
之前设置断点 然后给他kill -SIGUSR1 556127

断点将起作用

在 Linux,您可以读取 /proc/self/status 文件以检索 TracerPid 字段,调试器的 PID(如果有)。

func GetTracerPid() (int, error) {
    file, err := os.Open("/proc/self/status")
    if err != nil {
        return -1, fmt.Errorf("can't open process status file: %w", err)
    }
    defer file.Close()

    for {
        var tpid int
        num, err := fmt.Fscanf(file, "TracerPid: %d\n", &tpid)
        if err == io.EOF {
            break
        }
        if num != 0 {
            return tpid, nil
        }
    }

    return -1, errors.New("unknown format of process status file")
}

使用方法:

tpid, err := GetTracerPid()
if err != nil {
    log.Println("something went wrong", err)
} else if tpid != 0 {
    fmt.Println("we're under debugging: tracer_pid", tpid)
} else {
    fmt.Println("we're free of tracing")
}

这里有一个非常 simple-minded 的解决方案,用于检测 Delve 是否 运行 由它执行,而不是之后附加。到目前为止,这是大多数用例,至少

package isdebugging

import (
    "os"

    "github.com/mitchellh/go-ps"
)

// IsDebugging will return true if the process was launched from Delve or the
// gopls language server debugger.
//
// It does not detect situations where a debugger attached after process start.
func IsDebugging() bool {
    pid := os.Getppid()

    // We loop in case there were intermediary processes like the gopls language server.
    for pid != 0 {
        switch p, err := ps.FindProcess(pid); {
        case err != nil:
            return false
        case p.Executable() == "dlv":
            return true
        default:
            pid = p.PPid()
        }
    }
    return false
}