go-delve / delve

Delve is a debugger for the Go programming language.
MIT License
23.06k stars 2.14k forks source link

nil pointer dereference if dlv dap server started with DetachedProcess flag #3864

Open Catalyn45 opened 4 days ago

Catalyn45 commented 4 days ago
  1. What version of Delve are you using (dlv version)?
    • 1.23.1
  2. What version of Go are you using? (go version)?
    • go1.22.0
  3. What operating system and processor architecture are you using?
    • Windows x64
  4. What did you do? Created dlv dap server as detached process and connected to it with neovim dap client.
  5. What did you expect to see? Debugger working
  6. What did you see instead?
    
    [debug-adapter stderr] panic: runtime error: invalid memory address or nil pointer dereference
    [signal 0xc0000005 code=0x0 addr=0x38 pc=0xeea002
    [debug-adapter stderr] ]

goroutine 25 [running, locked to thread]: github.com/go-delve/delve/pkg/proc/native.(nativeProcess).addThread(0x0, 0x2b0, 0x10bc, 0x10?, 0x0, 0x0) D:/repos/delve/pkg/proc/native/proc_windows.go:302 +0x42 github.com/go-delve/delve/pkg/proc/native.(processGroup).waitForDebugEvent(0xc000382080, 0x1) D:/repos/delve/pkg/proc/native/proc_windows.go:406 +0x5e7 github.com/go-delve/delve/pkg/proc/native.initialize.func1() D:/repos/delve/pkg/proc/native/proc_windows.go:84 +0x25 github.com/go-delve/delve/pkg/proc/native.(*ptraceThread).handlePtraceFuncs(0xc0000c01b0) D:/repos/delve/pkg/proc/native/proc.go:405 +0x48 created by github.com/go-delve/delve/pkg/proc/native.newPtraceThread in goroutine 6 D:/repos/delve/pkg/proc/native/proc.go:492 +0xc6



Basically all started from this issue from `nvim-dap-go` project: https://github.com/leoluz/nvim-dap-go/issues/66

what `nvim-dap-go` does behind the scene is:
1. create a new dlv dap server (ex: `dlv dap -l 127.0.0.1:1234`)
2. connect to the dlv dap server
3. start sending commands to the dlv dap server in order to start debugging

the `nvim-dap` project (which is used by the `nvim-dap-go` project) also takes a flag (`detached`) which is by default `true`, which instructs the package how to create the `dlv dap` server. If `detached` is true, then the process will be created as a detached process.

The difference between a normal process and a detached one is that a detached process will not be considered a child to the process creating it, thus killing the parent process will not kill the child. Also a detached process does not inherit the parent's console, so the child detached process will also create it's own console window.

After investigating further, I found out the error stack trace (see above).

I also started investigated the `delve` project and found the following:
![image](https://github.com/user-attachments/assets/8c642bd7-ada6-462e-b434-d662f30fb8dd)
So it seems that the `dbp` for returned by the `procForPid` method is actually `nil`, which later will cause a nil pointer dereference.

After trying to understand what `procForPid` does, I noticed that it searched from a list that should be updated on a `_CREATE_PROCESS_DEBUG_EVENT` event.

I tried to also debug this event and found out the following.
![image](https://github.com/user-attachments/assets/ba217f27-a682-42de-8e21-d44e74c88cff)
It seems that there are two `_CREATE_PROCESS_DEBUG_EVENT` events, and both of them are taking the `procgrp.addTarget == nil` branch.

I tried to debug further and trying to see for what PIDs the `_CREATE_PROCESS_DEBUG_EVENT` is emitted, and also the PID from `_CREATE_THREAD_DEBUG_EVENT` for which the dbp is searched.
![image](https://github.com/user-attachments/assets/536277c4-19cf-478f-8921-f79e4e234455)

Also took a snapshot of the processes and the processes were as it follows:
![image](https://github.com/user-attachments/assets/104380e5-2043-4ace-8fa4-69c91089e657)
So it seems that the first time `_CREATE_PROCESS_DEBUG_EVENT` was emitted, it was with the pid of the processed to be debugger itself, and the second time with the PID of it's child process, which is `conhost.exe`, which is the windows console (terminal).

I think the reason why the `conhost.exe` was created, is because the `dlv` process was created as a detached process, so it didn't have a console (terminal) of it's own, thus creating one.

To make the setup in order to reproduce the issue, you can create a new detached `dlv dap -l 127.0.0.1:1234` process, and try to connect with a dap client to it. I only tried with `nvim-go-dap`.

This is the furthest I could go, If someone with more expertise could look into that or tell me if this is somehow expected/not supported behavior it would be great.

Thanks.
Catalyn45 commented 4 days ago

Also to mention that the output of the detached process is redirected, which cause that the console of the dlv process itself to close, thus not passing it to the debugged process, forcing the debugged process to make a new one.

Example of go code for creating detached dlv process with redirected output:

package main

import (
    "fmt"
    "os"
    "syscall"
)

const (
    DETACHED_PROCESS         = 0x00000008
)

func main() {
    // Path to the executable
    exePath := "D:\\repos\\delve\\dlv.exe"

    argv := []string{exePath}
    argv = append(argv, os.Args[1:]...)

    // Process attributes
    procAttr := &os.ProcAttr{
        Files: []*os.File{
            os.Stdin,
            os.Stdout,
            os.Stderr,
        },
        Sys: &syscall.SysProcAttr{
            CreationFlags: DETACHED_PROCESS,
        },
    }

    // Start the process
    process, err := os.StartProcess(exePath, argv, procAttr)
    if err != nil {
        fmt.Printf("Error starting process: %v\n", err)
        return
    }

    fmt.Printf("Detached process started with PID: %d\n", process.Pid)

    // Wait for process to finish
    state, err := process.Wait()
    if err != nil {
        fmt.Printf("Error waiting for process: %v\n", err)
    }

    fmt.Printf("exit code: %d\n", state.ExitCode())
}

So basically you could compile this program, then run output.exe dap -l 127.0.0.1:1234, then try to connect with a dap client and start debugging something.

aarzilli commented 2 days ago

Figuring out how to setup and use nvim, nvim-dap and nvim-go-dap on windows sounds like something that would take me a long time, but I think I figured out how to reproduce and fix this problem using your program. Can you test https://github.com/go-delve/delve/pull/3867 and see if it works?

Catalyn45 commented 2 days ago

Tested your fix and it seems it solves the issue.

Thanks for working on that.