mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-21 00:23:05 +00:00
292 lines
6.4 KiB
Go
292 lines
6.4 KiB
Go
package ptrace
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// Syscall numbers
|
|
const (
|
|
READ = 0
|
|
WRITE = 1
|
|
OPEN = 2
|
|
CLOSE = 3
|
|
STAT = 4
|
|
MMAP = 9
|
|
MPROTECT = 10
|
|
MUNMAP = 11
|
|
SELECT = 23
|
|
MADVISE = 28
|
|
SOCKET = 41
|
|
CONNECT = 42
|
|
ACCEPT = 43
|
|
SENDTO = 44
|
|
RECVFROM = 45
|
|
SHUTDOWN = 48
|
|
CLONE = 56
|
|
GETTIMEOFDAY = 96
|
|
GETID = 186
|
|
FUTEX = 202
|
|
SETROBUSTLIST = 273
|
|
ACCEPT4 = 288
|
|
)
|
|
|
|
// States for a given thread
|
|
const (
|
|
NORMAL = iota
|
|
INSYSCALL
|
|
)
|
|
|
|
type thread struct {
|
|
tid int
|
|
attached bool
|
|
process *process // might be nil!
|
|
tracer *PTracer
|
|
|
|
state int
|
|
callRegs syscall.PtraceRegs
|
|
resultRegs syscall.PtraceRegs
|
|
|
|
currentIncoming map[int]*Fd
|
|
currentOutgoing map[int]*Fd
|
|
closedOutgoing []*Fd
|
|
}
|
|
|
|
func newThread(pid int, process *process, tracer *PTracer) *thread {
|
|
t := &thread{
|
|
tid: pid,
|
|
process: process,
|
|
tracer: tracer,
|
|
currentIncoming: map[int]*Fd{},
|
|
currentOutgoing: map[int]*Fd{},
|
|
}
|
|
return t
|
|
}
|
|
|
|
// trace thread calls this
|
|
func (t *thread) syscallStopped() {
|
|
var err error
|
|
|
|
if t.state == NORMAL {
|
|
if err = syscall.PtraceGetRegs(t.tid, &t.callRegs); err != nil {
|
|
t.logf("GetRegs failed, pid=%d, err=%v", t.tid, err)
|
|
}
|
|
t.state = INSYSCALL
|
|
return
|
|
}
|
|
|
|
t.state = NORMAL
|
|
|
|
if err = syscall.PtraceGetRegs(t.tid, &t.resultRegs); err != nil {
|
|
t.logf("GetRegs failed, pid=%d, err=%v", t.tid, err)
|
|
return
|
|
}
|
|
|
|
if t.process == nil {
|
|
t.logf("Got syscall, but don't know parent process!")
|
|
return
|
|
}
|
|
|
|
switch t.callRegs.Orig_rax {
|
|
case ACCEPT, ACCEPT4:
|
|
t.handleAccept(&t.callRegs, &t.resultRegs)
|
|
|
|
case CLOSE:
|
|
t.handleClose(&t.callRegs, &t.resultRegs)
|
|
|
|
case CONNECT:
|
|
t.handleConnect(&t.callRegs, &t.resultRegs)
|
|
|
|
case READ, WRITE, RECVFROM, SENDTO:
|
|
t.handleIO(&t.callRegs, &t.resultRegs)
|
|
|
|
// we can ignore these syscalls
|
|
case SETROBUSTLIST, GETID, MMAP, MPROTECT, MADVISE, SOCKET, CLONE, STAT, SELECT:
|
|
return
|
|
|
|
case OPEN, FUTEX, SHUTDOWN, GETTIMEOFDAY, MUNMAP:
|
|
return
|
|
|
|
default:
|
|
t.logf("syscall(%d)", t.callRegs.Orig_rax)
|
|
}
|
|
}
|
|
|
|
func ntohl(b uint16) uint16 {
|
|
return (b << 8) | ((b & 0xff00) >> 8)
|
|
}
|
|
|
|
func (t *thread) getSocketAddress(ptr uintptr) (addr net.IP, port uint16, err error) {
|
|
var (
|
|
buf = make([]byte, syscall.SizeofSockaddrAny)
|
|
read int
|
|
)
|
|
|
|
if ptr == 0 {
|
|
err = fmt.Errorf("Null ptr")
|
|
return
|
|
}
|
|
|
|
read, err = syscall.PtracePeekData(t.tid, ptr, buf)
|
|
if read != syscall.SizeofSockaddrAny || err != nil {
|
|
return
|
|
}
|
|
|
|
var sockaddr4 = (*syscall.RawSockaddrInet4)(unsafe.Pointer(&buf[0]))
|
|
if sockaddr4.Family != syscall.AF_INET {
|
|
return
|
|
}
|
|
|
|
addr = net.IP(sockaddr4.Addr[0:])
|
|
port = ntohl(sockaddr4.Port)
|
|
return
|
|
}
|
|
|
|
func (t *thread) handleAccept(call, result *syscall.PtraceRegs) {
|
|
var (
|
|
err error
|
|
ok bool
|
|
listeningFdNum int
|
|
connectionFdNum int
|
|
addrPtr uintptr
|
|
addr net.IP
|
|
port uint16
|
|
listeningFd *Fd
|
|
connection *Fd
|
|
)
|
|
|
|
listeningFdNum = int(result.Rdi)
|
|
connectionFdNum = int(result.Rax)
|
|
addrPtr = uintptr(result.Rsi)
|
|
addr, port, err = t.getSocketAddress(addrPtr)
|
|
if err != nil {
|
|
t.logf("failed to read sockaddr: %v", err)
|
|
return
|
|
}
|
|
|
|
listeningFd, ok = t.process.fds[listeningFdNum]
|
|
if !ok {
|
|
listeningFd, err = newListeningFd(t.process.pid, listeningFdNum)
|
|
if err != nil {
|
|
t.logf("Failed to read listening port: %v", err)
|
|
return
|
|
}
|
|
t.process.fds[listeningFdNum] = listeningFd
|
|
}
|
|
|
|
connection, err = listeningFd.newConnection(addr, port, connectionFdNum)
|
|
if err != nil {
|
|
t.logf("Failed to create connection fd: %v", err)
|
|
return
|
|
}
|
|
|
|
t.process.newFd(connection)
|
|
|
|
t.logf("Accepted connection from %s:%d -> %s:%d on fd %d, new fd %d",
|
|
addr, port, connection.ToAddr, connection.ToPort, listeningFdNum, connectionFdNum)
|
|
}
|
|
|
|
func (t *thread) handleConnect(call, result *syscall.PtraceRegs) {
|
|
fd := int(result.Rdi)
|
|
ptr := result.Rsi
|
|
addr, port, err := t.getSocketAddress(uintptr(ptr))
|
|
if err != nil {
|
|
t.logf("failed to read sockaddr: %v", err)
|
|
return
|
|
}
|
|
|
|
connection, err := newConnectionFd(t.process.pid, fd, addr, port)
|
|
if err != nil {
|
|
t.logf("Failed to create connection fd: %v", err)
|
|
return
|
|
}
|
|
|
|
t.process.newFd(connection)
|
|
|
|
t.logf("Made connection from %s:%d -> %s:%d on fd %d",
|
|
connection.FromAddr, connection.FromPort,
|
|
connection.ToAddr, connection.ToPort, fd)
|
|
}
|
|
|
|
func (t *thread) handleClose(call, result *syscall.PtraceRegs) {
|
|
fdNum := int(call.Rdi)
|
|
fd, ok := t.process.fds[fdNum]
|
|
if !ok {
|
|
t.logf("Got close unknown fd %d", fdNum)
|
|
return
|
|
}
|
|
|
|
//t.logf("Closing fd %d", fdNum)
|
|
fd.close()
|
|
|
|
// if this connection was incoming, add it to 'the registry'
|
|
if fd.direction == incoming {
|
|
// collect all the outgoing connections this thread has made
|
|
// and treat them as caused by this incoming connections
|
|
for _, outgoing := range t.currentOutgoing {
|
|
//t.logf("Fd %d caused %d", fdNum, outgoing.fd)
|
|
fd.Children = append(fd.Children, outgoing)
|
|
}
|
|
t.currentOutgoing = map[int]*Fd{}
|
|
|
|
for _, outgoing := range t.closedOutgoing {
|
|
//t.logf("Fd %d caused %d", fdNum, outgoing.fd)
|
|
fd.Children = append(fd.Children, outgoing)
|
|
}
|
|
t.closedOutgoing = []*Fd{}
|
|
|
|
t.tracer.store.RecordConnection(t.process.pid, fd)
|
|
}
|
|
|
|
// now make sure we've remove it from everywhere
|
|
delete(t.process.fds, fdNum)
|
|
for _, thread := range t.process.threads {
|
|
delete(thread.currentIncoming, fdNum)
|
|
|
|
if _, ok := thread.currentOutgoing[fdNum]; ok {
|
|
thread.closedOutgoing = append(thread.closedOutgoing, fd)
|
|
}
|
|
delete(thread.currentOutgoing, fdNum)
|
|
}
|
|
}
|
|
|
|
func (t *thread) handleIO(call, result *syscall.PtraceRegs) {
|
|
fdNum := int(call.Rdi)
|
|
fd, ok := t.process.fds[fdNum]
|
|
if !ok {
|
|
//t.logf("IO on unknown fd %d", fdNum)
|
|
return
|
|
}
|
|
|
|
if fd.direction == incoming {
|
|
//t.logf("IO on incoming connection %d; setting affinity", fdNum)
|
|
t.currentIncoming[fdNum] = fd
|
|
} else {
|
|
//t.logf("IO on outgoing connection %d; setting affinity", fdNum)
|
|
t.currentOutgoing[fdNum] = fd
|
|
}
|
|
}
|
|
|
|
func (t *thread) handleClone(pid int) error {
|
|
// We can't use the pid in the process, as it may be in a namespace
|
|
newPid, err := syscall.PtraceGetEventMsg(pid)
|
|
if err != nil {
|
|
log.Printf("PtraceGetEventMsg failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
t.logf("New thread clone'd, pid=%d", newPid)
|
|
return nil
|
|
}
|
|
|
|
func (t *thread) handleExit() {
|
|
t.logf("Exiting")
|
|
}
|
|
|
|
func (t *thread) logf(fmt string, args ...interface{}) {
|
|
log.Printf("[thread %d] "+fmt, append([]interface{}{t.tid}, args...)...)
|
|
}
|