Skip to content

Instantly share code, notes, and snippets.

@agileone
Forked from supershabam/goebpfhacks.go
Created July 10, 2017 21:06
Show Gist options
  • Save agileone/0a3b1a077d1c08dd851515e2d6b9cb96 to your computer and use it in GitHub Desktop.
Save agileone/0a3b1a077d1c08dd851515e2d6b9cb96 to your computer and use it in GitHub Desktop.
ugly hot-off-the-experimentation-press golang to load a noop ebpf program (it just returns zero on a kprobe and isn't even attached to anything)
package main
import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"syscall"
"unsafe"
"time"
"github.com/davecgh/go-spew/spew"
)
// http://man7.org/linux/man-pages/man2/bpf.2.html
// http://elixir.free-electrons.com/linux/latest/source/include/linux/syscalls.h#L889
// https://ferrisellis.com/posts/ebpf_syscall_and_maps/#the-linux-bpf-syscall
// http://elixir.free-electrons.com/linux/latest/source/include/uapi/linux/bpf.h#L73
// https://github.com/iovisor/bpf-docs/blob/master/eBPF.md
// http://www.virtsync.com/c-error-codes-include-errno
// https://golang.org/src/syscall/syscall.go
// https://golang.org/src/syscall/zsyscall_linux_amd64.go?s=12082:12134#L491
// Single-word zero for use when we need a valid pointer to 0 bytes.
// See mksyscall.pl.
var _zero uintptr
const (
syscallBPF = 321 // /usr/src/linux-headers-4.4.0-81-generic/arch/x86/include/generated/uapi/asm/unistd_64.h
bpfProgLoad = 5
bpfMapCreate = 0
bpfProgTypeKProbe = 2
)
// struct { /* Used by BPF_MAP_CREATE */
// __u32 map_type;
// __u32 key_size; /* size of key in bytes */
// __u32 value_size; /* size of value in bytes */
// __u32 max_entries; /* maximum number of entries
// in a map */
// };
type bpfMapCreateConfig struct {
mapType uint32
keySize uint32
valueSize uint32
maxEntries uint32
}
func (mc *bpfMapCreateConfig) Bytes() []byte {
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, mc.mapType)
binary.Write(buf, binary.LittleEndian, mc.keySize)
binary.Write(buf, binary.LittleEndian, mc.valueSize)
binary.Write(buf, binary.LittleEndian, mc.maxEntries)
return buf.Bytes()
}
// struct { /* anonymous struct used by BPF_PROG_LOAD command */
// __u32 prog_type; /* one of enum bpf_prog_type */
// __u32 insn_cnt;
// __aligned_u64 insns;
// __aligned_u64 license;
// __u32 log_level; /* verbosity level of verifier */
// __u32 log_size; /* size of user buffer */
// __aligned_u64 log_buf; /* user supplied buffer */
// __u32 kern_version; /* checked when prog_type=kprobe */
// __u32 prog_flags;
// }
type BPFProgType uint32
const (
SockerFilter BPFProgType = 1
KProbe BPFProgType = 2
)
type BPFInst uint64
type bpfProgLoadConfig struct {
ProgType BPFProgType
Instns []BPFInst
License string
LogLevel uint32
LogBufSize uint32
Log []byte
}
// Ptr serializes the config into a usable ptr to pass to syscall. The returned slice of
// byte slices must be referenced by the caller to avoid garbage collection on the underlaying
// pointer.
func (plc *bpfProgLoadConfig) Ptr() (unsafe.Pointer, int, [][]byte) {
gc := make([][]byte, 0)
insns := new(bytes.Buffer)
for _, insn := range plc.Instns {
binary.Write(insns, binary.LittleEndian, insn)
}
insnsBytes := insns.Bytes()
gc = append(gc, insnsBytes)
i, _ := strconv.ParseInt(fmt.Sprintf("%p", insnsBytes), 0, 64)
insnsBytesAddr := uint64(i)
license := new(bytes.Buffer)
binary.Write(license, binary.LittleEndian, bytes.NewBufferString(plc.License).Bytes())
binary.Write(license, binary.LittleEndian, uint8(0))
licenseBytes := license.Bytes()
gc = append(gc, licenseBytes)
i, _ = strconv.ParseInt(fmt.Sprintf("%p", licenseBytes), 0, 64)
licenseBytesAddr := uint64(i)
logBuf := make([]byte, plc.LogBufSize)
gc = append(gc, logBuf)
i, _ = strconv.ParseInt(fmt.Sprintf("%p", logBuf), 0, 64)
logBufAddr := uint64(i)
plc.Log = logBuf
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, uint32(plc.ProgType))
binary.Write(buf, binary.LittleEndian, uint32(len(plc.Instns)))
binary.Write(buf, binary.LittleEndian, insnsBytesAddr)
binary.Write(buf, binary.LittleEndian, licenseBytesAddr)
binary.Write(buf, binary.LittleEndian, plc.LogLevel)
binary.Write(buf, binary.LittleEndian, plc.LogBufSize)
binary.Write(buf, binary.LittleEndian, logBufAddr)
// LINUX_VERSION_CODE
// # error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig'
binary.Write(buf, binary.LittleEndian, uint32(0x040400))
binary.Write(buf, binary.LittleEndian, uint32(0))
bufBytes := buf.Bytes()
spew.Dump(bufBytes)
return unsafe.Pointer(&bufBytes[0]), len(bufBytes), gc
}
func bpfMap(config *bpfMapCreateConfig) {
buf := config.Bytes()
spew.Dump(buf)
bufPtr := unsafe.Pointer(&buf[0])
r0, _, e1 := syscall.Syscall(syscallBPF, uintptr(int(bpfMapCreate)), uintptr(bufPtr), uintptr(len(buf)))
fmt.Printf("r0: %d\ne1: %d\n", r0, e1)
}
func use(i interface{}) {
return
}
func bpfProg(config *bpfProgLoadConfig) {
ptr, n, gc := config.Ptr()
use(gc)
r0, _, e1 := syscall.Syscall(syscallBPF, uintptr(int(bpfProgLoad)), uintptr(ptr), uintptr(n))
fmt.Printf("r0: %d\ne1: %d\n", r0, e1)
}
func main() {
// bpfMap(&bpfMapCreateConfig{
// mapType: 1,
// keySize: 8,
// valueSize: 8,
// maxEntries: 32,
// })
config := &bpfProgLoadConfig{
ProgType: SockerFilter,
Instns: []BPFInst{
0xb7, // BPF_MOV64_IMM(BPF_REG_0, 0)
0x95, // return r0
},
License: "GPL",
LogLevel: 1,
LogBufSize: 255,
}
bpfProg(config)
fmt.Printf("log: %s\n", config.Log)
time.Sleep(time.Minute * 60)
// bpfProg(&bpfAttr{
// progType: bpfProgTypeKProbe,
// insns: []uint64{0x95},
// license: "GPL",
// logLevel: 1,
// log: make([]uint64, 256),
// kernVersion: 0x0a0b0c0d,
// flags: 0,
// })
// time.Sleep(time.Minute * 5)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment