why is permission denied when calling DebugActiveProcessStop on windows after some time?

Viewed 203

On windows I need to have a go program that suspends and unsuspends a process (generated with exec.Command).

This process is run for 5 seconds and then suspended with DebugActiveProcess. After other 5 seconds it is restarted with DebugActiveProcessStop.

It works flawlessly in this way... the problem arises when I wait for 60min or more (sometimes even less): after that amount of time the DebugActiveProcessStop fails with Permission denied.

this is the code of example:

package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "path/filepath"
    "syscall"
    "time"
)

var (
    // DebugActiveProcess function (debugapi.h)
    // https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugactiveprocess
    modkernel32                = syscall.NewLazyDLL("kernel32.dll")
    procDebugActiveProcess     = modkernel32.NewProc("DebugActiveProcess")
    procDebugActiveProcessStop = modkernel32.NewProc("DebugActiveProcessStop")
)

func main() {
    dir := "C:\\path\\to\\folder"
    execName := "counter.bat" //using a batch file as an example of a program that runs for long time
    cmd := exec.Command(filepath.Join(dir, execName))
    cmd.Dir = dir

    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
    }

    cmdprinter(cmd)

    cmd.Start()

    // wait 5 seconds
    time.Sleep(time.Duration(5) * time.Second)

    fmt.Println("--- STOPPING ---")
    // https://github.com/go-delve/delve/blob/master/pkg/proc/native/zsyscall_windows.go#L177-L199
    r1, _, e1 := syscall.Syscall(procDebugActiveProcess.Addr(), 1, uintptr(cmd.Process.Pid), 0, 0)
    if r1 == 0 {
        fmt.Println("error stopping process tree:", e1.Error())
    }
    fmt.Println("STOPPING done")

    // wait 15 min
    for n := 0; n < 3600; n++ {
        fmt.Print(n, " ")
        time.Sleep(time.Second)
    }

    fmt.Println("\n--- STARTING ---")
    r1, r2, e1 := syscall.Syscall(procDebugActiveProcessStop.Addr(), 1, uintptr(cmd.Process.Pid), 0, 0)
    if r1 == 0 {
        fmt.Println("error starting process tree:", e1.Error(), "r2:", r2, "GetLastError:", syscall.GetLastError())
    }
    fmt.Println("STARTING done")

    cmd.Wait()
    fmt.Println("exiting")
}

func cmdprinter(cmd *exec.Cmd) {
    outP, _ := cmd.StdoutPipe()
    errP, _ := cmd.StderrPipe()
    go func() {
        scanner := bufio.NewScanner(outP)
        for scanner.Scan() {
            line := scanner.Text()
            fmt.Println(line)
        }
    }()
    go func() {
        scanner := bufio.NewScanner(errP)
        for scanner.Scan() {
            line := scanner.Text()
            fmt.Println(line)
        }
    }()
}

this is counter.bat:

echo off
set /a n = 0
:START
ping 127.0.0.1 -n 2 > nul
echo counter says %n%
set /a "n=%n%+1"
if %n%==30 (EXIT 0)
GOTO START
  • What might be the cause of this? how can I fix it?
  • Is there any other way to suspend a process and unsuspend it on windows (like kill -STOP on linux)? (i heard of the windows api suspend function but was unable to make it work)
1 Answers

This is because the DebugActiveProcessStop command must be run in the same thread than the thread which called the original DebugActiveProcess. A very old thread gave a hint in this direction.

Now this presents a problem in GO because you do not have direct control over which thread a goroutine is run... GO abstracts this "nitty gritty" away. The workaround that has held true for me so far is using runtime.LockOSThread().

Something like:

func exampleSuspendResume(){

   var wg sync.WaitGroup
   runtime.LockOSThread()
   defer runtime.UnlockOSThread()

   // call DebugActiveProcess

   // do more stuff WITHOUT calling `go func`

   // call DebugActiveProcessStop
   

   
}
Related