Files
ghidra/Ghidra/Processors/eBPF/data/languages/eBPF.sinc
Nicolas Iooss af8a370421 Fix eBPF compare-and-exchange instruction
Linux kernel's documentation tells in
https://www.kernel.org/doc/html/v6.0/bpf/instruction-set.html#atomic-operations

> The BPF_CMPXCHG operation atomically compares the value addressed by
> dst_reg + off with R0. If they match, the value addressed by
> dst_reg + off is replaced with src_reg. In either case, the value that
> was at dst_reg + off before the operation is zero-extended and loaded
> back to R0.

If the values don't match, *(dst_reg + off) is not supposed to be
modified.

Moreover, register R0 is always modified and the 32-bit instruction
truncates its value (with a zero-extension). This is also clear in the
implementation of BPF_CMPXCHG in
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/bpf/core.c?h=v6.18#n2186

    case BPF_CMPXCHG:
            if (BPF_SIZE(insn->code) == BPF_W)
                    BPF_R0 = (u32) atomic_cmpxchg(
                            (atomic_t *)(unsigned long) (DST + insn->off),
                            (u32) BPF_R0, (u32) SRC);
            else if (BPF_SIZE(insn->code) == BPF_DW)
                    BPF_R0 = (u64) atomic64_cmpxchg(
                            (atomic64_t *)(unsigned long) (DST + insn->off),
                            (u64) BPF_R0, (u64) SRC);

Fix the semantic of the compare-and-exchange instruction accordingly.
2025-12-29 19:49:13 +00:00

460 lines
22 KiB
Plaintext

###############################################################################
# eBPF Processor Specification for Ghidra
###############################################################################
define endian=$(ENDIAN);
#eBPF is a RISC register machine with a total of 11 64-bit registers, a program counter and a 512 byte fixed-size stack.
#9 registers are general purpose read-write, one is a read-only stack pointer and the program counter is implicit,
#i.e. we can only jump to a certain offset from it. The eBPF registers are always 64-bit wide.
define space ram type=ram_space size=8 default;
define space register type=register_space size=4;
define space syscall type=ram_space size=4;
define register offset=0 size=8 [ R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 PC ];
# Instruction encoding: Insop:8, dst_reg:4, src_reg:4, off:16, imm:32 - from lsb to msb
@if ENDIAN == "little"
define token instr(64)
llvm_imm_callx_zero=(36, 63)
imm=(32, 63) signed
llvm_reg_callx=(32, 35) # special encoding for callx instruction emitted by LLVM
off=(16, 31) signed
src=(12, 15)
dst=(8, 11)
op_alu_jmp_opcode=(4, 7)
op_alu_jmp_source=(3, 3)
op_ld_st_mode=(5, 7)
op_ld_st_size=(3, 4)
op_insn_class=(0, 2)
;
#We'll need this token to operate with LDDW instruction, which has 64 bit imm value
define token immtoken(64)
imm2=(32, 63)
;
@else # ENDIAN == "big"
define token instr(64)
imm=(0, 31) signed
llvm_reg_callx=(0, 3) # special encoding for callx instruction emitted by LLVM
llvm_imm_callx_zero=(4, 31)
off=(32, 47) signed
src=(48, 51)
dst=(52, 55)
op_insn_class=(56, 58)
op_ld_st_size=(59, 60)
op_ld_st_mode=(61, 63)
op_alu_jmp_source=(59, 59)
op_alu_jmp_opcode=(60, 63)
;
define token immtoken(64)
imm2=(0, 31)
;
@endif # ENDIAN = "big"
#To operate with registers
attach variables [ src dst llvm_reg_callx ] [ R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 _ _ _ _ _ ];
#Arithmetic instructions
#BPF_ALU64
###############################################################################
SRC8: src is src & op_alu_jmp_source=1 { export src; }
SRC8: imm is imm & op_alu_jmp_source=0 { export *[const]:8 imm; }
SRC4: src is src & op_alu_jmp_source=1 { local tmp:4 = src:4; export tmp; }
SRC4: imm is imm & op_alu_jmp_source=0 { export *[const]:4 imm; }
DST4: dst is dst { local tmp:4 = dst:4; export tmp; }
:MOV dst, SRC8 is SRC8 & dst & off=0 & op_alu_jmp_opcode=0xb & op_insn_class=0x7 { dst = SRC8; }
:MOVSB dst, src is src & dst & off=8 & op_alu_jmp_opcode=0xb & op_alu_jmp_source=1 & op_insn_class=0x7 { dst = sext(src:1); }
:MOVSH dst, src is src & dst & off=16 & op_alu_jmp_opcode=0xb & op_alu_jmp_source=1 & op_insn_class=0x7 { dst = sext(src:2); }
:MOVSW dst, src is src & dst & off=32 & op_alu_jmp_opcode=0xb & op_alu_jmp_source=1 & op_insn_class=0x7 { dst = sext(src:4); }
:ADD dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x0 & op_insn_class=0x7 { dst = dst + SRC8; }
:SUB dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x1 & op_insn_class=0x7 { dst = dst - SRC8; }
:MUL dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x2 & op_insn_class=0x7 { dst = dst * SRC8; }
:DIV dst, SRC8 is SRC8 & dst & off=0 & op_alu_jmp_opcode=0x3 & op_insn_class=0x7 { dst = dst / SRC8; }
:SDIV dst, SRC8 is SRC8 & dst & off=1 & op_alu_jmp_opcode=0x3 & op_insn_class=0x7 { dst = dst s/ SRC8; }
:OR dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x4 & op_insn_class=0x7 { dst = dst | SRC8; }
:AND dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x5 & op_insn_class=0x7 { dst = dst & SRC8; }
:LSH dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x6 & op_insn_class=0x7 { dst = dst << SRC8; }
:RSH dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0x7 & op_insn_class=0x7 { dst = dst >> SRC8; }
:NEG dst is dst & op_alu_jmp_opcode=0x8 & op_alu_jmp_source=0 & op_insn_class=0x7 { dst = -dst; }
:MOD dst, SRC8 is SRC8 & dst & off=0 & op_alu_jmp_opcode=0x9 & op_insn_class=0x7 { dst = dst % SRC8; }
:SMOD dst, SRC8 is SRC8 & dst & off=1 & op_alu_jmp_opcode=0x9 & op_insn_class=0x7 { dst = dst s% SRC8; }
:XOR dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0xa & op_insn_class=0x7 { dst = dst ^ SRC8; }
:ARSH dst, SRC8 is SRC8 & dst & op_alu_jmp_opcode=0xc & op_insn_class=0x7 { dst = dst s>> SRC8; }
#BPF_ALU
###############################################################################
:MOV dst, SRC4 is SRC4 & dst & off=0 & op_alu_jmp_opcode=0xb & op_insn_class=0x4 { dst = zext(SRC4); }
:MOVSB dst, src is src & dst & off=8 & op_alu_jmp_opcode=0xb & op_alu_jmp_source=1 & op_insn_class=0x4 { local tmp:4 = sext(src:1); dst = zext(tmp); }
:MOVSH dst, src is src & dst & off=16 & op_alu_jmp_opcode=0xb & op_alu_jmp_source=1 & op_insn_class=0x4 { local tmp:4 = sext(src:2); dst = zext(tmp); }
:ADD dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x0 & op_insn_class=0x4 { dst = zext(dst:4 + SRC4); }
:SUB dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x1 & op_insn_class=0x4 { dst = zext(dst:4 - SRC4); }
:MUL dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x2 & op_insn_class=0x4 { dst = zext(dst:4 * SRC4); }
:DIV dst, SRC4 is SRC4 & dst & off=0 & op_alu_jmp_opcode=0x3 & op_insn_class=0x4 { dst = zext(dst:4 / SRC4); }
:SDIV dst, SRC4 is SRC4 & dst & off=1 & op_alu_jmp_opcode=0x3 & op_insn_class=0x4 { dst = zext(dst:4 s/ SRC4); }
:OR dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x4 & op_insn_class=0x4 { dst = zext(dst:4 | SRC4); }
:AND dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x5 & op_insn_class=0x4 { dst = zext(dst:4 & SRC4); }
:LSH dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x6 & op_insn_class=0x4 { dst = zext(dst:4 << SRC4); }
:RSH dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0x7 & op_insn_class=0x4 { dst = zext(dst:4 >> SRC4); }
:NEG dst is dst & op_alu_jmp_opcode=0x8 & op_alu_jmp_source=0 & op_insn_class=0x4 { dst = zext(-dst:4); }
:MOD dst, SRC4 is SRC4 & dst & off=0 & op_alu_jmp_opcode=0x9 & op_insn_class=0x4 { dst = zext(dst:4 % SRC4); }
:SMOD dst, SRC4 is SRC4 & dst & off=1 & op_alu_jmp_opcode=0x9 & op_insn_class=0x4 { dst = zext(dst:4 s% SRC4); }
:XOR dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0xa & op_insn_class=0x4 { dst = zext(dst:4 ^ SRC4); }
:ARSH dst, SRC4 is SRC4 & dst & op_alu_jmp_opcode=0xc & op_insn_class=0x4 { dst = zext(dst:4 s>> SRC4); }
#Bytewasp instructions
###############################################################################
@if ENDIAN == "little"
# BPF_ALU | BPF_K | BPF_END
:LE16 dst is imm=0x10 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x4 { dst = zext(dst:2); }
:LE32 dst is imm=0x20 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x4 { dst = zext(dst:4); }
:LE64 dst is imm=0x40 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x4 {}
# BPF_ALU | BPF_X | BPF_END
:BE16 dst is imm=0x10 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=1 & op_insn_class=0x4 {
dst = ((dst & 0xff00) >> 8) | ((dst & 0x00ff) << 8);
}
:BE32 dst is imm=0x20 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=1 & op_insn_class=0x4 {
dst = ((dst & 0xff000000) >> 24) | (((dst) & 0x00ff0000) >> 8) | (((dst) & 0x0000ff00) << 8) | ((dst & 0x000000ff) << 24);
}
:BE64 dst is imm=0x40 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=1 & op_insn_class=0x4 {
dst = ((dst << 56) & 0xff00000000000000) |
((dst << 40) & 0x00ff000000000000) |
((dst << 24) & 0x0000ff0000000000) |
((dst << 8) & 0x000000ff00000000) |
((dst >> 8) & 0x00000000ff000000) |
((dst >> 24) & 0x0000000000ff0000) |
((dst >> 40) & 0x000000000000ff00) |
((dst >> 56) & 0x00000000000000ff);
}
@else # ENDIAN == "big"
# BPF_ALU | BPF_K | BPF_END
:LE16 dst is imm=0x10 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x4 {
dst = ((dst & 0xff00) >> 8) | ((dst & 0x00ff) << 8);
}
:LE32 dst is imm=0x20 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x4 {
dst = ((dst & 0xff000000) >> 24) | (((dst) & 0x00ff0000) >> 8) | (((dst) & 0x0000ff00) << 8) | ((dst & 0x000000ff) << 24);
}
:LE64 dst is imm=0x40 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x4 {
dst = ((dst << 56) & 0xff00000000000000) |
((dst << 40) & 0x00ff000000000000) |
((dst << 24) & 0x0000ff0000000000) |
((dst << 8) & 0x000000ff00000000) |
((dst >> 8) & 0x00000000ff000000) |
((dst >> 24) & 0x0000000000ff0000) |
((dst >> 40) & 0x000000000000ff00) |
((dst >> 56) & 0x00000000000000ff);
}
# BPF_ALU | BPF_X | BPF_END
:BE16 dst is imm=0x10 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=1 & op_insn_class=0x4 { dst = zext(dst:2); }
:BE32 dst is imm=0x20 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=1 & op_insn_class=0x4 { dst = zext(dst:4); }
:BE64 dst is imm=0x40 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=1 & op_insn_class=0x4 {}
@endif # ENDIAN = "big"
# BPF_ALU64 | BPF_K | BPF_END
:BSWAP16 dst is imm=0x10 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x7 {
dst = ((dst & 0xff00) >> 8) | ((dst & 0x00ff) << 8);
}
:BSWAP32 dst is imm=0x20 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x7 {
dst = ((dst & 0xff000000) >> 24) | (((dst) & 0x00ff0000) >> 8) | (((dst) & 0x0000ff00) << 8) | ((dst & 0x000000ff) << 24);
}
:BSWAP64 dst is imm=0x40 & dst & op_alu_jmp_opcode=0xd & op_alu_jmp_source=0 & op_insn_class=0x7 {
dst = ((dst << 56) & 0xff00000000000000) |
((dst << 40) & 0x00ff000000000000) |
((dst << 24) & 0x0000ff0000000000) |
((dst << 8) & 0x000000ff00000000) |
((dst >> 8) & 0x00000000ff000000) |
((dst >> 24) & 0x0000000000ff0000) |
((dst >> 40) & 0x000000000000ff00) |
((dst >> 56) & 0x00000000000000ff);
}
#Memory instructions - Load and Store
###############################################################################
#LDDW is the only 16-byte eBPF instruction which consists of two consecutive 8-byte blocks ('struct bpf_insn')
#and interpreted as single instruction which loads 64-bit imm value into dst. Encoding of LDDW:
#LSR MSR
# opcode src dst offset Low 8-byte imm zero-block High 8-byte imm
#bits 8 4 4 16 32 32 32
# So, imm64 consists of concatination of high 8-byte imm and low 8-byte imm.
:LDDW dst, concat is imm & dst & op_ld_st_mode=0x0 & op_ld_st_size=0x3 & op_insn_class=0x0; imm2 [ concat= (imm2 << 32) | ((imm) & 0xFFFFFFFF); ] { dst = concat; }
#BPF_LD_MAP_FD(DST, MAP_FD) -> second LDDW = pseudo LDDW insn used to refer to process-local map_fd
#For each instruction which needs relocation, it inject corresponding file descriptor to imm field.
#As a part of protocol, src_reg is set to BPF_PSEUDO_MAP_FD (which defined as 1) to notify kernel this is a map loading instruction.
:LDDW dst, imm is imm & src=1 & dst & op_ld_st_mode=0x0 & op_ld_st_size=0x3 & op_insn_class=0x0; imm2 { dst = *:8 imm:8; }
:LDABSW dst, imm is imm & dst & op_ld_st_mode=0x1 & op_ld_st_size=0x0 & op_insn_class=0x0 { dst = zext(*:4 imm:8); }
:LDABSH dst, imm is imm & dst & op_ld_st_mode=0x1 & op_ld_st_size=0x1 & op_insn_class=0x0 { dst = zext(*:2 imm:8); }
:LDABSB dst, imm is imm & dst & op_ld_st_mode=0x1 & op_ld_st_size=0x2 & op_insn_class=0x0 { dst = zext(*:1 imm:8); }
:LDABSDW dst, imm is imm & dst & op_ld_st_mode=0x1 & op_ld_st_size=0x3 & op_insn_class=0x0 { dst = *:8 imm:8; }
:LDINDW src, dst, imm is imm & src & dst & op_ld_st_mode=0x2 & op_ld_st_size=0x0 & op_insn_class=0x0 { dst = zext(*:4 (src + imm)); }
:LDINDH src, dst, imm is imm & src & dst & op_ld_st_mode=0x2 & op_ld_st_size=0x1 & op_insn_class=0x0 { dst = zext(*:2 (src + imm)); }
:LDINDB src, dst, imm is imm & src & dst & op_ld_st_mode=0x2 & op_ld_st_size=0x2 & op_insn_class=0x0 { dst = zext(*:1 (src + imm)); }
:LDINDDW src, dst, imm is imm & src & dst & op_ld_st_mode=0x2 & op_ld_st_size=0x3 & op_insn_class=0x0 { dst = *:8 (src + imm); }
:LDXW dst, [src + off] is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x0 & op_insn_class=0x1 { dst = zext(*:4 (src + off)); }
:LDXH dst, [src + off] is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x1 & op_insn_class=0x1 { dst = zext(*:2 (src + off)); }
:LDXB dst, [src + off] is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x2 & op_insn_class=0x1 { dst = zext(*:1 (src + off)); }
:LDXDW dst, [src + off] is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x3 & op_insn_class=0x1 { dst = *:8 (src + off); }
:STW [dst + off], imm is imm & off & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x0 & op_insn_class=0x2 { *:4 (dst + off)=imm:4; }
:STH [dst + off], imm is imm & off & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x1 & op_insn_class=0x2 { *:2 (dst + off)=imm:2; }
:STB [dst + off], imm is imm & off & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x2 & op_insn_class=0x2 { *:1 (dst + off)=imm:1; }
:STDW [dst + off], imm is imm & off & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x3 & op_insn_class=0x2 { *:8 (dst + off)=imm:8; }
:STXW [dst + off], src is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off)=src:4; }
:STXH [dst + off], src is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x1 & op_insn_class=0x3 { *:2 (dst + off)=src:2; }
:STXB [dst + off], src is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x2 & op_insn_class=0x3 { *:1 (dst + off)=src:1; }
:STXDW [dst + off], src is off & src & dst & op_ld_st_mode=0x3 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off)=src:8; }
:LDSXW dst, [src + off] is off & src & dst & op_ld_st_mode=0x4 & op_ld_st_size=0x0 & op_insn_class=0x1 { dst = sext(*:4 (src + off)); }
:LDSXH dst, [src + off] is off & src & dst & op_ld_st_mode=0x4 & op_ld_st_size=0x1 & op_insn_class=0x1 { dst = sext(*:2 (src + off)); }
:LDSXB dst, [src + off] is off & src & dst & op_ld_st_mode=0x4 & op_ld_st_size=0x2 & op_insn_class=0x1 { dst = sext(*:1 (src + off)); }
# BPF_ATOMIC
# BPF_ADD:
# BPF_STX | BPF_ATOMIC | BPF_W
:AADD32 [dst + off], src is imm=0x0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) + src:4; }
# BPF_STX | BPF_ATOMIC | BPF_DW
:AADD [dst + off], src is imm=0x0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) + src; }
# BPF_OR:
:AOR32 [dst + off], src is imm=0x40 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) | src:4; }
:AOR [dst + off], src is imm=0x40 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) | src; }
# BPF_AND:
:AAND32 [dst + off], src is imm=0x50 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) & src:4; }
:AAND [dst + off], src is imm=0x50 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) & src; }
# BPF_XOR:
:AXOR32 [dst + off], src is imm=0xa0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) ^ src:4; }
:AXOR [dst + off], src is imm=0xa0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) ^ src; }
# BPF_ADD | BPF_FETCH -> src = atomic_fetch_add(dst + off, src):
:AFADD32 [dst + off], src is imm=0x1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 {
local tmp:4 = *:4 (dst + off);
*:4 (dst + off) = *:4 (dst + off) + src:4;
src = zext(tmp);
}
:AFADD [dst + off], src is imm=0x1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 {
local tmp:8 = *:8 (dst + off);
*:8 (dst + off) = *:8 (dst + off) + src;
src = tmp;
}
# BPF_OR | BPF_FETCH -> src = atomic_fetch_or(dst + off, src):
:AFOR32 [dst + off], src is imm=0x41 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 {
local tmp:4 = *:4 (dst + off);
*:4 (dst + off) = *:4 (dst + off) | src:4;
src = zext(tmp);
}
:AFOR [dst + off], src is imm=0x41 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 {
local tmp:8 = *:8 (dst + off);
*:8 (dst + off) = *:8 (dst + off) | src;
src = tmp;
}
# BPF_AND | BPF_FETCH -> src = atomic_fetch_and(dst + off, src):
:AFAND32 [dst + off], src is imm=0x51 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 {
local tmp:4 = *:4 (dst + off);
*:4 (dst + off) = *:4 (dst + off) & src:4;
src = zext(tmp);
}
:AFAND [dst + off], src is imm=0x51 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 {
local tmp:8 = *:8 (dst + off);
*:8 (dst + off) = *:8 (dst + off) & src;
src = tmp;
}
# BPF_XOR | BPF_FETCH -> src = atomic_fetch_xor(dst + off, src):
:AFXOR32 [dst + off], src is imm=0xa1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 {
local tmp:4 = *:4 (dst + off);
*:4 (dst + off) = *:4 (dst + off) ^ src:4;
src = zext(tmp);
}
:AFXOR [dst + off], src is imm=0xa1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 {
local tmp:8 = *:8 (dst + off);
*:8 (dst + off) = *:8 (dst + off) ^ src;
src = tmp;
}
# BPF_XCHG -> src_reg = atomic_xchg(dst + off, src):
:AXCHG32 [dst + off], src is imm=0xe1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 {
local tmp:4 = *:4 (dst + off);
*:4 (dst + off) = src:4;
src = zext(tmp);
}
:AXCHG [dst + off], src is imm=0xe1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 {
local tmp:8 = *:8 (dst + off);
*:8 (dst + off) = src;
src = tmp;
}
# BPF_CMPXCHG -> R0 = atomic_cmpxchg(dst + off, R0, src):
:ACMP32 [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 {
local tmp:4 = *:4 (dst + off);
if (R0:4 != tmp) goto <notEqual>;
*:4 (dst + off) = src:4;
<notEqual>
R0 = zext(tmp);
}
:ACMP [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 {
local tmp:8 = *:8 (dst + off);
if (R0 != tmp) goto <notEqual>;
*:8 (dst + off) = src;
goto inst_next;
<notEqual>
R0 = tmp;
}
#Jump instructions (BPF_JMP, BPF_JMP32)
###############################################################################
joff: reloc is off [ reloc = inst_next + off * 8; ] { export *:8 reloc; }
jimm: reloc is imm [ reloc = inst_next + imm * 8; ] { export *:8 reloc; }
cond: "EQ" is op_alu_jmp_opcode=0x1 & op_insn_class=0x5 & dst & SRC8 { local cmp = dst == SRC8; export cmp; }
cond: "EQ" is op_alu_jmp_opcode=0x1 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 == SRC4; export cmp; }
cond: "GT" is op_alu_jmp_opcode=0x2 & op_insn_class=0x5 & dst & SRC8 { local cmp = dst > SRC8; export cmp; }
cond: "GT" is op_alu_jmp_opcode=0x2 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 > SRC4; export cmp; }
cond: "GE" is op_alu_jmp_opcode=0x3 & op_insn_class=0x5 & dst & SRC8 { local cmp = dst >= SRC8; export cmp; }
cond: "GE" is op_alu_jmp_opcode=0x3 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 >= SRC4; export cmp; }
cond: "LT" is op_alu_jmp_opcode=0xa & op_insn_class=0x5 & dst & SRC8 { local cmp = dst < SRC8; export cmp; }
cond: "LT" is op_alu_jmp_opcode=0xa & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 < SRC4; export cmp; }
cond: "LE" is op_alu_jmp_opcode=0xb & op_insn_class=0x5 & dst & SRC8 { local cmp = dst <= SRC8; export cmp; }
cond: "LE" is op_alu_jmp_opcode=0xb & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 <= SRC4; export cmp; }
cond: "NE" is op_alu_jmp_opcode=0x5 & op_insn_class=0x5 & dst & SRC8 { local cmp = dst != SRC8; export cmp; }
cond: "NE" is op_alu_jmp_opcode=0x5 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 != SRC4; export cmp; }
cond: "SET" is op_alu_jmp_opcode=0x4 & op_insn_class=0x5 & dst & SRC8 { local cmp = (dst & SRC8) != 0; export cmp; }
cond: "SET" is op_alu_jmp_opcode=0x4 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = (DST4 & SRC4) != 0; export cmp; }
cond: "SGT" is op_alu_jmp_opcode=0x6 & op_insn_class=0x5 & dst & SRC8 { local cmp = dst s> SRC8; export cmp; }
cond: "SGT" is op_alu_jmp_opcode=0x6 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 s> SRC4; export cmp; }
cond: "SGE" is op_alu_jmp_opcode=0x7 & op_insn_class=0x5 & dst & SRC8 { local cmp = dst s>= SRC8; export cmp; }
cond: "SGE" is op_alu_jmp_opcode=0x7 & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 s>= SRC4; export cmp; }
cond: "SLT" is op_alu_jmp_opcode=0xc & op_insn_class=0x5 & dst & SRC8 { local cmp = dst s< SRC8; export cmp; }
cond: "SLT" is op_alu_jmp_opcode=0xc & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 s< SRC4; export cmp; }
cond: "SLE" is op_alu_jmp_opcode=0xd & op_insn_class=0x5 & dst & SRC8 { local cmp = dst s<= SRC8; export cmp; }
cond: "SLE" is op_alu_jmp_opcode=0xd & op_insn_class=0x6 & DST4 & SRC4 { local cmp = DST4 s<= SRC4; export cmp; }
:JA joff is joff & op_alu_jmp_opcode=0x0 & op_alu_jmp_source=0 & op_insn_class=0x5 {
goto joff;
}
:JA jimm is jimm & op_alu_jmp_opcode=0x0 & op_alu_jmp_source=0 & op_insn_class=0x6 {
goto jimm;
}
:J^cond dst, SRC8, joff is joff & SRC8 & dst & cond {
if (cond) goto joff;
}
SysCall: imm is imm { export *[syscall]:1 imm; }
:CALL SysCall is imm & src=0 & op_alu_jmp_opcode=0x8 & op_alu_jmp_source=0 & op_insn_class=0x5 & SysCall {
call SysCall;
}
disp32: reloc is imm [ reloc = inst_next + imm * 8; ] { export *:4 reloc; }
:CALL disp32 is imm & src=1 & op_alu_jmp_opcode=0x8 & op_alu_jmp_source=0 & op_insn_class=0x5 & disp32 {
call disp32;
}
# GCC encoding and LLVM 19.1+ encoding
:CALLX dst is op_alu_jmp_opcode=0x8 & op_alu_jmp_source=1 & op_insn_class=0x5 & src=0 & imm=0 & dst {
call [dst];
}
# LLVM encoding used until LLVM 19.1
# Introduced in https://github.com/llvm/llvm-project/commit/9a67245d881f4cf89fd8f897ae2cd0bccec49496
# Modified in https://github.com/llvm/llvm-project/commit/c43ad6c0fddac0bbed5e881801dd2bc2f9eeba2d
:CALLX llvm_reg_callx is op_alu_jmp_opcode=0x8 & op_alu_jmp_source=1 & op_insn_class=0x5 & dst=0 & src=0 & llvm_imm_callx_zero=0 & llvm_reg_callx {
call [llvm_reg_callx];
}
# Both CALLX encodings are matched when both dst and imm are zero
:CALLX R0 is op_alu_jmp_opcode=0x8 & op_alu_jmp_source=1 & op_insn_class=0x5 & dst=0 & src=0 & imm=0 & R0 {
call [R0];
}
:EXIT is op_alu_jmp_opcode=0x9 & op_alu_jmp_source=0 & op_insn_class=0x5 { return [*:8 R10]; }