-
-
Notifications
You must be signed in to change notification settings - Fork 98
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
base: unstable
Are you sure you want to change the base?
Add BPF 'cpu' #299
Changes from all commits
e152aad
8ee3275
3a0f3f4
dec3d9b
f49a794
3b1e895
928eb81
594e55b
d0ade8d
8dbeed0
84c4631
cb6e94d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
}, | ||
|
||
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) {}, | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} |
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this already import as usercorn? Are you just being explicit? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"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) } |
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{} { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
There was a problem hiding this comment.
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