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

Alan-Jowett / ebpf-verifier / 22235569093

20 Feb 2026 02:12AM UTC coverage: 88.002% (-0.2%) from 88.157%
22235569093

push

github

web-flow
Handle Call builtins: fix handling of Falco tests  (#1025)

* falco: fix raw_tracepoint privilege and group expected failures

Mark raw_tracepoint/raw_tracepoint_writable as privileged program types so Falco raw-tracepoint sections are not treated as unprivileged argument checks.

Update Falco sample matrix to move now-passing sections out of TEST_SECTION_FAIL and group the remaining expected failures by root-cause class (offset lower-bound loss vs size lower-bound loss at correlated joins).

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

* elf/unmarshal: gate builtin relocations via platform call model

Add platform hooks to resolve builtin symbols and provide builtin call contracts, thread relocation-gated builtin call offsets through ProgramInfo, and only treat static helper IDs as builtins at gated call sites.

Also extend platform-table, marshal, and YAML-platform tests to cover builtin resolver wiring and call unmarshal behavior.
* crab: canonicalize unsigned intervals in bitwise_and
When uvalue intervals temporarily carry signed lower bounds (e.g. after joins), Interval::bitwise_and asserted in debug builds. Canonicalize both operands via zero_extend(64) before unsigned bitwise reasoning, preserving soundness and avoiding debug aborts.

Validated by reproducing SIGABRT on reverted code in [falco][verify] and confirming the patched build completes with expected 73 pass / 20 failed-as-expected.

* Fix unsound bitwise_and case for non-singleton all-ones rhs

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

239 of 252 new or added lines in 9 files covered. (94.84%)

602 existing lines in 16 files now uncovered.

11743 of 13344 relevant lines covered (88.0%)

3262592.78 hits per line

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

94.64
/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) {
79,140✔
18
    switch (opcode & INST_SIZE_MASK) {
79,140✔
19
    case INST_SIZE_B: return 1;
2,991✔
20
    case INST_SIZE_H: return 2;
9,262✔
21
    case INST_SIZE_W: return 4;
21,620✔
22
    case INST_SIZE_DW: return 8;
42,276✔
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,113✔
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 {
160,218✔
60
    switch (opcode & INST_CLS_MASK) {
160,218✔
61
    case INST_CLS_LD:
40,937✔
62
    case INST_CLS_LDX: return true;
40,937✔
63
    case INST_CLS_ST:
78,344✔
64
    case INST_CLS_STX: return false;
78,344✔
65
    default: CRAB_ERROR("unexpected opcode", opcode);
×
66
    }
67
}
68

69
static auto getMemWidth(const uint8_t opcode) -> int {
161,622✔
70
    switch (opcode & INST_SIZE_MASK) {
161,622✔
71
    case INST_SIZE_B: return 1;
31,701✔
72
    case INST_SIZE_H: return 2;
15,498✔
73
    case INST_SIZE_W: return 4;
35,192✔
74
    case INST_SIZE_DW: return 8;
47,530✔
75
    default: CRAB_ERROR("unexpected opcode", opcode);
×
76
    }
77
}
78

79
static Instruction shift32(const Reg dst, const Bin::Op op) {
323,712✔
80
    return Bin{.op = op, .dst = dst, .v = Imm{32}, .is64 = true, .lddw = false};
323,712✔
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); }
858✔
88
    // ReSharper disable once CppMemberFunctionMayBeConst
89
    void note_next_pc() { notes.emplace_back(); }
636,084✔
90
    explicit Unmarshaller(vector<vector<string>>& notes, const ProgramInfo& info) : notes{notes}, info{info} {
4,376✔
91
        note_next_pc();
6,564✔
92
    }
4,376✔
93

94
    auto getAluOp(const size_t pc, const EbpfInst inst) -> std::variant<Bin::Op, Un::Op> {
317,372✔
95
        // First handle instructions that support a non-zero offset.
96
        switch (inst.opcode & INST_ALU_OP_MASK) {
317,372✔
97
        case INST_ALU_OP_DIV:
2,522✔
98
            switch (inst.offset) {
2,522✔
99
            case 0: return Bin::Op::UDIV;
2,444✔
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:
166✔
104
            switch (inst.offset) {
166✔
105
            case 0: return Bin::Op::UMOD;
74✔
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:
145,724✔
110
            if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) {
145,724✔
111
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
6✔
112
            }
113
            switch (inst.offset) {
145,720✔
114
            case 0: return Bin::Op::MOV;
145,644✔
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;
168,960✔
121
        }
122

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

128
        switch (inst.opcode & INST_ALU_OP_MASK) {
168,866✔
129
        case INST_ALU_OP_ADD: return Bin::Op::ADD;
48,600✔
130
        case INST_ALU_OP_SUB: return Bin::Op::SUB;
1,140✔
131
        case INST_ALU_OP_MUL: return Bin::Op::MUL;
508✔
132
        case INST_ALU_OP_OR: return Bin::Op::OR;
34,888✔
133
        case INST_ALU_OP_AND: return Bin::Op::AND;
12,824✔
134
        case INST_ALU_OP_LSH: return Bin::Op::LSH;
36,372✔
135
        case INST_ALU_OP_RSH: return Bin::Op::RSH;
24,708✔
136
        case INST_ALU_OP_NEG:
198✔
137
            // Negation is a unary operation. The SRC bit, src, and imm must be all 0.
138
            if (inst.opcode & INST_SRC_REG) {
198✔
139
                throw InvalidInstruction{pc, inst.opcode};
4✔
140
            }
141
            if (inst.src != 0) {
194✔
142
                throw InvalidInstruction{pc, inst.opcode};
4✔
143
            }
144
            if (inst.imm != 0) {
190✔
145
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
6✔
146
            }
147
            return Un::Op::NEG;
186✔
148
        case INST_ALU_OP_XOR: return Bin::Op::XOR;
1,142✔
149
        case INST_ALU_OP_ARSH:
5,100✔
150
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) {
5,100✔
151
                note("arsh32 is not allowed");
372✔
152
            }
153
            return Bin::Op::ARSH;
5,100✔
154
        case INST_ALU_OP_END:
3,370✔
155
            if (inst.src != 0) {
3,370✔
156
                throw InvalidInstruction{pc, inst.opcode};
18✔
157
            }
158
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) {
3,352✔
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) {
3,300✔
170
            case 16: return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16;
3,054✔
171
            case 32: return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32;
194✔
172
            case 64: return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64;
59✔
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 {
634✔
182
        switch (const auto op = gsl::narrow<Atomic::Op>(inst.imm & ~INST_FETCH)) {
634✔
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:
301✔
189
        case Atomic::Op::OR:
301✔
190
        case Atomic::Op::AND:
301✔
191
        case Atomic::Op::XOR: return op;
602✔
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}); }
221,860✔
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 {
313,660✔
201
        if (inst.opcode & INST_SRC_REG) {
313,660✔
202
            if (inst.imm != 0) {
133,760✔
203
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
66✔
204
            }
205
            return Reg{inst.src};
133,694✔
206
        }
207
        if (inst.src != 0) {
179,900✔
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)};
179,844✔
212
    }
213

214
    static auto getJmpOp(const size_t pc, const uint8_t opcode) -> Condition::Op {
63,248✔
215
        using Op = Condition::Op;
31,624✔
216
        switch ((opcode >> 4) & 0xF) {
63,248✔
217
        case 0x0: return {}; // goto
5,906✔
218
        case 0x1: return Op::EQ;
11,958✔
219
        case 0x2: return Op::GT;
6,192✔
220
        case 0x3: return Op::GE;
450✔
221
        case 0x4: return Op::SET;
82✔
222
        case 0x5: return Op::NE;
12,746✔
223
        case 0x6: return Op::SGT;
5,042✔
224
        case 0x7: return Op::SGE;
154✔
225
        case 0x8: return {}; // call
226
        case 0x9: return {}; // exit
227
        case 0xa: return Op::LT;
832✔
228
        case 0xb: return Op::LE;
136✔
229
        case 0xc: return Op::SLT;
1,782✔
230
        case 0xd: return Op::SLE;
88✔
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 {
161,760✔
238
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
161,760✔
239
            throw InvalidInstruction(pc, "bad register");
138✔
240
        }
241

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

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

262
        case INST_MODE_IND:
388✔
263
            if (!isLD || (width == 8)) {
388✔
264
                throw InvalidInstruction(pc, inst.opcode);
26✔
265
            }
266
            if (inst.dst != 0) {
362✔
267
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
6✔
268
            }
269
            if (inst.src > R10_STACK_POINTER) {
356✔
270
                throw InvalidInstruction(pc, "bad register");
271
            }
272
            if (inst.offset != 0) {
356✔
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}};
350✔
276

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

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

296
            if (basereg == R10_STACK_POINTER &&
199,759✔
297
                (inst.offset + opcode_to_width(inst.opcode) > 0 || inst.offset < -EBPF_TOTAL_STACK_SIZE)) {
79,134✔
298
                note("Stack access out of bounds");
16✔
299
            }
300
            auto res = Mem{
160,192✔
301
                .access =
302
                    Deref{
303
                        .width = width,
304
                        .basereg = Reg{basereg},
305
                        .offset = inst.offset,
160,192✔
306
                    },
307
                .value = isLoad  ? Value{Reg{inst.dst}}
40,932✔
308
                         : isImm ? Value{Imm{zero_extend(inst.imm)}}
78,328✔
309
                                 : Value{Reg{inst.src}},
39,127✔
310
                .is_load = isLoad,
311
            };
238,520✔
312
            return res;
160,192✔
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:
662✔
343
            if (((inst.opcode & INST_CLS_MASK) != INST_CLS_STX) ||
662✔
344
                ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) {
638✔
345
                throw InvalidInstruction(pc, inst.opcode);
28✔
346
            }
347
            return Atomic{
935✔
348
                .op = getAtomicOp(pc, inst),
634✔
349
                .fetch = (inst.imm & INST_FETCH) == INST_FETCH,
602✔
350
                .access =
351
                    Deref{
352
                        .width = width,
353
                        .basereg = Reg{inst.dst},
602✔
354
                        .offset = inst.offset,
602✔
355
                    },
356
                .valreg = Reg{inst.src},
602✔
357
            };
602✔
358
        default: throw InvalidInstruction(pc, inst.opcode);
64✔
359
        }
360
    }
361

362
    auto makeAluOp(const size_t pc, const EbpfInst inst) -> Instruction {
317,584✔
363
        const bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64;
317,584✔
364
        if (inst.dst == R10_STACK_POINTER) {
317,584✔
365
            throw InvalidInstruction(pc, "invalid target r10");
2✔
366
        }
367
        if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) {
317,582✔
368
            throw InvalidInstruction(pc, "bad register");
210✔
369
        }
370
        return std::visit(
158,686✔
371
            Overloaded{[&](const Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; },
162,159✔
372
                       [&](const Bin::Op op) -> Instruction {
313,660✔
373
                           Bin res{
313,660✔
374
                               .op = op,
375
                               .dst = Reg{inst.dst},
313,660✔
376
                               .v = getBinValue(pc, inst),
313,660✔
377
                               .is64 = is64,
313,538✔
378
                           };
313,660✔
379
                           if (!thread_local_options.allow_division_by_zero &&
313,538✔
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;
470,307✔
388
                       }},
389
            getAluOp(pc, inst));
475,958✔
390
    }
391

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

409
        switch (inst.src) {
29,856✔
410
        case INST_LD_MODE_IMM:
13,684✔
411
            return Bin{
34,210✔
412
                .op = Bin::Op::MOV,
413
                .dst = Reg{inst.dst},
13,684✔
414
                .v = Imm{merge(inst.imm, next_imm)},
20,526✔
415
                .is64 = true,
416
                .lddw = true,
417
            };
13,684✔
418
        case INST_LD_MODE_MAP_FD: {
15,692✔
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) {
15,692✔
422
                throw InvalidInstruction(pc, "lddw uses reserved fields");
2✔
423
            }
424
            return LoadMapFd{.dst = Reg{inst.dst}, .mapfd = inst.imm};
15,690✔
425
        }
426
        case INST_LD_MODE_MAP_VALUE: return LoadMapAddress{.dst = Reg{inst.dst}, .mapfd = inst.imm, .offset = next_imm};
432✔
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) {
61,582✔
460
        switch (t) {
61,582✔
461
        case EBPF_ARGUMENT_TYPE_ANYTHING: return ArgSingle::Kind::ANYTHING;
9,933✔
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;
6✔
464
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
7,439✔
465
        case EBPF_ARGUMENT_TYPE_CONST_PTR_TO_MAP: return ArgSingle::Kind::MAP_FD;
1✔
466
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS: return ArgSingle::Kind::MAP_FD_PROGRAMS;
427✔
467
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY: return ArgSingle::Kind::PTR_TO_MAP_KEY;
6,696✔
468
        case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
2,498✔
469
        case EBPF_ARGUMENT_TYPE_PTR_TO_UNINIT_MAP_VALUE: return ArgSingle::Kind::PTR_TO_MAP_VALUE;
470
        case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: return ArgSingle::Kind::PTR_TO_CTX;
3,761✔
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;
6✔
473
        default: break;
474
        }
475
        return {};
476
    }
477

478
    static ArgPair::Kind toArgPairKind(const ebpf_argument_type_t t) {
8,994✔
479
        switch (t) {
8,994✔
480
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
2,196✔
481
        case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
2,196✔
482
        case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM_OR_NULL:
1✔
483
        case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM: return ArgPair::Kind::PTR_TO_READABLE_MEM;
1✔
484
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
4,600✔
485
        case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: return ArgPair::Kind::PTR_TO_WRITABLE_MEM;
4,600✔
486
        default: break;
487
        }
488
        return {};
489
    }
490

491
    [[nodiscard]]
492
    auto makeCall(const int32_t imm) const -> Call {
28,660✔
493
        const EbpfHelperPrototype proto = info.platform->get_helper_prototype(imm);
28,660✔
494
        Call res;
28,660✔
495
        res.func = imm;
28,660✔
496
        res.name = proto.name;
28,660✔
497
        auto mark_unsupported = [&](const std::string& why) -> Call {
14,338✔
498
            res.is_supported = false;
8✔
499
            res.unsupported_reason = why;
12✔
500
            return res;
8✔
501
        };
28,660✔
502
        const auto return_info = classify_call_return_type(proto.return_type);
28,660✔
503
        if (!return_info.has_value()) {
28,660✔
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;
28,658✔
507
        res.return_nullable = return_info->pointer_nullable;
28,658✔
508
        res.reallocate_packet = proto.reallocate_packet;
28,658✔
509
        res.is_map_lookup = proto.return_type == EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL;
28,658✔
510
        const std::array<ebpf_argument_type_t, 7> args = {
28,658✔
511
            {EBPF_ARGUMENT_TYPE_DONTCARE, proto.argument_type[0], proto.argument_type[1], proto.argument_type[2],
28,658✔
512
             proto.argument_type[3], proto.argument_type[4], EBPF_ARGUMENT_TYPE_DONTCARE}};
28,658✔
513
        for (size_t i = 1; i < args.size() - 1; i++) {
148,875✔
514
            switch (args[i]) {
94,886✔
UNCOV
515
            case EBPF_ARGUMENT_TYPE_UNSUPPORTED:
×
UNCOV
516
                return mark_unsupported(std::string("helper argument type is unavailable on this platform: ") +
×
UNCOV
517
                                        proto.name);
×
518
            case EBPF_ARGUMENT_TYPE_DONTCARE: return res;
24,288✔
519
            case EBPF_ARGUMENT_TYPE_ANYTHING:
61,522✔
520
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP:
30,761✔
521
            case EBPF_ARGUMENT_TYPE_CONST_PTR_TO_MAP:
30,761✔
522
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_OF_PROGRAMS:
30,761✔
523
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_KEY:
30,761✔
524
            case EBPF_ARGUMENT_TYPE_PTR_TO_MAP_VALUE:
30,761✔
525
            case EBPF_ARGUMENT_TYPE_PTR_TO_UNINIT_MAP_VALUE:
30,761✔
526
            case EBPF_ARGUMENT_TYPE_PTR_TO_STACK:
30,761✔
527
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX:
30,761✔
528
            case EBPF_ARGUMENT_TYPE_PTR_TO_FUNC:
30,761✔
529
                res.singles.push_back({toArgSingleKind(args[i]), false, Reg{gsl::narrow<uint8_t>(i)}});
92,282✔
530
                break;
61,522✔
531
            case EBPF_ARGUMENT_TYPE_PTR_TO_STACK_OR_NULL:
60✔
532
            case EBPF_ARGUMENT_TYPE_PTR_TO_CTX_OR_NULL:
30✔
533
                res.singles.push_back({toArgSingleKind(args[i]), true, Reg{gsl::narrow<uint8_t>(i)}});
60✔
534
                break;
60✔
535
            case EBPF_ARGUMENT_TYPE_PTR_TO_BTF_ID_SOCK_COMMON:
2✔
536
            case EBPF_ARGUMENT_TYPE_PTR_TO_SOCK_COMMON:
1✔
537
                res.singles.push_back({ArgSingle::Kind::PTR_TO_SOCKET, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
538
                break;
2✔
539
            case EBPF_ARGUMENT_TYPE_PTR_TO_BTF_ID:
4✔
540
            case EBPF_ARGUMENT_TYPE_PTR_TO_PERCPU_BTF_ID:
2✔
541
                res.singles.push_back({ArgSingle::Kind::PTR_TO_BTF_ID, false, Reg{gsl::narrow<uint8_t>(i)}});
4✔
542
                break;
4✔
543
            case EBPF_ARGUMENT_TYPE_PTR_TO_ALLOC_MEM:
2✔
544
                res.singles.push_back({ArgSingle::Kind::PTR_TO_ALLOC_MEM, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
545
                break;
2✔
UNCOV
546
            case EBPF_ARGUMENT_TYPE_PTR_TO_SPIN_LOCK:
×
UNCOV
547
                res.singles.push_back({ArgSingle::Kind::PTR_TO_SPIN_LOCK, false, Reg{gsl::narrow<uint8_t>(i)}});
×
UNCOV
548
                break;
×
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:
2✔
553
                res.singles.push_back({ArgSingle::Kind::CONST_SIZE_OR_ZERO, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
554
                break;
2✔
555
            case EBPF_ARGUMENT_TYPE_PTR_TO_LONG:
2✔
556
                res.singles.push_back({ArgSingle::Kind::PTR_TO_WRITABLE_LONG, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
557
                break;
2✔
558
            case EBPF_ARGUMENT_TYPE_PTR_TO_INT:
2✔
559
                res.singles.push_back({ArgSingle::Kind::PTR_TO_WRITABLE_INT, false, Reg{gsl::narrow<uint8_t>(i)}});
2✔
560
                break;
2✔
561
            case EBPF_ARGUMENT_TYPE_PTR_TO_CONST_STR:
6✔
562
                return mark_unsupported(std::string("helper argument type is unavailable on this platform: ") +
15✔
563
                                        proto.name);
12✔
UNCOV
564
            case EBPF_ARGUMENT_TYPE_CONST_SIZE: {
×
565
                // Sanity check: This argument should never be seen in isolation.
566
                return mark_unsupported(
UNCOV
567
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE: ") +
×
UNCOV
568
                    proto.name);
×
569
            }
UNCOV
570
            case EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: {
×
571
                // Sanity check: This argument should never be seen in isolation.
572
                return mark_unsupported(
UNCOV
573
                    std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
574
                    proto.name);
×
575
            }
576
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL:
8,994✔
577
            case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM:
4,497✔
578
            case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM_OR_NULL:
4,497✔
579
            case EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM:
4,497✔
580
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL:
4,497✔
581
            case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM:
4,497✔
582
                // Sanity check: This argument must be followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or
583
                // EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO.
584
                if (args.size() - i < 2) {
8,994✔
585
                    return mark_unsupported(
586
                        std::string(
587
                            "missing EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
588
                        proto.name);
589
                }
590
                if (args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE &&
11,867✔
591
                    args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO) {
5,746✔
592
                    return mark_unsupported(
UNCOV
593
                        std::string("Pointer argument not followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or "
×
UNCOV
594
                                    "EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") +
×
UNCOV
595
                        proto.name);
×
596
                }
597
                const bool can_be_zero = (args[i + 1] == EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO);
8,994✔
598
                const bool or_null = args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL ||
8,994✔
599
                                     args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_READONLY_MEM_OR_NULL ||
8,455✔
600
                                     args[i] == EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM_OR_NULL;
8,455✔
601
                res.pairs.push_back({toArgPairKind(args[i]), or_null, Reg{gsl::narrow<uint8_t>(i)},
15,791✔
602
                                     Reg{gsl::narrow<uint8_t>(i + 1)}, can_be_zero});
8,994✔
603
                i++;
8,994✔
604
                break;
8,994✔
605
            }
606
        }
607
        return res;
4,364✔
608
    }
28,660✔
609

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

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

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

650
    [[nodiscard]]
651
    auto makeJmp(const EbpfInst inst, const vector<EbpfInst>& insts, const Pc pc) const -> Instruction {
97,802✔
652
        switch ((inst.opcode >> 4) & 0xF) {
97,802✔
653
        case INST_CALL:
31,098✔
654
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) {
31,098✔
655
                throw InvalidInstruction(pc, inst.opcode);
4✔
656
            }
657
            if (inst.src > INST_CALL_BTF_HELPER) {
31,094✔
658
                throw InvalidInstruction(pc, inst.opcode);
2✔
659
            }
660
            if (inst.offset != 0) {
31,092✔
661
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
8✔
662
            }
663
            if (inst.src == INST_CALL_LOCAL) {
31,084✔
664
                return makeCallLocal(inst, insts, pc);
42✔
665
            }
666
            if (inst.opcode & INST_SRC_REG) {
31,054✔
667
                // Register-call opcode form is reserved for callx and must not be used for src-based call modes.
668
                if (inst.src != 0) {
28✔
669
                    throw InvalidInstruction(pc, inst.opcode);
2✔
670
                }
671
                return makeCallx(inst, pc);
26✔
672
            }
673
            if (inst.src == INST_CALL_BTF_HELPER) {
31,026✔
674
                if (inst.dst != 0) {
44✔
675
                    throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
676
                }
677
                return CallBtf{.btf_id = inst.imm};
42✔
678
            }
679
            if (inst.dst != 0) {
30,982✔
680
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
681
            }
682
            if (info.builtin_call_offsets.contains(pc)) {
30,980✔
683
                if (info.platform->get_builtin_call) {
2,450✔
684
                    if (const auto builtin_call = info.platform->get_builtin_call(inst.imm)) {
2,450✔
685
                        return *builtin_call;
2,450✔
686
                    }
2,450✔
687
                }
NEW
688
                return Call{.func = inst.imm,
×
689
                            .name = std::to_string(inst.imm),
690
                            .is_supported = false,
NEW
691
                            .unsupported_reason = "helper function is unavailable on this platform"};
×
692
            }
693
            if (!info.platform->is_helper_usable(inst.imm)) {
28,530✔
694
                std::string name = std::to_string(inst.imm);
4✔
695
                try {
2✔
696
                    name = info.platform->get_helper_prototype(inst.imm).name;
4✔
697
                } catch (const std::exception&) {
4✔
698
                }
4✔
699
                return Call{.func = inst.imm,
10✔
700
                            .name = std::move(name),
2✔
701
                            .is_supported = false,
702
                            .unsupported_reason = "helper function is unavailable on this platform"};
6✔
703
            }
4✔
704
            return makeCall(inst.imm);
42,789✔
705
        case INST_EXIT:
3,444✔
706
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) {
3,444✔
707
                throw InvalidInstruction(pc, inst.opcode);
6✔
708
            }
709
            if (inst.src != 0) {
3,438✔
710
                throw InvalidInstruction(pc, inst.opcode);
2✔
711
            }
712
            if (inst.dst != 0) {
3,436✔
713
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
2✔
714
            }
715
            if (inst.imm != 0) {
3,434✔
716
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
717
            }
718
            if (inst.offset != 0) {
3,432✔
719
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
720
            }
721
            return Exit{};
3,430✔
722
        case INST_JA:
11,824✔
723
            if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) {
11,824✔
UNCOV
724
                throw InvalidInstruction(pc, inst.opcode);
×
725
            }
726
            if (inst.opcode & INST_SRC_REG) {
11,824✔
727
                throw InvalidInstruction(pc, inst.opcode);
4✔
728
            }
729
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP && (inst.imm != 0)) {
11,820✔
730
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
2✔
731
            }
732
            if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) {
11,818✔
733
                throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode));
2✔
734
            }
735
            if (inst.dst != 0) {
11,816✔
736
                throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode));
4✔
737
            }
738
        default: {
31,624✔
739
            // First validate the opcode, src, and imm.
740
            const auto op = getJmpOp(pc, inst.opcode);
63,248✔
741
            if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) {
63,232✔
742
                throw InvalidInstruction(pc, inst.opcode);
48✔
743
            }
744
            if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) {
63,184✔
745
                throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode));
44✔
746
            }
747

748
            const int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset;
63,140✔
749
            const Label target = getJumpTarget(offset, insts, pc);
63,140✔
750
            if (inst.opcode != INST_OP_JA16 && inst.opcode != INST_OP_JA32) {
63,004✔
751
                if (inst.dst > R10_STACK_POINTER) {
51,200✔
752
                    throw InvalidInstruction(pc, "bad register");
88✔
753
                }
754
                if ((inst.opcode & INST_SRC_REG) && inst.src > R10_STACK_POINTER) {
51,112✔
755
                    throw InvalidInstruction(pc, "bad register");
44✔
756
                }
757
            }
758

759
            const auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32)
56,976✔
760
                                  ? std::optional<Condition>{}
62,872✔
761
                                  : Condition{.op = op,
25,534✔
762
                                              .left = Reg{inst.dst},
51,068✔
763
                                              .right = (inst.opcode & INST_SRC_REG) ? Value{Reg{inst.src}}
51,068✔
764
                                                                                    : Value{Imm{sign_extend(inst.imm)}},
42,016✔
765
                                              .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP};
62,872✔
766
            return Jmp{.cond = cond, .target = target};
94,374✔
767
        }
62,938✔
768
        }
769
    }
770

771
    vector<LabeledInstruction> unmarshal(vector<EbpfInst> const& insts,
4,242✔
772
                                         const prevail::ebpf_verifier_options_t& options) {
773
        vector<LabeledInstruction> prog;
4,242✔
774
        int exit_count = 0;
4,242✔
775
        if (insts.empty()) {
4,242✔
UNCOV
776
            throw std::invalid_argument("Zero length programs are not allowed");
×
777
        }
778
        for (size_t pc = 0; pc < insts.size();) {
599,440✔
779
            const EbpfInst inst = insts[pc];
596,684✔
780
            Instruction new_ins;
596,684✔
781
            bool skip_instruction = false;
596,684✔
782
            bool fallthrough = true;
596,684✔
783
            switch (inst.opcode & INST_CLS_MASK) {
596,684✔
784
            case INST_CLS_LD:
30,512✔
785
                if (inst.opcode == INST_OP_LDDW_IMM) {
30,512✔
786
                    const int32_t next_imm = pc < insts.size() - 1 ? insts[pc + 1].imm : 0;
29,918✔
787
                    new_ins = makeLddw(inst, next_imm, insts, pc);
44,841✔
788
                    skip_instruction = true;
29,846✔
789
                    break;
29,846✔
790
                }
791
                // fallthrough
792
            case INST_CLS_LDX:
80,880✔
793
            case INST_CLS_ST:
80,880✔
794
            case INST_CLS_STX: new_ins = makeMemOp(pc, inst); break;
242,420✔
795

796
            case INST_CLS_ALU:
307,204✔
797
            case INST_CLS_ALU64: {
153,602✔
798
                new_ins = makeAluOp(pc, inst);
460,539✔
799

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

808
                if (new_ins != shift32(dst, Bin::Op::LSH)) {
306,468✔
809
                    break;
147,472✔
810
                }
811

812
                if ((next.opcode & INST_CLS_MASK) != INST_CLS_ALU64) {
11,524✔
813
                    break;
572✔
814
                }
815
                auto next_ins = makeAluOp(pc + 1, next);
10,380✔
816
                if (next_ins == shift32(dst, Bin::Op::RSH)) {
10,380✔
817
                    new_ins = Bin{.op = Bin::Op::MOV, .dst = dst, .v = dst, .is64 = false};
3,516✔
818
                    skip_instruction = true;
3,516✔
819
                } else if (next_ins == shift32(dst, Bin::Op::ARSH)) {
6,864✔
820
                    new_ins = Bin{.op = Bin::Op::MOVSX32, .dst = dst, .v = dst, .is64 = true};
3,148✔
821
                    skip_instruction = true;
3,148✔
822
                }
823

824
                break;
10,380✔
825
            }
10,380✔
826

827
            case INST_CLS_JMP32:
97,802✔
828
            case INST_CLS_JMP: {
48,901✔
829
                new_ins = makeJmp(inst, insts, pc);
146,483✔
830
                if (std::holds_alternative<Exit>(new_ins)) {
97,362✔
831
                    fallthrough = false;
3,430✔
832
                    exit_count++;
3,430✔
833
                }
834
                if (const auto pjmp = std::get_if<Jmp>(&new_ins)) {
97,362✔
835
                    if (!pjmp->cond) {
62,872✔
836
                        fallthrough = false;
303,501✔
837
                    }
838
                }
839
                break;
48,681✔
840
            }
841
            default: CRAB_ERROR("invalid class: ", inst.opcode & INST_CLS_MASK);
297,599✔
842
            }
843
            if (pc == insts.size() - 1 && fallthrough) {
595,198✔
844
                note("fallthrough in last instruction");
1,399✔
845
            }
846

847
            std::optional<btf_line_info_t> current_line_info = {};
595,198✔
848

849
            if (options.verbosity_opts.print_line_info && pc < info.line_info.size()) {
595,198✔
UNCOV
850
                current_line_info = info.line_info.at(pc);
×
851
            }
852

853
            prog.emplace_back(Label(gsl::narrow<int>(pc)), new_ins, current_line_info);
892,797✔
854

855
            pc++;
595,198✔
856
            note_next_pc();
595,198✔
857
            if (skip_instruction) {
595,198✔
858
                pc++;
36,510✔
859
                note_next_pc();
315,854✔
860
            }
861
        }
596,684✔
862
        if (exit_count == 0) {
2,756✔
863
            note("no exit instruction");
672✔
864
        }
865
        return prog;
2,756✔
866
    }
1,486✔
867
};
868

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

881
std::variant<InstructionSeq, std::string> unmarshal(const RawProgram& raw_prog,
4,242✔
882
                                                    const prevail::ebpf_verifier_options_t& options) {
883
    vector<vector<string>> notes;
4,242✔
884
    return unmarshal(raw_prog, notes, options);
8,484✔
885
}
4,242✔
886

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