• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alan-Jowett / ebpf-verifier / 22316701280

22 Feb 2026 08:43PM UTC coverage: 88.93% (+0.9%) from 88.002%
22316701280

push

github

web-flow
Human-friendly CLI output for bin/prevail (#1042)

* Replace CSV output with human-friendly PASS/FAIL CLI output

The default output was `{0|1},{seconds},{memory_kb}` — a CSV row for
benchmarking scripts that is unfriendly for humans. Benchmarking is
better done externally (time, /usr/bin/time -v).

New output: `PASS: section/function` or `FAIL: section/function` with
the first error and a hint line pointing to --failure-slice / -v.
Add -q/--quiet (exit code only) and --cfg (replaces --domain cfg).

Remove dead code: --domain option (linux, stats, zoneCrab selectors),
linux_verifier, memsize helpers, collect_stats/stats_headers, fnv1a64,
@headers special filename, bin/check alias, and stale benchmark scripts.

Move src/main/check.cpp → src/main.cpp (simplified, rewritten).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

* Graduate loop3.o from skip to expected failure

The >4x performance improvement means loop3.o no longer hangs.
It now completes quickly but is rejected due to type precision
loss through the loop join (VerifierTypeTracking).

* Remove redundant install exclude, fix doc path

- Remove `PATTERN "main.cpp" EXCLUDE` from install — the glob only
  matches *.hpp/*.h so main.cpp would never be included anyway.
- Fix `./prevail` → `./bin/prevail` in docs/architecture.md for
  consistency with the actual binary location.

---------

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

1 of 1 new or added line in 1 file covered. (100.0%)

198 existing lines in 11 files now uncovered.

13159 of 14797 relevant lines covered (88.93%)

4658715.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.89
/src/test/test_marshal.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <ranges>
5

6
#include <catch2/catch_all.hpp>
7

8
#include "ebpf_verifier.hpp"
9
#include "ir/marshal.hpp"
10
#include "ir/program.hpp"
11
#include "ir/unmarshal.hpp"
12
#include "linux/gpl/spec_type_descriptors.hpp"
13

14
using namespace prevail;
15

16
// Below we define a tample of instruction templates that specify
17
// what values each field are allowed to contain.  We first define
18
// a set of sentinel values that mean certain types of wildcards.
19
// For example, MEM_OFFSET and JMP_OFFSET are different wildcards
20
// for the 'offset' field of an instruction.  Any non-sentinel values
21
// in an instruction template are treated as literals.
22

23
constexpr int MEM_OFFSET = 3;                           // Any valid memory offset value.
24
constexpr int JMP_OFFSET = 5;                           // Any valid jump offset value.
25
constexpr int DST = 7;                                  // Any destination register number.
26
constexpr int HELPER_ID = 8;                            // Any helper ID.
27
constexpr int SRC = 9;                                  // Any source register number.
28
constexpr int IMM = -1;                                 // Any imm value.
29
constexpr int INVALID_REGISTER = R10_STACK_POINTER + 1; // Not a valid register.
30

31
struct EbpfInstructionTemplate {
32
    EbpfInst inst;
33
    bpf_conformance_groups_t groups;
34
};
35

36
// The following table is derived from the table in the Appendix of the
37
// BPF ISA specification (https://datatracker.ietf.org/doc/draft-ietf-bpf-isa/).
38
static const EbpfInstructionTemplate instruction_template[] = {
39
    // {opcode, dst, src, offset, imm}, group
40
    {{0x04, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
41
    {{0x05, 0, 0, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
42
    {{0x06, 0, 0, 0, JMP_OFFSET}, bpf_conformance_groups_t::base32},
43
    {{0x07, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
44
    {{0x0c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
45
    {{0x0f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
46
    {{0x14, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
47
    {{0x15, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
48
    {{0x16, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
49
    {{0x17, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
50
    {{0x18, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
51
    {{0x18, DST, 1, 0, IMM}, bpf_conformance_groups_t::base64},
52
    {{0x18, DST, 2, 0, IMM}, bpf_conformance_groups_t::base64},
53
    {{0x18, DST, 3, 0, IMM}, bpf_conformance_groups_t::base64},
54
    {{0x18, DST, 4, 0, IMM}, bpf_conformance_groups_t::base64},
55
    {{0x18, DST, 5, 0, IMM}, bpf_conformance_groups_t::base64},
56
    {{0x18, DST, 6, 0, IMM}, bpf_conformance_groups_t::base64},
57
    {{0x1c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
58
    {{0x1d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
59
    {{0x1e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
60
    {{0x1f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
61
    {{0x20, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet},
62
    {{0x24, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32},
63
    {{0x25, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
64
    {{0x26, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
65
    {{0x27, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64},
66
    {{0x28, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet},
67
    {{0x2c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32},
68
    {{0x2d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
69
    {{0x2e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
70
    {{0x2f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64},
71
    {{0x30, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet},
72
    {{0x34, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32},
73
    {{0x34, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul32},
74
    {{0x35, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
75
    {{0x36, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
76
    {{0x37, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64},
77
    {{0x37, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul64},
78
    {{0x3c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32},
79
    {{0x3c, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul32},
80
    {{0x3d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
81
    {{0x3e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
82
    {{0x3f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64},
83
    {{0x3f, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul64},
84
    {{0x40, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet},
85
    {{0x44, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
86
    {{0x45, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
87
    {{0x46, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
88
    {{0x47, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
89
    {{0x48, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet},
90
    {{0x4c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
91
    {{0x4d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
92
    {{0x4e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
93
    {{0x4f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
94
    {{0x50, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet},
95
    {{0x54, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
96
    {{0x55, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
97
    {{0x56, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
98
    {{0x57, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
99
    {{0x5c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
100
    {{0x5d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
101
    {{0x5e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
102
    {{0x5f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
103
    {{0x61, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
104
    {{0x62, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32},
105
    {{0x63, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
106
    {{0x64, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
107
    {{0x65, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
108
    {{0x66, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
109
    {{0x67, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
110
    {{0x69, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
111
    {{0x6a, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32},
112
    {{0x6b, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
113
    {{0x6c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
114
    {{0x6d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
115
    {{0x6e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
116
    {{0x6f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
117
    {{0x71, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
118
    {{0x72, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32},
119
    {{0x73, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32},
120
    {{0x74, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
121
    {{0x75, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
122
    {{0x76, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
123
    {{0x77, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
124
    {{0x79, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
125
    {{0x7a, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base64},
126
    {{0x7b, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
127
    {{0x7c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
128
    {{0x7d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
129
    {{0x7e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
130
    {{0x7f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
131
    {{0x81, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
132
    {{0x84, DST, 0, 0, 0}, bpf_conformance_groups_t::base32},
133
    {{0x85, 0, 0, 0, HELPER_ID}, bpf_conformance_groups_t::base32},
134
    {{0x85, 0, 1, 0, JMP_OFFSET}, bpf_conformance_groups_t::base32},
135
    {{0x85, 0, 2, 0, IMM}, bpf_conformance_groups_t::base32},
136
    {{0x87, DST, 0, 0, 0}, bpf_conformance_groups_t::base64},
137
    {{0x89, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
138
    {{0x8d, DST, 0, 0, 0}, bpf_conformance_groups_t::callx},
139
    {{0x91, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64},
140
    {{0x94, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32},
141
    {{0x94, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul32},
142
    {{0x95, 0, 0, 0, 0}, bpf_conformance_groups_t::base32},
143
    {{0x97, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64},
144
    {{0x97, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul64},
145
    {{0x9c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32},
146
    {{0x9c, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul32},
147
    {{0x9f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64},
148
    {{0x9f, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul64},
149
    {{0xa4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
150
    {{0xa5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
151
    {{0xa6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
152
    {{0xa7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
153
    {{0xac, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
154
    {{0xad, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
155
    {{0xae, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
156
    {{0xaf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
157
    {{0xb4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
158
    {{0xb5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
159
    {{0xb6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
160
    {{0xb7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
161
    {{0xbc, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
162
    {{0xbc, DST, SRC, 8, 0}, bpf_conformance_groups_t::base32},
163
    {{0xbc, DST, SRC, 16, 0}, bpf_conformance_groups_t::base32},
164
    {{0xbd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
165
    {{0xbe, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
166
    {{0xbf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
167
    {{0xbf, DST, SRC, 8, 0}, bpf_conformance_groups_t::base64},
168
    {{0xbf, DST, SRC, 16, 0}, bpf_conformance_groups_t::base64},
169
    {{0xbf, DST, SRC, 32, 0}, bpf_conformance_groups_t::base64},
170
    {{0xc3, DST, SRC, MEM_OFFSET, 0x00}, bpf_conformance_groups_t::atomic32},
171
    {{0xc3, DST, SRC, MEM_OFFSET, 0x01}, bpf_conformance_groups_t::atomic32},
172
    {{0xc3, DST, SRC, MEM_OFFSET, 0x40}, bpf_conformance_groups_t::atomic32},
173
    {{0xc3, DST, SRC, MEM_OFFSET, 0x41}, bpf_conformance_groups_t::atomic32},
174
    {{0xc3, DST, SRC, MEM_OFFSET, 0x50}, bpf_conformance_groups_t::atomic32},
175
    {{0xc3, DST, SRC, MEM_OFFSET, 0x51}, bpf_conformance_groups_t::atomic32},
176
    {{0xc3, DST, SRC, MEM_OFFSET, 0xa0}, bpf_conformance_groups_t::atomic32},
177
    {{0xc3, DST, SRC, MEM_OFFSET, 0xa1}, bpf_conformance_groups_t::atomic32},
178
    {{0xc3, DST, SRC, MEM_OFFSET, 0xe1}, bpf_conformance_groups_t::atomic32},
179
    {{0xc3, DST, SRC, MEM_OFFSET, 0xf1}, bpf_conformance_groups_t::atomic32},
180
    {{0xc4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32},
181
    {{0xc5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
182
    {{0xc6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
183
    {{0xc7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64},
184
    {{0xcc, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32},
185
    {{0xcd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
186
    {{0xce, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
187
    {{0xcf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64},
188
    {{0xd4, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32},
189
    {{0xd4, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32},
190
    {{0xd4, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64},
191
    {{0xd5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64},
192
    {{0xd6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32},
193
    {{0xd7, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32},
194
    {{0xd7, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32},
195
    {{0xd7, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64},
196
    {{0xdb, DST, SRC, MEM_OFFSET, 0x00}, bpf_conformance_groups_t::atomic64},
197
    {{0xdb, DST, SRC, MEM_OFFSET, 0x01}, bpf_conformance_groups_t::atomic64},
198
    {{0xdb, DST, SRC, MEM_OFFSET, 0x40}, bpf_conformance_groups_t::atomic64},
199
    {{0xdb, DST, SRC, MEM_OFFSET, 0x41}, bpf_conformance_groups_t::atomic64},
200
    {{0xdb, DST, SRC, MEM_OFFSET, 0x50}, bpf_conformance_groups_t::atomic64},
201
    {{0xdb, DST, SRC, MEM_OFFSET, 0x51}, bpf_conformance_groups_t::atomic64},
202
    {{0xdb, DST, SRC, MEM_OFFSET, 0xa0}, bpf_conformance_groups_t::atomic64},
203
    {{0xdb, DST, SRC, MEM_OFFSET, 0xa1}, bpf_conformance_groups_t::atomic64},
204
    {{0xdb, DST, SRC, MEM_OFFSET, 0xe1}, bpf_conformance_groups_t::atomic64},
205
    {{0xdb, DST, SRC, MEM_OFFSET, 0xf1}, bpf_conformance_groups_t::atomic64},
206
    {{0xdc, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32},
207
    {{0xdc, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32},
208
    {{0xdc, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64},
209
    {{0xdd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64},
210
    {{0xde, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32},
211
};
212

213
// Verify that we can successfully unmarshal an instruction.
214
static void check_unmarshal_succeed(const EbpfInst& ins, const ebpf_platform_t& platform = g_ebpf_platform_linux) {
670✔
215
    const ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
1,675✔
216
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
670✔
217
    const InstructionSeq parsed =
335✔
218
        std::get<InstructionSeq>(unmarshal(RawProgram{"", "", 0, "", {ins, exit, exit}, info}, thread_local_options));
2,680✔
219
    REQUIRE(parsed.size() == 3);
670✔
220
}
670✔
221

222
// Verify that we can successfully unmarshal a 64-bit immediate instruction.
223
static void check_unmarshal_succeed(EbpfInst inst1, EbpfInst inst2,
28✔
224
                                    const ebpf_platform_t& platform = g_ebpf_platform_linux) {
225
    const ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
70✔
226
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
28✔
227
    const InstructionSeq parsed = std::get<InstructionSeq>(
14✔
228
        unmarshal(RawProgram{"", "", 0, "", {inst1, inst2, exit, exit}, info}, thread_local_options));
112✔
229
    REQUIRE(parsed.size() == 3);
28✔
230
}
28✔
231

232
// Verify that if we unmarshal an instruction and then re-marshal it,
233
// we get what we expect.
234
static void compare_unmarshal_marshal(const EbpfInst& ins, const EbpfInst& expected_result,
22✔
235
                                      const ebpf_platform_t& platform = g_ebpf_platform_linux) {
236
    ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
55✔
237
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
22✔
238
    const InstructionSeq inst_seq =
11✔
239
        std::get<InstructionSeq>(unmarshal(RawProgram{"", "", 0, "", {ins, exit, exit}, info}, thread_local_options));
88✔
240
    REQUIRE(inst_seq.size() == 3);
22✔
241
    auto [_, single, _2] = inst_seq.front();
22✔
242
    (void)_;  // unused
11✔
243
    (void)_2; // unused
11✔
244
    std::vector<EbpfInst> marshaled = marshal(single, 0);
22✔
245
    REQUIRE(marshaled.size() == 1);
22✔
246
    EbpfInst result = marshaled.back();
22✔
247
    REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0);
22✔
248
}
22✔
249

250
// Verify that if we unmarshal two instructions and then re-marshal it,
251
// we get what we expect.
252
static void compare_unmarshal_marshal(const EbpfInst& ins1, const EbpfInst& ins2, const EbpfInst& expected_result) {
4✔
253
    ProgramInfo info{.platform = &g_ebpf_platform_linux,
4✔
254
                     .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")};
10✔
255
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
4✔
256
    InstructionSeq parsed = std::get<InstructionSeq>(
2✔
257
        unmarshal(RawProgram{"", "", 0, "", {ins1, ins2, exit, exit}, info}, thread_local_options));
16✔
258
    REQUIRE(parsed.size() == 3);
4✔
259
    auto [_, single, _2] = parsed.front();
4✔
260
    (void)_;  // unused
2✔
261
    (void)_2; // unused
2✔
262
    std::vector<EbpfInst> marshaled = marshal(single, 0);
4✔
263
    REQUIRE(marshaled.size() == 1);
4✔
264
    EbpfInst result = marshaled.back();
4✔
265
    REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0);
4✔
266
}
4✔
267

268
// Verify that if we unmarshal a 64-bit immediate instruction and then re-marshal it,
269
// we get what we expect.
270
static void compare_unmarshal_marshal(const EbpfInst& ins1, const EbpfInst& ins2, const EbpfInst& expected_result1,
16✔
271
                                      const EbpfInst& expected_result2) {
272
    ProgramInfo info{.platform = &g_ebpf_platform_linux,
16✔
273
                     .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")};
40✔
274
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
16✔
275
    const InstructionSeq inst_seq = std::get<InstructionSeq>(
8✔
276
        unmarshal(RawProgram{"", "", 0, "", {ins1, ins2, exit, exit}, info}, thread_local_options));
64✔
277
    REQUIRE(inst_seq.size() == 3);
16✔
278
    auto [_, single, _2] = inst_seq.front();
16✔
279
    (void)_;  // unused
8✔
280
    (void)_2; // unused
8✔
281
    std::vector<EbpfInst> marshaled = marshal(single, 0);
16✔
282
    REQUIRE(marshaled.size() == 2);
16✔
283
    EbpfInst result1 = marshaled.front();
16✔
284
    REQUIRE(memcmp(&expected_result1, &result1, sizeof(result1)) == 0);
16✔
285
    EbpfInst result2 = marshaled.back();
16✔
286
    REQUIRE(memcmp(&expected_result2, &result2, sizeof(result2)) == 0);
16✔
287
}
16✔
288

289
// Verify that if we marshal an instruction and then unmarshal it,
290
// we get the original.
291
static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false,
336✔
292
                                      const ebpf_platform_t& platform = g_ebpf_platform_linux) {
293
    ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
840✔
294
    const InstructionSeq inst_seq =
168✔
295
        std::get<InstructionSeq>(unmarshal(RawProgram{"", "", 0, "", marshal(ins, 0), info}, thread_local_options));
1,176✔
296
    REQUIRE(inst_seq.size() == 1);
336✔
297
    auto [_, single, _2] = inst_seq.back();
336✔
298
    (void)_;  // unused
168✔
299
    (void)_2; // unused
168✔
300
    REQUIRE(single == ins);
504✔
301
}
336✔
302

303
static void check_marshal_unmarshal_fail(const Instruction& ins, const std::string& expected_error_message,
56✔
304
                                         const ebpf_platform_t& platform = g_ebpf_platform_linux) {
305
    const ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
140✔
306
    auto result = unmarshal(RawProgram{"", "", 0, "", marshal(ins, 0), info}, thread_local_options);
168✔
307
    auto* error_message = std::get_if<std::string>(&result);
56✔
308
    REQUIRE(error_message != nullptr);
56✔
309
    REQUIRE(*error_message == expected_error_message);
84✔
310
}
56✔
311

312
static void check_unmarshal_fail(EbpfInst inst, const std::string& expected_error_message,
1,146✔
313
                                 const ebpf_platform_t& platform = g_ebpf_platform_linux) {
314
    ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
2,865✔
315
    std::vector insns = {inst};
1,719✔
316
    auto result = unmarshal(RawProgram{"", "", 0, "", insns, info}, thread_local_options);
3,438✔
317
    auto* error_message = std::get_if<std::string>(&result);
1,146✔
318
    REQUIRE(error_message != nullptr);
1,146✔
319
    REQUIRE(*error_message == expected_error_message);
1,719✔
320
}
1,146✔
321

322
static void check_unmarshal_fail_goto(EbpfInst inst, const std::string& expected_error_message,
226✔
323
                                      const ebpf_platform_t& platform = g_ebpf_platform_linux) {
324
    ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
565✔
325
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
226✔
326
    std::vector insns{inst, exit, exit};
339✔
327
    auto result = unmarshal(RawProgram{"", "", 0, "", insns, info}, thread_local_options);
678✔
328
    auto* error_message = std::get_if<std::string>(&result);
226✔
329
    REQUIRE(error_message != nullptr);
226✔
330
    REQUIRE(*error_message == expected_error_message);
339✔
331
}
226✔
332

333
// Check that unmarshaling a 64-bit immediate instruction fails.
334
static void check_unmarshal_fail(EbpfInst inst1, EbpfInst inst2, const std::string& expected_error_message,
54✔
335
                                 const ebpf_platform_t& platform = g_ebpf_platform_linux) {
336
    ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
135✔
337
    std::vector insns{inst1, inst2};
81✔
338
    auto result = unmarshal(RawProgram{"", "", 0, "", insns, info}, thread_local_options);
162✔
339
    auto* error_message = std::get_if<std::string>(&result);
54✔
340
    REQUIRE(error_message != nullptr);
54✔
341
    REQUIRE(*error_message == expected_error_message);
81✔
342
}
54✔
343

344
static Call unmarshal_single_call(const EbpfInst& call_inst, const ProgramInfo& info) {
4✔
345
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
4✔
346
    const auto parsed = unmarshal(RawProgram{"", "", 0, "", {call_inst, exit, exit}, info}, thread_local_options);
14✔
347
    auto* inst_seq = std::get_if<InstructionSeq>(&parsed);
4✔
348
    REQUIRE(inst_seq != nullptr);
4✔
349
    REQUIRE(inst_seq->size() == 3);
4✔
350
    auto* call = std::get_if<Call>(&std::get<1>((*inst_seq)[0]));
4✔
351
    REQUIRE(call != nullptr);
4✔
352
    return *call;
6✔
353
}
4✔
354

355
template <typename T>
356
static bool has_assertion(const std::vector<Assertion>& assertions, const T& expected) {
10✔
357
    return std::any_of(assertions.begin(), assertions.end(), [&](const Assertion& assertion) {
29✔
358
        const auto* typed = std::get_if<T>(&assertion);
25✔
359
        return typed != nullptr && *typed == expected;
24✔
360
    });
5✔
361
}
362

363
static constexpr auto ws = {1, 2, 4, 8};
364

365
TEST_CASE("disasm_marshal", "[disasm][marshal]") {
28✔
366
    SECTION("Bin") {
28✔
367
        SECTION("Reg src") {
6✔
368
            auto ops = {Bin::Op::MOV,  Bin::Op::ADD,  Bin::Op::SUB,    Bin::Op::MUL,     Bin::Op::UDIV,   Bin::Op::UMOD,
2✔
369
                        Bin::Op::OR,   Bin::Op::AND,  Bin::Op::LSH,    Bin::Op::RSH,     Bin::Op::ARSH,   Bin::Op::XOR,
370
                        Bin::Op::SDIV, Bin::Op::SMOD, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32};
2✔
371
            for (const auto op : ops) {
36✔
372
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = true});
34✔
373
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = false});
51✔
374
            }
375
        }
6✔
376
        SECTION("Imm src") {
6✔
377
            // MOVSX* instructions are not defined for Imm, only Reg.
378
            auto ops = {Bin::Op::MOV,  Bin::Op::ADD, Bin::Op::SUB,  Bin::Op::MUL, Bin::Op::UDIV,
4✔
379
                        Bin::Op::UMOD, Bin::Op::OR,  Bin::Op::AND,  Bin::Op::LSH, Bin::Op::RSH,
380
                        Bin::Op::ARSH, Bin::Op::XOR, Bin::Op::SDIV, Bin::Op::SMOD};
4✔
381
            for (const auto op : ops) {
60✔
382
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Imm{2}, .is64 = false});
56✔
383
                compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Imm{2}, .is64 = true});
84✔
384
            }
385
            SECTION("LDDW") {
4✔
386
                compare_marshal_unmarshal(
1✔
387
                    Bin{.op = Bin::Op::MOV, .dst = Reg{1}, .v = Imm{2}, .is64 = true, .lddw = true}, true);
4✔
388
            }
4✔
389
            SECTION("r10") {
4✔
390
                check_marshal_unmarshal_fail(Bin{.op = Bin::Op::ADD, .dst = Reg{10}, .v = Imm{4}, .is64 = true},
4✔
391
                                             "0: invalid target r10\n");
392
            }
4✔
393
        }
6✔
394
    }
28✔
395
    SECTION("Neg") {
28✔
396
        compare_marshal_unmarshal(Un{.op = Un::Op::NEG, .dst = Reg{1}, .is64 = false});
2✔
397
        compare_marshal_unmarshal(Un{.op = Un::Op::NEG, .dst = Reg{1}, .is64 = true});
3✔
398
    }
28✔
399
    SECTION("Endian") {
28✔
400
        // FIX: `.is64` comes from the instruction class (BPF_ALU or BPF_ALU64) but is unused since it can be derived
401
        // from `.op`.
402
        {
1✔
403
            auto ops = {
2✔
404
                Un::Op::BE16, Un::Op::BE32, Un::Op::BE64, Un::Op::LE16, Un::Op::LE32, Un::Op::LE64,
405
            };
2✔
406
            for (const auto op : ops) {
14✔
407
                compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = false});
18✔
408
            }
409
        }
410
        {
1✔
411
            auto ops = {
2✔
412
                Un::Op::SWAP16,
413
                Un::Op::SWAP32,
414
                Un::Op::SWAP64,
415
            };
2✔
416
            for (const auto op : ops) {
8✔
417
                compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = true});
9✔
418
            }
419
        }
420
    }
28✔
421

422
    SECTION("LoadMapFd") { compare_marshal_unmarshal(LoadMapFd{.dst = Reg{1}, .mapfd = 1}, true); }
29✔
423
    SECTION("LoadMapAddress") {
28✔
424
        compare_marshal_unmarshal(LoadMapAddress{.dst = Reg{1}, .mapfd = 1, .offset = 4}, true);
3✔
425
    }
28✔
426

427
    SECTION("Jmp") {
28✔
428
        auto ops = {Condition::Op::EQ, Condition::Op::GT, Condition::Op::GE, Condition::Op::SET,
6✔
429
                    // Condition::Op::NSET, does not exist in ebpf
430
                    Condition::Op::NE, Condition::Op::SGT, Condition::Op::SGE, Condition::Op::LT, Condition::Op::LE,
431
                    Condition::Op::SLT, Condition::Op::SLE};
6✔
432
        SECTION("goto offset") {
6✔
433
            EbpfInst jmp_offset{.opcode = INST_OP_JA16, .offset = 1};
2✔
434
            compare_unmarshal_marshal(jmp_offset, jmp_offset);
2✔
435

436
            // JA32 +1 is equivalent to JA16 +1 since the offset fits in 16 bits.
437
            compare_unmarshal_marshal(EbpfInst{.opcode = INST_OP_JA32, .imm = 1}, jmp_offset);
2✔
438
        }
6✔
439
        SECTION("Reg right") {
6✔
440
            for (const auto op : ops) {
24✔
441
                Condition cond{.op = op, .left = Reg{1}, .right = Reg{2}, .is64 = true};
22✔
442
                compare_marshal_unmarshal(Jmp{.cond = cond, .target = Label(0)});
44✔
443

444
                // The following should fail unmarshalling since it jumps past the end of the instruction set.
445
                check_marshal_unmarshal_fail(Jmp{.cond = cond, .target = Label(1)}, "0: jump out of bounds\n");
55✔
446
            }
447
        }
6✔
448
        SECTION("Imm right") {
6✔
449
            for (const auto op : ops) {
24✔
450
                Condition cond{.op = op, .left = Reg{1}, .right = Imm{2}, .is64 = true};
22✔
451
                compare_marshal_unmarshal(Jmp{.cond = cond, .target = Label(0)});
44✔
452

453
                // The following should fail unmarshalling since it jumps past the end of the instruction set.
454
                check_marshal_unmarshal_fail(Jmp{.cond = cond, .target = Label(1)}, "0: jump out of bounds\n");
55✔
455
            }
456
        }
6✔
457
    }
28✔
458

459
    SECTION("Call") {
28✔
460
        for (int func : {1, 17}) {
6✔
461
            compare_marshal_unmarshal(Call{func});
6✔
462
        }
463

464
        // Test callx without support: decode still succeeds.
465
        check_unmarshal_succeed(EbpfInst{.opcode = INST_OP_CALLX});
2✔
466

467
        // Test callx with support.  Note that callx puts the register number in 'dst' not 'src'.
468
        ebpf_platform_t platform = g_ebpf_platform_linux;
2✔
469
        platform.supported_conformance_groups |= bpf_conformance_groups_t::callx;
2✔
470
        compare_marshal_unmarshal(Callx{8}, false, platform);
2✔
471
        EbpfInst callx{.opcode = INST_OP_CALLX, .dst = 8};
2✔
472
        compare_unmarshal_marshal(callx, callx, platform);
2✔
473
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .dst = 11}, "0: bad register\n", platform);
3✔
474
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .dst = 8, .imm = 8}, "0: nonzero imm for op 0x8d\n", platform);
2✔
475

476
        // clang prior to v19 put the register into 'imm' instead of 'dst' so we treat it as equivalent.
477
        compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x8d */ INST_OP_CALLX, .imm = 8}, callx, platform);
2✔
478
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .imm = 11}, "0: bad register\n", platform);
3✔
479
        check_unmarshal_fail({.opcode = INST_OP_CALLX, .imm = -1}, "0: bad register\n", platform);
4✔
480
    }
28✔
481

482
    SECTION("Exit") { compare_marshal_unmarshal(Exit{}); }
29✔
483

484
    SECTION("Packet") {
28✔
485
        for (int w : ws) {
10✔
486
            if (w != 8) {
8✔
487
                compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = {}});
6✔
488
                compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = Reg{2}});
9✔
489
            }
490
        }
491
    }
28✔
492

493
    SECTION("Atomic") {
28✔
494
        for (int w : ws) {
10✔
495
            if (w == 4 || w == 8) {
8✔
496
                Deref access{.width = w, .basereg = Reg{2}, .offset = 17};
4✔
497
                compare_marshal_unmarshal(
2✔
498
                    Atomic{.op = Atomic::Op::ADD, .fetch = false, .access = access, .valreg = Reg{1}});
4✔
499
                compare_marshal_unmarshal(
2✔
500
                    Atomic{.op = Atomic::Op::ADD, .fetch = true, .access = access, .valreg = Reg{1}});
4✔
501
                compare_marshal_unmarshal(
2✔
502
                    Atomic{.op = Atomic::Op::OR, .fetch = false, .access = access, .valreg = Reg{1}});
4✔
503
                compare_marshal_unmarshal(
2✔
504
                    Atomic{.op = Atomic::Op::OR, .fetch = true, .access = access, .valreg = Reg{1}});
4✔
505
                compare_marshal_unmarshal(
2✔
506
                    Atomic{.op = Atomic::Op::AND, .fetch = false, .access = access, .valreg = Reg{1}});
4✔
507
                compare_marshal_unmarshal(
2✔
508
                    Atomic{.op = Atomic::Op::AND, .fetch = true, .access = access, .valreg = Reg{1}});
4✔
509
                compare_marshal_unmarshal(
2✔
510
                    Atomic{.op = Atomic::Op::XOR, .fetch = false, .access = access, .valreg = Reg{1}});
4✔
511
                compare_marshal_unmarshal(
2✔
512
                    Atomic{.op = Atomic::Op::XOR, .fetch = true, .access = access, .valreg = Reg{1}});
6✔
513
                check_marshal_unmarshal_fail(
8✔
514
                    Atomic{.op = Atomic::Op::XCHG, .fetch = false, .access = access, .valreg = Reg{1}},
6✔
515
                    "0: unsupported immediate\n");
516
                compare_marshal_unmarshal(
2✔
517
                    Atomic{.op = Atomic::Op::XCHG, .fetch = true, .access = access, .valreg = Reg{1}});
6✔
518
                check_marshal_unmarshal_fail(
8✔
519
                    Atomic{.op = Atomic::Op::CMPXCHG, .fetch = false, .access = access, .valreg = Reg{1}},
6✔
520
                    "0: unsupported immediate\n");
521
                compare_marshal_unmarshal(
4✔
522
                    Atomic{.op = Atomic::Op::CMPXCHG, .fetch = true, .access = access, .valreg = Reg{1}});
6✔
523
            }
524
        }
525
    }
28✔
526
}
28✔
527

528
TEST_CASE("marshal", "[disasm][marshal]") {
8✔
529
    SECTION("Load") {
8✔
530
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
2✔
531
        Mem m{.access = access, .value = Reg{3}, .is_load = true};
2✔
532
        auto ins = marshal(m, 0).at(0);
3✔
533
        EbpfInst expect{
2✔
534
            .opcode = gsl::narrow<uint8_t>(INST_CLS_LD | INST_MODE_MEM | width_to_opcode(1) | 0x1),
2✔
535
            .dst = 3,
536
            .src = 4,
537
            .offset = 6,
538
            .imm = 0,
539
        };
2✔
540
        REQUIRE(ins.dst == expect.dst);
2✔
541
        REQUIRE(ins.src == expect.src);
2✔
542
        REQUIRE(ins.offset == expect.offset);
2✔
543
        REQUIRE(ins.imm == expect.imm);
2✔
544
        REQUIRE(ins.opcode == expect.opcode);
3✔
545
    }
8✔
546
    SECTION("Load Imm") {
8✔
547
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
2✔
548
        REQUIRE_THROWS(marshal(Mem{.access = access, .value = Imm{3}, .is_load = true}, 0));
5✔
549
    }
8✔
550
    SECTION("Store") {
8✔
551
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
2✔
552
        auto ins = marshal(Mem{.access = access, .value = Reg{3}, .is_load = false}, 0).at(0);
3✔
553
        REQUIRE(ins.src == 3);
2✔
554
        REQUIRE(ins.dst == 4);
2✔
555
        REQUIRE(ins.offset == 6);
2✔
556
        REQUIRE(ins.imm == 0);
2✔
557
        REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | INST_MODE_MEM | width_to_opcode(1) | 0x1));
3✔
558
    }
8✔
559
    SECTION("StoreImm") {
8✔
560
        Deref access{.width = 1, .basereg = Reg{4}, .offset = 6};
2✔
561
        auto ins = marshal(Mem{.access = access, .value = Imm{3}, .is_load = false}, 0).at(0);
3✔
562
        REQUIRE(ins.src == 0);
2✔
563
        REQUIRE(ins.dst == 4);
2✔
564
        REQUIRE(ins.offset == 6);
2✔
565
        REQUIRE(ins.imm == 3);
2✔
566
        REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | INST_MODE_MEM | width_to_opcode(1) | 0x0));
3✔
567
    }
8✔
568
}
8✔
569

570
TEST_CASE("disasm_marshal_Mem", "[disasm][marshal]") {
8✔
571
    SECTION("Load") {
8✔
572
        for (const int w : ws) {
10✔
573
            Deref access;
8✔
574
            access.basereg = Reg{4};
8✔
575
            access.offset = 6;
8✔
576
            access.width = w;
8✔
577
            compare_marshal_unmarshal(Mem{.access = access, .value = Reg{3}, .is_load = true});
12✔
578
        }
579
    }
8✔
580
    SECTION("Load R10") {
8✔
581
        Deref access;
2✔
582
        access.basereg = Reg{0};
2✔
583
        access.offset = 0;
2✔
584
        access.width = 8;
2✔
585
        check_marshal_unmarshal_fail(Mem{.access = access, .value = Reg{10}, .is_load = true},
4✔
586
                                     "0: cannot modify r10\n");
587
    }
8✔
588
    SECTION("Store Register") {
8✔
589
        for (const int w : ws) {
10✔
590
            Deref access;
8✔
591
            access.basereg = Reg{9};
8✔
592
            access.offset = 8;
8✔
593
            access.width = w;
8✔
594
            compare_marshal_unmarshal(Mem{.access = access, .value = Reg{4}, .is_load = false});
12✔
595
        }
596
    }
8✔
597
    SECTION("Store Immediate") {
8✔
598
        for (const int w : ws) {
10✔
599
            Deref access;
8✔
600
            access.basereg = Reg{10};
8✔
601
            access.offset = 2;
8✔
602
            access.width = w;
8✔
603
            compare_marshal_unmarshal(Mem{.access = access, .value = Imm{5}, .is_load = false});
12✔
604
        }
605
    }
8✔
606
}
8✔
607

608
TEST_CASE("unmarshal extension opcodes", "[disasm][marshal]") {
2✔
609
    // Merge (rX <<= 32; rX >>>= 32) into wX = rX.
610
    compare_unmarshal_marshal(EbpfInst{.opcode = INST_ALU_OP_LSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
611
                              EbpfInst{.opcode = INST_ALU_OP_RSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
612
                              EbpfInst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU, .dst = 1, .src = 1});
2✔
613

614
    // Merge (rX <<= 32; rX >>= 32)  into rX s32= rX.
615
    compare_unmarshal_marshal(
2✔
616
        EbpfInst{.opcode = INST_ALU_OP_LSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
617
        EbpfInst{.opcode = INST_ALU_OP_ARSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32},
2✔
618
        EbpfInst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU64, .dst = 1, .src = 1, .offset = 32});
2✔
619
}
2✔
620

621
// Check that unmarshaling an invalid instruction fails with a given message.
622
static void check_unmarshal_instruction_fail(EbpfInst& inst, const std::string& message,
1,118✔
623
                                             const ebpf_platform_t& platform = g_ebpf_platform_linux) {
624
    if (inst.offset == JMP_OFFSET) {
1,118✔
625
        inst.offset = 1;
226✔
626
        check_unmarshal_fail_goto(inst, message);
226✔
627
    } else if (inst.opcode == INST_OP_LDDW_IMM) {
892✔
628
        check_unmarshal_fail(inst, EbpfInst{}, message, platform);
30✔
629
    } else {
630
        check_unmarshal_fail(inst, message, platform);
862✔
631
    }
632
}
1,118✔
633

634
static ebpf_platform_t get_template_platform(const EbpfInstructionTemplate& previous_template) {
1,368✔
635
    ebpf_platform_t platform = g_ebpf_platform_linux;
1,368✔
636
    platform.supported_conformance_groups |= previous_template.groups;
1,368✔
637
    return platform;
1,368✔
638
}
639

640
// Check whether an instruction matches an instruction template that may have wildcards.
641
static bool matches_template_inst(const EbpfInst inst, const EbpfInst template_inst) {
568✔
642
    if (inst.opcode != template_inst.opcode) {
568✔
643
        return false;
204✔
644
    }
645
    if (inst.dst != template_inst.dst && template_inst.dst != DST) {
160✔
646
        return false;
2✔
647
    }
648
    if (inst.src != template_inst.src && template_inst.src != SRC) {
156✔
649
        return false;
18✔
650
    }
651
    if (inst.offset != template_inst.offset && template_inst.offset != MEM_OFFSET &&
120✔
652
        template_inst.offset != JMP_OFFSET) {
20✔
653
        return false;
20✔
654
    }
655
    if (inst.imm != template_inst.imm && template_inst.imm != IMM && template_inst.imm != JMP_OFFSET) {
80✔
656
        return false;
32✔
657
    }
658
    return true;
24✔
659
}
660

661
// Check that various 'dst' variations between two valid instruction templates fail.
662
static void check_instruction_dst_variations(const EbpfInstructionTemplate& previous_template,
342✔
663
                                             const std::optional<const EbpfInstructionTemplate> next_template) {
664
    EbpfInst inst = previous_template.inst;
342✔
665
    const ebpf_platform_t platform = get_template_platform(previous_template);
342✔
666
    if (inst.dst == DST) {
342✔
667
        inst.dst = INVALID_REGISTER;
318✔
668
        check_unmarshal_instruction_fail(inst, "0: bad register\n", platform);
636✔
669
    } else {
670
        // This instruction doesn't put a register number in the 'dst' field.
671
        // Just try the next value unless that's what the next template has.
672
        inst.dst++;
24✔
673
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
24✔
674
            std::ostringstream oss;
24✔
675
            if (inst.dst == 1) {
24✔
676
                oss << "0: nonzero dst for register op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
24✔
677
            } else {
UNCOV
678
                oss << "0: bad instruction op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
×
679
            }
680
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
24✔
681
        }
24✔
682
    }
683
}
342✔
684

685
// Check that various 'src' variations between two valid instruction templates fail.
686
static void check_instruction_src_variations(const EbpfInstructionTemplate& previous_template,
342✔
687
                                             const std::optional<const EbpfInstructionTemplate> next_template) {
688
    EbpfInst inst = previous_template.inst;
342✔
689
    const ebpf_platform_t platform = get_template_platform(previous_template);
342✔
690
    if (inst.src == SRC) {
342✔
691
        inst.src = INVALID_REGISTER;
178✔
692
        check_unmarshal_instruction_fail(inst, "0: bad register\n", platform);
356✔
693
    } else {
694
        // This instruction doesn't put a register number in the 'src' field.
695
        // Just try the next value unless that's what the next template has.
696
        inst.src++;
164✔
697
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
164✔
698
            std::ostringstream oss;
148✔
699
            oss << "0: bad instruction op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
148✔
700
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
148✔
701
        }
148✔
702
    }
703
}
342✔
704

705
// Check that various 'offset' variations between two valid instruction templates fail.
706
static void check_instruction_offset_variations(const EbpfInstructionTemplate& previous_template,
342✔
707
                                                const std::optional<const EbpfInstructionTemplate> next_template) {
708
    EbpfInst inst = previous_template.inst;
342✔
709
    const ebpf_platform_t platform = get_template_platform(previous_template);
342✔
710
    if (inst.offset == JMP_OFFSET) {
342✔
711
        inst.offset = 0; // Not a valid jump offset.
90✔
712
        check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform);
180✔
713
    } else if (inst.offset != MEM_OFFSET) {
252✔
714
        // This instruction limits what can appear in the 'offset' field.
715
        // Just try the next value unless that's what the next template has.
716
        inst.offset++;
182✔
717
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
182✔
718
            std::ostringstream oss;
166✔
719
            if (inst.offset == 1 &&
236✔
720
                (!next_template || next_template->inst.opcode != inst.opcode || next_template->inst.offset == 0)) {
140✔
721
                oss << "0: nonzero offset for op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
136✔
722
            } else {
723
                oss << "0: invalid offset for op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
30✔
724
            }
725
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
166✔
726
        }
166✔
727
    }
728
}
342✔
729

730
// Check that various 'imm' variations between two valid instruction templates fail.
731
static void check_instruction_imm_variations(const EbpfInstructionTemplate& previous_template,
342✔
732
                                             const std::optional<const EbpfInstructionTemplate> next_template) {
733
    EbpfInst inst = previous_template.inst;
342✔
734
    const ebpf_platform_t platform = get_template_platform(previous_template);
342✔
735
    if (inst.imm == JMP_OFFSET) {
342✔
736
        inst.imm = 0; // Not a valid jump offset.
4✔
737
        check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform);
8✔
738
    } else if (inst.imm != IMM && inst.imm != HELPER_ID) {
338✔
739
        // This instruction limits what can appear in the 'imm' field.
740
        // Just try the next value unless that's what the next template has.
741
        inst.imm++;
200✔
742
        if (!next_template || !matches_template_inst(inst, next_template->inst)) {
200✔
743
            std::ostringstream oss;
184✔
744
            if (inst.imm == 1) {
184✔
745
                oss << "0: nonzero imm for op 0x" << std::hex << static_cast<int>(inst.opcode) << std::endl;
142✔
746
            } else {
747
                oss << "0: unsupported immediate" << std::endl;
42✔
748
            }
749
            check_unmarshal_instruction_fail(inst, oss.str(), platform);
184✔
750
        }
184✔
751
    }
752

753
    // Some instructions only permit non-zero imm values.
754
    // If the next template is for one of those, check the zero value now.
755
    if (next_template && (previous_template.inst.opcode != next_template->inst.opcode) &&
512✔
756
        (next_template->inst.imm > 0) && (next_template->inst.imm != HELPER_ID) &&
641✔
757
        (next_template->inst.imm != JMP_OFFSET)) {
8✔
758
        inst = next_template->inst;
6✔
759
        inst.imm = 0;
6✔
760
        check_unmarshal_instruction_fail(inst, "0: unsupported immediate\n");
12✔
761
    }
762
}
342✔
763

764
// Check that various variations between two valid instruction templates fail.
765
static void check_instruction_variations(const std::optional<const EbpfInstructionTemplate> previous_template,
344✔
766
                                         const std::optional<const EbpfInstructionTemplate> next_template) {
767
    if (previous_template) {
344✔
768
        check_instruction_dst_variations(*previous_template, next_template);
342✔
769
        check_instruction_src_variations(*previous_template, next_template);
342✔
770
        check_instruction_offset_variations(*previous_template, next_template);
342✔
771
        check_instruction_imm_variations(*previous_template, next_template);
342✔
772
    }
773

774
    // Check any invalid opcodes in between the previous and next templates.
775
    const int previous_opcode = previous_template ? previous_template->inst.opcode : -1;
344✔
776
    const int next_opcode = next_template ? next_template->inst.opcode : 0x100;
344✔
777
    for (int opcode = previous_opcode + 1; opcode < next_opcode; opcode++) {
604✔
778
        const EbpfInst inst{.opcode = static_cast<uint8_t>(opcode)};
260✔
779
        std::ostringstream oss;
260✔
780
        oss << "0: bad instruction op 0x" << std::hex << opcode << std::endl;
260✔
781
        check_unmarshal_fail(inst, oss.str());
260✔
782
    }
260✔
783
}
344✔
784

785
TEST_CASE("fail unmarshal bad instructions", "[disasm][marshal]") {
2✔
786
    constexpr size_t template_count = std::size(instruction_template);
2✔
787

788
    // Check any variations before the first template.
789
    check_instruction_variations({}, instruction_template[0]);
2✔
790

791
    for (size_t index = 1; index < template_count; index++) {
342✔
792
        check_instruction_variations(instruction_template[index - 1], instruction_template[index]);
340✔
793
    }
794

795
    // Check any remaining variations after the last template.
796
    check_instruction_variations(instruction_template[template_count - 1], {});
2✔
797
}
2✔
798

799
TEST_CASE("check unmarshal conformance groups", "[disasm][marshal]") {
2✔
800
    for (const auto& current : instruction_template) {
344✔
801
        // Try unmarshaling without support. Decoding should still succeed; rejection happens later.
802
        ebpf_platform_t platform = g_ebpf_platform_linux;
342✔
803
        platform.supported_conformance_groups &= ~current.groups;
342✔
804
        EbpfInst without_support = current.inst;
342✔
805
        if (without_support.offset == JMP_OFFSET) {
342✔
806
            without_support.offset = 1;
90✔
807
        }
808
        if (without_support.imm == JMP_OFFSET) {
342✔
809
            without_support.imm = 1;
4✔
810
        }
811
        if (without_support.opcode == INST_OP_LDDW_IMM) {
342✔
812
            check_unmarshal_succeed(without_support, EbpfInst{}, platform);
14✔
813
        } else {
814
            check_unmarshal_succeed(without_support, platform);
328✔
815
        }
816

817
        // Try unmarshaling with support.
818
        platform.supported_conformance_groups |= current.groups;
342✔
819
        EbpfInst inst = current.inst;
342✔
820
        if (inst.offset == JMP_OFFSET) {
342✔
821
            inst.offset = 1;
90✔
822
        }
823
        if (inst.imm == JMP_OFFSET) {
342✔
824
            inst.imm = 1;
4✔
825
        }
826
        if (inst.opcode == INST_OP_LDDW_IMM) {
342✔
827
            check_unmarshal_succeed(inst, EbpfInst{}, platform);
14✔
828
        } else {
829
            check_unmarshal_succeed(inst, platform);
328✔
830
        }
831
    }
832
}
2✔
833

834
TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") {
2✔
835
    // The following opcodes are deprecated and should no longer be used.
836
    static uint8_t supported_legacy_opcodes[] = {0x20, 0x28, 0x30, 0x40, 0x48, 0x50};
1✔
837
    for (const uint8_t opcode : supported_legacy_opcodes) {
14✔
838
        compare_unmarshal_marshal(EbpfInst{.opcode = opcode}, EbpfInst{.opcode = opcode});
12✔
839
    }
840

841
    // Disable legacy packet instruction support. Decoding should still succeed.
842
    ebpf_platform_t platform = g_ebpf_platform_linux;
2✔
843
    platform.supported_conformance_groups &= ~bpf_conformance_groups_t::packet;
2✔
844
    for (const uint8_t opcode : supported_legacy_opcodes) {
14✔
845
        check_unmarshal_succeed(EbpfInst{.opcode = opcode}, platform);
12✔
846
    }
847
}
2✔
848

849
TEST_CASE("unmarshal 64bit immediate", "[disasm][marshal]") {
2✔
850
    compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, EbpfInst{.imm = 2},
2✔
851
                              EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, EbpfInst{.imm = 2});
2✔
852
    compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, EbpfInst{},
2✔
853
                              EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, EbpfInst{});
2✔
854

855
    for (uint8_t src = 0; src <= 7; src++) {
18✔
856
        check_unmarshal_fail(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, "0: incomplete lddw\n");
24✔
857
        check_unmarshal_fail(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src},
32✔
858
                             EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: invalid lddw\n");
859
    }
860

861
    // For mode-specific LDDW encodings, next_imm is reserved for src={1,3,4,5}.
862
    // src=2 (map_value) and src=6 (map_value_by_idx) carry payload in next_imm.
863
    check_unmarshal_fail(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 1}, EbpfInst{.imm = 1},
3✔
864
                         "0: lddw uses reserved fields\n");
865
    check_unmarshal_fail(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 3}, EbpfInst{.imm = 1},
3✔
866
                         "0: lddw uses reserved fields\n");
867
    check_unmarshal_fail(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 4}, EbpfInst{.imm = 1},
3✔
868
                         "0: lddw uses reserved fields\n");
869
    check_unmarshal_fail(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 5}, EbpfInst{.imm = 1},
2✔
870
                         "0: lddw uses reserved fields\n");
871

872
    compare_unmarshal_marshal(
2✔
873
        EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 2, .imm = 7}, EbpfInst{.imm = 11},
2✔
874
        EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 2, .imm = 7}, EbpfInst{.imm = 11});
2✔
875
    compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 2, .imm = 7}, EbpfInst{},
2✔
876
                              EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 2, .imm = 7},
2✔
877
                              EbpfInst{});
2✔
878

879
    compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 3, .imm = 7}, EbpfInst{},
2✔
880
                              EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 3, .imm = 7},
2✔
881
                              EbpfInst{});
2✔
882
    compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 4, .imm = 7}, EbpfInst{},
2✔
883
                              EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 4, .imm = 7},
2✔
884
                              EbpfInst{});
2✔
885
    compare_unmarshal_marshal(EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 5, .imm = 7}, EbpfInst{},
2✔
886
                              EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 5, .imm = 7},
2✔
887
                              EbpfInst{});
2✔
888
    compare_unmarshal_marshal(
2✔
889
        EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 6, .imm = 7}, EbpfInst{.imm = 11},
2✔
890
        EbpfInst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .dst = 1, .src = 6, .imm = 7}, EbpfInst{.imm = 11});
2✔
891
}
2✔
892

893
TEST_CASE("unmarshal call-btf-id", "[disasm][marshal]") {
2✔
894
    compare_unmarshal_marshal(EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 17},
2✔
895
                              EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 17});
2✔
896
}
2✔
897

898
TEST_CASE("unmarshal builtin calls only when relocation-gated", "[disasm][marshal]") {
2✔
899
    REQUIRE(g_ebpf_platform_linux.resolve_builtin_call != nullptr);
2✔
900
    const auto memset_id = g_ebpf_platform_linux.resolve_builtin_call("memset");
3✔
901
    REQUIRE(memset_id.has_value());
2✔
902

903
    const EbpfInst call_memset{
2✔
904
        .opcode = INST_OP_CALL,
905
        .src = INST_CALL_STATIC_HELPER,
906
        .imm = *memset_id,
2✔
907
    };
2✔
908

909
    const EbpfProgramType type = g_ebpf_platform_linux.get_program_type("unspec", "");
6✔
910
    ProgramInfo info{.platform = &g_ebpf_platform_linux, .type = type};
2✔
911

912
    const Call ungated = unmarshal_single_call(call_memset, info);
2✔
913
    REQUIRE_FALSE(ungated.is_supported);
2✔
914
    REQUIRE(ungated.unsupported_reason == "helper function is unavailable on this platform");
2✔
915

916
    info.builtin_call_offsets.insert(0);
2✔
917
    const Call gated = unmarshal_single_call(call_memset, info);
2✔
918
    REQUIRE(gated.is_supported);
2✔
919
    REQUIRE(gated.name == "memset");
2✔
920
    REQUIRE(gated.func == *memset_id);
2✔
921
    REQUIRE(gated.singles.size() == 1);
2✔
922
    REQUIRE(gated.pairs.size() == 1);
2✔
923
    REQUIRE(gated.singles[0] == ArgSingle{ArgSingle::Kind::ANYTHING, false, Reg{2}});
3✔
924
    REQUIRE(gated.pairs[0] == ArgPair{ArgPair::Kind::PTR_TO_WRITABLE_MEM, false, Reg{1}, Reg{3}, false});
2✔
925

926
    const auto assertions = get_assertions(gated, info, Label{0});
4✔
927
    REQUIRE(has_assertion(assertions, TypeConstraint{Reg{1}, TypeGroup::mem}));
2✔
928
    REQUIRE(has_assertion(assertions, TypeConstraint{Reg{2}, TypeGroup::number}));
2✔
929
    REQUIRE(has_assertion(assertions, TypeConstraint{Reg{3}, TypeGroup::number}));
2✔
930
    REQUIRE(has_assertion(assertions, ValidSize{Reg{3}, false}));
2✔
931
    REQUIRE(has_assertion(assertions, ValidAccess{1, Reg{1}, 0, Value{Reg{3}}, false, AccessType::write}));
2✔
932
}
3✔
933
#define FAIL_UNMARSHAL(dirname, filename, sectionname)                                                       \
934
    TEST_CASE("Try unmarshalling bad program: " dirname "/" filename " " sectionname, "[unmarshal]") {       \
935
        thread_local_options = {};                                                                           \
936
        ElfObject elf{"ebpf-samples/" dirname "/" filename, {}, &g_ebpf_platform_linux};                     \
937
        const auto& raw_progs = elf.get_programs(sectionname);                                               \
938
        REQUIRE(raw_progs.size() == 1);                                                                      \
939
        const RawProgram& raw_prog = raw_progs.back();                                                       \
940
        std::variant<InstructionSeq, std::string> prog_or_error = unmarshal(raw_prog, thread_local_options); \
941
        REQUIRE(std::holds_alternative<std::string>(prog_or_error));                                         \
942
    }
943

944
// Some intentional unmarshal failures
945
FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text")
11✔
946

947
TEST_CASE("instruction feature handling after unmarshal", "[unmarshal]") {
54✔
948
    constexpr EbpfInst exit{.opcode = INST_OP_EXIT};
54✔
949
    ebpf_platform_t platform = g_ebpf_platform_linux;
54✔
950
    ProgramInfo info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")};
135✔
951

952
    SECTION("unknown kfunc btf id") {
54✔
953
        RawProgram raw_prog{
2✔
954
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1}, exit}, info};
5✔
955
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
956
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
957
        REQUIRE_THROWS_WITH(
9✔
958
            Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}),
959
            Catch::Matchers::ContainsSubstring("not implemented: kfunc prototype lookup failed for BTF id 1") &&
960
                Catch::Matchers::ContainsSubstring("(at 0)"));
961
    }
56✔
962

963
    SECTION("kfunc call by BTF id is accepted when prototype is known") {
54✔
964
        RawProgram raw_prog{
2✔
965
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1000}, exit}, info};
5✔
966
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
967
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
968
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
969
        REQUIRE(verify(prog));
2✔
970
    }
56✔
971

972
    SECTION("kfunc call in local subprogram does not use helper prototype lookup") {
54✔
973
        RawProgram raw_prog{"",
2✔
974
                            "",
1✔
975
                            0,
976
                            "",
1✔
977
                            {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_LOCAL, .imm = 1}, exit,
978
                             EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1000}, exit},
979
                            info};
5✔
980
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
981
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
982
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
983
        REQUIRE(verify(prog));
2✔
984
    }
56✔
985

986
    SECTION("kfunc in subprogram is not misclassified when BTF id overlaps helper id") {
54✔
987
        RawProgram raw_prog{"",
2✔
988
                            "",
1✔
989
                            0,
990
                            "",
1✔
991
                            {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_LOCAL, .imm = 1}, exit,
992
                             EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 12}, exit},
993
                            info};
5✔
994
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
995
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
996
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
997
        REQUIRE(verify(prog));
2✔
998
    }
56✔
999

1000
    SECTION("kfunc map-value return is lowered to map-lookup call contract") {
54✔
1001
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1002
        RawProgram raw_prog{"",
2✔
1003
                            "",
1✔
1004
                            0,
1005
                            "",
1✔
1006
                            {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1005},
1007
                             EbpfInst{.opcode = mov64_imm, .dst = 0, .imm = 0}, exit},
1008
                            info};
5✔
1009
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1010
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1011
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1012
        const auto* call = std::get_if<Call>(&prog.instruction_at(Label{0}));
4✔
1013
        REQUIRE(call != nullptr);
2✔
1014
        REQUIRE(call->is_map_lookup);
2✔
1015
        REQUIRE(verify(prog));
2✔
1016
    }
56✔
1017

1018
    SECTION("kfunc with acquire flag is accepted") {
54✔
1019
        RawProgram raw_prog{
2✔
1020
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1002}, exit}, info};
5✔
1021
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1022
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1023
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1024
        const auto* call = std::get_if<Call>(&prog.instruction_at(Label{0}));
4✔
1025
        REQUIRE(call != nullptr);
2✔
1026
        REQUIRE(call->name == "kfunc_test_acquire_flag");
2✔
1027
    }
56✔
1028

1029
    SECTION("kfunc with release flag is rejected") {
54✔
1030
        RawProgram raw_prog{
2✔
1031
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1008}, exit}, info};
5✔
1032
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1033
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1034
        REQUIRE_THROWS_WITH(Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}),
9✔
1035
                            Catch::Matchers::ContainsSubstring("not implemented: kfunc has unsupported flags") &&
1036
                                Catch::Matchers::ContainsSubstring("(at 0)"));
1037
    }
56✔
1038

1039
    SECTION("kfunc program type gating is enforced") {
54✔
1040
        RawProgram raw_prog{
2✔
1041
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1003}, exit}, info};
5✔
1042
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1043
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1044
        REQUIRE_THROWS_WITH(
8✔
1045
            Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}),
1046
            Catch::Matchers::ContainsSubstring("not implemented: kfunc is unavailable for program type") &&
1047
                Catch::Matchers::ContainsSubstring("(at 0)"));
1048

1049
        ProgramInfo xdp_info{.platform = &platform, .type = platform.get_program_type("xdp", "xdp")};
5✔
1050
        RawProgram xdp_raw_prog{
2✔
1051
            "",      "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1003}, exit},
1✔
1052
            xdp_info};
5✔
1053
        auto xdp_prog_or_error = unmarshal(xdp_raw_prog, {});
2✔
1054
        REQUIRE(std::holds_alternative<InstructionSeq>(xdp_prog_or_error));
2✔
1055
        const Program xdp_prog = Program::from_sequence(std::get<InstructionSeq>(xdp_prog_or_error), xdp_info, {});
2✔
1056
        REQUIRE(verify(xdp_prog));
2✔
1057
    }
56✔
1058

1059
    SECTION("kfunc privileged gating is enforced") {
54✔
1060
        RawProgram raw_prog{
2✔
1061
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1004}, exit}, info};
5✔
1062
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1063
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1064
        REQUIRE_THROWS_WITH(
8✔
1065
            Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}),
1066
            Catch::Matchers::ContainsSubstring("not implemented: kfunc requires privileged program type") &&
1067
                Catch::Matchers::ContainsSubstring("(at 0)"));
1068

1069
        ProgramInfo kprobe_info{
1✔
1070
            .platform = &platform,
1071
            .type = platform.get_program_type("kprobe/test_prog", ""),
3✔
1072
        };
5✔
1073
        RawProgram kprobe_raw_prog{
2✔
1074
            "",         "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1004}, exit},
1✔
1075
            kprobe_info};
5✔
1076
        auto kprobe_prog_or_error = unmarshal(kprobe_raw_prog, {});
2✔
1077
        REQUIRE(std::holds_alternative<InstructionSeq>(kprobe_prog_or_error));
2✔
1078
        const Program kprobe_prog =
1✔
1079
            Program::from_sequence(std::get<InstructionSeq>(kprobe_prog_or_error), kprobe_info, {});
2✔
1080
        REQUIRE(verify(kprobe_prog));
2✔
1081
    }
56✔
1082

1083
    SECTION("kfunc argument typing is enforced from prototype table") {
54✔
1084
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1085

1086
        RawProgram good_raw_prog{
2✔
1087
            "", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1001}, exit}, info};
5✔
1088
        auto good_prog_or_error = unmarshal(good_raw_prog, {});
2✔
1089
        REQUIRE(std::holds_alternative<InstructionSeq>(good_prog_or_error));
2✔
1090
        const Program good_prog = Program::from_sequence(std::get<InstructionSeq>(good_prog_or_error), info, {});
2✔
1091
        REQUIRE(verify(good_prog));
2✔
1092

1093
        RawProgram bad_raw_prog{"",
2✔
1094
                                "",
1✔
1095
                                0,
1096
                                "",
1✔
1097
                                {EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 0},
1098
                                 EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1001}, exit},
1099
                                info};
5✔
1100
        auto bad_prog_or_error = unmarshal(bad_raw_prog, {});
2✔
1101
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_prog_or_error));
2✔
1102
        const Program bad_prog = Program::from_sequence(std::get<InstructionSeq>(bad_prog_or_error), info, {});
2✔
1103
        REQUIRE_FALSE(verify(bad_prog));
2✔
1104
    }
56✔
1105

1106
    SECTION("kfunc pointer-size argument pairs enforce null and size constraints") {
54✔
1107
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1108

1109
        RawProgram good_raw_prog{"",
2✔
1110
                                 "",
1✔
1111
                                 0,
1112
                                 "",
1✔
1113
                                 {EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 0},
1114
                                  EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = 0},
1115
                                  EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1006}, exit},
1116
                                 info};
5✔
1117
        auto good_prog_or_error = unmarshal(good_raw_prog, {});
2✔
1118
        REQUIRE(std::holds_alternative<InstructionSeq>(good_prog_or_error));
2✔
1119
        const Program good_prog = Program::from_sequence(std::get<InstructionSeq>(good_prog_or_error), info, {});
2✔
1120
        REQUIRE(verify(good_prog));
2✔
1121

1122
        RawProgram bad_size_raw_prog{"",
2✔
1123
                                     "",
1✔
1124
                                     0,
1125
                                     "",
1✔
1126
                                     {EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 0},
1127
                                      EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = -1},
1128
                                      EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1006}, exit},
1129
                                     info};
5✔
1130
        auto bad_size_prog_or_error = unmarshal(bad_size_raw_prog, {});
2✔
1131
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_size_prog_or_error));
2✔
1132
        const Program bad_size_prog =
1✔
1133
            Program::from_sequence(std::get<InstructionSeq>(bad_size_prog_or_error), info, {});
2✔
1134
        REQUIRE_FALSE(verify(bad_size_prog));
2✔
1135

1136
        RawProgram bad_nullability_raw_prog{
2✔
1137
            "",
1✔
1138
            "",
1✔
1139
            0,
1140
            "",
1✔
1141
            {EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1}, EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = 0},
1142
             EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1006}, exit},
1143
            info};
5✔
1144
        auto bad_nullability_prog_or_error = unmarshal(bad_nullability_raw_prog, {});
2✔
1145
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_nullability_prog_or_error));
2✔
1146
        const Program bad_nullability_prog =
1✔
1147
            Program::from_sequence(std::get<InstructionSeq>(bad_nullability_prog_or_error), info, {});
2✔
1148
        REQUIRE_FALSE(verify(bad_nullability_prog));
2✔
1149
    }
56✔
1150

1151
    SECTION("kfunc writable-memory argument pairs enforce writeability and strict size") {
54✔
1152
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1153
        constexpr uint8_t mov64_reg = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_REG;
2✔
1154
        constexpr uint8_t add64_imm = INST_CLS_ALU64 | INST_ALU_OP_ADD | INST_SRC_IMM;
2✔
1155

1156
        RawProgram good_raw_prog{"",
2✔
1157
                                 "",
1✔
1158
                                 0,
1159
                                 "",
1✔
1160
                                 {EbpfInst{.opcode = mov64_reg, .dst = 1, .src = R10_STACK_POINTER},
1161
                                  EbpfInst{.opcode = add64_imm, .dst = 1, .imm = -8},
1162
                                  EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = 4},
1163
                                  EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1007}, exit},
1164
                                 info};
5✔
1165
        auto good_prog_or_error = unmarshal(good_raw_prog, {});
2✔
1166
        REQUIRE(std::holds_alternative<InstructionSeq>(good_prog_or_error));
2✔
1167
        const Program good_prog = Program::from_sequence(std::get<InstructionSeq>(good_prog_or_error), info, {});
2✔
1168
        REQUIRE(verify(good_prog));
2✔
1169

1170
        RawProgram bad_nullability_raw_prog{
2✔
1171
            "",
1✔
1172
            "",
1✔
1173
            0,
1174
            "",
1✔
1175
            {EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 0}, EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = 4},
1176
             EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1007}, exit},
1177
            info};
5✔
1178
        auto bad_nullability_prog_or_error = unmarshal(bad_nullability_raw_prog, {});
2✔
1179
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_nullability_prog_or_error));
2✔
1180
        const Program bad_nullability_prog =
1✔
1181
            Program::from_sequence(std::get<InstructionSeq>(bad_nullability_prog_or_error), info, {});
2✔
1182
        REQUIRE_FALSE(verify(bad_nullability_prog));
2✔
1183

1184
        RawProgram bad_size_raw_prog{"",
2✔
1185
                                     "",
1✔
1186
                                     0,
1187
                                     "",
1✔
1188
                                     {EbpfInst{.opcode = mov64_reg, .dst = 1, .src = R10_STACK_POINTER},
1189
                                      EbpfInst{.opcode = add64_imm, .dst = 1, .imm = -8},
1190
                                      EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = 0},
1191
                                      EbpfInst{.opcode = INST_OP_CALL, .src = INST_CALL_BTF_HELPER, .imm = 1007}, exit},
1192
                                     info};
5✔
1193
        auto bad_size_prog_or_error = unmarshal(bad_size_raw_prog, {});
2✔
1194
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_size_prog_or_error));
2✔
1195
        const Program bad_size_prog =
1✔
1196
            Program::from_sequence(std::get<InstructionSeq>(bad_size_prog_or_error), info, {});
2✔
1197
        REQUIRE_FALSE(verify(bad_size_prog));
2✔
1198
    }
56✔
1199

1200
    SECTION("lddw variable_addr pseudo") {
54✔
1201
        RawProgram raw_prog{
2✔
1202
            "",  "", 0, "", {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 1, .src = 3, .imm = 7}, EbpfInst{}, exit},
1✔
1203
            info};
5✔
1204
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1205
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1206
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1207
        const auto* bin = std::get_if<Bin>(&prog.instruction_at(Label{0}));
4✔
1208
        REQUIRE(bin != nullptr);
2✔
1209
        REQUIRE(bin->op == Bin::Op::MOV);
2✔
1210
        REQUIRE(bin->is64);
2✔
1211
        REQUIRE(bin->lddw);
2✔
1212
        REQUIRE(bin->dst == Reg{1});
2✔
1213
        const auto* imm = std::get_if<Imm>(&bin->v);
2✔
1214
        REQUIRE(imm != nullptr);
2✔
1215
        REQUIRE(imm->v == 7ULL);
2✔
1216
    }
56✔
1217

1218
    SECTION("lddw code_addr pseudo via INST_LD_MODE_CODE_ADDR") {
54✔
1219
        RawProgram raw_prog{
2✔
1220
            "",  "", 0, "", {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = 4, .imm = 11}, EbpfInst{}, exit},
1✔
1221
            info};
5✔
1222
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1223
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1224
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1225
        const auto* pseudo = std::get_if<LoadPseudo>(&prog.instruction_at(Label{0}));
4✔
1226
        REQUIRE(pseudo != nullptr);
2✔
1227
        REQUIRE(pseudo->dst == Reg{2});
2✔
1228
        REQUIRE(pseudo->addr.kind == PseudoAddress::Kind::CODE_ADDR);
2✔
1229
        REQUIRE(pseudo->addr.imm == 11);
2✔
1230
    }
56✔
1231

1232
    SECTION("lddw immediate merges high and low words") {
54✔
1233
        RawProgram raw_prog{
2✔
1234
            "",
1✔
1235
            "",
1✔
1236
            0,
1237
            "",
1✔
1238
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 3, .src = 0, .imm = 1}, EbpfInst{.imm = 2}, exit},
1239
            info};
5✔
1240
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1241
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1242
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1243
        const auto* bin = std::get_if<Bin>(&prog.instruction_at(Label{0}));
4✔
1244
        REQUIRE(bin != nullptr);
2✔
1245
        REQUIRE(bin->op == Bin::Op::MOV);
2✔
1246
        REQUIRE(bin->is64);
2✔
1247
        REQUIRE(bin->lddw);
2✔
1248
        REQUIRE(bin->dst == Reg{3});
2✔
1249
        const auto* imm = std::get_if<Imm>(&bin->v);
2✔
1250
        REQUIRE(imm != nullptr);
2✔
1251
        REQUIRE(imm->v == ((2ULL << 32) | 1ULL));
2✔
1252
    }
56✔
1253

1254
    SECTION("lddw immediate does not sign-extend low word") {
54✔
1255
        RawProgram raw_prog{
2✔
1256
            "",
1✔
1257
            "",
1✔
1258
            0,
1259
            "",
1✔
1260
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 3, .src = 0, .imm = -1}, EbpfInst{.imm = 0}, exit},
1261
            info};
5✔
1262
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1263
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1264
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1265
        const auto* bin = std::get_if<Bin>(&prog.instruction_at(Label{0}));
4✔
1266
        REQUIRE(bin != nullptr);
2✔
1267
        REQUIRE(bin->op == Bin::Op::MOV);
2✔
1268
        REQUIRE(bin->is64);
2✔
1269
        REQUIRE(bin->lddw);
2✔
1270
        REQUIRE(bin->dst == Reg{3});
2✔
1271
        const auto* imm = std::get_if<Imm>(&bin->v);
2✔
1272
        REQUIRE(imm != nullptr);
2✔
1273
        REQUIRE(imm->v == 0x00000000FFFFFFFFULL);
2✔
1274
    }
56✔
1275

1276
    SECTION("lddw code_addr pseudo") {
54✔
1277
        RawProgram raw_prog{
2✔
1278
            "",
1✔
1279
            "",
1✔
1280
            0,
1281
            "",
1✔
1282
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = INST_LD_MODE_CODE_ADDR, .imm = 7}, EbpfInst{}, exit},
1283
            info};
5✔
1284
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1285
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1286
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1287
        const auto* pseudo = std::get_if<LoadPseudo>(&prog.instruction_at(Label{0}));
4✔
1288
        REQUIRE(pseudo != nullptr);
2✔
1289
        REQUIRE(pseudo->addr.kind == PseudoAddress::Kind::CODE_ADDR);
2✔
1290
    }
56✔
1291

1292
    SECTION("helper ptr_to_func argument type is accepted for bpf_loop") {
54✔
1293
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1294
        RawProgram raw_prog{
2✔
1295
            "",
1✔
1296
            "",
1✔
1297
            0,
1298
            "",
1✔
1299
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = INST_LD_MODE_CODE_ADDR, .imm = 1}, EbpfInst{},
1300
             EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1}, EbpfInst{.opcode = mov64_imm, .dst = 3, .imm = 0},
1301
             EbpfInst{.opcode = mov64_imm, .dst = 4, .imm = 0}, EbpfInst{.opcode = INST_OP_CALL, .imm = 181}, exit},
1302
            info};
5✔
1303
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1304
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1305
        const Program prog = Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {});
2✔
1306
        const auto* call = std::get_if<Call>(&prog.instruction_at(Label{5}));
4✔
1307
        REQUIRE(call != nullptr);
2✔
1308
        REQUIRE(call->is_supported);
2✔
1309
        REQUIRE(std::ranges::any_of(call->singles, [](const ArgSingle& arg) {
6✔
1310
            return arg.kind == ArgSingle::Kind::PTR_TO_FUNC && arg.reg == Reg{2};
1311
        }));
1312
    }
56✔
1313

1314
    SECTION("ptr_to_func argument enforces function type") {
54✔
1315
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1316
        RawProgram good_raw_prog{
2✔
1317
            "",
1✔
1318
            "",
1✔
1319
            0,
1320
            "",
1✔
1321
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = INST_LD_MODE_CODE_ADDR, .imm = 7}, EbpfInst{},
1322
             EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1}, EbpfInst{.opcode = mov64_imm, .dst = 3, .imm = 0},
1323
             EbpfInst{.opcode = mov64_imm, .dst = 4, .imm = 0}, EbpfInst{.opcode = INST_OP_CALL, .imm = 181}, exit,
1324
             EbpfInst{.opcode = mov64_imm, .dst = 0, .imm = 0}, exit},
1325
            info};
5✔
1326
        auto good_prog_or_error = unmarshal(good_raw_prog, {});
2✔
1327
        REQUIRE(std::holds_alternative<InstructionSeq>(good_prog_or_error));
2✔
1328
        const Program good_prog = Program::from_sequence(std::get<InstructionSeq>(good_prog_or_error), info, {});
2✔
1329
        REQUIRE(verify(good_prog));
2✔
1330

1331
        RawProgram bad_raw_prog{
2✔
1332
            "",
1✔
1333
            "",
1✔
1334
            0,
1335
            "",
1✔
1336
            {EbpfInst{.opcode = mov64_imm, .dst = 2, .imm = 7}, EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1},
1337
             EbpfInst{.opcode = mov64_imm, .dst = 3, .imm = 0}, EbpfInst{.opcode = mov64_imm, .dst = 4, .imm = 0},
1338
             EbpfInst{.opcode = INST_OP_CALL, .imm = 181}, exit},
1339
            info};
5✔
1340
        auto bad_prog_or_error = unmarshal(bad_raw_prog, {});
2✔
1341
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_prog_or_error));
2✔
1342
        const Program bad_prog = Program::from_sequence(std::get<InstructionSeq>(bad_prog_or_error), info, {});
2✔
1343
        REQUIRE_FALSE(verify(bad_prog));
2✔
1344
    }
56✔
1345

1346
    SECTION("ptr_to_func callback target must be a valid instruction label") {
54✔
1347
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1348
        RawProgram good_raw_prog{
2✔
1349
            "",
1✔
1350
            "",
1✔
1351
            0,
1352
            "",
1✔
1353
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = INST_LD_MODE_CODE_ADDR, .imm = 7}, EbpfInst{},
1354
             EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1}, EbpfInst{.opcode = mov64_imm, .dst = 3, .imm = 0},
1355
             EbpfInst{.opcode = mov64_imm, .dst = 4, .imm = 0}, EbpfInst{.opcode = INST_OP_CALL, .imm = 181}, exit,
1356
             EbpfInst{.opcode = mov64_imm, .dst = 0, .imm = 0}, exit},
1357
            info};
5✔
1358
        auto good_prog_or_error = unmarshal(good_raw_prog, {});
2✔
1359
        REQUIRE(std::holds_alternative<InstructionSeq>(good_prog_or_error));
2✔
1360
        const Program good_prog = Program::from_sequence(std::get<InstructionSeq>(good_prog_or_error), info, {});
2✔
1361
        REQUIRE(verify(good_prog));
2✔
1362

1363
        RawProgram bad_raw_prog{
2✔
1364
            "",
1✔
1365
            "",
1✔
1366
            0,
1367
            "",
1✔
1368
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = INST_LD_MODE_CODE_ADDR, .imm = 1}, EbpfInst{},
1369
             EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1}, EbpfInst{.opcode = mov64_imm, .dst = 3, .imm = 0},
1370
             EbpfInst{.opcode = mov64_imm, .dst = 4, .imm = 0}, EbpfInst{.opcode = INST_OP_CALL, .imm = 181}, exit,
1371
             EbpfInst{.opcode = mov64_imm, .dst = 0, .imm = 0}, exit},
1372
            info};
5✔
1373
        auto bad_prog_or_error = unmarshal(bad_raw_prog, {});
2✔
1374
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_prog_or_error));
2✔
1375
        const Program bad_prog = Program::from_sequence(std::get<InstructionSeq>(bad_prog_or_error), info, {});
2✔
1376
        REQUIRE_FALSE(verify(bad_prog));
2✔
1377
    }
56✔
1378

1379
    SECTION("ptr_to_func callback target must have reachable exit") {
54✔
1380
        constexpr uint8_t mov64_imm = INST_CLS_ALU64 | INST_ALU_OP_MOV | INST_SRC_IMM;
2✔
1381
        RawProgram bad_raw_prog{
2✔
1382
            "",
1✔
1383
            "",
1✔
1384
            0,
1385
            "",
1✔
1386
            {EbpfInst{.opcode = INST_OP_LDDW_IMM, .dst = 2, .src = INST_LD_MODE_CODE_ADDR, .imm = 7}, EbpfInst{},
1387
             EbpfInst{.opcode = mov64_imm, .dst = 1, .imm = 1}, EbpfInst{.opcode = mov64_imm, .dst = 3, .imm = 0},
1388
             EbpfInst{.opcode = mov64_imm, .dst = 4, .imm = 0}, EbpfInst{.opcode = INST_OP_CALL, .imm = 181}, exit,
1389
             EbpfInst{.opcode = INST_OP_JA16, .offset = -1}},
1390
            info};
5✔
1391
        auto bad_prog_or_error = unmarshal(bad_raw_prog, {});
2✔
1392
        REQUIRE(std::holds_alternative<InstructionSeq>(bad_prog_or_error));
2✔
1393
        const Program bad_prog = Program::from_sequence(std::get<InstructionSeq>(bad_prog_or_error), info, {});
2✔
1394
        REQUIRE_FALSE(verify(bad_prog));
2✔
1395
    }
56✔
1396

1397
    SECTION("helper id not usable on platform") {
54✔
1398
        RawProgram raw_prog{"", "", 0, "", {EbpfInst{.opcode = INST_OP_CALL, .imm = 0x7fff}, exit}, info};
6✔
1399
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1400
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1401
        REQUIRE_THROWS_WITH(
9✔
1402
            Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}),
1403
            Catch::Matchers::ContainsSubstring("rejected: helper function is unavailable on this platform") &&
1404
                Catch::Matchers::ContainsSubstring("(at 0)"));
1405
    }
56✔
1406

1407
    SECTION("be64 requires base64 conformance group") {
54✔
1408
        ebpf_platform_t p = g_ebpf_platform_linux;
2✔
1409
        p.supported_conformance_groups &= ~bpf_conformance_groups_t::base64;
2✔
1410
        ProgramInfo pinfo{.platform = &p, .type = p.get_program_type("unspec", "unspec")};
5✔
1411
        RawProgram raw_prog{"",
2✔
1412
                            "",
1✔
1413
                            0,
1414
                            "",
1✔
1415
                            {EbpfInst{.opcode = static_cast<uint8_t>(INST_CLS_ALU | INST_ALU_OP_END | INST_END_BE),
1416
                                      .dst = 1,
1417
                                      .imm = 64},
1418
                             exit},
1419
                            pinfo};
5✔
1420
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1421
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1422
        REQUIRE_THROWS_WITH(Program::from_sequence(std::get<InstructionSeq>(prog_or_error), pinfo, {}),
9✔
1423
                            Catch::Matchers::ContainsSubstring("rejected: requires conformance group base64") &&
1424
                                Catch::Matchers::ContainsSubstring("(at 0)"));
1425
    }
56✔
1426

1427
    SECTION("call btf cannot use register-call opcode form") {
54✔
1428
        RawProgram raw_prog{
2✔
1429
            "",  "", 0, "", {EbpfInst{.opcode = INST_OP_CALLX, .dst = 0, .src = INST_CALL_BTF_HELPER, .imm = 1}, exit},
1✔
1430
            info};
5✔
1431
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1432
        REQUIRE(std::holds_alternative<std::string>(prog_or_error));
2✔
1433
        REQUIRE_THAT(std::get<std::string>(prog_or_error), Catch::Matchers::ContainsSubstring("bad instruction"));
7✔
1434
    }
56✔
1435

1436
    SECTION("tail call chain depth above 33 is rejected") {
54✔
1437
        std::vector<EbpfInst> insts;
2✔
1438
        for (size_t i = 0; i < 34; i++) {
70✔
1439
            insts.push_back(EbpfInst{.opcode = INST_OP_CALL, .imm = 12});
68✔
1440
        }
1441
        insts.push_back(exit);
2✔
1442
        RawProgram raw_prog{"", "", 0, "", std::move(insts), info};
5✔
1443
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1444
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1445
        REQUIRE_THROWS_WITH(Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}),
9✔
1446
                            Catch::Matchers::ContainsSubstring("tail call chain depth exceeds 33") &&
1447
                                Catch::Matchers::ContainsSubstring("(at"));
1448
    }
56✔
1449

1450
    SECTION("tail call chain depth of exactly 33 is accepted") {
54✔
1451
        std::vector<EbpfInst> insts;
2✔
1452
        for (size_t i = 0; i < 33; i++) {
68✔
1453
            insts.push_back(EbpfInst{.opcode = INST_OP_CALL, .imm = 12});
66✔
1454
        }
1455
        insts.push_back(exit);
2✔
1456
        RawProgram raw_prog{"", "", 0, "", std::move(insts), info};
5✔
1457
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1458
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1459
        REQUIRE_NOTHROW(Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}));
3✔
1460
    }
56✔
1461

1462
    SECTION("tail call cycle does not inflate chain depth") {
54✔
1463
        // No exit instruction is intentional; this checks SCC-based depth accounting, not termination.
1464
        RawProgram raw_prog{"",
2✔
1465
                            "",
1✔
1466
                            0,
1467
                            "",
1✔
1468
                            {
1469
                                EbpfInst{.opcode = INST_OP_CALL, .imm = 12},
1470
                                EbpfInst{.opcode = INST_OP_JA16, .offset = -2},
1471
                            },
1472
                            info};
5✔
1473
        auto prog_or_error = unmarshal(raw_prog, {});
2✔
1474
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));
2✔
1475
        REQUIRE_NOTHROW(Program::from_sequence(std::get<InstructionSeq>(prog_or_error), info, {}));
3✔
1476
    }
56✔
1477
}
54✔
1478

1479
#define TEST_LEGACY(dirname, filename, sectionname)                                                             \
1480
    TEST_CASE("Unsupported instructions: " dirname "/" filename " " sectionname, "[unmarshal]") {               \
1481
        ebpf_platform_t platform = g_ebpf_platform_linux;                                                       \
1482
        platform.supported_conformance_groups &= ~bpf_conformance_groups_t::packet;                             \
1483
        ElfObject elf{"ebpf-samples/" dirname "/" filename, {}, &platform};                                     \
1484
        const auto& raw_progs = elf.get_programs(sectionname);                                                  \
1485
        REQUIRE(raw_progs.size() == 1);                                                                         \
1486
        RawProgram raw_prog = raw_progs.back();                                                                 \
1487
        std::variant<InstructionSeq, std::string> prog_or_error = unmarshal(raw_prog, {});                      \
1488
        REQUIRE(std::holds_alternative<InstructionSeq>(prog_or_error));                                         \
1489
        REQUIRE_THROWS_WITH(Program::from_sequence(std::get<InstructionSeq>(prog_or_error), raw_prog.info, {}), \
1490
                            Catch::Matchers::ContainsSubstring("rejected: requires conformance group packet")); \
1491
    }
1492

1493
TEST_LEGACY("bpf_cilium_test", "bpf_lxc_jit.o", "2/10")
15✔
1494
TEST_LEGACY("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "from-container")
15✔
1495
TEST_LEGACY("bpf_cilium_test", "bpf_overlay.o", "from-overlay")
15✔
1496
TEST_LEGACY("cilium", "bpf_overlay.o", "from-overlay")
15✔
1497
TEST_LEGACY("linux", "sockex1_kern.o", "socket1")
15✔
1498
TEST_LEGACY("linux", "sockex2_kern.o", "socket2")
15✔
1499
TEST_LEGACY("linux", "sockex3_kern.o", "socket/3")
15✔
1500
TEST_LEGACY("linux", "sockex3_kern.o", "socket/4")
15✔
1501
TEST_LEGACY("linux", "sockex3_kern.o", "socket/1")
15✔
1502
TEST_LEGACY("linux", "sockex3_kern.o", "socket/2")
15✔
1503
TEST_LEGACY("linux", "sockex3_kern.o", "socket/0")
15✔
1504
TEST_LEGACY("linux", "tcbpf1_kern.o", "classifier")
15✔
1505
TEST_LEGACY("ovs", "datapath.o", "tail-3")
15✔
1506
TEST_LEGACY("ovs", "datapath.o", "tail-32")
15✔
1507
TEST_LEGACY("suricata", "bypass_filter.o", "filter")
15✔
1508
TEST_LEGACY("suricata", "lb.o", "loadbalancer")
15✔
1509
TEST_LEGACY("cilium", "bpf_netdev.o", "from-netdev")
15✔
1510
TEST_LEGACY("bpf_cilium_test", "bpf_netdev.o", "from-netdev")
15✔
1511
TEST_LEGACY("cilium", "bpf_lxc.o", "2/10")
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc