前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ebfp内核态开发经验总结

ebfp内核态开发经验总结

原创
作者头像
DifficultWork
修改2023-12-07 09:13:59
5670
修改2023-12-07 09:13:59
举报
文章被收录于专栏:阶梯计划阶梯计划

不定期更新

1 结构体字节对齐

以64位系统为例,最小的字节对齐是4字节(u32)对齐,最大字节对齐是8字节(u64),按需增加保留字段,否则会被编译器优化填充。

2 结构体在操作之前要确保所有字段都初始化

包含未初始化字段的对象被认为不可信,加载时会被verifier报错。

3 使用LLVM内置函数进行内存操作

  • __builtin_memset
  • __builtin_memcpy
  • __builtin_memmove

4 指针被操作后需要进行获取并校验

例如:

代码语言:c
复制
struct iphdr *ip4 = (struct iphdr*)skb->data + ETH_HLEN;                         // 首次获取
...
skb_store_bytes(skb, l3_off + offsetof(struct iphdr, saddr), &new_saddr, 4, 0);  // skb被操作了,因此ip4的值不可信,此时操作ip4会被拒绝
ip4 = (struct iphdr*)skb->data + ETH_HLEN;                                       // 需要再次获取
if (ip4->protocol == IPPROTO_TCP) {                                              // 重新校验后才能正常使用
    // do something
}

5 从bfp map中lookup的指针不能再update回bpf map

从bpf的map中lookup的指针不能再update回去,如果要修改里面的value直接操作指针即可。

6 单个bpf的程序栈空间限制再512byte

7 有限的循环

ebpf本身不允许循环的存在,因为会被判定为“无法及时退出”,影响内核执行效率。不过可以预见到循环将在有限次执行后退出,可以使用#pragma unroll在编译期间展开for循环。当然,循环的次数是有限的,因为低内核版本的bpf本身代码指令数就非常有限(4096)。

8 verifier常见错误

8.1 存在环

back-edge from insn xx to xx

指令回跳会增加指令分析的复杂度,所以 verifier 直接禁止出现指令回跳。一般for循环会造成指令回跳:

代码语言:c
复制
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(tcp_sendmsg, struct sock *sk)
{
    int i;
    for (i=0;i<1000;i++)
        bpf_printk("%d\n", i);
    return 0;
}

一般解决方法是:在 for 循环前面添加 #pragma unroll,进行循环展开,避免指令回跳。

8.2 栈限制512字节

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

因为 verifier 会保存栈内存的状态,所以栈的大小是有限的,目前是 512 字节。当栈内存大小超过 512 字节时,则会被 verifier 拒绝:

代码语言:c
复制
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(tcp_sendmsg, struct sock *sk)
{
#define MAX_NUM (512/8)
    volatile u64 arr[MAX_NUM + 1] = {};
    arr[MAX_NUM] = 0xff;
    bpf_printk("%lld\n", arr[MAX_NUM]);
    return 0;
}

8.3 栈偏移仅支持常量

variable stack access var_off=(0x0; 0x78) off=-128 size=8-

当访问栈时采用变量偏移,会导致无法推测寄存器的状态。所以 4.19 版本只支持常量偏移。下面是使用变量偏移的错误示例:

代码语言:c
复制
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(tcp_sendmsg, struct sock *sk)
{
    u64 volatile arr[16] = {};
    arr[bpf_ktime_get_ns() & 0xf] = 0;
    return 0;
}

PS:5.10内核已经支持变量类型的栈偏移。

8.4 未做范围检查

math between fp pointer and register with unbounded min value is not allowed

范围检查主要是用来判断内存访问是否越界:

代码语言:c
复制
struct
{
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, int);
    __type(value, int);
    __uint(max_entries, 1024);
} indexmap SEC(".maps");

SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(tcp_sendmsg, struct sock *sk)
{
    int map_key = 0;
    int map_val = 0;
    int array[10] = {};
    
    int *map_val_ptr = bpf_map_look_up(&indexmap, &map_key);
    if (map_val_ptr)
        map_val = *map_val_ptr;
    
    bpf_printk("array[%d] = %d\n", map_val, array[map_val]);
    return 0;
}
char LICENSE[] SEC("license") = "GPL";

一般解决方法是:在内存访问时,进行范围检查。

参考

Cilium:BPF 和 XDP 参考指南(2021)

ebpf专栏

eBPF verifier常见错误浅析

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 结构体字节对齐
  • 2 结构体在操作之前要确保所有字段都初始化
  • 3 使用LLVM内置函数进行内存操作
  • 4 指针被操作后需要进行获取并校验
  • 5 从bfp map中lookup的指针不能再update回bpf map
  • 6 单个bpf的程序栈空间限制再512byte
  • 7 有限的循环
  • 8 verifier常见错误
    • 8.1 存在环
      • 8.2 栈限制512字节
        • 8.3 栈偏移仅支持常量
          • 8.4 未做范围检查
          • 参考
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档