Extended Berkeley Packet Filter (eBPF) assembler and virtual machine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

257 lines
5.7 KiB

package main
import (
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"text/tabwriter"
"maze.io/ebpf"
cli "github.com/urfave/cli/v2"
)
func main() {
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: "le",
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)
}
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.Fprintf(os.Stderr, "error reading %s: %v\n", sourcePath, err)
os.Exit(1)
}
program, err := ebpf.Assemble(string(sourceBytes))
if err != nil {
fmt.Fprintf(os.Stderr, "error assembling %s: %v\n", sourcePath, err)
os.Exit(1)
}
/*
else if err = program.Verify(); err != nil {
fmt.Fprintf(os.Stderr, "error verifying program: %v\n", err)
os.Exit(1)
}
*/
var output io.WriteCloser
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)
}
}
defer output.Close()
return asm(output, program)
}
func disassemble(ctx *cli.Context) error {
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "disassemble")
}
var (
sourcePath = ctx.Args().First()
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()
program, err := ebpf.DisassembleReaderOrder(sourceFile, byteOrder)
if err != nil {
fmt.Fprintf(os.Stderr, "error disassembling: %v\n", err)
os.Exit(2)
}
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)
}
type assembler func(io.Writer, ebpf.Program) error
func formatBinary(byteOrder binary.ByteOrder) assembler {
return func(w io.Writer, program ebpf.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
}
}
func formatAssembly(w io.Writer, program ebpf.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()
}
func formatC(w io.Writer, program ebpf.Program) error {
raw, err := program.Assemble()
if err != nil {
return err
}
l := len(raw) - 1
for i, ri := range raw {
var comma string
if i < l {
comma = ","
}
if _, err := fmt.Fprintf(w, "{ %#02x, %s, %s, %#04x, %#08x }%s // %s\n",
ri.Op, ri.Dst, ri.Src, ri.Offset, ri.Immediate, comma, ri.Disassemble()); err != nil {
return err
}
}
return nil
}