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

Alan-Jowett / bpf_conformance / 21530748606

30 Jan 2026 09:10PM UTC coverage: 94.103% (-1.0%) from 95.089%
21530748606

Pull #446

github

web-flow
Merge d967cb3b0 into 52c74e11d
Pull Request #446: Add BPF disassembler for debugging

346 of 380 new or added lines in 5 files covered. (91.05%)

1468 of 1560 relevant lines covered (94.1%)

12354.9 hits per line

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

89.59
/src/bpf_disassembler.cc
1
// Copyright (c) Microsoft Corporation
2
// SPDX-License-Identifier: MIT
3

4
#include <functional>
5
#include <iomanip>
6
#include <sstream>
7
#include <unordered_map>
8
#include "bpf_disassembler.h"
9

10
// The _bpf_disassembler class is a helper class for the bpf_disassembler.
11
typedef class _bpf_disassembler
12
{
13
  private:
14
    typedef std::string (_bpf_disassembler::*bpf_decode_t)(const ebpf_inst& inst);
15

16
    static std::string
17
    _format_register(uint8_t reg)
16,746✔
18
    {
19
        return "%r" + std::to_string(reg);
33,492✔
20
    }
21

22
    static std::string
23
    _format_imm(int32_t imm)
7,264✔
24
    {
25
        if (imm < 0 && imm != INT32_MIN) {
7,264✔
26
            return std::to_string(imm);
1,028✔
27
        } else if (imm >= -10 && imm <= 10) {
6,236✔
28
            return std::to_string(imm);
3,982✔
29
        } else {
30
            std::ostringstream ss;
2,254✔
31
            ss << "0x" << std::hex << static_cast<uint32_t>(imm);
1,127✔
32
            return ss.str();
1,127✔
33
        }
2,254✔
34
    }
35

36
    static std::string
37
    _format_memory(uint8_t reg, int16_t offset)
1,992✔
38
    {
39
        if (offset == 0) {
1,992✔
40
            return "[" + _format_register(reg) + "]";
636✔
41
        } else {
42
            return "[" + _format_register(reg) + (offset >= 0 ? "+" : "") + std::to_string(offset) + "]";
3,834✔
43
        }
44
    }
45

46
    static std::string
47
    _format_jump_offset(int32_t offset)
2,642✔
48
    {
49
        if (offset >= 0) {
2,642✔
50
            return "+" + std::to_string(offset);
5,208✔
51
        } else {
52
            return std::to_string(offset);
38✔
53
        }
54
    }
55

56
    static std::string
57
    _get_size_suffix(uint8_t size)
1,724✔
58
    {
59
        switch (size) {
1,724✔
60
        case EBPF_SIZE_B:
152✔
61
            return "b";
304✔
62
        case EBPF_SIZE_H:
116✔
63
            return "h";
232✔
64
        case EBPF_SIZE_W:
314✔
65
            return "w";
628✔
66
        case EBPF_SIZE_DW:
280✔
67
            return "dw";
560✔
68
        default:
NEW
69
            return "?";
×
70
        }
71
    }
72

73
    // ALU operation mnemonic map (opcode -> mnemonic)
74
    const std::unordered_map<int, std::string> _bpf_decode_alu_ops{
75
        {EBPF_ALU_OP_ADD, "add"},
7,403✔
76
        {EBPF_ALU_OP_SUB, "sub"},
7,403✔
77
        {EBPF_ALU_OP_MUL, "mul"},
7,403✔
78
        {EBPF_ALU_OP_DIV, "div"},
7,403✔
79
        {EBPF_ALU_OP_OR, "or"},
7,403✔
80
        {EBPF_ALU_OP_AND, "and"},
7,403✔
81
        {EBPF_ALU_OP_LSH, "lsh"},
7,403✔
82
        {EBPF_ALU_OP_RSH, "rsh"},
7,403✔
83
        {EBPF_ALU_OP_NEG, "neg"},
7,403✔
84
        {EBPF_ALU_OP_MOD, "mod"},
7,403✔
85
        {EBPF_ALU_OP_XOR, "xor"},
7,403✔
86
        {EBPF_ALU_OP_MOV, "mov"},
7,403✔
87
        {EBPF_ALU_OP_ARSH, "arsh"},
7,403✔
88
    };
89

90
    // Jump operation mnemonic map (opcode >> 4 -> mnemonic)
91
    const std::unordered_map<int, std::string> _bpf_decode_jmp_ops{
92
        {0x1, "jeq"},
7,403✔
93
        {0x2, "jgt"},
7,403✔
94
        {0x3, "jge"},
7,403✔
95
        {0x4, "jset"},
7,403✔
96
        {0x5, "jne"},
7,403✔
97
        {0x6, "jsgt"},
7,403✔
98
        {0x7, "jsge"},
7,403✔
99
        {0xa, "jlt"},
7,403✔
100
        {0xb, "jle"},
7,403✔
101
        {0xc, "jslt"},
7,403✔
102
        {0xd, "jsle"},
7,403✔
103
    };
104

105
    // Atomic operation mnemonic map
106
    const std::unordered_map<int, std::string> _bpf_decode_atomic_ops{
107
        {EBPF_ALU_OP_ADD, "add"},
7,403✔
108
        {EBPF_ALU_OP_OR, "or"},
7,403✔
109
        {EBPF_ALU_OP_AND, "and"},
7,403✔
110
        {EBPF_ALU_OP_XOR, "xor"},
7,403✔
111
    };
112

113
    std::string
114
    _decode_alu(const ebpf_inst& inst)
7,844✔
115
    {
116
        std::ostringstream result;
7,844✔
117
        uint8_t opcode_class = inst.opcode & EBPF_CLS_MASK;
7,844✔
118
        uint8_t opcode_alu = inst.opcode & EBPF_ALU_OP_MASK;
7,844✔
119
        uint8_t opcode_src = inst.opcode & EBPF_SRC_REG;
7,844✔
120
        bool is_64 = (opcode_class == EBPF_CLS_ALU64);
3,922✔
121
        std::string suffix = is_64 ? "" : "32";
9,192✔
122

123
        // Handle endianness conversion
124
        if (opcode_alu == EBPF_ALU_OP_END) {
7,844✔
125
            if (opcode_src) {
312✔
126
                result << "be" << inst.imm << " " << _format_register(inst.dst);
480✔
127
            } else if (is_64) {
72✔
128
                result << "swap" << inst.imm << " " << _format_register(inst.dst);
48✔
129
            } else {
130
                result << "le" << inst.imm << " " << _format_register(inst.dst);
96✔
131
            }
132
            return result.str();
156✔
133
        }
134

135
        // Handle NEG (single operand)
136
        if (opcode_alu == EBPF_ALU_OP_NEG) {
7,532✔
137
            result << "neg" << suffix << " " << _format_register(inst.dst);
54✔
138
            return result.str();
18✔
139
        }
140

141
        // Handle MOV with sign extension
142
        // Format: movsx<source_bits><dest_bits> where dest is 64 (no suffix) or 32
143
        if (opcode_alu == EBPF_ALU_OP_MOV && inst.offset != 0) {
7,496✔
144
            std::string dest_bits = is_64 ? "64" : "32";
24✔
145
            result << "movsx" << inst.offset << dest_bits << " " << _format_register(inst.dst) << ", "
30✔
146
                   << _format_register(inst.src);
40✔
147
            return result.str();
10✔
148
        }
10✔
149

150
        // Handle signed div/mod
151
        std::string mnemonic;
3,738✔
152
        if (opcode_alu == EBPF_ALU_OP_DIV) {
7,476✔
153
            mnemonic = (inst.offset == 1) ? "sdiv" : "div";
150✔
154
        } else if (opcode_alu == EBPF_ALU_OP_MOD) {
7,326✔
155
            mnemonic = (inst.offset == 1) ? "smod" : "mod";
118✔
156
        } else {
157
            auto iter = _bpf_decode_alu_ops.find(opcode_alu);
7,208✔
158
            mnemonic = (iter != _bpf_decode_alu_ops.end()) ? iter->second : "unknown_alu";
10,812✔
159
        }
160

161
        result << mnemonic << suffix << " " << _format_register(inst.dst) << ", ";
11,214✔
162
        if (opcode_src) {
7,476✔
163
            result << _format_register(inst.src);
2,358✔
164
        } else {
165
            result << _format_imm(inst.imm);
8,856✔
166
        }
167
        return result.str();
3,738✔
168
    }
7,844✔
169

170
    std::string
NEW
171
    _decode_ld(const ebpf_inst& inst)
×
172
    {
173
        // lddw is a special case - it's a 2-instruction sequence (handled in main disassembler loop for full imm64)
NEW
174
        return "lddw " + _format_register(inst.dst) + ", " + _format_imm(inst.imm);
×
175
    }
176

177
    std::string
178
    _decode_ldx(const ebpf_inst& inst)
1,204✔
179
    {
180
        uint8_t size = inst.opcode & 0x18;
1,204✔
181
        uint8_t mode = inst.opcode & 0xe0;
1,204✔
182

183
        std::string mnemonic = (mode == EBPF_MODE_MEMSX) ? "ldxs" : "ldx";
1,803✔
184
        mnemonic += _get_size_suffix(size);
1,204✔
185

186
        return mnemonic + " " + _format_register(inst.dst) + ", " + _format_memory(inst.src, inst.offset);
3,010✔
187
    }
602✔
188

189
    std::string
190
    _decode_st(const ebpf_inst& inst)
138✔
191
    {
192
        uint8_t size = inst.opcode & 0x18;
138✔
193
        std::string mnemonic = "st" + _get_size_suffix(size);
138✔
194

195
        return mnemonic + " " + _format_memory(inst.dst, inst.offset) + ", " + _format_imm(inst.imm);
345✔
196
    }
69✔
197

198
    std::string
199
    _decode_stx(const ebpf_inst& inst)
650✔
200
    {
201
        uint8_t size = inst.opcode & 0x18;
650✔
202
        uint8_t mode = inst.opcode & 0xe0;
650✔
203

204
        // Check for atomic operations
205
        if (mode == EBPF_MODE_ATOMIC) {
650✔
206
            return _decode_atomic(inst);
268✔
207
        }
208

209
        std::string mnemonic = "stx" + _get_size_suffix(size);
382✔
210
        return mnemonic + " " + _format_memory(inst.dst, inst.offset) + ", " + _format_register(inst.src);
764✔
211
    }
191✔
212

213
    std::string
214
    _decode_atomic(const ebpf_inst& inst)
268✔
215
    {
216
        std::ostringstream result;
268✔
217
        uint8_t size = inst.opcode & 0x18;
268✔
218
        std::string width = (size == EBPF_SIZE_W) ? "32" : "";
336✔
219

220
        result << "lock ";
268✔
221

222
        if (inst.imm == EBPF_ATOMIC_OP_XCHG) {
268✔
223
            result << "xchg" << width << " ";
24✔
224
        } else if (inst.imm == EBPF_ATOMIC_OP_CMPXCHG) {
244✔
225
            result << "cmpxchg" << width << " ";
48✔
226
        } else {
227
            bool has_fetch = (inst.imm & EBPF_ATOMIC_OP_FETCH) != 0;
196✔
228
            uint8_t base_op = inst.imm & ~EBPF_ATOMIC_OP_FETCH;
196✔
229

230
            auto iter = _bpf_decode_atomic_ops.find(base_op);
196✔
231
            std::string op = (iter != _bpf_decode_atomic_ops.end()) ? iter->second : "unknown";
196✔
232

233
            // Assembler expects: lock [fetch] <op><width> [mem], reg
234
            if (has_fetch) {
196✔
235
                result << "fetch ";
96✔
236
            }
237
            result << op << width << " ";
196✔
238
        }
98✔
239

240
        result << _format_memory(inst.dst, inst.offset) << ", " << _format_register(inst.src);
536✔
241
        return result.str();
402✔
242
    }
268✔
243

244
    std::string
245
    _decode_jmp(const ebpf_inst& inst)
4,970✔
246
    {
247
        std::ostringstream result;
4,970✔
248
        uint8_t opcode_class = inst.opcode & EBPF_CLS_MASK;
4,970✔
249
        uint8_t opcode_src = inst.opcode & EBPF_SRC_REG;
4,970✔
250
        bool is_32 = (opcode_class == EBPF_CLS_JMP32);
2,485✔
251
        std::string suffix = is_32 ? "32" : "";
7,074✔
252

253
        // Handle exit
254
        if (inst.opcode == EBPF_OP_EXIT) {
4,970✔
255
            return "exit";
2,322✔
256
        }
257

258
        // Handle call (both immediate and register variants)
259
        if ((inst.opcode & ~EBPF_SRC_REG) == EBPF_OP_CALL) {
2,648✔
260
            return _decode_call(inst);
14✔
261
        }
262

263
        // Handle unconditional jump
264
        if (inst.opcode == EBPF_OP_JA || inst.opcode == EBPF_OP_JA32) {
2,634✔
265
            if (is_32) {
294✔
266
                return "ja32 " + _format_jump_offset(inst.imm);
12✔
267
            } else {
268
                return "ja " + _format_jump_offset(inst.offset);
576✔
269
            }
270
        }
271

272
        // Handle conditional jumps
273
        uint8_t jmp_op = (inst.opcode >> 4) & 0x0f;
2,340✔
274
        auto iter = _bpf_decode_jmp_ops.find(jmp_op);
2,340✔
275
        std::string mnemonic = (iter != _bpf_decode_jmp_ops.end()) ? iter->second : "junknown";
3,510✔
276

277
        result << mnemonic << suffix << " " << _format_register(inst.dst) << ", ";
3,510✔
278
        if (opcode_src) {
2,340✔
279
            result << _format_register(inst.src);
1,686✔
280
        } else {
281
            result << _format_imm(inst.imm);
1,824✔
282
        }
283
        result << ", " << _format_jump_offset(inst.offset);
3,510✔
284
        return result.str();
1,170✔
285
    }
4,970✔
286

287
    std::string
288
    _decode_call(const ebpf_inst& inst)
14✔
289
    {
290
        std::ostringstream result;
14✔
291
        uint8_t opcode_src = inst.opcode & EBPF_SRC_REG;
14✔
292

293
        result << "call ";
14✔
294
        if (opcode_src) {
14✔
295
            // Indirect call via register (callx) - inst.dst contains the register number
NEW
296
            result << "helper " << _format_register(inst.dst);
×
297
        } else if (inst.src == 0) {
14✔
298
            // Helper call with immediate
299
            result << "helper " << _format_imm(inst.imm);
9✔
300
        } else if (inst.src == 1) {
8✔
301
            // Local call (BPF-to-BPF)
302
            result << "local " << _format_jump_offset(inst.imm);
12✔
NEW
303
        } else if (inst.src == 2) {
×
304
            // Runtime/kfunc call
NEW
305
            result << "runtime " << _format_imm(inst.imm);
×
306
        } else {
307
            // Unknown call type
NEW
308
            result << "unknown " << _format_imm(inst.imm);
×
309
        }
310
        return result.str();
21✔
311
    }
14✔
312

313
    std::string
NEW
314
    _decode_unknown(const ebpf_inst& inst)
×
315
    {
NEW
316
        std::ostringstream result;
×
NEW
317
        result << "unknown 0x" << std::hex << static_cast<int>(inst.opcode);
×
NEW
318
        return result.str();
×
NEW
319
    }
×
320

321
    // Dispatch table: maps instruction class to decode handler
322
    const std::unordered_map<int, bpf_decode_t> _bpf_decode_class_map{
323
        {EBPF_CLS_LD, &_bpf_disassembler::_decode_ld},
324
        {EBPF_CLS_LDX, &_bpf_disassembler::_decode_ldx},
325
        {EBPF_CLS_ST, &_bpf_disassembler::_decode_st},
326
        {EBPF_CLS_STX, &_bpf_disassembler::_decode_stx},
327
        {EBPF_CLS_ALU, &_bpf_disassembler::_decode_alu},
328
        {EBPF_CLS_JMP, &_bpf_disassembler::_decode_jmp},
329
        {EBPF_CLS_JMP32, &_bpf_disassembler::_decode_jmp},
330
        {EBPF_CLS_ALU64, &_bpf_disassembler::_decode_alu},
331
    };
332

333
  public:
334
    _bpf_disassembler() = default;
458,986✔
335
    ~_bpf_disassembler() = default;
14,806✔
336

337
    std::string
338
    disassemble_inst(const ebpf_inst& inst)
14,806✔
339
    {
340
        uint8_t opcode_class = inst.opcode & EBPF_CLS_MASK;
14,806✔
341

342
        auto iter = _bpf_decode_class_map.find(opcode_class);
14,806✔
343
        if (iter != _bpf_decode_class_map.end()) {
14,806✔
344
            return (this->*(iter->second))(inst);
14,806✔
345
        }
346

NEW
347
        return _decode_unknown(inst);
×
348
    }
349
} bpf_disassembler_t;
350

351
// Helper function to format raw bytes
352
static std::string
NEW
353
format_raw_bytes(const ebpf_inst& inst)
×
354
{
NEW
355
    std::ostringstream ss;
×
356
    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&inst);
NEW
357
    for (size_t i = 0; i < sizeof(ebpf_inst); i++) {
×
NEW
358
        if (i > 0)
×
NEW
359
            ss << " ";
×
NEW
360
        ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]);
×
361
    }
NEW
362
    return ss.str();
×
NEW
363
}
×
364

365
std::string
366
bpf_disassemble_inst(const ebpf_inst& inst)
14,806✔
367
{
368
    bpf_disassembler_t disassembler;
14,806✔
369
    return disassembler.disassemble_inst(inst);
29,612✔
370
}
14,806✔
371

372
void
373
bpf_disassembler(const std::vector<ebpf_inst>& instructions, std::ostream& output, bool show_raw)
1,516✔
374
{
375
    for (size_t i = 0; i < instructions.size(); i++) {
17,234✔
376
        const ebpf_inst& inst = instructions[i];
7,859✔
377
        output << std::setw(4) << std::setfill(' ') << std::dec << i << ": ";
15,718✔
378

379
        // Handle lddw specially to show the full 64-bit immediate
380
        if ((inst.opcode & EBPF_CLS_MASK) == EBPF_CLS_LD && (inst.opcode & 0x18) == EBPF_SIZE_DW &&
15,718✔
381
            (inst.opcode & 0xe0) == EBPF_MODE_IMM) {
456✔
382
            uint64_t imm64 = static_cast<uint32_t>(inst.imm);
912✔
383
            if (i + 1 < instructions.size()) {
912✔
384
                imm64 |= (static_cast<uint64_t>(static_cast<uint32_t>(instructions[i + 1].imm)) << 32);
912✔
385
            }
386
            output << "lddw %r" << static_cast<int>(inst.dst) << ", 0x" << std::hex << imm64 << std::dec;
912✔
387

388
            if (show_raw) {
912✔
NEW
389
                output << " ; " << format_raw_bytes(inst);
×
390
            }
391
            output << std::endl;
456✔
392

393
            if (i + 1 < instructions.size()) {
912✔
394
                i++; // Skip the next instruction
456✔
395
            }
396
        } else {
456✔
397
            output << bpf_disassemble_inst(inst);
14,806✔
398

399
            if (show_raw) {
14,806✔
NEW
400
                output << " ; " << format_raw_bytes(inst);
×
401
            }
402
            output << std::endl;
7,403✔
403
        }
404
    }
405
}
1,516✔
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