diff --git a/analysers/procuse/table.go b/analysers/procuse/table.go new file mode 100644 index 0000000..56c6f8e --- /dev/null +++ b/analysers/procuse/table.go @@ -0,0 +1,88 @@ +package procuse + +import ( + "errors" + "fmt" + "io" + "strings" + + "github.com/loov/leakcheck/api" + "golang.org/x/sys/unix" +) + +type Table struct { + Verbose bool + Open map[int64]*Process +} + +type Process struct { + PID int64 + Name string + Status Status + Logged bool +} + +type Status int + +const ( + StatusUninitialized = Status(0) + StatusRunning = Status(1) + StatusExited = Status(2) + StatusKilled = Status(3) +) + +func New(verbose bool) *Table { + return &Table{ + Verbose: verbose, + Open: map[int64]*Process{}, + } +} + +func (table *Table) opened(pid int64) { + process := &Process{ + PID: pid, + Status: StatusRunning, + } + table.Open[pid] = process +} + +func (table *Table) shutdown(pid int64) { + process, ok := table.Open[pid] + if !ok { + return + } + + delete(table.Open, pid) + process.Status = StatusKilled +} + +func (table *Table) Handle(call api.Call) { + switch call := call.(type) { + case api.Clone: + childFlag := int64(unix.SIGCHLD) + if !call.Failed && call.Flag&childFlag == childFlag { + table.opened(call.ResultPID) + } + case api.Kill: + if call.Signal == unix.SIGKILL || call.Signal == unix.SIGQUIT || call.Signal == unix.SIGSTOP { + table.shutdown(call.PID) + } + } +} + +func (table *Table) Err() error { + var buf strings.Builder + + for _, proc := range table.Open { + fmt.Fprintf(&buf, "unclosed process %d\n", proc.PID) + } + + if buf.Len() == 0 { + return nil + } + return errors.New(buf.String()) +} + +func (table *Table) WriteTo(w io.Writer) (int64, error) { + return 0, nil +} diff --git a/api/call.go b/api/call.go index ffe935f..ddc6196 100644 --- a/api/call.go +++ b/api/call.go @@ -2,6 +2,7 @@ package api import ( "strconv" + "syscall" "github.com/loov/leakcheck/api/syscalls" ) @@ -43,6 +44,20 @@ type Close struct { Failed bool } +type Clone struct { + Syscall + Flag int64 // corresponding to unix.CLONE_* + ResultPID int64 + Failed bool +} + +type Kill struct { + Syscall + PID int64 + Signal syscall.Signal + Failed bool +} + // Syscall is the fallback when there isn't a specific struct type Syscall struct { Number uint64 diff --git a/main.go b/main.go index d104b65..df25b6e 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "github.com/loov/leakcheck/analysers/connuse" "github.com/loov/leakcheck/analysers/counter" "github.com/loov/leakcheck/analysers/fileuse" + "github.com/loov/leakcheck/analysers/procuse" "github.com/loov/leakcheck/analysers/tempuse" "github.com/loov/leakcheck/analysers/tracer" "github.com/loov/leakcheck/api" @@ -16,11 +17,16 @@ import ( ) func main() { - count := flag.Bool("count", false, "count syscalls") - dotrace := flag.Bool("trace", false, "trace all monitored syscalls") verbose := flag.Bool("verbose", false, "enable verbose output") summary := flag.Bool("summary", false, "summary of analysers") - temponly := flag.Bool("temponly", false, "creating files only allowed in temporary directory") + + fileuseEnabled := flag.Bool("file", true, "monitor file usage") + connuseEnabled := flag.Bool("conn", true, "monitor connection usage") + procuseEnabled := flag.Bool("proc", false, "monitor process usage (flaky)") + counterEnabled := flag.Bool("count", false, "count syscalls") + tempuseEnabled := flag.Bool("temp", false, "monitor proper temporary usage") + tracerEnabled := flag.Bool("trace", false, "trace all monitored syscalls") + flag.Parse() if *verbose { @@ -28,16 +34,22 @@ func main() { } var analysers api.Analysers - analysers.Add(fileuse.New(*verbose)) - analysers.Add(connuse.New(*verbose)) - - if *temponly { + if *fileuseEnabled { + analysers.Add(fileuse.New(*verbose)) + } + if *connuseEnabled { + analysers.Add(connuse.New(*verbose)) + } + if *procuseEnabled { + analysers.Add(procuse.New(*verbose)) + } + if *tempuseEnabled { analysers.Add(tempuse.New(*verbose)) } - if *count { + if *counterEnabled { analysers.Add(counter.New()) } - if *dotrace { + if *tracerEnabled { analysers.Add(tracer.New()) } diff --git a/main_test.go b/main_test.go index 3807337..7e7f596 100644 --- a/main_test.go +++ b/main_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "net" "os" + "os/exec" "testing" ) @@ -68,3 +69,27 @@ func TestConnLeak(t *testing.T) { } _ = conn } + +func TestExecNormal(t *testing.T) { + cmd := exec.Command("sleep", "0") + if err := cmd.Run(); err != nil { + t.Fatal(err) + } +} + +func TestExecLeak(t *testing.T) { + cmd := exec.Command("sleep", "5") + if err := cmd.Start(); err != nil { + t.Fatal(err) + } +} + +func TestExecKill(t *testing.T) { + cmd := exec.Command("sleep", "5") + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + if err := cmd.Process.Kill(); err != nil { + t.Fatal(err) + } +} diff --git a/trace/ptrace/trace_linux_386.go b/trace/ptrace/trace_linux_386.go index fb12c63..ee6464b 100644 --- a/trace/ptrace/trace_linux_386.go +++ b/trace/ptrace/trace_linux_386.go @@ -9,7 +9,7 @@ func registersToCall(pid int, registers unix.PtraceRegs) api.Call { raw := api.Syscall{ Number: uint64(registers.Orig_eax), } - + // arguments: %ebx, %ecx, %edx, %esi, %edi, %ebp switch raw.Number { case unix.SYS_OPEN: return api.Open{ @@ -70,6 +70,9 @@ func registersToCall(pid int, registers unix.PtraceRegs) api.Call { Failed: registers.Eax != 0, } } + + // case unix.SYS_CLONE: + // case unix.SYS_KILL: } return raw diff --git a/trace/ptrace/trace_linux_amd64.go b/trace/ptrace/trace_linux_amd64.go index 8ef920b..81c8308 100644 --- a/trace/ptrace/trace_linux_amd64.go +++ b/trace/ptrace/trace_linux_amd64.go @@ -1,6 +1,8 @@ package ptrace import ( + "syscall" + "github.com/loov/leakcheck/api" "golang.org/x/sys/unix" ) @@ -10,6 +12,7 @@ func registersToCall(pid int, registers unix.PtraceRegs) api.Call { Number: uint64(registers.Orig_rax), } + // arguments %rdi, %rsi, %rdx, %rcx, %r8 and %r9 switch raw.Number { case unix.SYS_OPEN: return api.Open{ @@ -62,6 +65,21 @@ func registersToCall(pid int, registers unix.PtraceRegs) api.Call { Addr: addr, Failed: registers.Rax != 0, } + + case unix.SYS_CLONE: + return api.Clone{ + Syscall: raw, + Flag: int64(registers.Rdi), + ResultPID: int64(registers.Rax), + Failed: int64(registers.Rax) < 0, + } + case unix.SYS_KILL: + return api.Kill{ + Syscall: raw, + PID: int64(registers.Rdi), + Signal: syscall.Signal(registers.Rsi), + Failed: int64(registers.Rax) != 0, + } } return raw