microsoft / ebpf-for-windows

eBPF implementation that runs on top of Windows
MIT License
2.92k stars 231 forks source link

bpf2c and eBPF-for-Windows should support global variables in BPF programs (aka platform variables) as defined in 5.4 of BPF ISA #3958

Open Alan-Jowett opened 2 days ago

Alan-Jowett commented 2 days ago

The BPF ISA defines additional 64bit immediate load instructions. eBPF-for-Windows should add support for type 3 at a minimum. This support can improve the performance of certain BPF programs by eliminating the need to perform map lookups.

5.4.  64-bit immediate instructions

   Instructions with the IMM 'mode' modifier use the wide instruction
   encoding defined in Instruction encoding (Section 3), and use the
   'src_reg' field of the basic instruction to hold an opcode subtype.

   The following table defines a set of {IMM, DW, LD} instructions with
   opcode subtypes in the 'src_reg' field, using new terms such as "map"
   defined further below:

    +=========+================================+==========+==========+
    | src_reg | pseudocode                     | imm type | dst type |
    +=========+================================+==========+==========+
    | 0x0     | dst = (next_imm << 32) | imm   | integer  | integer  |
    +---------+--------------------------------+----------+----------+
    | 0x1     | dst = map_by_fd(imm)           | map fd   | map      |
    +---------+--------------------------------+----------+----------+
    | 0x2     | dst = map_val(map_by_fd(imm))  | map fd   | data     |
    |         | + next_imm                     |          | address  |
    +---------+--------------------------------+----------+----------+
    | 0x3     | dst = var_addr(imm)            | variable | data     |
    |         |                                | id       | address  |
    +---------+--------------------------------+----------+----------+
    | 0x4     | dst = code_addr(imm)           | integer  | code     |
    |         |                                |          | address  |
    +---------+--------------------------------+----------+----------+
    | 0x5     | dst = map_by_idx(imm)          | map      | map      |
    |         |                                | index    |          |
    +---------+--------------------------------+----------+----------+
    | 0x6     | dst = map_val(map_by_idx(imm)) | map      | data     |
    |         | + next_imm                     | index    | address  |
    +---------+--------------------------------+----------+----------+

                 Table 12: 64-bit immediate instructions
Alan-Jowett commented 2 days ago

https://github.com/iovisor/ubpf/issues/590

Alan-Jowett commented 2 days ago

https://github.com/vbpf/ebpf-verifier/issues/763

saxena-anurag commented 1 day ago

Question: Are these global variables compile time constants?

lmb commented 1 day ago

On Linux, the use case for this feature is to be able to modify a "compile time constant" at load time of a BPF program. For example being able to change ETH_HLEN to accomodate VLAN tagging or similar. The verifier will then inline the constant into the BPF instruction stream before JITing. This makes this essentially free in terms of performance.

Roughly how it works from user space (I think this is type 0x2 in the list above):

  1. Compile a program which refers to a volatile const. The compiler turns the reference to the const into a load from the .rodata section at a specific offset
  2. Load the ELF and synthesis a single-element array map from the .rodata section
  3. User space then has the opportunity to change the contents of the array map via some API
  4. Before load, the map is loaded and frozen via BPF_MAP_FREEZE
  5. Verifier sees the load, realises that the map is frozen, and replaces the ldimm64 with a constant load