Linux kernel 如何解决这个问题;R0内存访问无效';inv&x27&引用;加载eBPF文件对象时出错

Linux kernel 如何解决这个问题;R0内存访问无效';inv&x27&引用;加载eBPF文件对象时出错,linux-kernel,clang,jit,bpf,Linux Kernel,Clang,Jit,Bpf,我正试图用libbpf在内核中加载一个eBPF对象,但没有成功,而是得到了标题中指定的错误。但是让我展示一下我的BPF*\u kern.c是多么简单 SEC("entry_point_prog") int entry_point(struct xdp_md *ctx) { int act = XDP_DROP; int rc, i = 0; struct global_vars *globals; struct ip_addr addr = {}; str

我正试图用
libbpf
在内核中加载一个eBPF对象,但没有成功,而是得到了标题中指定的错误。但是让我展示一下我的BPF
*\u kern.c
是多么简单

SEC("entry_point_prog")
int entry_point(struct xdp_md *ctx)
{
    int act = XDP_DROP;
    int rc, i = 0;
    struct global_vars *globals;
    struct ip_addr addr = {};
    struct some_key key = {};
    void *temp;

    globals = bpf_map_lookup_elem(&globals_map, &i);
    if (!globals)
        return XDP_ABORTED;

    rc = some_inlined_func(ctx, &key);

    addr = key.dst_ip;
    temp = bpf_map_lookup_elem(&some_map, &addr);

    switch(rc)
    {
    case 0:
        if(temp)
        {
            // no rocket science here ...
        } else
            act = XDP_PASS;
        break;
    default:
        break;
    }

    return act;  // this gives the error
    //return XDP_<whatever>;  // this works fine
}

我真的看不出有什么问题。我的意思是,这是如此简单,但它打破了。为什么这不起作用?我错过了什么?要么验证程序发疯了,要么我在做一些非常愚蠢的事情。

好吧,在3天之后,更准确地说,3 x 8小时=24小时,值得代码搜索,我想我终于找到了发痒的问题

问题一直存在于
一些_inlined_func()
中,它比挑战性更棘手。我在这里写下了一个代码模板来解释这个问题,这样其他人就可以看到,并且希望花在头痛上的时间少于24小时;我为此经历了地狱,所以要集中注意力

__alwais_inline static
int some_inlined_func(struct xdp_md *ctx, /* other non important args */)
{
    if (!ctx)
        return AN_ERROR_CODE;

    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth;
    struct iphdr *ipv4_hdr = NULL;
    struct ipv6hdr *ipv6_hdr = NULL;
    struct udphdr *udph;
    uint16_t ethertype;

    eth = (struct ethhdr *)data;
    if (eth + 1 > data_end)
        return AN_ERROR_CODE;

    ethertype = __constant_ntohs(eth->h_proto);
    if (ethertype == ETH_P_IP)
    {
        ipv4_hdr = (void *)eth + ETH_HLEN;
        if (ipv4_hdr + 1 > data_end)
            return AN_ERROR_CODE;

        // stuff non related to the issue ...
    } else if (ethertype == ETH_P_IPV6)
    {
        ipv6_hdr = (void *)eth + ETH_HLEN;
        if (ipv6_hdr + 1 > data_end)
            return AN_ERROR_CODE;

        // stuff non related to the issue ...
    } else
        return A_RET_CODE_1;

    /* here's the problem, but ... */
    udph = (ipv4_hdr) ? ((void *)ipv4_hdr + sizeof(*ipv4_hdr)) :
            ((void *)ipv6_hdr + sizeof(*ipv6_hdr));
    if (udph + 1 > data_end)
        return AN_ERROR_CODE;

    /* it actually breaks HERE, when dereferencing 'udph' */
    uint16_t dst_port = __constant_ntohs(udph->dest);

    // blablabla other stuff here unrelated to the problem ...

    return A_RET_CODE_2;
}
那么,为什么它会在这一点上断裂呢?我认为这是因为验证器假设
ipv6\u hdr
可能是
NULL
,这是完全错误的,因为如果执行达到该点,那只是因为设置了
ipv4\u hdr
ipv6\u hdr
(即,如果不是ipv4或ipv6,则执行在此点之前终止). 所以,很明显,验证者无法推断。但是,有一个问题,如果明确检查了ipv6_hdr的有效性,它会很高兴,如下所示:

if (ipv4_hdr)
    udph = (void *)ipv4_hdr + sizeof(*ipv4_hdr);
else if (ipv6_hdr)
    udph = (void *)ipv6_hdr + sizeof(*ipv6_hdr);
else return A_RET_CODE_1;  // this is redundant
如果我们这样做,它也会起作用:

// "(ethertype == ETH_P_IP)" instead of "(ipv4_hdr)"
udph = (ethertype == ETH_P_IP) ? ((void *)ipv4_hdr + sizeof(*ipv4_hdr)) :
        ((void *)ipv6_hdr + sizeof(*ipv6_hdr));
因此,在我看来,这里的验证器似乎有些奇怪,因为它不够聪明(也许也不需要),无法意识到如果它达到这一点,那只是因为
ctx
引用了IPv4或IPv6数据包

所有这些是如何解释抱怨
退货行为的入口点()内的code>
?简单,请容忍我。
some_inlined_func()
没有改变
ctx
,其剩余的参数也没有被
entry_point()
使用。因此,在返回
act
的情况下,由于它取决于
some\u inlined\u func()
结果,因此执行
some\u inlined\u func()
,此时验证器会抱怨。但是,在返回
XDP
的情况下,作为
开关盒
主体,以及
某些内联函数()
,都不会改变
入口点()
程序/函数的内部状态,编译器(使用O2)足够聪明,可以意识到为
某些内联函数()生成程序集没有意义
和整个
开关箱
(这里是O2优化)。因此,综上所述,在返回
XDP\ufunc
的情况下,验证器很高兴,因为问题实际上存在于
some\u inlined\u func()
中,但实际生成的BPF程序集没有这方面的内容,因此验证器没有检查
some\u inlined\u func()
,因为一开始就没有。有道理吗


这种BPF“限制”已知吗?有没有任何文件说明这些已知的限制?因为我没有发现任何东西。

好的,所以,经过3天,更准确地说,3 x 8小时=24小时,值得寻找代码,我想我终于找到了痒的问题

问题一直存在于
一些_inlined_func()
中,它比挑战性更棘手。我在这里写下了一个代码模板来解释这个问题,这样其他人就可以看到,并且希望花在头痛上的时间少于24小时;我为此经历了地狱,所以要集中注意力

__alwais_inline static
int some_inlined_func(struct xdp_md *ctx, /* other non important args */)
{
    if (!ctx)
        return AN_ERROR_CODE;

    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth;
    struct iphdr *ipv4_hdr = NULL;
    struct ipv6hdr *ipv6_hdr = NULL;
    struct udphdr *udph;
    uint16_t ethertype;

    eth = (struct ethhdr *)data;
    if (eth + 1 > data_end)
        return AN_ERROR_CODE;

    ethertype = __constant_ntohs(eth->h_proto);
    if (ethertype == ETH_P_IP)
    {
        ipv4_hdr = (void *)eth + ETH_HLEN;
        if (ipv4_hdr + 1 > data_end)
            return AN_ERROR_CODE;

        // stuff non related to the issue ...
    } else if (ethertype == ETH_P_IPV6)
    {
        ipv6_hdr = (void *)eth + ETH_HLEN;
        if (ipv6_hdr + 1 > data_end)
            return AN_ERROR_CODE;

        // stuff non related to the issue ...
    } else
        return A_RET_CODE_1;

    /* here's the problem, but ... */
    udph = (ipv4_hdr) ? ((void *)ipv4_hdr + sizeof(*ipv4_hdr)) :
            ((void *)ipv6_hdr + sizeof(*ipv6_hdr));
    if (udph + 1 > data_end)
        return AN_ERROR_CODE;

    /* it actually breaks HERE, when dereferencing 'udph' */
    uint16_t dst_port = __constant_ntohs(udph->dest);

    // blablabla other stuff here unrelated to the problem ...

    return A_RET_CODE_2;
}
那么,为什么它会在这一点上断裂呢?我认为这是因为验证器假设
ipv6\u hdr
可能是
NULL
,这是完全错误的,因为如果执行达到该点,那只是因为设置了
ipv4\u hdr
ipv6\u hdr
(即,如果不是ipv4或ipv6,则执行在此点之前终止). 所以,很明显,验证者无法推断。但是,有一个问题,如果明确检查了ipv6_hdr的有效性,它会很高兴,如下所示:

if (ipv4_hdr)
    udph = (void *)ipv4_hdr + sizeof(*ipv4_hdr);
else if (ipv6_hdr)
    udph = (void *)ipv6_hdr + sizeof(*ipv6_hdr);
else return A_RET_CODE_1;  // this is redundant
如果我们这样做,它也会起作用:

// "(ethertype == ETH_P_IP)" instead of "(ipv4_hdr)"
udph = (ethertype == ETH_P_IP) ? ((void *)ipv4_hdr + sizeof(*ipv4_hdr)) :
        ((void *)ipv6_hdr + sizeof(*ipv6_hdr));
因此,在我看来,这里的验证器似乎有些奇怪,因为它不够聪明(也许也不需要),无法意识到如果它达到这一点,那只是因为
ctx
引用了IPv4或IPv6数据包

所有这些是如何解释抱怨
退货行为的入口点()内的code>
?简单,请容忍我。
some_inlined_func()
没有改变
ctx
,其剩余的参数也没有被
entry_point()
使用。因此,在返回
act
的情况下,由于它取决于
some\u inlined\u func()
结果,因此执行
some\u inlined\u func()
,此时验证器会抱怨。但是,在返回
XDP
的情况下,作为
开关盒
主体,以及
某些内联函数()
,都不会改变
入口点()
程序/函数的内部状态,编译器(使用O2)足够聪明,可以意识到为
某些内联函数()生成程序集没有意义
和整个
开关箱
(这里是O2优化)。因此,综上所述,在返回
XDP\ufunc
的情况下,验证器很高兴,因为问题实际上存在于
some\u inlined\u func()
中,但实际生成的BPF程序集没有这方面的内容,因此验证器没有检查
some\u inlined\u func()
,因为一开始就没有。有道理吗


这种BPF“限制”已知吗?有没有任何文件说明这些已知的限制?因为我没有找到任何返回类型。

您可以尝试使用u64返回类型吗?我无法用最小的示例重现。看起来程序在某个时候