分叉的 ptraced 进程挂起了。

huangapple go评论93阅读模式
英文:

Forked ptraced process hangs

问题

我正在尝试在调用Go程序时拦截系统调用,但是遇到了两个问题。

子进程似乎挂起了,这也导致了父进程的挂起。似乎wait4(2)在阻塞,这看起来很奇怪,难道子进程最终不会调用exit(2)退出吗?

我得到的输出到stdout的系统调用不一致,有时最后一个系统调用是3,有时是6192。我的代码中是否存在竞争条件?为什么会发生这种情况?

我尝试在父进程上监听信号,但是我没有收到任何信号。

我已经用/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)
}

希望这对你有帮助!

英文:

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 (
  &quot;syscall&quot;
  &quot;fmt&quot;
  &quot;os/signal&quot;
  &quot;os&quot;
)

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(&quot;/bin/ls&quot;, nil, attr)

  if err != nil {
    panic(err)
  }

  var wstat syscall.WaitStatus
  var regs syscall.PtraceRegs

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

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

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

    syscall.PtraceSyscall(pid, 0)
  }
}

func SignalListener(c &lt;-chan os.Signal) {
  s := &lt;-c

  fmt.Printf(&quot;Got signal %d\n&quot;, s)
}

答案1

得分: 1

简短回答是,使用Go拦截系统调用会非常困难,而且使用ptrace可能不起作用。

Go具有将go-routine多路复用到操作系统线程上的运行时。系统调用是一个调度点,因此在系统调用返回后,你可能会在不同的线程上,而我认为ptrace只跟踪单个线程。

假设你正在跟踪的线程正在运行你的主要go-routine。然后你调用fmt.Println(它执行syscall.Write),因此Go运行时将你的go-routine从该线程中移除,并在不同的操作系统线程中运行系统调用(系统调用总是在不同的线程中运行)。当系统调用返回时,你的主要go-routine将被放回可运行例程的调度器列表中,并且它将继续在任何可用的操作系统线程上运行,这可能不是你正在跟踪的线程。

这也是为什么你不能使用gdb逐步执行Go程序的原因。

如果你只想执行外部程序(比如/bin/ls),你可以使用标准库中的os/exec

最接近你尝试做的事情的程序可能是delve。我认为它在每个单步操作时在每个线程上设置断点,然后根据go-routine的ID尝试找到你的go-routine现在在哪个线程上。

英文:

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.

huangapple
  • 本文由 发表于 2013年8月29日 12:19:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/18502203.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定