Skip to content

Add eBPF instruction CALLX for indirect calls #7972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

niooss-ledger
Copy link
Contributor

Hello,

When clang encounters indirect calls in eBPF programs, it emits a call instruction with a register parameter (BPF_X) instead of an immediate value (BPF_K). This encoding (BPF_JMP | BPF_CALL | BPF_X = 0x8d) is decoded by llvm-objdump as callx.

For example, here is a simple C program with an indirect call:

extern void (*ptr_to_some_function)(void);

void call_ptr_to_some_function(void) {
    ptr_to_some_function();
}

Compiling and disassembling it gives with clang 14.0 (and LLVM 14.0):

$ clang -O2 -target bpf -c indirect_call.c -o indirect_call.ebpf
$ llvm-objdump -rd indirect_call.ebpf

indirect_call.ebpf:  file format elf64-bpf

Disassembly of section .text:

0000000000000000 <call_ptr_to_some_function>:
       0:  18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00  r1 = 0 ll
                0000000000000000:  R_BPF_64_64  ptr_to_some_function
       2:  79 11 00 00 00 00 00 00  r1 = *(u64 *)(r1 + 0)
       3:  8d 00 00 00 01 00 00 00  callx r1
       4:  95 00 00 00 00 00 00 00  exit

Contrary to usual eBPF instructions, callx's register operand is encoded in the immediate field. This encoding is actually specific to LLVM (and clang). GCC used the destination register to store the target register.

LLVM 19.1 was modified to use GCC's encoding: llvm/llvm-project#81546 ("BPF: Change callx insn encoding"). For example, in an Alpine Linux 3.21 system:

$ clang -target bpf --version
Alpine clang version 19.1.4
Target: bpf
Thread model: posix
InstalledDir: /usr/lib/llvm19/bin

$ clang -O2 -target bpf -c indirect_call.c -o indirect_call.ebpf
$ llvm-objdump -rd indirect_call.ebpf

indirect_call.ebpf:  file format elf64-bpf

Disassembly of section .text:

0000000000000000 <call_ptr_to_some_function>:
       0:  18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00  r1 = 0x0 ll
                0000000000000000:  R_BPF_64_64  ptr_to_some_function
       2:  79 11 00 00 00 00 00 00  r1 = *(u64 *)(r1 + 0x0)
       3:  8d 01 00 00 00 00 00 00  callx r1
       4:  95 00 00 00 00 00 00 00  exit

The instruction is now encoded 8d 01 00....

For reference, here are similar commands using GCC showing it is using the same encoding (here, compiler option -mxbpf is required to enable several features including indirect calls, cf. https://gcc.gnu.org/onlinedocs/gcc-12.4.0/gcc/eBPF-Options.html).

$ bpf-gcc --version
bpf-gcc (12-20220319-1ubuntu1+2) 12.0.1 20220319 (experimental) [master r12-7719-g8ca61ad148f]

$ bpf-gcc -O2 -c indirect_call.c -o indirect_call.ebpf -mxbpf
$ bpf-objdump -mxbpf -rd indirect_call.ebpf

indirect_call_gcc-12.ebpf:     file format elf64-bpfle

Disassembly of section .text:

0000000000000000 <call_ptr_to_some_function>:
   0:  18 00 00 00 00 00 00 00   lddw %r0,0
   8:  00 00 00 00 00 00 00 00
      0: R_BPF_INSN_64  ptr_to_some_function
  10:  79 01 00 00 00 00 00 00   ldxdw %r1,[%r0+0]
  18:  8d 01 00 00 00 00 00 00   call %r1
  20:  95 00 00 00 00 00 00 00   exit

Add both callx instruction encodings to eBPF processor.

By the way, the eBPF Verifier used by Linux kernel currently forbids indirect calls (it fails when BPF_SRC(insn->code) != BPF_K, in https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/bpf/verifier.c?h=v6.14#n19141). But other deployments of eBPF may already support this feature.

When clang encounters indirect calls in eBPF programs, it emits a call
instruction with a register parameter (`BPF_X`) instead of an immediate
value (`BPF_K`). This encoding (`BPF_JMP | BPF_CALL | BPF_X = 0x8d`) is
decoded by llvm-objdump as `callx`.

For example, here is a simple C program with an indirect call:

    extern void (*ptr_to_some_function)(void);
    void call_ptr_to_some_function(void) {
        ptr_to_some_function();
    }

Compiling and disassembling it gives with clang 14.0 (and LLVM 14.0):

    $ clang -O2 -target bpf -c indirect_call.c -o indirect_call.ebpf
    $ llvm-objdump -rd indirect_call.ebpf

    indirect_call.ebpf:  file format elf64-bpf

    Disassembly of section .text:

    0000000000000000 <call_ptr_to_some_function>:
           0:  18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00  r1 = 0 ll
                    0000000000000000:  R_BPF_64_64  ptr_to_some_function
           2:  79 11 00 00 00 00 00 00  r1 = *(u64 *)(r1 + 0)
           3:  8d 00 00 00 01 00 00 00  callx r1
           4:  95 00 00 00 00 00 00 00  exit

Contrary to usual eBPF instructions, `callx`'s register operand is
encoded in the immediate field. This encoding is actually specific to
LLVM (and clang). GCC used the destination register to store the target
register.

LLVM 19.1 was modified to use GCC's encoding:
llvm/llvm-project#81546 ("BPF: Change callx insn
encoding"). For example, in an Alpine Linux 3.21 system:

    $ clang -target bpf --version
    Alpine clang version 19.1.4
    Target: bpf
    Thread model: posix
    InstalledDir: /usr/lib/llvm19/bin

    $ clang -O2 -target bpf -c indirect_call.c -o indirect_call.ebpf
    $ llvm-objdump -rd indirect_call.ebpf

    indirect_call.ebpf:  file format elf64-bpf

    Disassembly of section .text:

    0000000000000000 <call_ptr_to_some_function>:
           0:  18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00  r1 = 0x0 ll
                    0000000000000000:  R_BPF_64_64  ptr_to_some_function
           2:  79 11 00 00 00 00 00 00  r1 = *(u64 *)(r1 + 0x0)
           3:  8d 01 00 00 00 00 00 00  callx r1
           4:  95 00 00 00 00 00 00 00  exit

The instruction is now encoded `8d 01 00...`.

For reference, here are similar commands using GCC showing it is using
the same encoding (here, compiler option `-mxbpf` is required to enable
several features including indirect calls, cf.
https://gcc.gnu.org/onlinedocs/gcc-12.4.0/gcc/eBPF-Options.html ).

    $ bpf-gcc --version
    bpf-gcc (12-20220319-1ubuntu1+2) 12.0.1 20220319 (experimental) [master r12-7719-g8ca61ad148f]

    $ bpf-gcc -O2 -c indirect_call.c -o indirect_call.ebpf -mxbpf
    $ bpf-objdump -mxbpf -rd indirect_call.ebpf

    indirect_call_gcc-12.ebpf:     file format elf64-bpfle

    Disassembly of section .text:

    0000000000000000 <call_ptr_to_some_function>:
       0:  18 00 00 00 00 00 00 00   lddw %r0,0
       8:  00 00 00 00 00 00 00 00
          0: R_BPF_INSN_64  ptr_to_some_function
      10:  79 01 00 00 00 00 00 00   ldxdw %r1,[%r0+0]
      18:  8d 01 00 00 00 00 00 00   call %r1
      20:  95 00 00 00 00 00 00 00   exit

Add both `callx` instruction encodings to eBPF processor.

By the way, the eBPF Verifier used by Linux kernel currently forbids
indirect calls (it fails when `BPF_SRC(insn->code) != BPF_K`, in
https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/bpf/verifier.c?h=v6.14#n19141
). But other deployments of eBPF may already support this feature.
@niooss-ledger niooss-ledger force-pushed the ebpf-add-instruction-callx branch from d754321 to ab59413 Compare April 1, 2025 20:25
@GhidorahRex GhidorahRex self-assigned this Apr 2, 2025
@GhidorahRex GhidorahRex added Type: Bug Something isn't working Status: Triage Information is being gathered Feature: Processor/eBPF labels Apr 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature: Processor/eBPF Status: Triage Information is being gathered Type: Bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants