// waitStatusToStateUpdate converts syscall.WaitStatus to a StateUpdate. func waitStatusToStateUpdate(ws syscall.WaitStatus) *stateUpdate { switch { case ws.Exited(): es := ws.ExitStatus() if es == 0 { return newExitedStateUpdate(ok) } return newExitedStateUpdate(newFailure(fmt.Sprint(es))) case ws.Signaled(): msg := fmt.Sprintf("signaled %v", ws.Signal()) if ws.CoreDump() { msg += " (core dumped)" } return newUnexitedStateUpdate(msg) case ws.Stopped(): msg := fmt.Sprintf("stopped %v", ws.StopSignal()) trap := ws.TrapCause() if trap != -1 { msg += fmt.Sprintf(" (trapped %v)", trap) } return newUnexitedStateUpdate(msg) case ws.Continued(): return newUnexitedStateUpdate("continued") default: return newUnexitedStateUpdate(fmt.Sprint("unknown status", ws)) } }
func handleSigchld(mpid int) *resultPack { for { var status syscall.WaitStatus var spid int var err error spid, err = syscall.Wait4(-mpid, &status, syscall.WNOHANG|syscall.WALL, nil) if err != nil { poePanic(err, "wait4 failed") } else if spid == 0 { return nil } if spid == mpid && status.Exited() { return &resultPack{POE_SUCCESS, status.ExitStatus(), ""} } else if spid == mpid && status.Signaled() { return &resultPack{POE_SIGNALED, -1, fmt.Sprintf("Program terminated with signal %d (%s)", int(status.Signal()), status.Signal().String())} } else if status.Stopped() { e := status >> 16 & 0xff switch e { case PTRACE_EVENT_SECCOMP: if res := handleSyscall(spid); res != nil { return res } case syscall.PTRACE_EVENT_CLONE, syscall.PTRACE_EVENT_FORK, syscall.PTRACE_EVENT_VFORK: syscall.PtraceCont(spid, 0) default: syscall.PtraceCont(spid, int(status.StopSignal())) } } } }
func (c *CloneParams) CloneFrozen() (int, error) { pid := callClone(c) // TODO: clone errors? c.CommWriter.Close() c.stdhandles.Close() c.comm = make(chan CommStatus) go commReader(c.CommReader, c.comm) var status syscall.WaitStatus for { wpid, err := syscall.Wait4(pid, &status, 0, nil) // TODO: rusage if err != nil { return -1, os.NewSyscallError("Wait4", err) } if wpid == pid { break } } if status.Stopped() && status.StopSignal() == syscall.SIGTRAP { return pid, nil } if status.Exited() { co, ok := <-c.comm if ok { return -1, childError(co) } return -1, fmt.Errorf("DAFUQ") } err := syscall.Kill(pid, syscall.SIGKILL) if err != nil { return -1, os.NewSyscallError("Kill", err) } return -1, fmt.Errorf("traps, signals, dafuq is this") }
// StartProcess kicks off the event loop and forever waits for signals from // the traced process. This is currently done in a super-silly fashion and will // hopefully benefit from Go channels/goroutines in the future. func (p *Process) StartProcess() (ret int) { var status syscall.WaitStatus L: for { _, err := syscall.Wait4( /*p.Pid*/ -1, &status, 0, nil) p.isRunning = false switch { // status == 0 means terminated?? case status.Exited() || status == 0 || err != nil: ret = status.ExitStatus() break L case status.Stopped(): if bp, hit := p.InBreakpoint(); hit { p.handleBreakpoint(bp) } //case status.Continued(): //case status.CoreDump(): //case status.Signaled(): //case status.ExitStatus(): //case status.StopSignal(): //case status.TrapCause(): default: // fmt.Printf("Got status: %v\n", status) } p.Continue() } return }
func printStatus(ws syscall.WaitStatus) string { switch { case ws.Exited(): es := ws.ExitStatus() if es == 0 { return "" } return fmt.Sprintf("exited %v", es) case ws.Signaled(): msg := fmt.Sprintf("signaled %v", ws.Signal()) if ws.CoreDump() { msg += " (core dumped)" } return msg case ws.Stopped(): msg := fmt.Sprintf("stopped %v", ws.StopSignal()) trap := ws.TrapCause() if trap != -1 { msg += fmt.Sprintf(" (trapped %v)", trap) } return msg case ws.Continued(): return "continued" default: return fmt.Sprintf("unknown status %v", ws) } }
func monitor(active chan bool, notify chan notification) { for { monitoring := <-active for monitoring { var rusage syscall.Rusage var status syscall.WaitStatus options := syscall.WUNTRACED pid, err := syscall.Wait4(-1, &status, options, &rusage) if err != nil { println("Wait4:", err.Error()) } if pid <= 0 { break } if status.Stopped() { if pid == task0.Job.Group { incoming <- syscall.SIGTSTP } continue } if status.Signaled() { if status.Signal() == syscall.SIGINT && pid == task0.Job.Group { incoming <- syscall.SIGINT } status += 128 } notify <- notification{pid, status} monitoring = <-active } } }
func ChildWaitingFunc(pid int, sig chan *ChildWaitData) { var status syscall.WaitStatus var rusage syscall.Rusage result := &ChildWaitData{} for { wpid, err := syscall.Wait4(pid, &status, syscall.WUNTRACED|syscall.WCONTINUED, &rusage) if wpid != pid { continue } if status.Exited() { result.ExitCode = uint32(status.ExitStatus()) break } if status.Stopped() { result.SuccessCode |= EF_STOPPED result.StopSignal = uint32(status.StopSignal()) syscall.Kill(pid, syscall.SIGKILL) } if status.Signaled() { result.SuccessCode |= EF_KILLED_BY_OTHER result.KillSignal = uint32(status.Signal()) break } if err != nil { break } } result.RusageCpuUser = time.Nanosecond * time.Duration(rusage.Utime.Nano()) result.RusageCpuKernel = time.Nanosecond * time.Duration(rusage.Stime.Nano()) sig <- result close(sig) }
// waitStatusToError converts syscall.WaitStatus to an Error. func waitStatusToError(ws syscall.WaitStatus) error { switch { case ws.Exited(): es := ws.ExitStatus() if es == 0 { return nil } return errors.New(fmt.Sprint(es)) case ws.Signaled(): msg := fmt.Sprintf("signaled %v", ws.Signal()) if ws.CoreDump() { msg += " (core dumped)" } return errors.New(msg) case ws.Stopped(): msg := fmt.Sprintf("stopped %v", ws.StopSignal()) trap := ws.TrapCause() if trap != -1 { msg += fmt.Sprintf(" (trapped %v)", trap) } return errors.New(msg) /* case ws.Continued(): return newUnexitedStateUpdate("continued") */ default: return fmt.Errorf("unknown WaitStatus", ws) } }
func NewExternalCmdExit(name string, ws syscall.WaitStatus, pid int) error { if ws.Exited() && ws.ExitStatus() == 0 { return nil } if !ws.Stopped() { pid = 0 } return ExternalCmdExit{ws, name, pid} }
func execFgCmd(cmd []string, sigStateChanged chan string) { cmdStr := strings.Join(cmd, " ") // TODO: Extract start process into common function. argv0, err := exec.LookPath(cmd[0]) if err != nil { if cmd[0] != "" { fmt.Printf("Unknown command: %s\n", cmd[0]) } // Don't execute new process with empty return. Will cause panic. sigPrompt <- struct{}{} return } var procAttr os.ProcAttr procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} p, err := os.StartProcess(argv0, cmd, &procAttr) if err != nil { fmt.Printf("Start process %s, %s failed: %v", err, argv0, cmd) } for { sigChild := make(chan os.Signal) defer close(sigChild) // SIGCONT not receivable: https://github.com/golang/go/issues/8953 // This causes some bugs. Eg. CONT signal not captured by handler means subsequent KILL or STOP signals will be ignored by this handler. signal.Notify(sigChild, syscall.SIGTSTP, syscall.SIGINT, syscall.SIGCONT, syscall.SIGKILL) defer signal.Stop(sigChild) var ws syscall.WaitStatus // Ignoring error. May return "no child processes" error. Eg. Sending Ctrl-c on `cat` command. wpid, _ := syscall.Wait4(p.Pid, &ws, syscall.WUNTRACED, nil) if ws.Exited() { break } if ws.Stopped() { jobHandler(wpid, runningState, cmdStr) jobHandler(wpid, suspendedState, cmdStr) // Return prompt when fg has become bg sigPrompt <- struct{}{} } //if ws.Continued() { // state = contState //} if ws == 9 { jobHandler(wpid, killedState, cmdStr) break } } p.Wait() sigPrompt <- struct{}{} }
func execBgCmd(cmd []string, sigStateChanged chan string) { cmdStr := strings.Join(cmd, " ") argv0, err := exec.LookPath(cmd[0]) if err != nil { if cmd[0] != "" { fmt.Printf("Unknown command: %s\n", cmd[0]) } sigPrompt <- struct{}{} return } var procAttr os.ProcAttr procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} p, err := os.StartProcess(argv0, cmd, &procAttr) if err != nil { fmt.Printf("Start process %s, %s failed: %v", err, argv0, cmd) } jobHandler(p.Pid, runningState, cmdStr) sigPrompt <- struct{}{} //FIXME: Bg processes should not receive keyboard signals sent to fg process. for { sigChild := make(chan os.Signal) defer close(sigChild) signal.Notify(sigChild, syscall.SIGCHLD) defer signal.Stop(sigChild) var ws syscall.WaitStatus wpid, _ := syscall.Wait4(p.Pid, &ws, syscall.WUNTRACED, nil) if ws.Exited() { jobHandler(wpid, doneState, cmdStr) break } if ws.Stopped() { jobHandler(wpid, suspendedState, cmdStr) sigPrompt <- struct{}{} } //if ws.Continued() { // state = contState //} if ws == 9 { jobHandler(wpid, killedState, cmdStr) break } } p.Wait() sigPrompt <- struct{}{} }
func waitSyscall(proc *os.Process) bool { for { syscall.PtraceSyscall(proc.Pid, 0) var status syscall.WaitStatus syscall.Wait4(proc.Pid, &status, 0, nil) if status.Stopped() { return false } if status.Exited() { return true } } }
func wait_for_syscall(pid int) (exited bool, err error) { var waitStatus syscall.WaitStatus for { // Entering a syscall if err = syscall.PtraceSyscall(pid, 0); err != nil { return } if _, err = syscall.Wait4(pid, &waitStatus, 0, nil); err != nil { return } // Is it for us ? if waitStatus.Stopped() && waitStatus.StopSignal()&0x80 == 0x80 { return } if waitStatus.Exited() { exited = true return } } }
func (t *PTracer) handleStopped(pid int, status syscall.WaitStatus) { signal := syscall.Signal(0) target, err := t.thread(pid) if err != nil { log.Printf("thread failed: %v", err) return } if !target.attached { target.attached = true err = syscall.PtraceSetOptions(pid, ptraceOptions) if err != nil { log.Printf("SetOptions failed, pid=%d, err=%v", pid, err) return } } else if status.Stopped() && status.StopSignal() == syscall.SIGTRAP|ptraceTracesysgoodBit { // pid entered Syscall-enter-stop or syscall-exit-stop target.syscallStopped() } else if status.Stopped() && status.StopSignal() == syscall.SIGTRAP { // pid entered PTRACE_EVENT stop switch status.TrapCause() { case syscall.PTRACE_EVENT_CLONE: err := target.handleClone(pid) if err != nil { log.Printf("clone failed: %v", err) return } default: log.Printf("Unknown PTRACE_EVENT %d for pid %d", status.TrapCause(), pid) } } else if status.Exited() || status.Signaled() { // "tracer can safely assume pid will exit" t.threadExited(target) return } else if status.Stopped() { // tracee received a non-trace related signal signal = status.StopSignal() if signal == syscall.SIGSTOP && target.process.detaching { t.detachThread(target) return } } else { // unknown stop - shouldn't happen! log.Printf("Pid %d random stop with status %x", pid, status) } // Restart stopped caller in syscall trap mode. // log.Printf("Restarting pid %d with signal %d", pid, int(signal)) err = syscall.PtraceSyscall(pid, int(signal)) if err != nil { log.Printf("PtraceSyscall failed, pid=%d, err=%v", pid, err) } }
func Run(startChan <-chan int, stopChan chan struct{}, appName string, appArgs []string, dirName string) <-chan *report.PtMonitorReport { log.Info("ptmon: starting...") sysInfo := system.GetSystemInfo() archName := system.MachineToArchName(sysInfo.Machine) syscallResolver := system.CallNumberResolver(archName) reportChan := make(chan *report.PtMonitorReport, 1) go func() { ptReport := &report.PtMonitorReport{ ArchName: string(archName), SyscallStats: map[string]report.SyscallStatInfo{}, } syscallStats := map[int16]uint64{} eventChan := make(chan syscallEvent) doneMonitoring := make(chan int) var app *exec.Cmd go func() { //Ptrace is not pretty... and it requires that you do all ptrace calls from the same thread runtime.LockOSThread() var err error app, err = target.Start(appName, appArgs, dirName, true) utils.FailOn(err) targetPid := app.Process.Pid log.Debugf("ptmon: target PID ==> %d\n", targetPid) var wstat syscall.WaitStatus _, err = syscall.Wait4(targetPid, &wstat, 0, nil) if err != nil { log.Warnf("ptmon: error waiting for %d: %v\n", targetPid, err) doneMonitoring <- 1 } log.Debugln("ptmon: initial process status =>", wstat) if wstat.Exited() { log.Warn("ptmon: app exited (unexpected)") doneMonitoring <- 2 } if wstat.Signaled() { log.Warn("ptmon: app signalled (unexpected)") doneMonitoring <- 3 } syscallReturn := false gotCallNum := false gotRetVal := false var callNum uint64 var retVal uint64 for wstat.Stopped() { var regs syscall.PtraceRegs switch syscallReturn { case false: if err := syscall.PtraceGetRegs(targetPid, ®s); err != nil { log.Fatalf("ptmon: PtraceGetRegs(call): %v", err) } callNum = regs.Orig_rax syscallReturn = true gotCallNum = true case true: if err := syscall.PtraceGetRegs(targetPid, ®s); err != nil { log.Fatalf("ptmon: PtraceGetRegs(return): %v", err) } retVal = regs.Rax syscallReturn = false gotRetVal = true } err = syscall.PtraceSyscall(targetPid, 0) if err != nil { log.Warnf("ptmon: PtraceSyscall error: %v\n", err) break } _, err = syscall.Wait4(targetPid, &wstat, 0, nil) if err != nil { log.Warnf("ptmon: error waiting 4 %d: %v\n", targetPid, err) break } if gotCallNum && gotRetVal { gotCallNum = false gotRetVal = false eventChan <- syscallEvent{ callNum: int16(callNum), retVal: retVal, } } } log.Infoln("ptmon: monitor is exiting... status=", wstat) doneMonitoring <- 0 }() done: for { select { case rc := <-doneMonitoring: log.Info("ptmon: done =>", rc) break done case <-stopChan: log.Info("ptmon: stopping...") //NOTE: need a better way to stop the target app... if err := app.Process.Signal(syscall.SIGTERM); err != nil { log.Warnln("ptmon: error stopping target app =>", err) if err := app.Process.Kill(); err != nil { log.Warnln("ptmon: error killing target app =>", err) } } break done case e := <-eventChan: ptReport.SyscallCount++ if _, ok := syscallStats[e.callNum]; ok { syscallStats[e.callNum]++ } else { syscallStats[e.callNum] = 1 } } } log.Debugf("ptmon: executed syscall count = %d\n", ptReport.SyscallCount) log.Debugf("ptmon: number of syscalls: %v\n", len(syscallStats)) for scNum, scCount := range syscallStats { log.Debugf("[%v] %v = %v", scNum, syscallResolver(scNum), scCount) ptReport.SyscallStats[strconv.FormatInt(int64(scNum), 10)] = report.SyscallStatInfo{ Number: scNum, Name: syscallResolver(scNum), Count: scCount, } } ptReport.SyscallNum = uint32(len(ptReport.SyscallStats)) reportChan <- ptReport }() return reportChan }
func main() { var wstat syscall.WaitStatus var complete func(syscall.PtraceRegs) = nil var die = false regs := syscall.PtraceRegs{} isSyscall := func(wstat syscall.WaitStatus) bool { return (((uint32(wstat) & 0xff00) >> 8) & 0x80) != 0 } sc := initSyscalls() c := make(chan os.Signal, 1) signal.Notify(c, os.Kill, os.Interrupt) go check(c, &die) if pid == -1 { log.Fatal("No pid set") } err := syscall.PtraceAttach(pid) if err != nil { log.Print("attach") log.Print(err) goto fail } _, err = syscall.Wait4(pid, &wstat, 0, nil) if err != nil { log.Printf("wait %d err %s\n", pid, err) goto fail } err = syscall.PtraceSetOptions(pid, syscall.PTRACE_O_TRACESYSGOOD) if err != nil { log.Print("ptrace set options") log.Print(err) goto fail } for !die { err = syscall.PtraceSyscall(pid, 0) if err != nil { log.Print("syscall") log.Print(err) goto fail } _, err = syscall.Wait4(pid, &wstat, 0, nil) if err != nil { log.Printf("wait %d err %s\n", pid, err) goto fail } // ENTER if wstat.Stopped() { if isSyscall(wstat) { err = syscall.PtraceGetRegs(pid, ®s) if err != nil { log.Print("regs") log.Print(err) goto fail } complete = sc.Call(regs) } } err = syscall.PtraceSyscall(pid, 0) if err != nil { log.Print("syscall 2") log.Print(err) goto fail } _, err = syscall.Wait4(pid, &wstat, 0, nil) if err != nil { log.Printf("wait %d err %s\n", pid, err) goto fail } os.Stdout.Sync() if wstat.Stopped() { if isSyscall(wstat) { err = syscall.PtraceGetRegs(pid, ®s) if err != nil { log.Print("regs") log.Print(err) goto fail } //log.Printf("NUM: %d ::%#v", syscallNum, regs) if complete != nil { complete(regs) complete = nil } } } } fail: syscall.Kill(pid, 18) err = syscall.PtraceDetach(pid) if err != nil { log.Print("detach") log.Print(err) } }