An assembler for eBPF programs written in an Intel-like assembly syntax.
ebpf_asm.py <sourcefile> [...] -o <outputfile>
It's great that you can write eBPF programs in C and then compile them with clang/LLVM. But clang's really rather big, and sometimes you don't have room for a gigantic toolchain — and your program is really small and simple. For such a case, writing the program directly in assembly is a feasible alternative.
I chose the Intel syntax because my ability to read assembly code is directly proportional to how much it resembles Z80. If you dislike that as much as I dislike AT&T-syntax x86 assembly, this may not be the tool for you.
ebpf_asm itself and the supplied header files (
provided under the MIT license (see comment block at the top of
The included example programs (
call.s) are dual
Comments are introduced with a semicolon
; and continue to end of line.
Following sections will consist of program text (i.e. executable instructions).
Following sections will consist of program data (currently just asciz strings).
.section maps starts (or continues) the maps section, containing map
.section name starts (or continues) a section with the given name.
This section will contain either text or data (depending on the last
.data directive) and will run until the next
.include name includes the specified file (relative to the cwd) textually.
.equ name, immediate defines the given name to equal the
could be a literal, or the name of another equate). The
immediate does not
accept a size suffix.
name is any string which does not start with a digit and does not contain a
,). It may contain internal whitespace.
Register names are legal as equate names, but where an operand could be either it will be treated as a register name. However, operands which are required to be immediates, not registers, will treat it as an equate. This is potentially very confusing, so don't do this!
An equate can be defined with a
name that ends in a size suffix, but accessing
the equate in a context where a size suffix would be allowed will require using
two size suffixes. This is also confusing, so don't do this either!
An equate can be redefined; the new value takes effect from the following line. This could also be confusing, so maybe you shouldn't do it.
A label consists of a sequence of alphanumeric characters followed by a colon
:, which is omitted when referring to the label. The label points at the
following instruction (in .text) or datum (in .data). A label may not begin
with a digit, since that could cause confusion if references to the label look
like numeric literals. (Strictly speaking we could allow this, because jumps
always prefix their literals with
-, but we forbid it so that when you
+ you get a meaningful error.)
Note that code cannot appear on the same line as the label! This is something we probably ought to support, but currently don't.
Text sections consist of instructions generally in the form
op dst, src,
though a few instructions take more (or fewer) operands.
Operands typically may be either register names (
fp as a
r10) or literals (decimal, 0octal, 0xhex, or an equate name).
Literals normally must fit in a 32-bit signed integer, except for
ld reg.q, imm. Some instructions can also take
[reg+disp] for some operands.
Operands in many cases can also include a size suffix, a dot
. followed by a
.wword (16 bits)
.llong (32 bits)
.qquad (64 bits)
The load instruction
ld dst, src is used for register-to-register, register-
to-memory and memory-to-register moves.
If both operands have size suffixes, they must match; if neither has, then quad
.q) is assumed.
ld dst_reg, src_reg
ld dst_reg, src_imm
Size must be quad (
.q) or long (
.l). For size quad,
src_imm may be a map
name (defined in the maps section); otherwise, it is an unsigned 64-bit
ld [ptr_reg+disp], src_reg
ld [ptr_reg+disp], src_imm
disp may be omitted (as
ld [ptr_reg], src) or negative (as
ld [ptr_reg-disp], src). It is a signed 16-bit quantity (i.e. word) and does
not accept a size suffix.
A size suffix goes outside the brackets (as
ld [ptr_reg].sz, src), not inside
(since the pointer must always be full-sized).
Regardless of size suffix,
src_imm must fit in a signed 32-bit integer.
ld dst_reg, [ptr_reg+disp]
The same notes apply to the
[ptr_reg+disp] as for Register-to-memory, above.
The packet-load instruction
ldpkt r0, src is used for reading packet data into
registers, in a complicated way for historical reasons. It represents the
BPF_ABS and BPF_IND modes of the BPF_LD opcode, which can only be used in
socket filter, sched_cls and sched_act programs.
ldpkt r0, [disp]
ldpkt r0, [off_reg+disp]
If both operands have size suffixes, they must match; if neither has, then,
unlike most other instructions, long (
.l) is assumed. This is because
these instructions, being holdovers from classic BPF, do not have quad-sized
forms (which would be rejected by the verifier). The displacement
disp may be
omitted from the latter form, and in either case does not accept a size suffix.
There are other restrictions on its use: the destination register must be
r6 must contain a pointer to the sk_buff, and registers
clobbered. The value read will be converted to host-endianness.
Unless you know you want this, you probably want an ordinary memory-to-register load using a packet-pointer, instead.
See the kernel's BPF documentation for further enlightenment.
xadd [ptr_reg+disp], src_reg
Atomic memory add (BPF_STX | BPF_XADD). The same notes apply to the
[ptr_reg+disp] as for
ld instructions, above.
The relative jump instruction,
jr offset or
jr cc, dst, src, offset, is used
to jump elsewhere in the program.
offset may be either a signed literal (the
+ must be included for positive values) or a label name; it does not accept a
jr cc, dst, src, offset
Jump if condition
cc holds on
dst (a register) and
src (a register or
immediate). There are multiple synonyms for each condition.
z: Jump if
dstis equal to
nz: Jump if
dstis not equal to
>: Jump if
dstis strictly greater than
>=: Jump if
dstis greater than or equal to
<: Jump if
dstis strictly less than
<=: Jump if
dstis less than or equal to
s>: Signed greater-than.
p: Signed greater-than-or-equal.
n: Signed less-than.
s<=: Signed less-than-or-equal.
and: Jump if the bitwise AND of
src registers are considered as quads (
is considered a long (
.l). Explicit size suffixes are not accepted.
In eBPF, the original call instruction calls a helper function identified by an
integer (see defs.i), taking arguments
r5 and returning in
registers are clobbered, while the remaining registers (
are preserved across the call. Consult the kernel's eBPF documentation for
helper_function_id does not accept a size suffix.
Since Linux 4.16, eBPF programs can make calls to other functions within the
same program. Currently these must be statically linked; the kernel is unable
to resolve the relocation entries at program load time. Thus the
call instruction is similar to that on a
jr. However, since negative
numbers are accepted as
helper_function_ids, a call with a negative literal
offset has to be written like
call +-1 to mark it as an
Thus, the possible forms of BPF-to-BPF call are as follows:
In most circumstances, however, only the first (
label) form is likely to be
useful. A simple example of usage can be found in the
call.s sample program.
Exit the program, returning the current value of
add, sub, mul, div, mod, and, or, xor, lsh, rsh, arsh
alu_op dst_reg, src_reg
alu_op dst_reg, src_imm
Size must be either quad (
.q) or long (
.l). If both operands have size
suffixes, they must match; if neither has, then quad (
.q) is assumed.
src_imm is a signed 32-bit quantity, even when size is quad (
Note the slight oddity that even for
arsh instructions (where
the size of the source operand should be irrelevant), the size suffix rules
still apply - e.g.
lsh r1, 2.l is a 32-bit shift.
Negate the specified register. Size must be either quad (
.q) or long (
if omitted, quad (
.q) is assumed.
end le, dst_reg.sz
end be, dst_reg.sz
Converts the specified register between Little or Big Endian and CPU endianness.
.sz must be one of quad (
.q), long (
.l) or word (
.w); if omitted,
.q) is assumed.
The same operation is used for conversions both from and to CPU endianness.
May only appear in
name: type, key_size, value_size, max_entries
name: type, key_size, value_size, max_entries, flags
Defines a map with the given
name, which can subsequently be used as a quad
type is an integer ID (see defs.i).
are the sizes, in bytes, of the map key and map value.
max_entries is the
maximum number of entries this map can hold.
flags is one or more of the following letters:
Consult the kernel documentation for details of these flags and of the various map types.
As it is not possible to reference .data sections from eBPF code, they have rather limited uses; hence the assembler has rather limited support for them.
asciz "String text"
NUL-terminated ASCII string. Typically this is only used for the following snippet:
.data .section license _license: asciz "GPL"
The assembler generates ELF object files, suitable for passing to standard tools
ip link set dev ethX xdp obj object-file.o verb. Currently
only little-endian output (aka 'bpfel') is supported.
ebpf_asm has a suite of regression tests: run
./regression.py. If all is
well, there should be no output, and the return code will be zero. For verbose
mode, use the switch
Ideas for the future.
- Test behaviour around trying to use labels as immediates/displacements.
- Tests for map definitions.
- "Loose mode" that allows bad things like registers
rawinstruction that takes a 5-tuple, invalid sizes to various ops, etc.; in order to construct bad binaries to test the kernel's verifier.
- Support big-endian output ('bpfeb') and maybe default to host endianness.
- Constant expressions. Wherever a literal is expected, we should be able to
have an expression instead. We can even use
(parentheses)for grouping, since indirection uses