eBPF: 存储堆栈变量在映射中时超过了 BPF 堆栈限制

huangapple go评论73阅读模式
英文:

eBPF: BPF stack limit exceeded when storing stack variable in map

问题

我有以下的eBPF程序:

#include <stdio.h>
#include <string.h>

#include <linux/bpf.h>
#include <sys/socket.h>

#include <bpf/bpf_helpers.h>

char LICENSE[] SEC("license") = "GPL";

// msg_data_map携带(msg_id, msg_length)的键值对,最多可以同时记录65535条消息。
#define MAX_MSG_LEN 128
struct {
  __uint(type, BPF_MAP_TYPE_HASH);
  __uint(max_entries, 65535);
  __type(key, int);
  __type(value, char[MAX_MSG_LEN]);
} msg_data_map SEC(".maps");

SEC("sk_msg")
int msg_prog(struct sk_msg_md *msg) {
  long len = (long)msg->data_end - (long)msg->data;

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

  // 为了满足验证器的要求,进行边界检查
  if (data + MAX_MSG_LEN > data_end) {
    return SK_PASS;
  }

  char buf[MAX_MSG_LEN] = {0};
  if (len > MAX_MSG_LEN) {
    __builtin_memcpy(buf, data, MAX_MSG_LEN);
  } else {
    __builtin_memcpy(buf, data, len);
  }

  // 在映射中进行更新
  int index = 0;
  bpf_map_update_elem(&msg_data_map, &index, &buf, BPF_ANY);
 
  return SK_PASS;
}

编译上述程序会出现以下错误:

看起来BPF堆栈限制超过了512字节。请将堆栈上的大型变量移动到BPF per-cpu数组映射中。
  1. 由于buf数组只有128字节,不应该超过堆栈限制。
  2. 如果我注释掉映射更新的行,程序可以成功编译。为什么会这样呢?
英文:

I have the following eBPF program:

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

#include &lt;linux/bpf.h&gt;
#include &lt;sys/socket.h&gt;

#include &lt;bpf/bpf_helpers.h&gt;

char LICENSE[] SEC(&quot;license&quot;) = &quot;GPL&quot;;

// msg_data_map carries a key-value pair of (msg_id, msg_length), and can record
// upto 65535 messages at once.
#define MAX_MSG_LEN 128
struct {
  __uint(type, BPF_MAP_TYPE_HASH);
  __uint(max_entries, 65535);
  __type(key, int);
  __type(value, char[MAX_MSG_LEN]);
} msg_data_map SEC(&quot;.maps&quot;);

SEC(&quot;sk_msg&quot;)
int msg_prog(struct sk_msg_md *msg) {
  long len = (long)msg-&gt;data_end - (long)msg-&gt;data;

  void *data_end = (void *)(long) msg-&gt;data_end;
  void *data = (void *)(long) msg-&gt;data;

  // Bounds check to make verifier happy
  if (data + MAX_MSG_LEN &gt; data_end) {
    return SK_PASS;
  }

  char buf[MAX_MSG_LEN] = {0};
  if (len &gt; MAX_MSG_LEN) {
    __builtin_memcpy(buf, data, MAX_MSG_LEN);
  } else {
    __builtin_memcpy(buf, data, len);
  }

  // Update in map
  int index = 0;
  bpf_map_update_elem(&amp;msg_data_map, &amp;index, &amp;buf, BPF_ANY);
 
  return SK_PASS;
}

Compiling the above program gives the following error:

Looks like the BPF stack limit of 512 bytes is exceeded. Please move large on stack variables into BPF per-cpu array map.
  1. Since the buf array is only 128 bytes, it should not have exceeded the stack limit.
  2. If I comment the map update lines, the program compiles fine. Why is this the case?

答案1

得分: 2

错误消息

> 看起来 BPF 的堆栈限制已超过 512 字节。请将堆栈上的大型变量移动到 BPF 每个 CPU 数组映射中。

在 bpftrace 中有文档记录 https://github.com/iovisor/bpftrace/blob/master/docs/internals_development.md#stack-limit-exceeded

这似乎是错误的更友好版本通常由内核引发

> %d 次调用的组合堆栈大小为 %d。太大了

但我无法找到这个更友好版本实际来自何处。无论如何,这是一个有效的错误。验证器对每个堆栈帧(每个 BPF 到 BPF 函数)强制执行 512 字节的堆栈限制

摆脱这个错误的唯一方法是减少堆栈上的变量数量。错误建议使用每个 CPU 映射来存储当前堆栈上的一些数据,因为 BPF 程序不会被抢占,因此可以放心使用映射值,而不用担心与其他程序冲突。

我应该注意,在 RT(实时)内核上,关于每个 CPU 映射的这种假设并不成立,因为 BPF 程序永远不会被迁移,但仍然可以被抢占。

英文:

The error message

> Looks like the BPF stack limit of 512 bytes is exceeded. Please move large on stack variables into BPF per-cpu array map.

Is documented in bpftrace as well https://github.com/iovisor/bpftrace/blob/master/docs/internals_development.md#stack-limit-exceeded

It seems to be the nicer version of the error normally thrown by the kernel

> combined stack size of %d calls is %d. Too large

but I am unable to find where this nicer version actually originates. In any case, its a valid error. The verifier enforces a 512 byte stack limit per stack frame (per BPF-to-BPF function).

The only way to get rid of this error is to decrease the amount of variables on the stack. The error suggests to use a per-CPU map to store some of the data currently on the stack since BPF programs are not preempted and thus the map value can be used without fear of conflicting with other program.

I should note that on RT (Real Time) kernels this assumption about per-CPU maps isn't true since BPF programs are never migrated but can still be preempted.

huangapple
  • 本文由 发表于 2023年6月26日 09:15:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76553013.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定