skinkelynet
skinkelynet

Reputation: 987

Forked ptraced process hangs

I am trying to intercept syscalls when calling a program from Go, however I'm running into two issues.

The child seems to hang, which hangs the parent process as well. It seems wait4(2) is blocking which seems weird, wouldn't the child finally call exit(2) to exit?

The syscalls I get to stdout are not consistent, sometimes the last syscall is 3, other times it's 6 or 192. Do I have a race condition in my code? Why does this happen?

I tried listening for signals on the parent, but I don't receive anything..

I've substituted the program I usually run with /bin/ls.

package main

import (
  "syscall"
  "fmt"
  "os/signal"
  "os"
)

func main() {
  c := make(chan os.Signal, 1)
  signal.Notify(c, os.Interrupt, os.Kill)
  go SignalListener(c)

  attr := new(syscall.ProcAttr)
  attr.Sys = new(syscall.SysProcAttr)
  attr.Sys.Ptrace = true

  pid, err := syscall.ForkExec("/bin/ls", nil, attr)

  if err != nil {
    panic(err)
  }

  var wstat syscall.WaitStatus
  var regs syscall.PtraceRegs

  for {
    fmt.Println("Waiting..")
    _, err := syscall.Wait4(pid, &wstat, 0, nil)
    fmt.Printf("Exited: %d\n", wstat.Exited())

    if err != nil {
      fmt.Println(err)
      break
    }

    syscall.PtraceGetRegs(pid, &regs);
    fmt.Printf("syscall: %d\n", regs.Orig_eax)

    syscall.PtraceSyscall(pid, 0)
  }
}

func SignalListener(c <-chan os.Signal) {
  s := <-c

  fmt.Printf("Got signal %d\n", s)
}

Upvotes: 3

Views: 605

Answers (1)

Graham King
Graham King

Reputation: 5720

The short answer is that intercepting syscalls with Go is going to be very difficult, and anything ptrace will probably not work.

Go has a runtime which multiplexes go-routines onto OS threads. A syscall is a scheduling point, so after the syscall returns you could be on a different thread, whereas I think ptrace follows a single thread.

Say the thread you are ptrace-ing is running your main go-routine. Then you call fmt.Println (which does syscall.Write), so the Go runtime takes your go-routine off that thread, and runs the syscall in a different os thread (syscalls always run in different threads). When the syscall returns, your main go-routine is put back on the schedulers list of runnable routines, and it will continue on whichever os thread is available, which may not be the one you are ptrace-ing.

This is also the reason you cannot step through a Go program with gdb.

If you just wanted to execute an external program (like /bin/ls) you could use os/exec from the standard library.

The program that comes closes to what you are trying to do is probably delve. I think that sets a breakpoint on every thread at each single-step, then tries to find which thread your go-routine is now on based on the go-routine id.

Upvotes: 1

Related Questions