forked from hugelgupf/u-root
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is the start of strace. First things first, this simple package lets you trace system calls. It also includes a simple command. Now that I understand we have to lock things down for PTRACE_TRACEME, and Ryan pointed out I could use LockOSThread(), this works pretty well. Signed-off-by: Ronald G. Minnich <rminnich@gmail.com>
- Loading branch information
Showing
4 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright 2012-2018 the u-root Authors. All rights reserved | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// strace is a simple single-process tracer. | ||
// It starts the comand and lets the strace.Run() do all the work. | ||
// | ||
// Synopsis: | ||
// strace <command> [args...] | ||
// | ||
// Description: | ||
// trace a single process given a command name. | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"os/exec" | ||
|
||
flag "github.com/spf13/pflag" | ||
"github.com/u-root/u-root/pkg/strace" | ||
) | ||
|
||
var ( | ||
cmdUsage = "Usage: strace <command> [args...]" | ||
debug = flag.BoolP("debug", "d", false, "enable debug printing") | ||
) | ||
|
||
func usage() { | ||
log.Fatalf(cmdUsage) | ||
} | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
if *debug { | ||
strace.Debug = log.Printf | ||
} | ||
a := flag.Args() | ||
if len(a) < 1 { | ||
usage() | ||
} | ||
|
||
c := exec.Command(a[0], a[1:]...) | ||
c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr | ||
|
||
t, err := strace.New() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
go t.RunTracerFromCmd(c) | ||
|
||
for r := range t.Records { | ||
fmt.Printf("%s\n", r.String()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright 2018 the u-root 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 strace supports tracing programs. | ||
// The basic control of tracing is via a Tracer, which returns raw | ||
// TraceRecords via a chan. The easiest way to create a Tracer is via | ||
// RunTracerFromCommand, which uses a filled out exec.Cmd to start a | ||
// process and produce trace records. | ||
// Forking and nice printing are not yet supported. | ||
package strace | ||
|
||
import ( | ||
"fmt" | ||
"os/exec" | ||
"runtime" | ||
"syscall" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
var Debug = func(string, ...interface{}) {} | ||
|
||
// Tracer has information to trace one process. It can be created by | ||
// starting a command, or attaching. Attaching is not supported yet. | ||
type Tracer struct { | ||
Pid int | ||
Records chan *TraceRecord | ||
Count int | ||
} | ||
|
||
func New() (*Tracer, error) { | ||
return &Tracer{Pid: -1, Records: make(chan *TraceRecord)}, nil | ||
} | ||
|
||
// StartTracerFromCommand runs a Tracer given an exec.Cmd. | ||
func (t *Tracer) RunTracerFromCmd(c *exec.Cmd) { | ||
defer close(t.Records) | ||
if c.SysProcAttr == nil { | ||
c.SysProcAttr = &syscall.SysProcAttr{} | ||
} | ||
c.SysProcAttr.Ptrace = true | ||
// Because the go runtime forks traced processes with PTRACE_TRACEME | ||
// we need to maintain the parent-child relationship for ptrace to work. | ||
// We've learned this the hard way. So we lock down this thread to | ||
// this proc, and start the command here. | ||
// Note this function will block; if you want it to be nonblocking you | ||
// need to use go etc. | ||
runtime.LockOSThread() | ||
defer runtime.UnlockOSThread() | ||
if err := c.Start(); err != nil { | ||
Debug("Start gets err %v", err) | ||
t.Records <- &TraceRecord{Err: err} | ||
return | ||
} | ||
Debug("Start gets pid %v", c.Process.Pid) | ||
if err := c.Wait(); err != nil { | ||
fmt.Printf("Wait returned: %v\n", err) | ||
t.Records <- &TraceRecord{Err: err} | ||
} | ||
t.Pid = c.Process.Pid | ||
t.Run() | ||
} | ||
|
||
func (t *Tracer) Run() error { | ||
x := Enter | ||
for { | ||
t.Count++ | ||
r := &TraceRecord{Serial: t.Count, EX: x, Pid: t.Pid} | ||
if err := unix.PtraceGetRegs(t.Pid, &r.Regs); err != nil { | ||
Debug("ptracegetregs for %d gets %v", t.Pid, err) | ||
r.Err = err | ||
t.Records <- r | ||
break | ||
} | ||
t.Records <- r | ||
switch x { | ||
case Enter: | ||
x = Exit | ||
default: | ||
x = Enter | ||
} | ||
if err := unix.PtraceSyscall(t.Pid, 0); err != nil { | ||
r := &TraceRecord{Serial: t.Count, EX: x, Pid: t.Pid} | ||
Debug("ptracesyscall for %d gets %v", t.Pid, err) | ||
r.Err = err | ||
t.Records <- r | ||
break | ||
} | ||
if w, err := Wait(t.Pid); err != nil { | ||
r := &TraceRecord{Serial: t.Count, EX: x, Pid: t.Pid} | ||
Debug("wait4 for %d gets %v, %v", t.Pid, w, err) | ||
r.Err = err | ||
t.Records <- r | ||
break | ||
} | ||
|
||
} | ||
Debug("Pushed %d records", t.Count) | ||
return nil | ||
} | ||
|
||
// EventType describes whether a record is system call Entry or Exit | ||
type EventType string | ||
|
||
const ( | ||
Enter EventType = "E" | ||
Exit = "X" | ||
) | ||
|
||
// TraceRecord has information about a ptrace event. | ||
type TraceRecord struct { | ||
EX EventType | ||
Regs unix.PtraceRegs | ||
Serial int | ||
Pid int | ||
Err error | ||
} | ||
|
||
// String is a stringer for TraceRecords | ||
// TODO: stringer for Regs. | ||
func (t *TraceRecord) String() string { | ||
pre := fmt.Sprintf("%s %d#%d:", t.EX, t.Pid, t.Serial) | ||
if t.Err != nil { | ||
return fmt.Sprintf("%s(%v)", pre, t.Err) | ||
} | ||
return fmt.Sprintf("%s %v", pre, t.Regs) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright 2018 the u-root 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 strace | ||
|
||
import ( | ||
"os/exec" | ||
"testing" | ||
) | ||
|
||
// It's not really easy to write a full up general tester for this. | ||
// Even the simplest commands on Linux have dozens of system calls. | ||
// The Go assembler should in principle let us write a 3 line assembly | ||
// program that just does an exit system call: | ||
// MOVQ $exit, RARG | ||
// SYSCALL | ||
// But that's for someone else to do :-) | ||
|
||
func TestNoCommandFail(t *testing.T) { | ||
Debug = t.Logf | ||
c, err := New() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
go c.RunTracerFromCmd(exec.Command("hi", "/etc/hosts")) | ||
r := <-c.Records | ||
if r.Err == nil { | ||
t.Fatalf("Got nil, want a non-nil error") | ||
} | ||
} | ||
|
||
func TestBasicStrace(t *testing.T) { | ||
Debug = t.Logf | ||
c, err := New() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
go c.RunTracerFromCmd(exec.Command("ls", "/etc/hosts")) | ||
|
||
for r := range c.Records { | ||
t.Logf("%s", r.String()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2018 the u-root 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 strace | ||
|
||
import ( | ||
"syscall" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
func Wait(pid int) (unix.WaitStatus, error) { | ||
var w syscall.WaitStatus | ||
_, err := syscall.Wait4(pid, &w, 0, nil) | ||
uw := unix.WaitStatus(w) | ||
return uw, err | ||
} |