mirror of
https://github.com/golang/go
synced 2024-11-12 10:30:23 -07:00
Implementation of process tracing using Linux's ptrace.
R=rsc APPROVED=rsc DELTA=1543 (1528 added, 0 deleted, 15 changed) OCL=31570 CL=31630
This commit is contained in:
parent
14bb806cb4
commit
c105de748d
@ -23,16 +23,32 @@ type Cause interface {
|
||||
|
||||
// Regs is a set of named machine registers, including a program
|
||||
// counter, link register, and stack pointer.
|
||||
//
|
||||
// TODO(austin) There's quite a proliferation of methods here. We
|
||||
// could make a Reg interface with Get and Set and make this just PC,
|
||||
// Link, SP, Names, and Reg. We could also put Index in Reg and that
|
||||
// makes it easy to get the index of things like the PC (currently
|
||||
// there's just no way to know that). This would also let us include
|
||||
// other per-register information like how to print it.
|
||||
type Regs interface {
|
||||
// PC returns the value of the program counter.
|
||||
PC() Word;
|
||||
|
||||
// SetPC sets the program counter to val.
|
||||
SetPC(val Word) os.Error;
|
||||
|
||||
// Link returns the link register, if any.
|
||||
Link() Word;
|
||||
|
||||
// SetLink sets the link register to val.
|
||||
SetLink(val Word) os.Error;
|
||||
|
||||
// SP returns the value of the stack pointer.
|
||||
SP() Word;
|
||||
|
||||
// SetSP sets the stack pointer register to val.
|
||||
SetSP(val Word) os.Error;
|
||||
|
||||
// Names returns the names of all of the registers.
|
||||
Names() []string;
|
||||
|
||||
@ -42,7 +58,7 @@ type Regs interface {
|
||||
Get(i int) Word;
|
||||
|
||||
// Set sets the value of a register.
|
||||
Set(i int, val Word);
|
||||
Set(i int, val Word) os.Error;
|
||||
}
|
||||
|
||||
// Thread is a thread in the process being traced.
|
||||
@ -86,7 +102,7 @@ type Thread interface {
|
||||
// process's state extends to all of its threads.
|
||||
type Process interface {
|
||||
// Threads returns an array of all threads in this process.
|
||||
Threads() []*Thread;
|
||||
Threads() []Thread;
|
||||
|
||||
// AddBreakpoint creates a new breakpoint at program counter
|
||||
// pc. Breakpoints can only be created when the process is
|
||||
@ -105,7 +121,8 @@ type Process interface {
|
||||
// Continue resumes execution of all threads in this process.
|
||||
// Any thread that is stopped on a breakpoint will be stepped
|
||||
// over that breakpoint. Any thread that is stopped because
|
||||
// of a signal will receive the pending signal.
|
||||
// of a signal (other than SIGSTOP or SIGTRAP) will receive
|
||||
// the pending signal.
|
||||
Continue() os.Error;
|
||||
|
||||
// WaitStop waits until all threads in process p are stopped
|
||||
@ -118,14 +135,14 @@ type Process interface {
|
||||
Detach() os.Error;
|
||||
}
|
||||
|
||||
// Paused is a stop cause used for threads that are stopped either by
|
||||
// Stopped is a stop cause used for threads that are stopped either by
|
||||
// user request (e.g., from the Stop method or after single stepping),
|
||||
// or that are stopped because some other thread caused the program to
|
||||
// stop.
|
||||
type Paused struct {}
|
||||
type Stopped struct {}
|
||||
|
||||
func (c Paused) String() string {
|
||||
return "paused";
|
||||
func (c Stopped) String() string {
|
||||
return "stopped";
|
||||
}
|
||||
|
||||
// Breakpoint is a stop cause resulting from a thread reaching a set
|
||||
@ -176,7 +193,7 @@ func (c ThreadCreate) String() string {
|
||||
// accessible.
|
||||
type ThreadExit struct {
|
||||
exitStatus int;
|
||||
signal int;
|
||||
signal string;
|
||||
}
|
||||
|
||||
// Exited returns true if the thread exited normally.
|
||||
@ -192,12 +209,12 @@ func (c ThreadExit) ExitStatus() int {
|
||||
|
||||
// Signaled returns true if the thread was terminated by a signal.
|
||||
func (c ThreadExit) Signaled() bool {
|
||||
return c.signal != -1;
|
||||
return c.exitStatus == -1;
|
||||
}
|
||||
|
||||
// StopSignal returns the signal that terminated the thread, or -1 if
|
||||
// StopSignal returns the signal that terminated the thread, or "" if
|
||||
// it was not terminated by a signal.
|
||||
func (c ThreadExit) StopSignal() int {
|
||||
func (c ThreadExit) StopSignal() string {
|
||||
return c.signal;
|
||||
}
|
||||
|
||||
@ -207,7 +224,7 @@ func (c ThreadExit) String() string {
|
||||
case c.Exited():
|
||||
res += "with status " + strconv.Itoa(c.ExitStatus());
|
||||
case c.Signaled():
|
||||
res += "from signal " + strconv.Itoa(c.StopSignal());
|
||||
res += "from signal " + c.StopSignal();
|
||||
default:
|
||||
res += "from unknown cause";
|
||||
}
|
||||
|
131
usr/austin/ptrace/ptrace-nptl.txt
Normal file
131
usr/austin/ptrace/ptrace-nptl.txt
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
ptrace and NTPL, the missing manpage
|
||||
|
||||
== Signals ==
|
||||
|
||||
A signal sent to a ptrace'd process or thread causes only the thread
|
||||
that receives it to stop and report to the attached process.
|
||||
|
||||
Use tgkill to target a signal (for example, SIGSTOP) at a particular
|
||||
thread. If you use kill, the signal could be delivered to another
|
||||
thread in the same process.
|
||||
|
||||
Note that SIGSTOP differs from its usual behavior when a process is
|
||||
being traced. Usually, a SIGSTOP sent to any thread in a thread group
|
||||
will stop all threads in the thread group. When a thread is traced,
|
||||
however, a SIGSTOP affects only the receiving thread (and any other
|
||||
threads in the thread group that are not traced).
|
||||
|
||||
SIGKILL behaves like it does for non-traced processes. It affects all
|
||||
threads in the process and terminates them without the WSTOPSIG event
|
||||
generated by other signals. However, if PTRACE_O_TRACEEXIT is set,
|
||||
the attached process will still receive PTRACE_EVENT_EXIT events
|
||||
before receiving WIFSIGNALED events.
|
||||
|
||||
See "Following thread death" for a caveat regarding signal delivery to
|
||||
zombie threads.
|
||||
|
||||
== Waiting on threads ==
|
||||
|
||||
Cloned threads in ptrace'd processes are treated similarly to cloned
|
||||
threads in your own process. Thus, you must use the __WALL option in
|
||||
order to receive notifications from threads created by the child
|
||||
process. Similarly, the __WCLONE option will wait only on
|
||||
notifications from threads created by the child process and *not* on
|
||||
notifications from the initial child thread.
|
||||
|
||||
Even when waiting on a specific thread's PID using waitpid or similar,
|
||||
__WALL or __WCLONE is necessary or waitpid will return ECHILD.
|
||||
|
||||
== Attaching to existing threads ==
|
||||
|
||||
libthread_db (which gdb uses), attaches to existing threads by pulling
|
||||
the pthread data structures out of the traced process. The much
|
||||
easier way is to traverse the /proc/PID/task directory, though it's
|
||||
unclear how the semantics of these two approaches differ.
|
||||
|
||||
Unfortunately, if the main thread has exited (but the overall process
|
||||
has not), it sticks around as a zombie process. This zombie will
|
||||
appear in the /proc/PID/task directory, but trying to attach to it
|
||||
will yield EPERM. In this case, the third field of the
|
||||
/proc/PID/task/PID/stat file will be "Z". Attempting to open the stat
|
||||
file is also a convenient way to detect races between listing the task
|
||||
directory and the thread exiting. Coincidentally, gdb will simply
|
||||
fail to attach to a process whose main thread is a zombie.
|
||||
|
||||
Because new threads may be created while the debugger is in the
|
||||
process of attaching to existing threads, the debugger must repeatedly
|
||||
re-list the task directory until it has attached to (and thus stopped)
|
||||
every thread listed.
|
||||
|
||||
In order to follow new threads created by existing threads,
|
||||
PTRACE_O_TRACECLONE must be set on each thread attached to.
|
||||
|
||||
== Following new threads ==
|
||||
|
||||
With the child process stopped, use PTRACE_SETOPTIONS to set the
|
||||
PTRACE_O_TRACECLONE option. This option is per-thread, and thus must
|
||||
be set on each existing thread individually. When an existing thread
|
||||
with PTRACE_O_TRACECLONE set spawns a new thread, the existing thread
|
||||
will stop with (SIGTRAP | PTRACE_EVENT_CLONE << 8) and the PID of the
|
||||
new thread can be retrieved with PTRACE_GETEVENTMSG on the creating
|
||||
thread. At this time, the new thread will exist, but will initially
|
||||
be stopped with a SIGSTOP. The new thread will automatically be
|
||||
traced and will inherit the PTRACE_O_TRACECLONE option from its
|
||||
parent. The attached process should wait on the new thread to receive
|
||||
the SIGSTOP notification.
|
||||
|
||||
When using waitpid(-1, ...), don't rely on the parent thread reporting
|
||||
a SIGTRAP before receiving the SIGSTOP from the new child thread.
|
||||
|
||||
Without PTRACE_O_TRACECLONE, newly cloned threads will not be
|
||||
ptrace'd. As a result, signals received by new threads will be
|
||||
handled in the usual way, which may affect the parent and in turn
|
||||
appear to the attached process, but attributed to the parent (possibly
|
||||
in unexpected ways).
|
||||
|
||||
== Following thread death ==
|
||||
|
||||
If any thread with the PTRACE_O_TRACEEXIT option set exits (either by
|
||||
returning or pthread_exit'ing), the tracing process will receive an
|
||||
immediate PTRACE_EVENT_EXIT. At this point, the thread will still
|
||||
exist. The exit status, encoded as for wait, can be queried using
|
||||
PTRACE_GETEVENTMSG on the exiting thread's PID. The thread should be
|
||||
continued so it can actually exit, after which its wait behavior is
|
||||
the same as for a thread without the PTRACE_O_TRACEEXIT option.
|
||||
|
||||
If a non-main thread exits (either by returning or pthread_exit'ing),
|
||||
its corresponding process will also exit, producing a WIFEXITED event
|
||||
(after the process is continued from a possible PTRACE_EVENT_EXIT
|
||||
event). It is *not* necessary for another thread to ptrace_join for
|
||||
this to happen.
|
||||
|
||||
If the main thread exits by returning, then all threads will exit,
|
||||
first generating a PTRACE_EVENT_EXIT event for each thread if
|
||||
appropriate, then producing a WIFEXITED event for each thread.
|
||||
|
||||
If the main thread exits using pthread_exit, then it enters a
|
||||
non-waitable zombie state. It will still produce an immediate
|
||||
PTRACE_O_TRACEEXIT event, but the WIFEXITED event will be delayed
|
||||
until the entire process exits. This state exists so that shells
|
||||
don't think the process is done until all of the threads have exited.
|
||||
Unfortunately, signals cannot be delivered to non-waitable zombies.
|
||||
Most notably, SIGSTOP cannot be delivered; as a result, when you
|
||||
broadcast SIGSTOP to all of the threads, you must not wait for
|
||||
non-waitable zombies to stop.
|
||||
|
||||
== Multi-threaded debuggers ==
|
||||
|
||||
If the debugger itself is multi-threaded, ptrace calls must come from
|
||||
the same thread that originally attached to the remote thread. The
|
||||
kernel simply compares the PID of the caller of ptrace against the
|
||||
tracer PID of the process passed to ptrace. Because each debugger
|
||||
thread has a different PID, calling ptrace from a different thread
|
||||
might as well be calling it from a different process and the kernel
|
||||
will return ESRCH.
|
||||
|
||||
wait, on the other hand, does not have this restriction. Any debugger
|
||||
thread can wait on any thread in the attached process.
|
1242
usr/austin/ptrace/ptrace_linux.go
Normal file
1242
usr/austin/ptrace/ptrace_linux.go
Normal file
File diff suppressed because it is too large
Load Diff
150
usr/austin/ptrace/regs_linux_amd64.go
Normal file
150
usr/austin/ptrace/regs_linux_amd64.go
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ptrace
|
||||
|
||||
import (
|
||||
"os";
|
||||
"ptrace";
|
||||
"strconv";
|
||||
"syscall";
|
||||
)
|
||||
|
||||
type amd64Regs struct {
|
||||
syscall.PtraceRegs;
|
||||
setter func (*syscall.PtraceRegs) os.Error;
|
||||
}
|
||||
|
||||
var names = [...]string {
|
||||
"rax",
|
||||
"rbx",
|
||||
"rcx",
|
||||
"rdx",
|
||||
"rsi",
|
||||
"rdi",
|
||||
"rbp",
|
||||
"rsp",
|
||||
"r8",
|
||||
"r9",
|
||||
"r10",
|
||||
"r11",
|
||||
"r12",
|
||||
"r13",
|
||||
"r14",
|
||||
"r15",
|
||||
"rip",
|
||||
"eflags",
|
||||
"cs",
|
||||
"ss",
|
||||
"ds",
|
||||
"es",
|
||||
"fs",
|
||||
"gs",
|
||||
|
||||
// PtraceRegs contains these registers, but I don't think
|
||||
// they're actually meaningful.
|
||||
//"orig_rax",
|
||||
//"fs_base",
|
||||
//"gs_base",
|
||||
}
|
||||
|
||||
func (r *amd64Regs) PC() Word {
|
||||
return Word(r.Rip);
|
||||
}
|
||||
|
||||
func (r *amd64Regs) SetPC(val Word) os.Error {
|
||||
r.Rip = uint64(val);
|
||||
return r.setter(&r.PtraceRegs);
|
||||
}
|
||||
|
||||
func (r *amd64Regs) Link() Word {
|
||||
// TODO(austin)
|
||||
panic("No link register");
|
||||
}
|
||||
|
||||
func (r *amd64Regs) SetLink(val Word) os.Error {
|
||||
panic("No link register");
|
||||
}
|
||||
|
||||
func (r *amd64Regs) SP() Word {
|
||||
return Word(r.Rsp);
|
||||
}
|
||||
|
||||
func (r *amd64Regs) SetSP(val Word) os.Error {
|
||||
r.Rsp = uint64(val);
|
||||
return r.setter(&r.PtraceRegs);
|
||||
}
|
||||
|
||||
func (r *amd64Regs) Names() []string {
|
||||
return &names;
|
||||
}
|
||||
|
||||
func (r *amd64Regs) Get(i int) Word {
|
||||
switch i {
|
||||
case 0: return Word(r.Rax);
|
||||
case 1: return Word(r.Rbx);
|
||||
case 2: return Word(r.Rcx);
|
||||
case 3: return Word(r.Rdx);
|
||||
case 4: return Word(r.Rsi);
|
||||
case 5: return Word(r.Rdi);
|
||||
case 6: return Word(r.Rbp);
|
||||
case 7: return Word(r.Rsp);
|
||||
case 8: return Word(r.R8);
|
||||
case 9: return Word(r.R9);
|
||||
case 10: return Word(r.R10);
|
||||
case 11: return Word(r.R11);
|
||||
case 12: return Word(r.R12);
|
||||
case 13: return Word(r.R13);
|
||||
case 14: return Word(r.R14);
|
||||
case 15: return Word(r.R15);
|
||||
case 16: return Word(r.Rip);
|
||||
case 17: return Word(r.Eflags);
|
||||
case 18: return Word(r.Cs);
|
||||
case 19: return Word(r.Ss);
|
||||
case 20: return Word(r.Ds);
|
||||
case 21: return Word(r.Es);
|
||||
case 22: return Word(r.Fs);
|
||||
case 23: return Word(r.Gs);
|
||||
}
|
||||
panic("invalid register index ", strconv.Itoa(i));
|
||||
}
|
||||
|
||||
func (r *amd64Regs) Set(i int, val Word) os.Error {
|
||||
switch i {
|
||||
case 0: r.Rax = uint64(val);
|
||||
case 1: r.Rbx = uint64(val);
|
||||
case 2: r.Rcx = uint64(val);
|
||||
case 3: r.Rdx = uint64(val);
|
||||
case 4: r.Rsi = uint64(val);
|
||||
case 5: r.Rdi = uint64(val);
|
||||
case 6: r.Rbp = uint64(val);
|
||||
case 7: r.Rsp = uint64(val);
|
||||
case 8: r.R8 = uint64(val);
|
||||
case 9: r.R9 = uint64(val);
|
||||
case 10: r.R10 = uint64(val);
|
||||
case 11: r.R11 = uint64(val);
|
||||
case 12: r.R12 = uint64(val);
|
||||
case 13: r.R13 = uint64(val);
|
||||
case 14: r.R14 = uint64(val);
|
||||
case 15: r.R15 = uint64(val);
|
||||
case 16: r.Rip = uint64(val);
|
||||
case 17: r.Eflags = uint64(val);
|
||||
case 18: r.Cs = uint64(val);
|
||||
case 19: r.Ss = uint64(val);
|
||||
case 20: r.Ds = uint64(val);
|
||||
case 21: r.Es = uint64(val);
|
||||
case 22: r.Fs = uint64(val);
|
||||
case 23: r.Gs = uint64(val);
|
||||
default:
|
||||
panic("invalid register index ", strconv.Itoa(i));
|
||||
}
|
||||
return r.setter(&r.PtraceRegs);
|
||||
}
|
||||
|
||||
func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs {
|
||||
res := amd64Regs{};
|
||||
res.PtraceRegs = *regs;
|
||||
res.setter = setter;
|
||||
return &res;
|
||||
}
|
Loading…
Reference in New Issue
Block a user