• 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

95.16
/src/ir/unmarshal.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <cassert>
4
#include <iostream>
5
#include <string>
6
#include <vector>
7

8
#include "crab_utils/debug.hpp"
9
#include "crab_utils/num_safety.hpp"
10
#include "ir/unmarshal.hpp"
11
#include "spec/vm_isa.hpp"
12

13
using std::string;
14
using std::vector;
15

16
namespace prevail {
17
int opcode_to_width(const uint8_t opcode) {
160,982✔
18
    switch (opcode & INST_SIZE_MASK) {
160,982✔
19
    case INST_SIZE_B: return 1;
7,108✔
20
    case INST_SIZE_H: return 2;
22,518✔
21
    case INST_SIZE_W: return 4;
46,618✔
22
    case INST_SIZE_DW: return 8;
77,630✔
23
    default: CRAB_ERROR("unexpected opcode", opcode);
×
24
    }
25
}
26

27
uint8_t width_to_opcode(const int width) {
134✔
28
    switch (width) {
134✔
29
    case 1: return INST_SIZE_B;
14✔
30
    case 2: return INST_SIZE_H;
14✔
31
    case 4: return INST_SIZE_W;
38✔
32
    case 8: return INST_SIZE_DW;
54✔
33
    default: CRAB_ERROR("unexpected width", width);
×
34
    }
35
}
36

37
template <typename T>
38
void compare(const string& field, T actual, T expected) {
39
    if (actual != expected) {
40
        std::cerr << field << ": (actual) " << std::hex << static_cast<int>(actual)
41
                  << " != " << static_cast<int>(expected) << " (expected)\n";
42
    }
43
}
44

45
static std::string make_opcode_message(const char* msg, const uint8_t opcode) {
744✔
46
    std::ostringstream oss;
744✔
47
    oss << msg << " op 0x" << std::hex << static_cast<int>(opcode);
744✔
48
    return oss.str();
1,488✔
49
}
744✔
50

51
struct InvalidInstruction final : std::invalid_argument {
52
    size_t pc;
53
    explicit InvalidInstruction(const size_t pc, const char* what) : std::invalid_argument{what}, pc{pc} {}
1,116✔
54
    InvalidInstruction(const size_t pc, const std::string& what) : std::invalid_argument{what}, pc{pc} {}
237✔
55
    InvalidInstruction(const size_t pc, const uint8_t opcode)
402✔
56
        : std::invalid_argument{make_opcode_message("bad instruction", opcode)}, pc{pc} {}
603✔
57
};
58

59
static auto getMemIsLoad(const uint8_t opcode) -> bool {
290,154✔
60
    switch (opcode & INST_CLS_MASK) {
290,154✔
61
    case INST_CLS_LD:
71,392✔
62
    case INST_CLS_LDX: return true;
71,392✔
63
    case INST_CLS_ST:
147,370✔
64
    case INST_CLS_STX: return false;
147,370✔
65
    default: CRAB_ERROR("unexpected opcode", opcode);
×
66
    }
67
}
68

69
static auto getMemWidth(const uint8_t opcode) -> int {
292,432✔
70
    switch (opcode & INST_SIZE_MASK) {
292,432✔
71
    case INST_SIZE_B: return 1;
43,031✔
72
    case INST_SIZE_H: return 2;
35,106✔
73
    case INST_SIZE_W: return 4;
81,352✔
74
    case INST_SIZE_DW: return 8;
89,912✔
75
    default: CRAB_ERROR("unexpected opcode", opcode);
×
76
    }
77
}
78

79
static Instruction shift32(const Reg dst, const Bin::Op op) {
604,138✔
80
    return Bin{.op = op, .dst = dst, .v = Imm{32}, .is64 = true, .lddw = false};
604,138✔
81
}
82

83
struct Unmarshaller {
84
    vector<vector<string>>& notes;
85
    const ProgramInfo& info;
86
    // ReSharper disable once CppMemberFunctionMayBeConst
87
    void note(const string& what) { notes.back().emplace_back(what); }
1,598✔
88
    // ReSharper disable once CppMemberFunctionMayBeConst
89
    void note_next_pc() { notes.emplace_back(); }
1,202,464✔
90
    explicit Unmarshaller(vector<vector<string>>& notes, const ProgramInfo& info) : notes{notes}, info{info} {
5,414✔
91
        note_next_pc();
8,121✔
92
    }
5,414✔
93

94
    auto getAluOp(const size_t pc, const EbpfInst inst) -> std::variant<Bin::Op, Un::Op> {
596,450✔
95
        // First handle instructions that support a non-zero offset.
96
        switch (inst.opcode & INST_ALU_OP_MASK) {
596,450✔
97
        case INST_ALU_OP_DIV:
6,492✔
98
            switch (inst.offset) {
6,492✔
99
            case 0: return Bin::Op::UDIV;
6,414✔
100
            case 1: return Bin::Op::SDIV;
70✔
101
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
12✔
102
            }
103
        case INST_ALU_OP_MOD:
206✔
104
            switch (inst.offset) {
206✔
105
            case 0: return Bin::Op::UMOD;
114✔
106
            case 1: return Bin::Op::SMOD;
84✔
107
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
12✔
108
            }
109
        case INST_ALU_OP_MOV:
292,898✔
110
            if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) {
292,898✔
111
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
112
            }
113
            switch (inst.offset) {
292,894✔
114
            case 0: return Bin::Op::MOV;
292,818✔
115
            case 8: return Bin::Op::MOVSX8;
24✔
116
            case 16: return Bin::Op::MOVSX16;
24✔
117
            case 32: return Bin::Op::MOVSX32;
14✔
118
            default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode));
21✔
119
            }
120
        default: break;
296,854✔
121
        }
122

123
        // All the rest require a zero offset.
124
        if (inst.offset != 0) {
296,854✔
125
            throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
141✔
126
        }
127

128
        switch (inst.opcode & INST_ALU_OP_MASK) {
296,760✔
129
        case INST_ALU_OP_ADD: return Bin::Op::ADD;
103,440✔
130
        case INST_ALU_OP_SUB: return Bin::Op::SUB;
2,896✔
131
        case INST_ALU_OP_MUL: return Bin::Op::MUL;
918✔
132
        case INST_ALU_OP_OR: return Bin::Op::OR;
52,168✔
133
        case INST_ALU_OP_AND: return Bin::Op::AND;
30,148✔
134
        case INST_ALU_OP_LSH: return Bin::Op::LSH;
51,748✔
135
        case INST_ALU_OP_RSH: return Bin::Op::RSH;
37,126✔
136
        case INST_ALU_OP_NEG:
298✔
137
            // Negation is a unary operation. The SRC bit, src, and imm must be all 0.
138
            if (inst.opcode & INST_SRC_REG) {
298✔
139
                throw InvalidInstruction{pc, inst.opcode};
4✔
140
            }
141
            if (inst.src != 0) {
294✔
142
                throw InvalidInstruction{pc, inst.opcode};
4✔
143
            }
144
            if (inst.imm != 0) {
290✔
145
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
6✔
146
            }
147
            return Un::Op::NEG;
286✔
148
        case INST_ALU_OP_XOR: return Bin::Op::XOR;
3,778✔
149
        case INST_ALU_OP_ARSH:
6,394✔
150
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) {
6,394✔
151
                note("arsh32 is not allowed");
1,852✔
152
            }
153
            return Bin::Op::ARSH;
6,394✔
154
        case INST_ALU_OP_END:
7,830✔
155
            if (inst.src != 0) {
7,830✔
156
                throw InvalidInstruction{pc, inst.opcode};
18✔
157
            }
158
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) {
7,812✔
159
                if (inst.opcode & INST_END_BE) {
52✔
160
                    throw InvalidInstruction(pc, inst.opcode);
2✔
161
                }
162
                switch (inst.imm) {
50✔
163
                case 16: return Un::Op::SWAP16;
14✔
164
                case 32: return Un::Op::SWAP32;
14✔
165
                case 64: return Un::Op::SWAP64;
14✔
166
                default: throw InvalidInstruction(pc, "unsupported immediate");
8✔
167
                }
168
            }
169
            switch (inst.imm) {
7,760✔
170
            case 16: return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16;
7,338✔
171
            case 32: return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32;
356✔
172
            case 64: return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64;
73✔
173
            default: throw InvalidInstruction(pc, "unsupported immediate");
16✔
174
            }
175
        case 0xe0: throw InvalidInstruction{pc, inst.opcode};
8✔
176
        case 0xf0: throw InvalidInstruction{pc, inst.opcode};
8✔
177
        default: return {};
×
178
        }
179
    }
180

181
    static auto getAtomicOp(const size_t pc, const EbpfInst inst) -> Atomic::Op {
1,400✔
182
        switch (const auto op = gsl::narrow<Atomic::Op>(inst.imm & ~INST_FETCH)) {
1,400✔
183
        case Atomic::Op::XCHG:
56✔
184
        case Atomic::Op::CMPXCHG:
28✔
185
            if ((inst.imm & INST_FETCH) == 0) {
56✔
186
                throw InvalidInstruction(pc, "unsupported immediate");
8✔
187
            }
188
        case Atomic::Op::ADD:
684✔
189
        case Atomic::Op::OR:
684✔
190
        case Atomic::Op::AND:
684✔
191
        case Atomic::Op::XOR: return op;
1,368✔
192
        }
193
        throw InvalidInstruction(pc, "unsupported immediate");
24✔
194
    }
195

196
    static uint64_t sign_extend(const int32_t imm) { return to_unsigned(int64_t{imm}); }
430,256✔
197

198
    static uint64_t zero_extend(const int32_t imm) { return uint64_t{to_unsigned(imm)}; }
74✔
199

200
    static auto getBinValue(const Pc pc, const EbpfInst inst) -> Value {
588,178✔
201
        if (inst.opcode & INST_SRC_REG) {
588,178✔
202
            if (inst.imm != 0) {
244,280✔
203
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
66✔
204
            }
205
            return Reg{inst.src};
244,214✔
206
        }
207
        if (inst.src != 0) {
343,898✔
208
            throw InvalidInstruction{pc, inst.opcode};
56✔
209
        }
210
        // Imm is a signed 32-bit number.  Sign extend it to 64-bits for storage.
211
        return Imm{sign_extend(inst.imm)};
343,842✔
212
    }
213

214
    static auto getJmpOp(const size_t pc, const uint8_t opcode) -> Condition::Op {
126,140✔
215
        using Op = Condition::Op;
63,070✔
216
        switch ((opcode >> 4) & 0xF) {
126,140✔
217
        case 0x0: return {}; // goto
11,338✔
218
        case 0x1: return Op::EQ;
23,506✔
219
        case 0x2: return Op::GT;
9,622✔
220
        case 0x3: return Op::GE;
802✔
221
        case 0x4: return Op::SET;
82✔
222
        case 0x5: return Op::NE;
29,030✔
223
        case 0x6: return Op::SGT;
7,046✔
224
        case 0x7: return Op::SGE;
158✔
225
        case 0x8: return {}; // call
226
        case 0x9: return {}; // exit
227
        case 0xa: return Op::LT;
2,722✔
228
        case 0xb: return Op::LE;
332✔
229
        case 0xc: return Op::SLT;
6,552✔
230
        case 0xd: return Op::SLE;
90✔
231
        case 0xe: throw InvalidInstruction(pc, opcode);
8✔
232
        case 0xf: throw InvalidInstruction(pc, opcode);
8✔
233
        default: return {};
234
        }
235
    }
236

237
    auto makeMemOp(const Pc pc, const EbpfInst inst) -> Instruction {
292,570✔
238
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
292,570✔
239
            throw InvalidInstruction(pc, "bad register");
138✔
240
        }
241

242
        const int width = getMemWidth(inst.opcode);
292,432✔
243
        const bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD;
292,432✔
244
        switch (inst.opcode & INST_MODE_MASK) {
292,432✔
245
        case INST_MODE_IMM: throw InvalidInstruction(pc, inst.opcode);
30✔
246

247
        case INST_MODE_ABS:
210✔
248
            if (!isLD || (width == 8)) {
210✔
249
                throw InvalidInstruction(pc, inst.opcode);
26✔
250
            }
251
            if (inst.dst != 0) {
184✔
252
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
6✔
253
            }
254
            if (inst.src > 0) {
178✔
255
                throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
6✔
256
            }
257
            if (inst.offset != 0) {
172✔
258
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
259
            }
260
            return Packet{.width = width, .offset = inst.imm, .regoffset = {}};
166✔
261

262
        case INST_MODE_IND:
488✔
263
            if (!isLD || (width == 8)) {
488✔
264
                throw InvalidInstruction(pc, inst.opcode);
26✔
265
            }
266
            if (inst.dst != 0) {
462✔
267
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
6✔
268
            }
269
            if (inst.src > R10_STACK_POINTER) {
456✔
270
                throw InvalidInstruction(pc, "bad register");
271
            }
272
            if (inst.offset != 0) {
456✔
273
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
274
            }
275
            return Packet{.width = width, .offset = inst.imm, .regoffset = Reg{inst.src}};
450✔
276

277
        case INST_MODE_MEM: {
290,162✔
278
            if (isLD) {
290,162✔
279
                throw InvalidInstruction(pc, inst.opcode);
8✔
280
            }
281
            const bool isLoad = getMemIsLoad(inst.opcode);
290,154✔
282
            if (isLoad && inst.dst == R10_STACK_POINTER) {
290,154✔
283
                throw InvalidInstruction(pc, "cannot modify r10");
2✔
284
            }
285
            const bool isImm = !(inst.opcode & 1);
290,152✔
286
            if (isImm && inst.src != 0) {
290,152✔
287
                throw InvalidInstruction(pc, inst.opcode);
8✔
288
            }
289
            if (!isImm && inst.imm != 0) {
290,107✔
290
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
16✔
291
            }
292

293
            assert(!(isLoad && isImm));
290,128✔
294
            const uint8_t basereg = isLoad ? inst.src : inst.dst;
290,128✔
295

296
            if (basereg == R10_STACK_POINTER &&
370,590✔
297
                (inst.offset + opcode_to_width(inst.opcode) > 0 || inst.offset < -EBPF_TOTAL_STACK_SIZE)) {
160,924✔
298
                note("Stack access out of bounds");
16✔
299
            }
300
            auto res = Mem{
290,128✔
301
                .access =
302
                    Deref{
303
                        .width = width,
304
                        .basereg = Reg{basereg},
305
                        .offset = inst.offset,
290,128✔
306
                    },
307
                .value = isLoad  ? Value{Reg{inst.dst}}
71,387✔
308
                         : isImm ? Value{Imm{zero_extend(inst.imm)}}
147,354✔
309
                                 : Value{Reg{inst.src}},
73,640✔
310
                .is_load = isLoad,
311
            };
437,482✔
312
            return res;
290,128✔
313
        }
314
        case INST_MODE_MEMSX: {
50✔
315
            // Sign-extending loads are only valid for LDX B/H/W forms.
316
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_LDX || width == 8) {
50✔
317
                throw InvalidInstruction(pc, inst.opcode);
26✔
318
            }
319
            if (inst.dst == R10_STACK_POINTER) {
24✔
320
                throw InvalidInstruction(pc, "cannot modify r10");
×
321
            }
322
            if (inst.imm != 0) {
24✔
323
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
6✔
324
            }
325
            if (inst.src == R10_STACK_POINTER &&
21✔
326
                (inst.offset + opcode_to_width(inst.opcode) > 0 || inst.offset < -EBPF_TOTAL_STACK_SIZE)) {
6✔
327
                note("Stack access out of bounds");
×
328
            }
329
            return Mem{
36✔
330
                .access =
331
                    Deref{
332
                        .width = width,
333
                        .basereg = Reg{inst.src},
18✔
334
                        .offset = inst.offset,
18✔
335
                    },
336
                .value = Value{Reg{inst.dst}},
27✔
337
                .is_load = true,
338
                .is_signed = true,
339
            };
18✔
340
        }
341

342
        case INST_MODE_ATOMIC:
1,428✔
343
            if (((inst.opcode & INST_CLS_MASK) != INST_CLS_STX) ||
1,428✔
344
                ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) {
1,404✔
345
                throw InvalidInstruction(pc, inst.opcode);
28✔
346
            }
347
            return Atomic{
2,084✔
348
                .op = getAtomicOp(pc, inst),
1,400✔
349
                .fetch = (inst.imm & INST_FETCH) == INST_FETCH,
1,368✔
350
                .access =
351
                    Deref{
352
                        .width = width,
353
                        .basereg = Reg{inst.dst},
1,368✔
354
                        .offset = inst.offset,
1,368✔
355
                    },
356
                .valreg = Reg{inst.src},
1,368✔
357
            };
1,368✔
358
        default: throw InvalidInstruction(pc, inst.opcode);
64✔
359
        }
360
    }
361

362
    auto makeAluOp(const size_t pc, const EbpfInst inst) -> Instruction {
596,662✔
363
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
596,662✔
364
        if (inst.dst == R10_STACK_POINTER) {
596,662✔
365
            throw InvalidInstruction(pc, "invalid target r10");
2✔
366
        }
367
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
596,660✔
368
            throw InvalidInstruction(pc, "bad register");
210✔
369
        }
370
        return std::visit(
298,225✔
371
            Overloaded{[&](const Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; },
306,258✔
372
                       [&](const Bin::Op op) -> Instruction {
588,178✔
373
                           Bin res{
588,178✔
374
                               .op = op,
375
                               .dst = Reg{inst.dst},
588,178✔
376
                               .v = getBinValue(pc, inst),
588,178✔
377
                               .is64 = is64,
588,056✔
378
                           };
588,178✔
379
                           if (!thread_local_options.allow_division_by_zero &&
588,056✔
380
                               (op == Bin::Op::UDIV || op == Bin::Op::UMOD)) {
×
381
                               if (const auto pimm = std::get_if<Imm>(&res.v)) {
×
382
                                   if (pimm->v == 0) {
×
383
                                       note("division by zero");
×
384
                                   }
385
                               }
386
                           }
387
                           return res;
882,084✔
388
                       }},
389
            getAluOp(pc, inst));
894,575✔
390
    }
391

392
    [[nodiscard]]
393
    auto makeLddw(const EbpfInst inst, const int32_t next_imm, const vector<EbpfInst>& insts, const Pc pc) const
60,212✔
394
        -> Instruction {
395
        if (pc >= insts.size() - 1) {
60,212✔
396
            throw InvalidInstruction(pc, "incomplete lddw");
20✔
397
        }
398
        const EbpfInst next = insts[pc + 1];
60,192✔
399
        if (next.opcode != 0 || next.dst != 0 || next.src != 0 || next.offset != 0) {
60,192✔
400
            throw InvalidInstruction(pc, "invalid lddw");
16✔
401
        }
402
        if (inst.offset != 0) {
60,176✔
403
            throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
14✔
404
        }
405
        if (inst.dst > R10_STACK_POINTER) {
60,162✔
406
            throw InvalidInstruction(pc, "bad register");
14✔
407
        }
408

409
        switch (inst.src) {
60,148✔
410
        case INST_LD_MODE_IMM:
18,038✔
411
            return Bin{
45,095✔
412
                .op = Bin::Op::MOV,
413
                .dst = Reg{inst.dst},
18,038✔
414
                .v = Imm{merge(inst.imm, next_imm)},
27,057✔
415
                .is64 = true,
416
                .lddw = true,
417
            };
18,038✔
418
        case INST_LD_MODE_MAP_FD: {
37,564✔
419
            // magic number, meaning we're a per-process file descriptor defining the map.
420
            // (for details, look for BPF_PSEUDO_MAP_FD in the kernel)
421
            if (next.imm != 0) {
37,564✔
422
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
423
            }
424
            return LoadMapFd{.dst = Reg{inst.dst}, .mapfd = inst.imm};
37,562✔
425
        }
426
        case INST_LD_MODE_MAP_VALUE: return LoadMapAddress{.dst = Reg{inst.dst}, .mapfd = inst.imm, .offset = next_imm};
4,498✔
427
        case INST_LD_MODE_VARIABLE_ADDR:
10✔
428
            if (next.imm != 0) {
10✔
429
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
430
            }
431
            return LoadPseudo{.dst = Reg{inst.dst},
8✔
432
                              .addr = PseudoAddress{
433
                                  .kind = PseudoAddress::Kind::VARIABLE_ADDR, .imm = inst.imm, .next_imm = next_imm}};
8✔
434
        case INST_LD_MODE_CODE_ADDR:
22✔
435
            if (next.imm != 0) {
22✔
436
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
437
            }
438
            return LoadPseudo{
20✔
439
                .dst = Reg{inst.dst},
20✔
440
                .addr = PseudoAddress{.kind = PseudoAddress::Kind::CODE_ADDR, .imm = inst.imm, .next_imm = next_imm}};
20✔
441
        case INST_LD_MODE_MAP_BY_IDX:
8✔
442
            if (next.imm != 0) {
8✔
443
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
444
            }
445
            return LoadPseudo{
6✔
446
                .dst = Reg{inst.dst},
6✔
447
                .addr = PseudoAddress{.kind = PseudoAddress::Kind::MAP_BY_IDX, .imm = inst.imm, .next_imm = next_imm}};
6✔
448
        case INST_LD_MODE_MAP_VALUE_BY_IDX:
6✔
449
            // map_value_by_idx carries the value offset in next_imm (same encoding role as map_value),
450
            // so next.imm is not reserved in this mode.
451
            return LoadPseudo{.dst = Reg{inst.dst},
6✔
452
                              .addr = PseudoAddress{.kind = PseudoAddress::Kind::MAP_VALUE_BY_IDX,
453
                                                    .imm = inst.imm,
3✔
454
                                                    .next_imm = next_imm}};
6✔
455
        default: throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode));
2✔
456
        }
457
    }
458

459
    static ArgSingle::Kind toArgSingleKind(const ebpf_argument_type_t t) {
132,446✔
460
        switch (t) {
132,446✔
461
        case EBPF_ARGUMENT_TYPE_ANYTHING: return ArgSingle::Kind::ANYTHING;
18,875✔
462
        case EBPF_ARGUMENT_TYPE_PTR_TO_STACK: return ArgSingle::Kind::PTR_TO_STACK;
463
        case EBPF_ARGUMENT_TYPE_PTR_TO_STACK_OR_NULL: return ArgSingle::Kind::PTR_TO_STACK;
11✔
464
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
17,150✔
465
        case EBPF_ARGUMENT_TYPE_CONST_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
2✔
466
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS: return ArgSingle::Kind::MAP_FD_PROGRAMS;
821✔
467
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY: return ArgSingle::Kind::PTR_TO_MAP_KEY;
16,171✔
468
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
6,827✔
469
        case EBPF_ARGUMENT_TYPE_PTR_TO_UNINIT_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
1✔
470
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: return ArgSingle::Kind::PTR_TO_CTX;
6,330✔
471
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX_OR_NULL: return ArgSingle::Kind::PTR_TO_CTX;
24✔
472
        case EBPF_ARGUMENT_TYPE_PTR_TO_FUNC: return ArgSingle::Kind::PTR_TO_FUNC;
11✔
473
        default: break;
474
        }
475
        return {};
476
    }
477

478
    static ArgPair::Kind toArgPairKind(const ebpf_argument_type_t t) {
13,834✔
479
        switch (t) {
13,834✔
480
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
3,490✔
481
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
3,490✔
482
        case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM_OR_NULL:
36✔
483
        case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
36✔
484
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
6,782✔
485
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: return ArgPair::Kind::PTR_TO_WRITABLE_MEM;
6,782✔
486
        default: break;
487
        }
488
        return {};
489
    }
490

491
    [[nodiscard]]
492
    auto makeCall(const int32_t imm) const -> Call {
58,924✔
493
        const EbpfHelperPrototype proto = info.platform->get_helper_prototype(imm);
58,924✔
494
        Call res;
58,924✔
495
        res.func = imm;
58,924✔
496
        res.name = proto.name;
58,924✔
497
        auto mark_unsupported = [&](const std::string& why) -> Call {
29,470✔
498
            res.is_supported = false;
8✔
499
            res.unsupported_reason = why;
12✔
500
            return res;
8✔
501
        };
58,924✔
502
        const auto return_info = classify_call_return_type(proto.return_type);
58,924✔
503
        if (!return_info.has_value()) {
58,924✔
504
            return mark_unsupported(std::string("helper prototype is unavailable on this platform: ") + proto.name);
6✔
505
        }
506
        res.return_ptr_type = return_info->pointer_type;
58,922✔
507
        res.return_nullable = return_info->pointer_nullable;
58,922✔
508
        res.reallocate_packet = proto.reallocate_packet;
58,922✔
509
        res.is_map_lookup = proto.return_type == EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL;
58,922✔
510
        const std::array<ebpf_argument_type_t, 7> args = {
58,922✔
511
            {EBPF_ARGUMENT_TYPE_DONTCARE, proto.argument_type[0], proto.argument_type[1], proto.argument_type[2],
58,922✔
512
             proto.argument_type[3], proto.argument_type[4], EBPF_ARGUMENT_TYPE_DONTCARE}};
58,922✔
513
        for (size_t i = 1; i < args.size() - 1; i++) {
308,160✔
514
            switch (args[i]) {
198,764✔
515
            case EBPF_ARGUMENT_TYPE_UNSUPPORTED:
×
516
                return mark_unsupported(std::string("helper argument type is unavailable on this platform: ") +
×
517
                                        proto.name);
×
518
            case EBPF_ARGUMENT_TYPE_DONTCARE: return res;
52,240✔
519
            case EBPF_ARGUMENT_TYPE_ANYTHING:
132,376✔
520
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP:
66,188✔
521
            case EBPF_ARGUMENT_TYPE_CONST_PTR_TO_MAP:
66,188✔
522
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS:
66,188✔
523
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY:
66,188✔
524
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE:
66,188✔
525
            case EBPF_ARGUMENT_TYPE_PTR_TO_UNINIT_MAP_VALUE:
66,188✔
526
            case EBPF_ARGUMENT_TYPE_PTR_TO_STACK:
66,188✔
527
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX:
66,188✔
528
            case EBPF_ARGUMENT_TYPE_PTR_TO_FUNC:
66,188✔
529
                res.singles.push_back({toArgSingleKind(args[i]), false, Reg{gsl::narrow<uint8_t>(i)}});
198,562✔
530
                break;
132,376✔
531
            case EBPF_ARGUMENT_TYPE_PTR_TO_STACK_OR_NULL:
70✔
532
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX_OR_NULL:
35✔
533
                res.singles.push_back({toArgSingleKind(args[i]), true, Reg{gsl::narrow<uint8_t>(i)}});
70✔
534
                break;
70✔
535
            case EBPF_ARGUMENT_TYPE_PTR_TO_BTF_ID_SOCK_COMMON:
144✔
536
            case EBPF_ARGUMENT_TYPE_PTR_TO_SOCK_COMMON:
72✔
537
                res.singles.push_back({ArgSingle::Kind::PTR_TO_SOCKET, false, Reg{gsl::narrow<uint8_t>(i)}});
144✔
538
                break;
144✔
539
            case EBPF_ARGUMENT_TYPE_PTR_TO_BTF_ID:
12✔
540
            case EBPF_ARGUMENT_TYPE_PTR_TO_PERCPU_BTF_ID:
6✔
541
                res.singles.push_back({ArgSingle::Kind::PTR_TO_BTF_ID, false, Reg{gsl::narrow<uint8_t>(i)}});
12✔
542
                break;
12✔
543
            case EBPF_ARGUMENT_TYPE_PTR_TO_ALLOC_MEM:
22✔
544
                res.singles.push_back({ArgSingle::Kind::PTR_TO_ALLOC_MEM, false, Reg{gsl::narrow<uint8_t>(i)}});
22✔
545
                break;
22✔
546
            case EBPF_ARGUMENT_TYPE_PTR_TO_SPIN_LOCK:
32✔
547
                res.singles.push_back({ArgSingle::Kind::PTR_TO_SPIN_LOCK, false, Reg{gsl::narrow<uint8_t>(i)}});
32✔
548
                break;
32✔
549
            case EBPF_ARGUMENT_TYPE_PTR_TO_TIMER:
2✔
550
                res.singles.push_back({ArgSingle::Kind::PTR_TO_TIMER, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
551
                break;
2✔
552
            case EBPF_ARGUMENT_TYPE_CONST_ALLOC_SIZE_OR_ZERO:
22✔
553
                res.singles.push_back({ArgSingle::Kind::CONST_SIZE_OR_ZERO, false, Reg{gsl::narrow<uint8_t>(i)}});
22✔
554
                res.alloc_size_reg = Reg{gsl::narrow<uint8_t>(i)};
73,281✔
555
                break;
11✔
556
            case EBPF_ARGUMENT_TYPE_PTR_TO_LONG:
2✔
557
                res.singles.push_back({ArgSingle::Kind::PTR_TO_WRITABLE_LONG, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
558
                break;
2✔
559
            case EBPF_ARGUMENT_TYPE_PTR_TO_INT:
2✔
560
                res.singles.push_back({ArgSingle::Kind::PTR_TO_WRITABLE_INT, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
561
                break;
2✔
562
            case EBPF_ARGUMENT_TYPE_PTR_TO_CONST_STR:
6✔
563
                return mark_unsupported(std::string("helper argument type is unavailable on this platform: ") +
15✔
564
                                        proto.name);
12✔
UNCOV
565
            case EBPF_ARGUMENT_TYPE_CONST_SIZE: {
×
566
                // Sanity check: This argument should never be seen in isolation.
567
                return mark_unsupported(
568
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE: ") +
×
UNCOV
569
                    proto.name);
×
570
            }
UNCOV
571
            case EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: {
×
572
                // Sanity check: This argument should never be seen in isolation.
573
                return mark_unsupported(
574
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
UNCOV
575
                    proto.name);
×
576
            }
577
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
13,834✔
578
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM:
6,917✔
579
            case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM_OR_NULL:
6,917✔
580
            case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM:
6,917✔
581
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
6,917✔
582
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM:
6,917✔
583
                // Sanity check: This argument must be followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or
584
                // EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO.
585
                if (args.size() - i < 2) {
13,834✔
586
                    return mark_unsupported(
587
                        std::string(
588
                            "missing EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
589
                        proto.name);
590
                }
591
                if (args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE &&
17,589✔
592
                    args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO) {
7,510✔
593
                    return mark_unsupported(
594
                        std::string("Pointer argument not followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or "
×
595
                                    "EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
UNCOV
596
                        proto.name);
×
597
                }
598
                const bool can_be_zero = (args[i + 1] == EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO);
13,834✔
599
                const bool or_null = args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL ||
13,834✔
600
                                     args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM_OR_NULL ||
12,790✔
601
                                     args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL;
12,790✔
602
                res.pairs.push_back({toArgPairKind(args[i]), or_null, Reg{gsl::narrow<uint8_t>(i)},
24,142✔
603
                                     Reg{gsl::narrow<uint8_t>(i + 1)}, can_be_zero});
13,834✔
604
                i++;
13,834✔
605
                break;
13,834✔
606
            }
607
        }
608
        return res;
6,676✔
609
    }
58,924✔
610

611
    /// Given a program counter and an offset, get the label of the target instruction.
612
    static Label getJumpTarget(const int32_t offset, const vector<EbpfInst>& insts, const Pc pc) {
126,964✔
613
        const Pc new_pc = pc + 1 + offset;
126,964✔
614
        if (new_pc >= insts.size()) {
126,964✔
615
            throw InvalidInstruction(pc, "jump out of bounds");
138✔
616
        }
617
        if (insts[new_pc].opcode == 0) {
126,826✔
UNCOV
618
            throw InvalidInstruction(pc, "jump to middle of lddw");
×
619
        }
620
        return Label{gsl::narrow<int>(new_pc)};
126,826✔
621
    }
622

623
    static auto makeCallLocal(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) -> CallLocal {
936✔
624
        if (inst.opcode & INST_SRC_REG) {
936✔
625
            throw InvalidInstruction(pc, inst.opcode);
2✔
626
        }
627
        if (inst.dst != 0) {
934✔
628
            throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
629
        }
630
        return CallLocal{.target = getJumpTarget(inst.imm, insts, pc)};
932✔
631
    }
632

633
    static auto makeCallx(const EbpfInst inst, const Pc pc) -> Callx {
26✔
634
        // callx puts the register number in the 'dst' field rather than the 'src' field.
635
        if (inst.dst > R10_STACK_POINTER) {
26✔
636
            throw InvalidInstruction(pc, "bad register");
4✔
637
        }
638
        if (inst.imm != 0) {
22✔
639
            // Clang prior to v19 put the register number into the 'imm' field.
640
            if (inst.dst > 0) {
10✔
641
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
4✔
642
            }
643
            if (inst.imm < 0 || inst.imm > R10_STACK_POINTER) {
6✔
644
                throw InvalidInstruction(pc, "bad register");
4✔
645
            }
646
            return Callx{gsl::narrow<uint8_t>(inst.imm)};
2✔
647
        }
648
        return Callx{inst.dst};
12✔
649
    }
650

651
    [[nodiscard]]
652
    auto makeJmp(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) const -> Instruction {
193,842✔
653
        switch ((inst.opcode >> 4) & 0xF) {
193,842✔
654
        case INST_CALL:
62,308✔
655
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) {
62,308✔
656
                throw InvalidInstruction(pc, inst.opcode);
4✔
657
            }
658
            if (inst.src > INST_CALL_BTF_HELPER) {
62,304✔
659
                throw InvalidInstruction(pc, inst.opcode);
2✔
660
            }
661
            if (inst.offset != 0) {
62,302✔
662
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
8✔
663
            }
664
            if (inst.src == INST_CALL_LOCAL) {
62,294✔
665
                return makeCallLocal(inst, insts, pc);
1,401✔
666
            }
667
            if (inst.opcode & INST_SRC_REG) {
61,358✔
668
                // Register-call opcode form is reserved for callx and must not be used for src-based call modes.
669
                if (inst.src != 0) {
28✔
670
                    throw InvalidInstruction(pc, inst.opcode);
2✔
671
                }
672
                return makeCallx(inst, pc);
26✔
673
            }
674
            if (inst.src == INST_CALL_BTF_HELPER) {
61,330✔
675
                if (inst.dst != 0) {
46✔
676
                    throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
677
                }
678
                return CallBtf{.btf_id = inst.imm};
44✔
679
            }
680
            if (inst.dst != 0) {
61,284✔
681
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
682
            }
683
            if (info.builtin_call_offsets.contains(pc)) {
61,282✔
684
                if (info.platform->get_builtin_call) {
2,486✔
685
                    if (const auto builtin_call = info.platform->get_builtin_call(inst.imm)) {
2,486✔
686
                        return *builtin_call;
2,486✔
687
                    }
2,486✔
688
                }
UNCOV
689
                return Call{.func = inst.imm,
×
690
                            .name = std::to_string(inst.imm),
691
                            .is_supported = false,
UNCOV
692
                            .unsupported_reason = "helper function is unavailable on this platform"};
×
693
            }
694
            if (!info.platform->is_helper_usable(inst.imm)) {
58,796✔
695
                std::string name = std::to_string(inst.imm);
6✔
696
                try {
3✔
697
                    name = info.platform->get_helper_prototype(inst.imm).name;
6✔
698
                } catch (const std::exception&) {
6✔
699
                }
6✔
700
                return Call{.func = inst.imm,
15✔
701
                            .name = std::move(name),
3✔
702
                            .is_supported = false,
703
                            .unsupported_reason = "helper function is unavailable on this platform"};
9✔
704
            }
6✔
705
            return makeCall(inst.imm);
88,185✔
706
        case INST_EXIT:
5,382✔
707
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) {
5,382✔
708
                throw InvalidInstruction(pc, inst.opcode);
6✔
709
            }
710
            if (inst.src != 0) {
5,376✔
711
                throw InvalidInstruction(pc, inst.opcode);
2✔
712
            }
713
            if (inst.dst != 0) {
5,374✔
714
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
715
            }
716
            if (inst.imm != 0) {
5,372✔
717
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
718
            }
719
            if (inst.offset != 0) {
5,370✔
720
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
721
            }
722
            return Exit{};
5,368✔
723
        case INST_JA:
22,688✔
724
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) {
22,688✔
UNCOV
725
                throw InvalidInstruction(pc, inst.opcode);
×
726
            }
727
            if (inst.opcode & INST_SRC_REG) {
22,688✔
728
                throw InvalidInstruction(pc, inst.opcode);
4✔
729
            }
730
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP && (inst.imm != 0)) {
22,684✔
731
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
732
            }
733
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) {
22,682✔
734
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
735
            }
736
            if (inst.dst != 0) {
22,680✔
737
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
4✔
738
            }
739
        default: {
63,070✔
740
            // First validate the opcode, src, and imm.
741
            const auto op = getJmpOp(pc, inst.opcode);
126,140✔
742
            if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) {
126,124✔
743
                throw InvalidInstruction(pc, inst.opcode);
48✔
744
            }
745
            if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) {
126,076✔
746
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
44✔
747
            }
748

749
            const int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset;
126,032✔
750
            const Label target = getJumpTarget(offset, insts, pc);
126,032✔
751
            if (inst.opcode != INST_OP_JA16 && inst.opcode != INST_OP_JA32) {
125,896✔
752
                if (inst.dst > R10_STACK_POINTER) {
103,228✔
753
                    throw InvalidInstruction(pc, "bad register");
88✔
754
                }
755
                if ((inst.opcode & INST_SRC_REG) && inst.src > R10_STACK_POINTER) {
103,140✔
756
                    throw InvalidInstruction(pc, "bad register");
44✔
757
                }
758
            }
759

760
            const auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32)
114,436✔
761
                                  ? std::optional<Condition>{}
125,764✔
762
                                  : Condition{.op = op,
51,548✔
763
                                              .left = Reg{inst.dst},
103,096✔
764
                                              .right = (inst.opcode & INST_SRC_REG) ? Value{Reg{inst.src}}
103,096✔
765
                                                                                    : Value{Imm{sign_extend(inst.imm)}},
86,414✔
766
                                              .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP};
125,764✔
767
            return Jmp{.cond = cond, .target = target};
188,712✔
768
        }
125,830✔
769
        }
770
    }
771

772
    vector<LabeledInstruction> unmarshal(vector<EbpfInst> const& insts,
5,280✔
773
                                         const prevail::ebpf_verifier_options_t& options) {
774
        vector<LabeledInstruction> prog;
5,280✔
775
        int exit_count = 0;
5,280✔
776
        if (insts.empty()) {
5,280✔
UNCOV
777
            throw std::invalid_argument("Zero length programs are not allowed");
×
778
        }
779
        for (size_t pc = 0; pc < insts.size();) {
1,132,246✔
780
            const EbpfInst inst = insts[pc];
1,128,454✔
781
            Instruction new_ins;
1,128,454✔
782
            bool skip_instruction = false;
1,128,454✔
783
            bool fallthrough = true;
1,128,454✔
784
            switch (inst.opcode & INST_CLS_MASK) {
1,128,454✔
785
            case INST_CLS_LD:
60,914✔
786
                if (inst.opcode == INST_OP_LDDW_IMM) {
60,914✔
787
                    const int32_t next_imm = pc < insts.size() - 1 ? insts[pc + 1].imm : 0;
60,212✔
788
                    new_ins = makeLddw(inst, next_imm, insts, pc);
90,281✔
789
                    skip_instruction = true;
60,138✔
790
                    break;
60,138✔
791
                }
792
                // fallthrough
793
            case INST_CLS_LDX:
146,285✔
794
            case INST_CLS_ST:
146,285✔
795
            case INST_CLS_STX: new_ins = makeMemOp(pc, inst); break;
438,635✔
796

797
            case INST_CLS_ALU:
581,830✔
798
            case INST_CLS_ALU64: {
290,915✔
799
                new_ins = makeAluOp(pc, inst);
872,478✔
800

801
                // Merge (rX <<= 32; rX >>>= 32) into wX = rX
802
                //       (rX <<= 32; rX >>= 32)  into rX s32= rX
803
                if (pc >= insts.size() - 1) {
581,296✔
804
                    break;
101✔
805
                }
806
                const EbpfInst next = insts[pc + 1];
581,094✔
807
                auto dst = Reg{inst.dst};
581,094✔
808

809
                if (new_ins != shift32(dst, Bin::Op::LSH)) {
581,094✔
810
                    break;
282,415✔
811
                }
812

813
                if ((next.opcode & INST_CLS_MASK) != INST_CLS_ALU64) {
16,264✔
814
                    break;
716✔
815
                }
816
                auto next_ins = makeAluOp(pc + 1, next);
14,832✔
817
                if (next_ins == shift32(dst, Bin::Op::RSH)) {
14,832✔
818
                    new_ins = Bin{.op = Bin::Op::MOV, .dst = dst, .v = dst, .is64 = false};
6,620✔
819
                    skip_instruction = true;
6,620✔
820
                } else if (next_ins == shift32(dst, Bin::Op::ARSH)) {
8,212✔
821
                    new_ins = Bin{.op = Bin::Op::MOVSX32, .dst = dst, .v = dst, .is64 = true};
3,326✔
822
                    skip_instruction = true;
3,326✔
823
                }
824

825
                break;
14,832✔
826
            }
14,832✔
827

828
            case INST_CLS_JMP32:
193,842✔
829
            case INST_CLS_JMP: {
96,921✔
830
                new_ins = makeJmp(inst, insts, pc);
290,543✔
831
                if (std::holds_alternative<Exit>(new_ins)) {
193,402✔
832
                    fallthrough = false;
5,368✔
833
                    exit_count++;
5,368✔
834
                }
835
                if (const auto pjmp = std::get_if<Jmp>(&new_ins)) {
193,402✔
836
                    if (!pjmp->cond) {
125,764✔
837
                        fallthrough = false;
574,817✔
838
                    }
839
                }
840
                break;
96,701✔
841
            }
842
            default: CRAB_ERROR("invalid class: ", inst.opcode & INST_CLS_MASK);
563,483✔
843
            }
844
            if (pc == insts.size() - 1 && fallthrough) {
1,126,966✔
845
                note("fallthrough in last instruction");
1,400✔
846
            }
847

848
            std::optional<btf_line_info_t> current_line_info = {};
1,126,966✔
849

850
            if (options.verbosity_opts.print_line_info && pc < info.line_info.size()) {
1,126,966✔
UNCOV
851
                current_line_info = info.line_info.at(pc);
×
852
            }
853

854
            prog.emplace_back(Label(gsl::narrow<int>(pc)), new_ins, current_line_info);
1,690,449✔
855

856
            pc++;
1,126,966✔
857
            note_next_pc();
1,126,966✔
858
            if (skip_instruction) {
1,126,966✔
859
                pc++;
70,084✔
860
                note_next_pc();
598,525✔
861
            }
862
        }
1,128,454✔
863
        if (exit_count == 0) {
3,792✔
864
            note("no exit instruction");
672✔
865
        }
866
        return prog;
3,792✔
867
    }
1,488✔
868
};
869

870
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog, vector<vector<string>>& notes,
5,280✔
871
                                                    const prevail::ebpf_verifier_options_t& options) {
872
    thread_local_program_info = raw_prog.info;
5,280✔
873
    try {
2,640✔
874
        return Unmarshaller{notes, raw_prog.info}.unmarshal(raw_prog.prog, options);
7,920✔
875
    } catch (InvalidInstruction& arg) {
1,488✔
876
        std::ostringstream ss;
1,488✔
877
        ss << arg.pc << ": " << arg.what() << "\n";
1,488✔
878
        return ss.str();
1,488✔
879
    }
1,488✔
880
}
881

882
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog,
5,280✔
883
                                                    const prevail::ebpf_verifier_options_t& options) {
884
    vector<vector<string>> notes;
5,280✔
885
    return unmarshal(raw_prog, notes, options);
10,560✔
886
}
5,280✔
887

888
Call make_call(const int imm, const ebpf_platform_t& platform) {
134✔
889
    vector<vector<string>> notes;
134✔
890
    const ProgramInfo info{.platform = &platform};
134✔
891
    return Unmarshaller{notes, info}.makeCall(imm);
335✔
892
}
134✔
893
} // namespace prevail
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