尝试加载 BPF 程序时参数无效 (EINVAL)

Invalid argument (EINVAL) when trying to load a BPF program

我正在尝试使用 bpf 系统调用加载 BPF 程序,但我在 return 上收到 invalid argument (EINVAL)。从man page来看,可能的原因是:

EINVAL

For BPF_PROG_LOAD, indicates an attempt to load an invalid program. 
eBPF programs can be deemed invalid due to unrecognized instructions, 
the use of reserved fields, jumps out of range, infinite loops or calls 
of unknown functions.

看来我的BPF程序有问题。我的BPF程序如下:

#include <uapi/linux/bpf.h>

int prog(struct pt_regs *ctx)
{
    return 0;
}

这肯定不会有任何问题。

我正在使用 Makefile here 进行编译(我从 test_overhead_kprobe_kern.c 中删除了大部分代码以提供一个非常简单的测试程序)。

我的程序有什么问题导致它被拒绝?

uname -a: Linux ubuntu1710 4.13.0-32-generic #35-Ubuntu SMP Thu Jan 25 09:13:46 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

我的完整用户空间代码(在 Go 中)如下:

package main

/*
#include <stdio.h>
#include <stdlib.h>

void print(char* s) {
 printf("%s\n", s);
}
*/
import "C"

import (
    "unsafe"

    "golang.org/x/sys/unix"

    "github.com/cilium/cilium/pkg/bpf"
)
import (
    "fmt"
    "io/ioutil"
)

const (
    bufferSize           = 256
    sessionIDHTTPHeader  = "X-Session-ID"
    defaultServerAddress = "localhost"
    defaultPort          = 5050
)

const (
    BPF_PROG_TYPE_UNSPEC        = 0
    BPF_PROG_TYPE_SOCKET_FILTER = 1
    BPF_PROG_TYPE_KPROBE        = 2
    BPF_PROG_TYPE_SCHED_CLS     = 3
    BPF_PROG_TYPE_SCHED_ACT     = 4
)

type ttyWrite struct {
    Count     int32
    Buf       [bufferSize]byte
    SessionID int32
}

func main() { 

  //for i := 0; i < 6; i++ {
    //b, err := ioutil.ReadFile(fmt.Sprintf("bpf/test%d.o", i))
    b, err := ioutil.ReadFile("bpf/bpf_tty.o")
    if err != nil {
        fmt.Print(err)
    }

    err = loadProgram(BPF_PROG_TYPE_KPROBE, unsafe.Pointer(&b), len(b))
    if err != nil {
        fmt.Printf("%s\n", err)
    }
    //}

}

func loadProgram(progType int, insns unsafe.Pointer, insnCnt int) error {

    licenseBuf := "GPL"
    licenseStr := C.CString(licenseBuf)
    defer C.free(unsafe.Pointer(licenseStr))

    logStr := C.CString("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
    defer C.free(unsafe.Pointer(logStr))

    lba := struct {
        progType uint32
        //pad0        [4]byte
        insnCnt uint32
        //pad1        [4]byte
        insns    uint64
        license  uint64
        logLevel uint32
        //pad2        [4]byte
        logSize uint32
        //pad3    [4]byte
        logBuf  uint64
        kernVersion uint32
        //pad4        [4]byte
    }{
        progType: uint32(progType),     
        insnCnt:  uint32(insnCnt),
        insns:    uint64(uintptr(insns)),
        license:  uint64(uintptr(unsafe.Pointer(licenseStr))),      
        logLevel: uint32(1),
        logSize:  uint32(50),
        logBuf:   uint64(uintptr(unsafe.Pointer(logStr))),
        //logBuf: uint64(uintptr(unsafe.Pointer(bufStr))),
        // /usr/src/linux-headers-4.13.0-32-generic/include/generated/uapi/linux/version.h
        kernVersion: uint32(265485),
    }

    ret, _, err := unix.Syscall(
        unix.SYS_BPF,
        bpf.BPF_PROG_LOAD,
        uintptr(unsafe.Pointer(&lba)),
        unsafe.Sizeof(lba),
    )

    //fmt.Printf("%s\n", logBuf)
    //cs := C.CString("XXXXXXXXXX")
    C.print(logStr)
    //fmt.Printf("%c\n", *logStr)

    if ret != 0 || err != 0 {
        //fmt.Printf("%#v %d\n", logBuf, unsafe.Sizeof(lba))
        return fmt.Errorf("Unable to load program: ret: %d: %s", int(ret), err)
    }

    return nil
}

作为,你的用户空间Go程序需要从目标文件中提取BPF指令(.text部分)。否则,内核会尝试将二进制内容解释为 BPF 指令,并且不可避免地会失败。