Skip to content

Commit

Permalink
feat: Support all syscalls on amd64
Browse files Browse the repository at this point in the history
  • Loading branch information
liamg committed Nov 7, 2022
1 parent 64bbbf3 commit 28deabf
Show file tree
Hide file tree
Showing 158 changed files with 14,378 additions and 942 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
jobs:
build:
name: releasing
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
go-version: '1.19'
cache: true
- name: Run tests
run: sudo apt install -y musl-tools && make test
run: make test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.idea
/grace
/dist
/linux
/headers
4 changes: 2 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
before:
hooks:
- sudo apt install -y musl-tools
- make headers

builds:
- id: grace
Expand All @@ -10,7 +10,7 @@ builds:
- "-linkmode external -s -w -extldflags '-fno-PIC -static'"
env:
- CGO_ENABLED=1
- CC=musl-gcc
- CGO_CFLAGS="-Iheaders/include"
goos:
- linux
goarch:
Expand Down
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM ubuntu:22.04

RUN apt-get update -y && apt-get install -y git build-essential golang ca-certificates rsync
RUN git clone --depth 1 https://github.com/torvalds/linux.git /linux
COPY . /src
WORKDIR /src
RUN make test
24 changes: 20 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
default: build

.PHONY: clean
clean:
rm -rf headers
rm -rf linux

.PHONY: test
test:
CC=musl-gcc CGO_ENABLED=1 go test ./...
test: headers
CGO_ENABLED=1 CGO_CFLAGS="-I$$(pwd)/headers/include" go test ./tracer ./printer ./filter

linux:
git clone --depth 1 https://github.com/torvalds/linux.git ./linux

headers: linux
cd linux && make headers_install ARCH=x86_64 INSTALL_HDR_PATH=../headers
rm -rf linux

.PHONY: build
build:
CGO_ENABLED=1 CC=musl-gcc go build --ldflags '-linkmode external -extldflags "-static"'
build: headers
CGO_ENABLED=1 CGO_CFLAGS="-I$$(pwd)/headers/include" go build --ldflags '-linkmode external -extldflags "-static"'

.PHONY: install
install: headers
CGO_ENABLED=1 CGO_CFLAGS="-I$$(pwd)/headers/include" go install --ldflags '-linkmode external -extldflags "-static"'

.PHONY: demo
demo: build
Expand Down
113 changes: 93 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,124 @@
# grace

_grace_ is a tool for monitoring and modifying syscalls for a given process.
_grace_ is a tool for monitoring and annotating syscalls for a given process.

It's essentially a lightweight [strace](https://en.wikipedia.org/wiki/Strace), in Go, with colours and pretty output.

![](screenshot.png)
<p align="center">
<img src="demo.gif"/>
</p>

// TODO: new screenshot
<p align="center">
<img src="compare.png"/>
</p>

It's possible to tweak and filter the output to make it quite readable, for example (using `-vnmx`):

<p align="center">
<img src="ux.png"/>
</p>

You can also review a summary of encountered syscalls (and sort by various columns):

<p align="center">
<img src="summary.png"/>
</p>

## Features/Usage Examples

### grace vs. strace

_grace_ isn't meant to compete with _strace_, it's purely meant to be a user-friendly, lightweight alternative. However, the following should provide a rough idea of what is supported in _grace_ so far.

Over time grace is meant to become a simpler, more readable alternative to strace (_strace for dummies?_), albeit with reduced functionality/advanced features.

| Feature | grace | strace |
|---------------------------------------------------------------------------------------|-------|--------|
| Start a program and print all syscalls it makes |||
| Attach to an existing process by `pid` and print all syscalls it makes |||
| Filter syscalls by name, e.g. only show occurrences of the `open` syscall | ||
| Filter syscalls using a given path, e.g. only show syscalls that access `/etc/passwd` | ||
| Dump I/O for certain file descriptors | ||
| Count occurrences and duration of all syscalls and present in a useful format | ||
| Print relative/absolute timestamps | ||
| Filter syscalls by name, e.g. only show occurrences of the `open` syscall | ||
| Filter syscalls using a given path, e.g. only show syscalls that access `/etc/passwd` | ||
| Dump I/O for certain file descriptors | ||
| Count occurrences and duration of all syscalls and present in a useful format | ||
| Print relative/absolute timestamps | ||
| Tamper with syscalls |||
| Print extra information about file descriptors, such as path, socket addresses etc. | some ||
| Print extra information about file descriptors, such as path, socket addresses etc. | ||
| Print stack traces |||
| Filter by return value |||
| Decode SELinux context info |||
| Filter by return value |||
| Pretty colours to make output easier to read |||
| Lots of output options and customisation vectors |||
| Output to file |||
| Filter by failing/non-failing syscalls |||

_NOTE: Please feel free to add important strace features to this table, I'm working with a limited knowledge of strace._

### Usage Examples

```
// TODO
```

## Installation

Grab a statically compiled binary from the [latest release](https://github.com/liamg/grace/releases/latest).

## Build Dependencies

If you want to build _grace_ yourself instead of using the precompiled binaries, you'll need a recent version of Go (1.19+), `musl-gcc` installed (you can install `musl-tools` on Ubuntu or `musl` on Arch), and kernel headers (install `linux-headers-$(uname -r)` on Ubuntu or `linux-headers` on Arch). _grace_ mainly just pulls constants from the kernel headers, so it's not a huge dependency. You should then have some success running `make build`. Note that many architectures are not yet supported (see below.)

## Supported Platforms/Architecture

Currently only Linux/amd64 is supported. Other architectures coming soon.

If you'd like to implement a new architecture, you can duplicate `tracer/sys_amd64.go` and convert it to contain the syscall definitions for your arch.

### Usage Examples

#### Trace a program

```bash
grace -- cat /dev/null # replace 'cat /dev/null' with your program
```

#### Trace an existing process

```bash
grace -p 123 # replace 123 with your pid

# e.g. you could use pgrep to find the pid of a process
grace -p `pgrep ping`
```

#### Trace a program and filter by syscall name

```bash
grace -f "name=openat" -- cat /dev/null

# you can also look for multiple syscalls
grace -f "name=openat&name=close" -- cat /dev/null
```

#### Trace a program and filter by syscall name and path

```bash
grace -f "name=openat&path=/dev/null" -- cat /dev/null
```

#### Trace a program and wire up stdin/out/err with the terminal

```bash
grace -F -- cat
```

#### Trace a program with maximum readability options

```bash
grace -vnmx -- cat /dev/null
```

#### Trace only failing syscalls

```bash
grace -Z -- cat /dev/null
```

#### Show a summary of syscalls with durations, counts and errors

```bash
grace -S -- cat /dev/null
```

## Build Dependencies

If you want to build _grace_ yourself instead of using the precompiled binaries, you'll need a recent version of Go (1.19+), `musl-gcc` installed (you can install `musl-tools` on Ubuntu or `musl` on Arch), and kernel headers (install `linux-headers-$(uname -r)` on Ubuntu or `linux-headers` and `kernel-headers-musl` on Arch). _grace_ mainly just pulls constants from the kernel headers, so it's not a huge dependency. You should then have some success running `make build`. Note that many architectures are not yet supported (see below.)
Binary file added compare.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions filter/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package filter

import (
"fmt"
"strconv"
"strings"

"github.com/liamg/grace/tracer"
)

type Filter struct {
allowNames []string
allowPaths []string
allowReturns []uint64
failingOnly bool
passingOnly bool
}

func Parse(input string) (*Filter, error) {
filter := NewFilter()
parts := strings.Split(input, "&")
for _, part := range parts {
if part == "" {
continue
}
bits := strings.Split(part, "=")
key := bits[0]
value := bits[len(bits)-1]
switch key {
case "syscall", "name", "trace":
filter.allowNames = append(filter.allowNames, strings.Split(value, ",")...)
case "path":
filter.allowPaths = append(filter.allowPaths, strings.Split(value, ",")...)
case "ret", "retval", "return":
ret, err := parseUint64(value)
if err != nil {
return nil, fmt.Errorf("failed to parse return value filter: %w", err)
}
filter.allowReturns = append(filter.allowReturns, ret)
default:
return nil, fmt.Errorf("invalid filter key: %s", key)
}
}
return filter, nil
}

func parseUint64(input string) (uint64, error) {
if strings.HasPrefix(input, "0x") {
return strconv.ParseUint(input[2:], 16, 64)
}
return strconv.ParseUint(input, 10, 64)
}

func NewFilter() *Filter {
return &Filter{}
}

func (f *Filter) Match(call *tracer.Syscall, exit bool) bool {

if len(f.allowNames) > 0 {
var match bool
for _, name := range f.allowNames {
if name == call.Name() {
match = true
break
}
}
if !match {
return false
}
}

if len(f.allowPaths) > 0 {
var match bool
for _, path := range f.allowPaths {
for _, realPath := range call.Paths() {
if realPath == path {
match = true
break
}
}
}
if !match {
return false
}
}

if len(f.allowReturns) > 0 {
if !exit {
return false
}
var match bool
for _, ret := range f.allowReturns {
if uintptr(ret) == call.Return().Raw() {
match = true
break
}
}
if !match {
return false
}
}

if (f.passingOnly || f.failingOnly) && !exit {
return false
}

if f.failingOnly && call.Return().Int() >= 0 {
return false
}

if f.passingOnly && call.Return().Int() < 0 {
return false
}

// TODO check more filters

return true
}

func (f *Filter) SetFailingOnly(failing bool) {
f.failingOnly = failing
}

func (f *Filter) SetPassingOnly(passing bool) {
f.passingOnly = passing
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/liamg/grace
go 1.19

require (
github.com/aquasecurity/table v1.8.0
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
golang.org/x/sys v0.1.0
Expand All @@ -11,7 +12,10 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0=
github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
Expand All @@ -20,6 +26,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
Loading

0 comments on commit 28deabf

Please sign in to comment.