使用原始系统调用加载 BPF 程序时如何链接/加载 .rodata 部分?

问题描述 投票:0回答:1

这个问题比我预想的要长。由于 BPF 很新,可能没有准确的答案。

我正在尝试将“hello world”eBPF 程序加载到 XDP 连接点上。我想使用原始系统调用和 without 使用

libbpf
bpftool
xdp-loader
等来完成此操作。

我的内核版本是

6.5.0-9-generic

BPF 程序是:

#include <linux/types.h>
#include <bpf/bpf_helpers.h>
#include <linux/bpf.h>
#include <linux/version.h>

SEC("xdp")
int xdp_prog_simple(struct xdp_md *ctx)
{
    bpf_printk("In xdp_prog_simple\n");
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
__u32 _version SEC("version") = LINUX_VERSION_CODE;

并编译为:

clang -O2 -g -Wall -target bpf -c bpf.c -o bpf.o

llvm-objdump --no-show-raw-insn --dr bpf.o
的输出是:

Disassembly of section xdp:

0000000000000000 <xdp_prog_simple>:
       0:   r1 = 0x0 ll
        0000000000000000:  R_BPF_64_64  .rodata
       2:   r2 = 0x14
       3:   call 0x6
       4:   r0 = 0x2
       5:   exit

我正在尝试使用 Rust 程序加载这个

bpf.o
文件。我从
xdp
部分提取字节,并在传递给
BPF_PROG_LOAD
系统调用的
SYS_bpf
命令的属性中引用这些字节:

(这段代码不是问题。将其包含在上下文中,并且因为我在互联网上没有看到很多 Rust 示例,所以它可能对未来的读者有所帮助)

use std::mem::size_of;
use crate::ffi::bpf;
use crate::ffi::syscall::check_err;
use crate::Error;
use object::{Object, ObjectSection};

const LOG_BUF_SIZE: usize = 65536;
static mut BPF_LOG_BUF: [u8; LOG_BUF_SIZE] = [0; LOG_BUF_SIZE];

pub fn load_xdp_program(elf: &[u8], section: &str) -> Result<i32, Error> {
    let obj = object::File::parse(elf).map_err(|err| Error::Boxed(Box::new(err)))?;

    // Extract bytes from the program section ("xdp" in this example)
    let text = obj
        .section_by_name(section)
        .ok_or_else(|| Error::NotFound("program section not found"))?
        .data()
        .map_err(|err| Error::Boxed(Box::new(err)))?;

    // Extract the license bytes
    let license = obj
        .section_by_name("license")
        .ok_or_else(|| Error::NotFound("section 'license' not found"))?
        .data()
        .map_err(|err| Error::Boxed(Box::new(err)))?;

    // Extract the kernel version bytes
    let version = {
        let data: [u8; 4] = obj
            .section_by_name("version")
            .ok_or_else(|| Error::NotFound("section 'version' not found"))?
            .data()
            .map_err(|err| Error::Boxed(Box::new(err)))?
            .try_into()
            .map_err(|err| Error::Boxed(Box::new(err)))?;
        u32::from_le_bytes(data)
    };

    let attr = &bpf::ProgramAttr {
        prog_type: XDP_PROG_TYPE,
        insn_cnt: (text.len() / size_of::<bpf::Insn>()) as u32,
        insns: ptr_to_u64(text),
        license: ptr_to_u64(license),
        log_level: 1,
        log_size: LOG_BUF_SIZE as u32,
        log_buf: ptr_to_u64(unsafe { &BPF_LOG_BUF }),
        kern_version: version,
        prog_flags: 0,
        prog_name: *b"xdp_prog_simple\0",
        prog_ifindex: 0,
        expected_attach_type: 37, // BPF_XDP
        prog_btf_fd: 0,
        func_info_rec_size: 0,
        func_info: 0,
        func_info_cnt: 0,
        line_info_rec_size: 0,
        line_info: 0,
        line_info_cnt: 0,
        attach_btf_id: 0,
        attach: bpf::AttachFd { object_fd: 0 },
        core_relo_cnt: 0,
        fd_array: 0,
        core_relos: 0,
        core_relo_rec_size: 0,
    };

    let prog_fd = check_err(unsafe {
        libc::syscall(
            libc::SYS_bpf,
            bpf::cmd::PROG_LOAD,
            attr as *const _ as *const libc::c_void,
            size_of::<bpf::BpfAttr>(),
        ) as i32
    });

    match prog_fd {
        Ok(fd) => Ok(fd),
        Err(err) => {
            let msg = String::from_utf8_lossy(unsafe { &BPF_LOG_BUF });
            eprintln!("Failed to load BPF program. Log buffer:\n\n{msg}");
            Err(err)
        }
    }
}

我构建并将其称为

sudo
以确保它具有 root 权限:

cargo build --release --bin xdp-loader
sudo ./target/release/xdp-loader bpf.o

这会从日志缓冲区返回错误:

0: R1=ctx(off=0,imm=0) R10=fp0
0: (18) r1 = 0x0                      ; R1_w=0
2: (b7) r2 = 20                       ; R2_w=20
3: (85) call bpf_trace_printk#6
R1 type=scalar expected=fp, pkt, pkt_meta, map_key, map_value, mem, ringbuf_mem, buf, trusted_ptr_
processed 3 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

BPF 系统调用返回错误代码 13:权限被拒绝。

这似乎暗示寄存器

r1
是一种类型
scalar
,而它应该是类型
fp
。这让我很困惑。据我了解,根据这些文档,
r1
应该将地址携带到上下文结构
struct xdp_md *ctx
我没有执行任何指针算术,因此不应将其转换为标量。

我觉得这是因为

struct xdp_md *ctx

没有被使用,所以编译出来了。相反,它用于 BPF 调用约定来调用 
bpf_printk
 帮助器,这实际上是一个被 
bpf_trace_printk
 替换的宏。

调用时,寄存器

r1

用于指向
"In xdp_prog_simple\n"
字符串。它将立即 64 位 
0x0
 地址(零偏移地址)加载到包含该字符串的 
.rodata
 部分中。这是
bpf_printk
的第一个论点。寄存器 
r2
 还加载立即值 
20
,因为这是 
"In xdp_prog_simple\n"
 字符串的长度 – 它是 
bpf_trace_printk
 的第二个参数。

但这并不能解释错误:

R1 type=scalar expected=fp, pkt, pkt_meta, map_key, map_value, mem, ringbuf_mem, buf, trusted_ptr_
它期望 

r1

 是一个帧指针,但它是一个标量。

认为这是因为当我加载包含程序字节代码的xdp

部分时,我没有加载
.rodata
部分,所以这不指向任何地方,并且验证器解释了
0x0
 地址作为标量。

似乎需要一个中间链接/加载步骤才能使 BPF JIT 可以访问

.rodata

这个 SO 答案证实了这一点,这意味着libbpf
会自动执行此操作。如果我重写我的代码:

SEC("xdp") int xdp_prog_simple(struct xdp_md *ctx) { char msg[] = "In xdp_prog_simple\n"; bpf_trace_printk(msg, sizeof(msg)); return XDP_PASS; }
字符串内联存储,而不是存储在不同的部分中:

Disassembly of section xdp: 0000000000000000 <xdp_prog_simple>: 0: r1 = 0xa656c 1: *(u32 *)(r10 - 0x8) = r1 2: r1 = 0x706d69735f676f72 ll 4: *(u64 *)(r10 - 0x10) = r1 5: r1 = 0x705f706478206e49 ll 7: *(u64 *)(r10 - 0x18) = r1 8: r1 = r10 9: r1 += -0x18 10: r2 = 0x14 11: call 0x6 12: r0 = 0x2 13: exit
程序加载成功!

在我的内核版本上,

bpf_printk

似乎不会自动进行此重组:

/* Helper macro to print out debug messages */ #define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
我的问题:如何自动执行此操作,而不需要拆分字符串的声明和 

bpf_trace_printk

 的调用?我相信我需要以某种方式链接/加载由反汇编中的 
.rodata
 重定位条目标识的 
R_BPF_64_64
 部分中的字符串:

(之前的片段)

0000000000000000 <xdp_prog_simple>: 0: r1 = 0x0 ll 0000000000000000: R_BPF_64_64 .rodata
以便它与字节码内联存储。那是对的吗?如果是这样,我该怎么做?

(我的想法是,我需要扫描 ELF 文件中的重定位条目,并使用它们以某种方式定位 .rodata 部分中的项目,然后将它们直接插入字节码中)

我知道 BPF 相当新并且有锋利的边缘。对任何为我指明正确方向的答案感到满意。

rust ebpf xdp-bpf
1个回答
0
投票
我想你可以在Aya的实现中找到一些处理。在

加载eBPF程序的过程中,Aya会首先解析ELF文件并找到.rodata

部分、
.bss
部分、
.data
部分等等。之后,Aya 将为这些部分
创建地图。这些地图的类型通常是BPF_MAP_TYPE_ARRAY
。这一步完成后,程序中访问这些段内容的指令
需要重定位。核心重定位逻辑如下:

if !map.data().is_empty() { instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_VALUE as u8); instructions[ins_index + 1].imm = instructions[ins_index].imm + sym.address as i32; } else { instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_FD as u8); } instructions[ins_index].imm = *fd;
您可以在 

内核文档中了解为什么需要这样做。

© www.soinside.com 2019 - 2024. All rights reserved.