Browse Source

More representable bpfasm command line tool

tags/v1.0.0
maze 11 months ago
parent
commit
5244f07ddf
3 changed files with 224 additions and 39 deletions
  1. +219
    -39
      cmd/bpfasm/main.go
  2. +1
    -0
      go.mod
  3. +4
    -0
      go.sum

+ 219
- 39
cmd/bpfasm/main.go View File

@@ -19,73 +19,253 @@ package main

import (
"encoding/binary"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"text/tabwriter"

cli "gopkg.in/urfave/cli.v2"
"maze.io/x/bpf"
)

func main() {
outASM := flag.Bool("asm", false, "output assembly")
outCode := flag.Bool("opc", false, "output opcodes")
outFile := flag.String("o", "", "output file (default: stdout)")
flag.Parse()
app := cli.App{
Name: "bpfasm",
Usage: "Berkeley Packet Filter assembler/disassembler",
Commands: []*cli.Command{
&cli.Command{
Name: "assemble",
Aliases: []string{"a"},
Usage: "Assemble a BPF ASM file",
ArgsUsage: "<input>",
Action: assemble,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "endian",
Aliases: []string{"e"},
Value: "be",
Usage: "Binary byte order (be, le)",
},
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Value: "bin",
Usage: "Output format (bin, asm, c)",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file",
DefaultText: "use stdout",
},
},
},
&cli.Command{
Name: "disassemble",
Aliases: []string{"d"},
Usage: "Disassemble a BPF binary file",
ArgsUsage: "<input>",
Action: disassemble,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "endian",
Aliases: []string{"e"},
Value: "be",
Usage: "Binary byte order (be, le)",
},
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Value: "asm",
Usage: "Output format (asm, c)",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file",
DefaultText: "use stdout",
},
},
},
},
}
app.Run(os.Args)
}

if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: bpfasm <source>")
os.Exit(1)
type assembler func(io.Writer, bpf.Program) error

func formatBinary(byteOrder binary.ByteOrder) assembler {
return func(w io.Writer, program bpf.Program) error {
raw, err := program.Assemble()
if err != nil {
return err
}
for _, ins := range raw {
if err := binary.Write(w, byteOrder, ins); err != nil {
return err
}
}
return nil
}
}

b, err := ioutil.ReadFile(flag.Arg(0))
if err != nil {
fmt.Fprintln(os.Stderr, "error loading", flag.Arg(0)+":", err)
os.Exit(2)
func formatAssembly(w io.Writer, program bpf.Program) error {
t := tabwriter.NewWriter(w, 0, 0, 4, ' ', tabwriter.TabIndent)
for _, ins := range program {
if s, ok := ins.(fmt.Stringer); ok {
fmt.Fprintln(t, strings.Replace(s.String(), " ", "\t", -1))
}
}
return t.Flush()
}

program, err := bpf.Assemble(string(b))
func formatC(w io.Writer, program bpf.Program) error {
raw, err := program.Assemble()
if err != nil {
fmt.Fprintln(os.Stderr, "error assembling", flag.Arg(0)+":", err)
os.Exit(2)
return err
}
raw, err := program.Assemble()
l := len(raw) - 1
for i, ins := range raw {
var comma string
if i < l {
comma = ","
}
if _, err := fmt.Fprintf(w, "{ %#04x, %d, %d, %#08x }%s\n",
ins.Op, ins.Jt, ins.Jf, ins.K, comma); err != nil {
return err
}
}
return nil
}

func assemble(ctx *cli.Context) error {
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "assemble")
}

var asm assembler
switch ctx.String("format") {
case "bin", "binary":
switch ctx.String("endian") {
case "be":
asm = formatBinary(binary.BigEndian)
case "le":
asm = formatBinary(binary.LittleEndian)
default:
fmt.Fprintf(os.Stderr, "invalid byte order %q\n", ctx.String("endian"))
os.Exit(1)
}

case "asm":
asm = formatAssembly
case "c", "C":
asm = formatC
default:
fmt.Fprintf(os.Stderr, "invalid output format %q\n", ctx.String("format"))
os.Exit(1)
}

sourcePath := ctx.Args().First()
sourceBytes, err := ioutil.ReadFile(sourcePath)
if err != nil {
fmt.Fprintln(os.Stderr, "error assembling instructions", flag.Arg(0)+":", err)
os.Exit(2)
fmt.Fprintf(os.Stderr, "error reading %s: %v\n", sourcePath, err)
os.Exit(1)
}

if !*outASM && !*outCode && *outFile == "" {
*outFile = flag.Arg(0) + ".bin"
fmt.Fprintln(os.Stderr, "writing to", *outFile)
program, err := bpf.Assemble(string(sourceBytes))
if err != nil {
fmt.Fprintf(os.Stderr, "error assembling %s: %v\n", sourcePath, err)
os.Exit(1)
}

var output io.WriteCloser
if *outFile != "" {
if output, err = os.Create(*outFile); err != nil {
fmt.Fprintln(os.Stderr, "error opening", *outFile+":", err)
os.Exit(2)
switch outputFile := ctx.String("output"); outputFile {
case "", "-":
if f := ctx.String("format"); f == "bin" || f == "binary" {
fmt.Fprintf(os.Stderr, "writing to %s.bin\n", sourcePath)
if output, err = os.Create(sourcePath + ".bin"); err != nil {
fmt.Fprintf(os.Stderr, "error opening output file: %v\n", err)
os.Exit(1)
}
} else {
output = os.Stdout
}

default:
fmt.Fprintf(os.Stderr, "writing to %s\n", outputFile)
if output, err = os.Create(outputFile); err != nil {
fmt.Fprintf(os.Stderr, "error opening output file: %v\n", err)
os.Exit(1)
}
} else {
output = os.Stdout
}
defer output.Close()

if *outCode {
for _, instruction := range raw {
fmt.Fprintf(output, "{ %#02x, %2d, %2d, %#08x },\n", instruction.Op, instruction.Jt, instruction.Jf, instruction.K)
}
} else if *outASM {
for _, instruction := range program {
fmt.Fprintln(output, instruction)
}
} else {
for _, instruction := range raw {
binary.Write(output, binary.BigEndian, instruction)
return asm(output, program)
}

func disassemble(ctx *cli.Context) error {
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "disassemble")
}

var (
sourcePath = ctx.Args().First()
program bpf.Program
byteOrder binary.ByteOrder
asm assembler
)
switch ctx.String("endian") {
case "be":
byteOrder = binary.BigEndian
case "le":
byteOrder = binary.LittleEndian
default:
fmt.Fprintf(os.Stderr, "invalid byte order %q\n", ctx.String("endian"))
os.Exit(1)
}
switch ctx.String("format") {
case "asm":
asm = formatAssembly
case "c", "C":
asm = formatC
default:
fmt.Fprintf(os.Stderr, "invalid output format %q\n", ctx.String("format"))
os.Exit(1)
}

sourceFile, err := os.Open(sourcePath)
if err != nil {
fmt.Fprintf(os.Stderr, "error opening %s: %v\n", sourcePath, err)
os.Exit(1)
}
defer sourceFile.Close()

for {
var instruction bpf.RawInstruction
if err = binary.Read(sourceFile, byteOrder, &instruction); err != nil {
if err == io.EOF {
break
}
return err
}
program = append(program, instruction.Disassemble())
}

//assembled := unsafe.Pointer(&program[0])
var output io.WriteCloser
switch outputFile := ctx.String("output"); outputFile {
case "", "-":
output = os.Stdout

default:
fmt.Fprintf(os.Stderr, "writing to %s\n", outputFile)
if output, err = os.Create(outputFile); err != nil {
fmt.Fprintf(os.Stderr, "error opening output file: %v\n", err)
os.Exit(1)
}
}
defer output.Close()

return asm(output, program)
}

+ 1
- 0
go.mod View File

@@ -3,4 +3,5 @@ module maze.io/x/bpf
require (
github.com/antlr/antlr4 v0.0.0-20190223165740-dade65a895c2
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7
gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 // indirect
)

+ 4
- 0
go.sum View File

@@ -2,3 +2,7 @@ github.com/antlr/antlr4 v0.0.0-20190223165740-dade65a895c2 h1:Q1TGw0wvj6lqZQ4/CM
github.com/antlr/antlr4 v0.0.0-20190223165740-dade65a895c2/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7 h1:C2F/nMkR/9sfUTpvR3QrjBuTdvMUC/cFajkphs1YLQo=
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 h1:Ggy3mWN4l3PUFPfSG0YB3n5fVYggzysUmiUQ89SnX6Y=
gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8/go.mod h1:cKXr3E0k4aosgycml1b5z33BVV6hai1Kh7uDgFOkbcs=

Loading…
Cancel
Save