-
Notifications
You must be signed in to change notification settings - Fork 5
/
ptrace.go
152 lines (136 loc) · 3.32 KB
/
ptrace.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Package ptrace provides an interface to the ptrace system call.
package ptrace
import (
"errors"
"os"
"runtime"
"syscall"
)
var (
// ErrExited is returned when a command is executed on a tracee
// that has already exited.
ErrExited = errors.New("tracee exited")
)
// An Event is sent on a Tracee's event channel whenever it changes state.
type Event interface{}
// A Tracee is a process that is being traced.
type Tracee struct {
proc *os.Process
events chan Event
err chan error
cmds chan func()
}
// Events returns the events channel for the tracee.
func (t *Tracee) Events() <-chan Event {
return t.events
}
// Exec executes a process with tracing enabled, returning the Tracee
// or an error if an error occurs while executing the process.
func Exec(name string, argv []string) (*Tracee, error) {
t := &Tracee{
events: make(chan Event, 1),
err: make(chan error, 1),
cmds: make(chan func()),
}
err := make(chan error)
proc := make(chan *os.Process)
go func() {
runtime.LockOSThread()
p, e := os.StartProcess(name, argv, &os.ProcAttr{
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
Sys: &syscall.SysProcAttr{
Ptrace: true,
Pdeathsig: syscall.SIGCHLD,
},
})
proc <- p
err <- e
if e != nil {
return
}
go t.wait()
t.trace()
}()
t.proc = <-proc
return t, <-err
}
// Detach detaches the tracee, allowing it to continue its execution normally.
// No more tracing is performed, and no events are sent on the event channel
// until the tracee exits.
func (t *Tracee) Detach() error {
err := make(chan error, 1)
if t.do(func() { err <- syscall.PtraceDetach(t.proc.Pid) }) {
return <-err
}
return ErrExited
}
// SingleStep continues the tracee for one instruction.
func (t *Tracee) SingleStep() error {
err := make(chan error, 1)
if t.do(func() { err <- syscall.PtraceSingleStep(t.proc.Pid) }) {
return <-err
}
return ErrExited
}
// Continue makes the tracee execute unmanaged by the tracer. Most
// commands are not possible in this state, with the notable exception
// of sending a syscall.SIGSTOP signal.
func (t *Tracee) Continue() error {
err := make(chan error, 1)
const signum = 0
if t.do(func() { err <- syscall.PtraceCont(t.proc.Pid, signum) }) {
return <-err
}
return ErrExited
}
// Kill sends the given signal to the tracee.
func (t *Tracee) Kill(sig syscall.Signal) error {
err := make(chan error, 1)
if t.do(func() { err <- syscall.Kill(t.proc.Pid, sig) }) {
return <-err
}
return ErrExited
}
// Sends the command to the tracer go routine. Returns whether the command
// was sent or not. The command may not have been sent if the tracee exited.
func (t *Tracee) do(f func()) bool {
if t.cmds != nil {
t.cmds <- f
return true
}
return false
}
// Close cleans up internal memory for managing the tracee. If an error is
// pending, it is returned.
func (t *Tracee) Close() error {
var err error
select {
case err = <-t.err:
default:
err = nil
}
close(t.err)
close(t.cmds)
t.cmds = nil
return err
}
func (t *Tracee) wait() {
defer close(t.events)
for {
state, err := t.proc.Wait()
if err != nil {
t.err <- err
return
}
if state.Exited() {
t.events <- Event(state.Sys().(syscall.WaitStatus))
return
}
t.events <- Event(state.Sys().(syscall.WaitStatus))
}
}
func (t *Tracee) trace() {
for cmd := range t.cmds {
cmd()
}
}