Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BPF 'cpu' #299

Open
wants to merge 12 commits into
base: unstable
Choose a base branch
from
2 changes: 2 additions & 0 deletions go/arch/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/lunixbochs/usercorn/go/arch/arm"
"github.com/lunixbochs/usercorn/go/arch/arm64"
"github.com/lunixbochs/usercorn/go/arch/bpf"
"github.com/lunixbochs/usercorn/go/arch/m68k"
"github.com/lunixbochs/usercorn/go/arch/mips"
"github.com/lunixbochs/usercorn/go/arch/sparc"
Expand All @@ -17,6 +18,7 @@ import (
var archMap = map[string]*models.Arch{
"arm": arm.Arch,
"arm64": arm64.Arch,
"bpf": bpf.Arch,
"m68k": m68k.Arch,
"mips": mips.Arch,
"sparc": sparc.Arch,
Expand Down
55 changes: 55 additions & 0 deletions go/arch/bpf/arch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package bpf

import (
"github.com/lunixbochs/usercorn/go/cpu/bpf"
"github.com/lunixbochs/usercorn/go/models"
)

var Arch = &models.Arch{
Name: "bpf",
Bits: 32,

Cpu: &bpf.Builder{},
Dis: &bpf.Dis{},
Asm: nil,

PC: bpf.PC,
SP: -1, // TODO: There is no stack pointer. How to handle?
Regs: map[string]int{
"M0": bpf.M0,
"M1": bpf.M1,
"M2": bpf.M2,
"M3": bpf.M3,
"M4": bpf.M4,
"M5": bpf.M5,
"M6": bpf.M6,
"M7": bpf.M7,
"M8": bpf.M8,
"M9": bpf.M9,
"M10": bpf.M10,
"M11": bpf.M11,
"M12": bpf.M12,
"M13": bpf.M13,
"M14": bpf.M14,
"M15": bpf.M15,
"A": bpf.A,
"X": bpf.X,
"PC": bpf.PC,
},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could totally generate this table with reflection on a Regs struct


DefaultRegs: []string{
"M0", "M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9",
"M10", "M11", "M12", "M13", "M14", "M15", "A", "X"},
}

func init() {
Arch.RegisterOS(&models.OS{
Name: "noos",
Kernels: func(_ models.Usercorn) []interface{} { return nil },
Init: func(u models.Usercorn, _, _ []string) error {
u.SetEntry(0x80000000)
return nil
},
Interrupt: func(_ models.Usercorn, _ uint32) {},
})
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, any feedback on the RegisterOS api for weird things that don't really have OS flavors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked into it much. I think for ebpf extensions we may want an OS, but before I considered this, I thought about creating a 'noos' or 'nullos' that basically panics in functions.

}
61 changes: 61 additions & 0 deletions go/cmd/bpf/bpf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package bpf

import (
"os"

"github.com/pkg/errors"

usercorn "github.com/lunixbochs/usercorn/go"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this already import as usercorn? Are you just being explicit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vim-go decided to format it this way when I ran :GoImports. I can fix it.

"github.com/lunixbochs/usercorn/go/cmd"
"github.com/lunixbochs/usercorn/go/loader"
"github.com/lunixbochs/usercorn/go/models"
)

func Main(args []string) {
c := cmd.NewUsercornRawCmd()
c.NoArgs = true
var packet *string

c.SetupFlags = func() error {
packet = c.Flags.String("packet", "", "packet file to run filter against")
return nil
}

c.MakeUsercorn = func(filter string) (models.Usercorn, error) {
l, err := loader.NewBpfLoader(filter, *packet)
if err != nil {
return nil, errors.Wrap(err, "failed to load BPF filter")
}
u, err := usercorn.NewUsercornRaw(l, c.Config)
if err != nil {
return nil, errors.Wrap(err, "failed to create usercorn")
}

// Map in both segments separately
segments, err := l.Segments()
if err != nil {
return nil, errors.Wrap(err, "failed to get segments from loader")
}
for _, seg := range segments {
err := u.MemMap(seg.Addr, seg.Size, seg.Prot)
if err != nil {
return nil, errors.Wrap(err, "failed to map in address space")
}

data, err := seg.Data()
if err != nil {
return nil, errors.Wrap(err, "failed to read segment data")
}

err = u.MemWrite(seg.Addr, data)
if err != nil {
return nil, errors.Wrap(err, "failed to write segment data")
}
}

return u, nil
}
os.Exit(c.Run(args, os.Environ()))
}

func init() { cmd.Register("bpf", "execute a BPF filter binary", Main) }
1 change: 1 addition & 0 deletions go/cmd/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

_ "github.com/lunixbochs/usercorn/go/cmd/run"

_ "github.com/lunixbochs/usercorn/go/cmd/bpf"
_ "github.com/lunixbochs/usercorn/go/cmd/cfg"
_ "github.com/lunixbochs/usercorn/go/cmd/cgc"
_ "github.com/lunixbochs/usercorn/go/cmd/com"
Expand Down
241 changes: 241 additions & 0 deletions go/cpu/bpf/bpf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package bpf

import (
"encoding/binary"

"github.com/lunixbochs/usercorn/go/models"
"github.com/lunixbochs/usercorn/go/models/cpu"
)

const (
M0 = iota
M1
M2
M3
M4
M5
M6
M7
M8
M9
M10
M11
M12
M13
M14
M15
A
X
PC
)

type Builder struct{}

func (b *Builder) New() (cpu.Cpu, error) {
c := &BpfCpu{
Regs: cpu.NewRegs(32, []int{
M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10,
M11, M12, M13, M14, M15, A, X, PC}),
Mem: cpu.NewMem(32, binary.LittleEndian),
}
c.Hooks = cpu.NewHooks(c, c.Mem)
return c, nil
}

type BpfCpu struct {
*cpu.Hooks
*cpu.Regs
*cpu.Mem

returnValue uint32
exitRequest bool
}

func NewCpu() (cpu.Cpu, error) {
c := &BpfCpu{
Regs: cpu.NewRegs(32, []int{
A, X, PC,
}),
Mem: cpu.NewMem(32, binary.LittleEndian),
}
c.Hooks = cpu.NewHooks(c, c.Mem)
return c, nil
}

func (c *BpfCpu) get(a arg) uint32 {
var val uint64
switch arg := a.(type) {
case *regX:
val, _ = c.RegRead(X)
case *abs:
val, _ = c.ReadUint(uint64(arg.val), arg.size, 0)
case *mem:
val = uint64(arg.val)
case *ind:
off, _ := c.RegRead(X)
val, _ = c.ReadUint(uint64(arg.val)+off, arg.size, 0)
case *imm:
val = uint64(arg.val)
case *msh:
val, _ = c.ReadUint(uint64(arg.val), 1, 0)
val = uint64((val & 0xf) * 4)
case *jabs:
val = uint64(arg.val)
case *jelse:
val = uint64(arg.val)
case *j:
val = uint64(arg.val)
case *regA:
val, _ = c.RegRead(A)
default:
panic("Unhandled arg")
}
return uint32(val)
}

// getJump gets the jump target offset in bytes
func (c *BpfCpu) getJump(a arg) uint64 {
switch a := a.(type) {
case *jabs:
return uint64(a.val) * 8
case *j:
return uint64(a.jf) * 8
case *jelse:
return uint64(a.jf) * 8
default:
panic("Jump with illegal args!")
}
}

// getJumpElse gets the jump else case offset in bytes
func (c *BpfCpu) getJumpElse(a arg) uint64 {
switch a := a.(type) {
case *j:
return 0
case *jelse:
return uint64(a.jt) * 8
default:
panic("Jump with illegal args!")
}
}

func (c *BpfCpu) Start(begin, until uint64) error {
var dis Dis
pc := begin
c.RegWrite(PC, (pc))
c.OnBlock(pc, 0)
var err error

c.exitRequest = false
for pc <= until && err == nil && !c.exitRequest {
var mem []byte
var code []models.Ins

if mem, err = c.ReadProt(pc, 8, cpu.PROT_EXEC); err != nil {
break
}
if code, err = dis.Dis(mem, pc); err != nil {
break
}
ins := code[0].(*ins)

c.OnCode(pc, uint32(len(ins.bytes)))
if c.exitRequest {
break
}

jumpoff := uint64(0)
al, _ := c.RegRead(A)
a := uint32(al)
xl, _ := c.RegRead(X)
x := uint32(xl)

switch ins.optype {
case CLASS_RET:
c.exitRequest = true
c.returnValue = c.get(ins.arg)
c.OnBlock(pc, 0)
err = models.ExitStatus(int(c.returnValue))
case CLASS_LD:
c.RegWrite(A, uint64(c.get(ins.arg)))
case CLASS_LDX:
c.RegWrite(X, uint64(c.get(ins.arg)))
case CLASS_ST:
c.RegWrite(int(c.get(ins.arg)), uint64(a))
case CLASS_STX:
c.RegWrite(int(c.get(ins.arg)), uint64(x))
case OP_JMP_JA:
jumpoff = c.getJump(ins.arg)
case OP_JMP_JEQ:
if c.get(ins.arg) == a {
jumpoff = c.getJump(ins.arg)
} else {
jumpoff = c.getJumpElse(ins.arg)
}
case OP_JMP_JGT:
if c.get(ins.arg) > a {
jumpoff = c.getJump(ins.arg)
} else {
jumpoff = c.getJumpElse(ins.arg)
}
case OP_JMP_JGE:
if c.get(ins.arg) >= a {
jumpoff = c.getJump(ins.arg)
} else {
jumpoff = c.getJumpElse(ins.arg)
}
case OP_JMP_JSET:
if (c.get(ins.arg) & a) != 0 {
jumpoff = c.getJump(ins.arg)
} else {
jumpoff = c.getJumpElse(ins.arg)
}
case OP_ALU_ADD:
c.RegWrite(A, uint64(a+c.get(ins.arg)))
case OP_ALU_SUB:
c.RegWrite(A, uint64(a-c.get(ins.arg)))
case OP_ALU_MUL:
c.RegWrite(A, uint64(a*c.get(ins.arg)))
case OP_ALU_DIV:
c.RegWrite(A, uint64(a/c.get(ins.arg)))
case OP_ALU_MOD:
c.RegWrite(A, uint64(a%c.get(ins.arg)))
case OP_ALU_NEG:
c.RegWrite(A, uint64(-a))
case OP_ALU_AND:
c.RegWrite(A, uint64(a&c.get(ins.arg)))
case OP_ALU_OR:
c.RegWrite(A, uint64(a|c.get(ins.arg)))
case OP_ALU_XOR:
c.RegWrite(A, uint64(a^c.get(ins.arg)))
case OP_ALU_LSH:
c.RegWrite(A, uint64(a<<c.get(ins.arg)))
case OP_ALU_RSH:
c.RegWrite(A, uint64(a>>c.get(ins.arg)))
case OP_TAX:
c.RegWrite(X, uint64(a))
case OP_TXA:
c.RegWrite(A, uint64(x))
}

pc += 8 + jumpoff
if jumpoff > 0 {
c.OnBlock(pc, 0)
}
c.RegWrite(PC, (pc))
}
return err
}

func (c *BpfCpu) Stop() error {
c.exitRequest = true
return nil
}

func (c *BpfCpu) Close() error {
return nil
}

func (c *BpfCpu) Backend() interface{} {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one isn't super important, as there's no backend handle for homemade cpus (it's used elsewhere to pull back the curtain and access unicorn internals)

return nil
}
Loading