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

Alan-Jowett / ebpf-verifier / 29279923487

12 Jul 2026 11:38AM UTC coverage: 86.649% (+0.3%) from 86.386%
29279923487

push

github

web-flow
Drive sample verification tests from the inventory; parallelize CI (#1195)

Full-program verification of the ebpf-samples corpus was 17 C++ files
generated by scripts/generate_verify_project_tests.py from
test-data/elf_inventory.json, then compiled into the test binary. The
generated files duplicated the inventory and could drift from it (there
was no check keeping them in sync), and the whole corpus ran in one
single-threaded Catch2 process.

Data-driven tests (dedup)
-------------------------
- Add src/test/test_verify_samples.cpp, which reads elf_inventory.json at
  runtime (via yaml-cpp; JSON is a subset of YAML) and registers one
  Catch2 DYNAMIC_SECTION per (object, section[, program]). Adding or
  retagging a sample is now a JSON edit: no code generation, no recompile.
- Delete the 17 generated test_verify_<project>.cpp files and
  scripts/generate_verify_project_tests.py. The inventory is the single
  source of truth. A coverage-guard test asserts the registered projects
  match the inventory exactly, so a new project cannot be left untested.
- Trim test_verify.hpp to the ELF-parse cache and the VerifyIssueKind
  taxonomy the driver and the multithreading test still use.

  Catch2's [!shouldfail] is a compile-time per-TEST_CASE tag and cannot be
  applied per data-driven entry, so an expected_failure (a safe program
  the verifier is currently too imprecise to accept) is asserted as
  *still rejected* -- an xfail/golden marker. A verifier improvement that
  starts accepting it fails the case, prompting an inventory update; a
  regression on a passing program fails it too.

Parallel CI (ctest)
-------------------
- Register ctest entries: one per project (sharded by tag) plus a "unit"
  entry for everything else. The shard list is derived from the inventory
  via string(JSON), so it cannot drift from the corpus.
- CI runs `ctest -j` instead of a single `bin/tests` process. Locally, a
  full run drops from ~104s to ~42s on 16 cores (bounded by t... (continued)

9313 of 10748 relevant lines covered (86.65%)

6391896.2 hits per line

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

84.43
/src/io/elf_reader.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <array>
5
#include <cerrno>
6
#include <concepts>
7
#include <cstddef>
8
#include <cstdint>
9
#include <cstring>
10
#include <deque>
11
#include <filesystem>
12
#include <fstream>
13
#include <functional>
14
#include <iostream>
15
#include <map>
16
#include <optional>
17
#include <set>
18
#include <string>
19
#include <sys/stat.h>
20
#include <type_traits>
21
#include <variant>
22
#include <vector>
23

24
#include <elfio/elfio.hpp>
25

26
#include "crab_utils/num_safety.hpp"
27
#include "io/elf_reader.hpp"
28

29
namespace prevail {
30

31
// ---------------------------------------------------------------------------
32
// Utility functions
33
// ---------------------------------------------------------------------------
34

35
static_assert(sizeof(ELFIO::Elf32_Ehdr) == 52);
36
static_assert(offsetof(ELFIO::Elf32_Ehdr, e_shoff) == 32);
37
static_assert(offsetof(ELFIO::Elf32_Ehdr, e_shentsize) == 46);
38
static_assert(offsetof(ELFIO::Elf32_Ehdr, e_shnum) == 48);
39
static_assert(sizeof(ELFIO::Elf64_Ehdr) == 64);
40
static_assert(offsetof(ELFIO::Elf64_Ehdr, e_shoff) == 40);
41
static_assert(offsetof(ELFIO::Elf64_Ehdr, e_shentsize) == 58);
42
static_assert(offsetof(ELFIO::Elf64_Ehdr, e_shnum) == 60);
43
static_assert(sizeof(ELFIO::Elf32_Shdr) == 40);
44
static_assert(offsetof(ELFIO::Elf32_Shdr, sh_size) == 20);
45
static_assert(sizeof(ELFIO::Elf64_Shdr) == 64);
46
static_assert(offsetof(ELFIO::Elf64_Shdr, sh_size) == 32);
47
static_assert(sizeof(ELFIO::Elf32_Rel) == 8);
48
static_assert(offsetof(ELFIO::Elf32_Rel, r_offset) == 0);
49
static_assert(offsetof(ELFIO::Elf32_Rel, r_info) == 4);
50
static_assert(sizeof(ELFIO::Elf32_Rela) == 12);
51
static_assert(offsetof(ELFIO::Elf32_Rela, r_addend) == 8);
52
static_assert(sizeof(ELFIO::Elf64_Rel) == 16);
53
static_assert(offsetof(ELFIO::Elf64_Rel, r_offset) == 0);
54
static_assert(offsetof(ELFIO::Elf64_Rel, r_info) == 8);
55
static_assert(sizeof(ELFIO::Elf64_Rela) == 24);
56
static_assert(offsetof(ELFIO::Elf64_Rela, r_addend) == 16);
57

58
std::pair<std::reference_wrapper<EbpfInst>, std::reference_wrapper<EbpfInst>>
59
validate_and_get_lddw_pair(std::vector<EbpfInst>& instructions, const size_t location, const std::string& context) {
12,414✔
60
    constexpr uint8_t BPF_LDDW = 0x18;
12,414✔
61
    constexpr uint8_t BPF_LDDW_HI = 0x00;
12,414✔
62

63
    if (instructions.size() <= location + 1) {
12,414✔
64
        throw UnmarshalError("Invalid relocation: " + std::string(context) + " reference at instruction boundary");
×
65
    }
66

67
    auto& lo_inst = instructions[location];
12,414✔
68
    auto& hi_inst = instructions[location + 1];
12,414✔
69

70
    if (lo_inst.opcode != BPF_LDDW) {
12,414✔
71
        throw UnmarshalError("Invalid relocation: expected LDDW first slot (opcode 0x18) for " + std::string(context) +
×
72
                             ", found opcode 0x" + std::to_string(static_cast<int>(lo_inst.opcode)));
×
73
    }
74
    if (hi_inst.opcode != BPF_LDDW_HI) {
12,414✔
75
        throw UnmarshalError("Invalid relocation: expected LDDW second slot (opcode 0x00) for " + std::string(context) +
×
76
                             ", found opcode 0x" + std::to_string(static_cast<int>(hi_inst.opcode)));
×
77
    }
78

79
    return {std::ref(lo_inst), std::ref(hi_inst)};
12,414✔
80
}
81

82
bool is_map_section(const std::string& name) {
122,470✔
83
    return name == "maps" || name == ".maps" || name.starts_with("maps/") || name.starts_with(".maps/");
243,133✔
84
}
85

86
bool is_global_section(const std::string& name) {
87,724✔
87
    return name == ".data" || name == ".rodata" || name == ".bss" || name.starts_with(".data.") ||
218,356✔
88
           name.starts_with(".rodata.") || name.starts_with(".bss.");
261,520✔
89
}
90

91
std::string bad_reloc_value(const size_t reloc_value) {
×
92
    return "Bad reloc value (" + std::to_string(reloc_value) + "). " + "Make sure to compile with -O2.";
×
93
}
94

95
bool is_supported_bpf_relocation_type(const unsigned type) {
1,298,616✔
96
    return type == R_BPF_NONE_TYPE || type == R_BPF_64_64_TYPE || type == R_BPF_64_32_TYPE;
1,298,616✔
97
}
98

99
template <std::unsigned_integral T>
100
T read_reloc_uint(const char* data, const size_t offset, const bool little_endian) {
2,600,130✔
101
    T value = 0;
2,600,130✔
102
    for (size_t i = 0; i < sizeof(T); ++i) {
23,401,170✔
103
        const size_t byte_index = little_endian ? i : sizeof(T) - 1U - i;
20,801,040✔
104
        value |= static_cast<T>(static_cast<unsigned char>(data[offset + byte_index])) << (8U * i);
20,801,040✔
105
    }
106
    return value;
2,600,130✔
107
}
108

109
template <std::signed_integral T>
110
T read_reloc_int(const char* data, const size_t offset, const bool little_endian) {
2,898✔
111
    const auto raw = read_reloc_uint<std::make_unsigned_t<T>>(data, offset, little_endian);
2,898✔
112
    T value{};
1,449✔
113
    std::memcpy(&value, &raw, sizeof(value));
1,449✔
114
    return value;
1,449✔
115
}
116

117
struct RelocationEntry {
118
    ELFIO::Elf64_Addr offset;
119
    ELFIO::Elf_Word symbol_index;
120
    unsigned type;
121
    ELFIO::Elf_Sxword addend;
122
};
123

124
struct RelocationSectionLayout {
125
    unsigned char elf_class;
126
    ELFIO::Elf_Word section_type;
127
    size_t entry_size;
128
    ELFIO::Elf_Xword entries_num;
129
    bool little_endian;
130
};
131

132
size_t expected_relocation_entry_size(const unsigned char elf_class, const ELFIO::Elf_Word section_type) {
29,932✔
133
    if (elf_class == ELFIO::ELFCLASS64) {
14,966✔
134
        return section_type == ELFIO::SHT_RELA ? sizeof(ELFIO::Elf64_Rela) : sizeof(ELFIO::Elf64_Rel);
29,932✔
135
    }
136
    return section_type == ELFIO::SHT_RELA ? sizeof(ELFIO::Elf32_Rela) : sizeof(ELFIO::Elf32_Rel);
×
137
}
138

139
RelocationSectionLayout relocation_section_layout(const ELFIO::elfio& reader, const ELFIO::section& section,
29,932✔
140
                                                  const std::string& section_name) {
141
    const auto elf_class = reader.get_class();
29,932✔
142
    const auto section_type = section.get_type();
29,932✔
143
    if (section_type != ELFIO::SHT_REL && section_type != ELFIO::SHT_RELA) {
29,932✔
144
        throw UnmarshalError("Malformed relocation section " + section_name);
×
145
    }
146

147
    const size_t expected_entry_size = expected_relocation_entry_size(elf_class, section_type);
29,932✔
148
    const size_t entry_size = section.get_entry_size();
29,932✔
149
    // ELFIO's relocation accessor silently treats zero entry size as no
150
    // entries and then uses typed loads for non-zero entries. Validate the raw
151
    // table first so every indexed byte read below is inside one complete entry.
152
    if (entry_size != expected_entry_size || section.get_data() == nullptr || section.get_size() % entry_size != 0) {
29,932✔
153
        throw UnmarshalError("Malformed relocation section " + section_name);
3✔
154
    }
155
    return {
14,965✔
156
        .elf_class = elf_class,
157
        .section_type = section_type,
158
        .entry_size = entry_size,
159
        .entries_num = section.get_size() / entry_size,
59,860✔
160
        .little_endian = reader.get_encoding() == ELFIO::ELFDATA2LSB,
59,860✔
161
    };
29,930✔
162
}
163

164
RelocationEntry read_relocation_entry(const ELFIO::section& section, const ELFIO::Elf_Xword index,
1,298,616✔
165
                                      const RelocationSectionLayout& layout) {
166
    const char* data = section.get_data() + index * layout.entry_size;
1,298,616✔
167
    if (layout.elf_class == ELFIO::ELFCLASS64) {
1,298,616✔
168
        const uint64_t info =
649,308✔
169
            read_reloc_uint<ELFIO::Elf_Xword>(data, offsetof(ELFIO::Elf64_Rel, r_info), layout.little_endian);
1,298,616✔
170
        return {
649,308✔
171
            .offset =
172
                read_reloc_uint<ELFIO::Elf64_Addr>(data, offsetof(ELFIO::Elf64_Rel, r_offset), layout.little_endian),
1,298,616✔
173
            .symbol_index = static_cast<ELFIO::Elf_Word>(info >> 32U),
1,298,616✔
174
            .type = static_cast<unsigned>(info & 0xffffffffULL),
175
            .addend = layout.section_type == ELFIO::SHT_RELA
1,298,616✔
176
                          ? read_reloc_int<ELFIO::Elf_Sxword>(data, offsetof(ELFIO::Elf64_Rela, r_addend),
649,308✔
177
                                                              layout.little_endian)
1,449✔
178
                          : 0,
179
        };
1,949,373✔
180
    }
181

182
    const uint32_t info =
183
        read_reloc_uint<ELFIO::Elf_Word>(data, offsetof(ELFIO::Elf32_Rel, r_info), layout.little_endian);
×
184
    return {
185
        .offset = read_reloc_uint<ELFIO::Elf32_Addr>(data, offsetof(ELFIO::Elf32_Rel, r_offset), layout.little_endian),
×
186
        .symbol_index = static_cast<ELFIO::Elf_Word>(info >> 8U),
×
187
        .type = static_cast<unsigned>(info & 0xffU),
×
188
        .addend =
189
            layout.section_type == ELFIO::SHT_RELA
×
190
                ? read_reloc_int<ELFIO::Elf_Sword>(data, offsetof(ELFIO::Elf32_Rela, r_addend), layout.little_endian)
×
191
                : 0,
192
    };
×
193
}
194

195
symbol_details_t get_symbol_details(const ELFIO::const_symbol_section_accessor& symbols, const ELFIO::Elf_Xword index) {
230,584,102✔
196
    symbol_details_t details;
230,584,102✔
197
    if (!symbols.get_symbol(index, details.name, details.value, details.size, details.bind, details.type,
230,584,102✔
198
                            details.section_index, details.other)) {
230,584,102✔
199
        throw UnmarshalError("Invalid symbol index in ELF symbol table: " + std::to_string(index));
×
200
    }
201
    return details;
230,584,102✔
202
}
×
203

204
std::tuple<std::string, ELFIO::Elf_Xword>
205
get_program_name_and_size(const ELFIO::section& sec, const ELFIO::Elf_Xword start,
33,564✔
206
                          const ELFIO::const_symbol_section_accessor& symbols) {
207
    const ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num();
33,564✔
208
    const ELFIO::Elf_Half section_index = sec.get_index();
33,564✔
209
    std::string program_name = sec.get_name();
33,564✔
210
    ELFIO::Elf_Xword size = sec.get_size() - start;
33,564✔
211
    for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) {
224,658,918✔
212
        auto symbol_details = get_symbol_details(symbols, index);
224,625,356✔
213
        if (symbol_details.section_index == section_index && !symbol_details.name.empty()) {
224,625,356✔
214
            if (symbol_details.type != ELFIO::STT_FUNC) {
1,198,500✔
215
                continue;
1,144,266✔
216
            }
217
            const auto relocation_offset = symbol_details.value;
54,234✔
218
            if (relocation_offset % sizeof(EbpfInst) != 0) {
54,234✔
219
                throw UnmarshalError("Non-instruction-aligned FUNC symbol '" + symbol_details.name + "' at offset " +
5✔
220
                                     std::to_string(relocation_offset) + " in section " + sec.get_name());
7✔
221
            }
222
            if (relocation_offset == start) {
54,232✔
223
                program_name = symbol_details.name;
111,769,184✔
224
            } else if (relocation_offset > start && relocation_offset < start + size) {
25,592✔
225
                size = relocation_offset - start;
2,268✔
226
            }
227
        }
228
    }
224,625,356✔
229
    return {program_name, size};
67,124✔
230
}
33,563✔
231

232
std::optional<std::string> find_function_symbol_at_offset(const ELFIO::const_symbol_section_accessor& symbols,
392✔
233
                                                          const ELFIO::Elf_Half section_index,
234
                                                          const ELFIO::Elf_Xword offset) {
235
    const ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num();
392✔
236
    for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) {
2,894✔
237
        const auto symbol = get_symbol_details(symbols, index);
2,894✔
238
        if (symbol.section_index != section_index || symbol.type != ELFIO::STT_FUNC || symbol.name.empty()) {
2,894✔
239
            continue;
1,404✔
240
        }
241
        if (symbol.value == offset) {
1,490✔
242
            return symbol.name;
392✔
243
        }
244
    }
2,894✔
245
    return std::nullopt;
×
246
}
247

248
ELFIO::Elf_Xword compute_reachable_program_span(const std::vector<EbpfInst>& section_instructions,
30,924✔
249
                                                const ELFIO::Elf_Xword program_offset,
250
                                                const ELFIO::Elf_Xword initial_size) {
251
    if (section_instructions.empty()) {
30,924✔
252
        return initial_size;
253
    }
254

255
    const size_t total = section_instructions.size();
30,924✔
256
    const size_t start = program_offset / sizeof(EbpfInst);
30,924✔
257
    size_t initial_end = (program_offset + initial_size) / sizeof(EbpfInst);
30,924✔
258
    if (start >= total || initial_end <= start) {
30,924✔
259
        return initial_size;
260
    }
261
    initial_end = std::min(initial_end, total);
30,924✔
262

263
    auto mark = [&](const int64_t index, std::vector<bool>& seen, std::deque<size_t>& work) {
35,373,082✔
264
        if (index < 0 || index >= gsl::narrow<int64_t>(total)) {
35,357,620✔
265
            return;
3,793,434✔
266
        }
267
        const size_t idx = gsl::narrow<size_t>(index);
35,357,474✔
268
        if (seen[idx]) {
35,357,474✔
269
            return;
1,896,644✔
270
        }
271
        seen[idx] = true;
31,564,186✔
272
        work.push_back(idx);
31,564,186✔
273
    };
30,924✔
274

275
    std::vector<bool> seen(total, false);
30,924✔
276
    std::deque<size_t> work;
30,924✔
277
    mark(gsl::narrow<int64_t>(start), seen, work);
30,924✔
278

279
    size_t max_reachable = initial_end - 1;
30,924✔
280
    while (!work.empty()) {
31,595,110✔
281
        const size_t pc = work.front();
31,564,186✔
282
        work.pop_front();
31,564,186✔
283
        max_reachable = std::max(max_reachable, pc);
31,564,186✔
284

285
        const EbpfInst& inst = section_instructions[pc];
31,564,186✔
286
        const bool is_lddw = inst.opcode == INST_OP_LDDW_IMM;
31,564,186✔
287
        const size_t fallthrough = pc + (is_lddw ? 2 : 1);
31,564,186✔
288

289
        // LDDW is a two-slot instruction, so keep the high slot in range.
290
        if (is_lddw && pc + 1 < total) {
31,564,186✔
291
            mark(gsl::narrow<int64_t>(pc + 1), seen, work);
1,207,286✔
292
            max_reachable = std::max(max_reachable, pc + 1);
1,207,340✔
293
        }
294

295
        const uint8_t cls = inst.opcode & INST_CLS_MASK;
31,564,186✔
296
        if (cls == INST_CLS_JMP || cls == INST_CLS_JMP32) {
31,564,186✔
297
            const uint8_t op = (inst.opcode >> 4) & 0xF;
4,275,542✔
298
            if (op == INST_EXIT) {
4,275,542✔
299
                continue;
4,275,542✔
300
            }
301
            if (op == INST_CALL) {
4,244,472✔
302
                if (inst.opcode == INST_OP_CALL && inst.src == INST_CALL_LOCAL) {
1,220,822✔
303
                    const int64_t target = gsl::narrow<int64_t>(pc) + 1 + inst.imm;
233,642✔
304
                    mark(target, seen, work);
233,642✔
305
                }
306
                mark(gsl::narrow<int64_t>(fallthrough), seen, work);
1,220,822✔
307
                continue;
1,220,822✔
308
            }
1,220,822✔
309

310
            const int64_t target = gsl::narrow<int64_t>(pc) + 1 + inst.offset;
3,023,650✔
311
            mark(target, seen, work);
3,023,650✔
312
            if (op != INST_JA) {
3,023,650✔
313
                mark(gsl::narrow<int64_t>(fallthrough), seen, work);
2,352,652✔
314
            }
315
            continue;
3,023,650✔
316
        }
3,023,650✔
317

318
        mark(gsl::narrow<int64_t>(fallthrough), seen, work);
27,288,644✔
319
    }
320

321
    const size_t span_end = std::max(initial_end, max_reachable + 1);
30,924✔
322
    return gsl::narrow<ELFIO::Elf_Xword>((span_end - start) * sizeof(EbpfInst));
30,924✔
323
}
30,924✔
324

325
std::vector<ELFIO::section*> global_sections(const ELFIO::elfio& reader) {
1,686✔
326
    std::vector<ELFIO::section*> result;
1,686✔
327
    for (auto& section : reader.sections) {
89,410✔
328
        if (!section || !is_global_section(section->get_name())) {
131,586✔
329
            continue;
86,224✔
330
        }
331

332
        const auto type = section->get_type();
1,500✔
333
        if ((type == ELFIO::SHT_NOBITS || type == ELFIO::SHT_PROGBITS) && section->get_size() != 0) {
1,500✔
334
            result.push_back(section.get());
1,500✔
335
        }
336
    }
337
    return result;
1,686✔
338
}
×
339

340
RawProgram* find_subprogram(std::vector<RawProgram>& programs, const ELFIO::section& subprogram_section,
772✔
341
                            const std::string& symbol_name) {
342
    for (auto& subprog : programs) {
2,810✔
343
        if (subprog.section_name == subprogram_section.get_name() && subprog.function_name == symbol_name) {
4,215✔
344
            return &subprog;
772✔
345
        }
346
    }
347
    return nullptr;
348
}
349

350
static std::uintmax_t get_data_size(std::istream& input_stream, const std::string& path) {
696✔
351
    std::error_code ec;
696✔
352
    const auto size = std::filesystem::file_size(path, ec);
696✔
353
    if (!ec) {
696✔
354
        return size;
344✔
355
    }
356
    // Fallback: compute size from the input stream (handles in-memory ELF data
357
    // where path is not a real filesystem path, e.g. path="memory").
358
    input_stream.seekg(0, std::ios::end);
8✔
359
    const auto end_pos = input_stream.tellg();
8✔
360
    if (end_pos < 0) {
8✔
361
        throw UnmarshalError("Cannot determine data size for " + path);
×
362
    }
363
    input_stream.seekg(0);
8✔
364
    return end_pos;
8✔
365
}
366

367
template <std::unsigned_integral T>
368
static T read_uint(const unsigned char* bytes, const bool little_endian) {
2,090✔
369
    T value = 0;
2,090✔
370
    for (size_t i = 0; i < sizeof(T); ++i) {
10,458✔
371
        const size_t byte_index = little_endian ? i : sizeof(T) - 1U - i;
8,368✔
372
        value |= static_cast<T>(bytes[byte_index]) << (8U * i);
8,368✔
373
    }
374
    return value;
2,090✔
375
}
376

377
static std::streamsize read_stream_bytes_at(std::istream& input_stream, const std::uintmax_t offset,
698✔
378
                                            unsigned char* output, const size_t size) {
379
    input_stream.clear();
698✔
380
    input_stream.seekg(gsl::narrow<std::streamoff>(offset));
698✔
381
    input_stream.read(reinterpret_cast<char*>(output), gsl::narrow<std::streamsize>(size));
698✔
382
    const auto bytes_read = input_stream.gcount();
698✔
383
    input_stream.clear();
698✔
384
    input_stream.seekg(0);
698✔
385
    return bytes_read;
698✔
386
}
387

388
struct SectionHeaderTableLayout {
389
    uint64_t offset{};
390
    uint16_t entry_size{};
391
    uint16_t count{};
392
    size_t min_entry_size{};
393
    size_t section_size_offset{};
394
};
395

396
static std::optional<SectionHeaderTableLayout>
397
read_section_header_table_layout(const std::array<unsigned char, sizeof(ELFIO::Elf64_Ehdr)>& header,
696✔
398
                                 const std::streamsize bytes_read, const unsigned char elf_class,
399
                                 const bool little_endian) {
400
    constexpr size_t elf64_header_size = sizeof(ELFIO::Elf64_Ehdr);
696✔
401
    constexpr size_t elf32_section_header_size = sizeof(ELFIO::Elf32_Shdr);
696✔
402
    constexpr size_t elf64_section_header_size = sizeof(ELFIO::Elf64_Shdr);
696✔
403
    constexpr size_t elf32_section_size_offset = offsetof(ELFIO::Elf32_Shdr, sh_size);
696✔
404
    constexpr size_t elf64_section_size_offset = offsetof(ELFIO::Elf64_Shdr, sh_size);
696✔
405

406
    if (elf_class == ELFIO::ELFCLASS32) {
696✔
407
        return SectionHeaderTableLayout{
×
408
            .offset = read_uint<ELFIO::Elf32_Off>(header.data() + offsetof(ELFIO::Elf32_Ehdr, e_shoff), little_endian),
×
409
            .entry_size =
410
                read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf32_Ehdr, e_shentsize), little_endian),
×
411
            .count = read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf32_Ehdr, e_shnum), little_endian),
×
412
            .min_entry_size = elf32_section_header_size,
413
            .section_size_offset = elf32_section_size_offset,
414
        };
×
415
    }
416

417
    if (elf_class == ELFIO::ELFCLASS64 && bytes_read >= gsl::narrow<std::streamsize>(elf64_header_size)) {
696✔
418
        return SectionHeaderTableLayout{
696✔
419
            .offset = read_uint<ELFIO::Elf64_Off>(header.data() + offsetof(ELFIO::Elf64_Ehdr, e_shoff), little_endian),
696✔
420
            .entry_size =
421
                read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf64_Ehdr, e_shentsize), little_endian),
696✔
422
            .count = read_uint<ELFIO::Elf_Half>(header.data() + offsetof(ELFIO::Elf64_Ehdr, e_shnum), little_endian),
696✔
423
            .min_entry_size = elf64_section_header_size,
424
            .section_size_offset = elf64_section_size_offset,
425
        };
696✔
426
    }
427

428
    return std::nullopt;
×
429
}
430

431
static void validate_section_header_table_bounds(std::istream& input_stream, const std::string& path,
696✔
432
                                                 const std::uintmax_t data_size) {
433
    // This pre-load check validates only the section header table extent.
434
    // Individual section payload ranges are checked after ELFIO loads metadata.
435
    constexpr size_t min_elf_header_size = sizeof(ELFIO::Elf32_Ehdr);
696✔
436
    constexpr size_t elf64_header_size = sizeof(ELFIO::Elf64_Ehdr);
696✔
437
    constexpr unsigned char elf_magic[] = {0x7f, 'E', 'L', 'F'};
696✔
438

439
    std::array<unsigned char, elf64_header_size> header{};
696✔
440
    const auto bytes_read = read_stream_bytes_at(input_stream, 0, header.data(), header.size());
1,044✔
441

442
    if (bytes_read < gsl::narrow<std::streamsize>(min_elf_header_size) ||
1,044✔
443
        !std::equal(std::begin(elf_magic), std::end(elf_magic), header.begin())) {
1,392✔
444
        return;
×
445
    }
446

447
    const unsigned char elf_class = header[ELFIO::EI_CLASS];
696✔
448
    const unsigned char elf_data = header[ELFIO::EI_DATA];
696✔
449
    const bool little_endian = elf_data == ELFIO::ELFDATA2LSB;
696✔
450
    if (elf_data != ELFIO::ELFDATA2LSB && elf_data != ELFIO::ELFDATA2MSB) {
696✔
451
        return;
452
    }
453

454
    const std::optional<SectionHeaderTableLayout> layout =
348✔
455
        read_section_header_table_layout(header, bytes_read, elf_class, little_endian);
696✔
456
    if (!layout) {
696✔
457
        return;
458
    }
459
    const SectionHeaderTableLayout table = *layout;
696✔
460

461
    if (table.offset == 0 && table.count == 0) {
696✔
462
        return;
463
    }
464
    if (table.entry_size < table.min_entry_size) {
696✔
465
        throw UnmarshalError("ELF section header table in " + path + " has invalid entry size");
6✔
466
    }
467
    if (table.offset > data_size) {
692✔
468
        throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
3✔
469
    }
470

471
    const std::uintmax_t available = data_size - table.offset;
690✔
472
    std::uintmax_t required_entries = table.count;
690✔
473
    if (table.count == 0) {
690✔
474
        if (available < table.min_entry_size) {
2✔
475
            throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
×
476
        }
477

478
        std::array<unsigned char, sizeof(ELFIO::Elf64_Shdr)> null_section_header{};
2✔
479
        const auto null_section_bytes_read =
1✔
480
            read_stream_bytes_at(input_stream, table.offset, null_section_header.data(), table.min_entry_size);
3✔
481
        if (null_section_bytes_read < gsl::narrow<std::streamsize>(table.min_entry_size)) {
2✔
482
            throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
×
483
        }
484

485
        required_entries =
2✔
486
            elf_class == ELFIO::ELFCLASS32
487
                ? read_uint<ELFIO::Elf_Word>(null_section_header.data() + table.section_size_offset, little_endian)
2✔
488
                : read_uint<ELFIO::Elf_Xword>(null_section_header.data() + table.section_size_offset, little_endian);
3✔
489
    }
490
    if (required_entries > available / table.entry_size) {
690✔
491
        throw UnmarshalError("ELF section header table in " + path + " is out of bounds");
3✔
492
    }
493
}
494

495
ELFIO::elfio load_elf(std::istream& input_stream, const std::string& path) {
696✔
496
    const std::uintmax_t data_size = get_data_size(input_stream, path);
696✔
497
    validate_section_header_table_bounds(input_stream, path, data_size);
696✔
498

499
    ELFIO::elfio reader;
688✔
500
    if (!reader.load(input_stream)) {
688✔
501
        throw UnmarshalError("Can't process ELF file " + path);
×
502
    }
503

504
    if (reader.get_machine() != ELFIO::EM_BPF && reader.get_machine() != ELFIO::EM_NONE) {
688✔
505
        throw UnmarshalError("Unsupported ELF machine in file " + path + ": expected EM_BPF");
3✔
506
    }
507

508
    for (const auto& section : reader.sections) {
15,940✔
509
        if (!section || section->get_type() == ELFIO::SHT_NOBITS) {
15,256✔
510
            continue;
86✔
511
        }
512

513
        const std::uintmax_t offset = section->get_offset();
15,170✔
514
        const std::uintmax_t size = section->get_size();
15,170✔
515
        if (offset > data_size || size > data_size - offset) {
15,170✔
516
            throw UnmarshalError("ELF section '" + section->get_name() + "' has out-of-bounds file range");
3✔
517
        }
518
    }
519

520
    return reader;
684✔
521
}
4✔
522

523
ELFIO::const_symbol_section_accessor read_and_validate_symbol_section(const ELFIO::elfio& reader,
684✔
524
                                                                      const std::string& path) {
525
    const ELFIO::section* symbol_section = reader.sections[".symtab"];
684✔
526
    if (!symbol_section) {
684✔
527
        throw UnmarshalError("No symbol section found in ELF file " + path);
×
528
    }
529
    const auto expected_entry_size =
342✔
530
        reader.get_class() == ELFIO::ELFCLASS32 ? sizeof(ELFIO::Elf32_Sym) : sizeof(ELFIO::Elf64_Sym);
684✔
531
    if (symbol_section->get_entry_size() != expected_entry_size || symbol_section->get_entry_size() == 0 ||
1,707✔
532
        symbol_section->get_data() == nullptr || symbol_section->get_size() % symbol_section->get_entry_size() != 0) {
1,707✔
533
        throw UnmarshalError("Invalid symbol section in ELF file " + path);
3✔
534
    }
535

536
    const auto linked_strtab_index = symbol_section->get_link();
682✔
537
    if (linked_strtab_index >= reader.sections.size() || reader.sections[linked_strtab_index] == nullptr ||
1,364✔
538
        reader.sections[linked_strtab_index]->get_data() == nullptr) {
682✔
539
        throw UnmarshalError("Invalid symbol string table link in ELF file " + path);
×
540
    }
541
    return ELFIO::const_symbol_section_accessor{reader, symbol_section};
682✔
542
}
543

544
// ---------------------------------------------------------------------------
545
// ProgramReader methods
546
// ---------------------------------------------------------------------------
547

548
void ProgramReader::record_function_relocation(FunctionRelocation reloc) {
850✔
549
    function_relocation_index_.emplace(reloc.prog_index, reloc.source_offset);
850✔
550
    function_relocations.push_back(std::move(reloc));
850✔
551
}
850✔
552

553
bool ProgramReader::has_function_relocation(const size_t prog_index, const size_t source_offset) const {
2,720✔
554
    return function_relocation_index_.contains({prog_index, source_offset});
2,720✔
555
}
556

557
void ProgramReader::enqueue_synthetic_local_calls(const std::vector<EbpfInst>& instructions,
30,918✔
558
                                                  const ELFIO::Elf_Half section_index,
559
                                                  const ELFIO::Elf_Xword program_offset) {
560
    if (section_index >= reader.sections.size()) {
30,918✔
561
        throw UnmarshalError("Invalid section index");
×
562
    }
563
    const auto* section = reader.sections[section_index];
30,918✔
564
    if (!section) {
30,918✔
565
        throw UnmarshalError("Invalid section index");
×
566
    }
567

568
    const int64_t section_insn_count = gsl::narrow<int64_t>(section->get_size() / sizeof(EbpfInst));
30,918✔
569
    const int64_t program_start = gsl::narrow<int64_t>(program_offset / sizeof(EbpfInst));
30,918✔
570
    const int64_t program_end = program_start + gsl::narrow<int64_t>(instructions.size());
30,918✔
571

572
    for (size_t loc = 0; loc < instructions.size(); ++loc) {
31,609,238✔
573
        const EbpfInst& inst = instructions[loc];
31,578,320✔
574
        if (!(inst.opcode == INST_OP_CALL && inst.src == INST_CALL_LOCAL)) {
31,578,320✔
575
            continue;
31,578,308✔
576
        }
577
        if (has_function_relocation(raw_programs.size(), loc)) {
1,882✔
578
            continue;
838✔
579
        }
580

581
        const int64_t target = program_start + gsl::narrow<int64_t>(loc) + 1 + inst.imm;
1,044✔
582
        if (target >= program_start && target < program_end) {
1,044✔
583
            continue;
1,032✔
584
        }
585
        if (target < 0 || target >= section_insn_count) {
12✔
586
            throw UnmarshalError("Local call target out of section bounds");
×
587
        }
588

589
        const auto target_offset = gsl::narrow<ELFIO::Elf_Xword>(target * sizeof(EbpfInst));
12✔
590
        const auto target_name = find_function_symbol_at_offset(symbols, section_index, target_offset);
12✔
591
        if (!target_name) {
12✔
592
            throw UnmarshalError("Subprogram not found at section offset " + std::to_string(target_offset));
×
593
        }
594

595
        record_function_relocation(FunctionRelocation{
18✔
596
            .prog_index = raw_programs.size(),
12✔
597
            .source_offset = loc,
598
            .relocation_entry_index = std::nullopt,
599
            .target_section_index = section_index,
600
            .target_function_name = *target_name,
601
        });
602
    }
12✔
603
}
30,918✔
604

605
std::string ProgramReader::append_subprograms(RawProgram& prog) {
31,686✔
606
    if (resolved_subprograms[&prog]) {
31,686✔
607
        return {};
772✔
608
    }
609

610
    if (currently_visiting.contains(&prog)) {
30,914✔
611
        throw UnmarshalError("Mutual recursion in subprogram calls");
×
612
    }
613
    currently_visiting.insert(&prog);
30,914✔
614

615
    std::map<std::string, ELFIO::Elf_Xword> subprogram_offsets;
30,914✔
616
    for (const auto& reloc : function_relocations) {
39,708✔
617
        // Match the relocation to its owning program by identity, not by name.
618
        // reloc.source_offset is an instruction index into raw_programs[reloc.prog_index];
619
        // applying it to a different program that merely shares function_name (ELF symbol
620
        // names need not be unique) can index prog.prog out of bounds.
621
        if (reloc.prog_index >= raw_programs.size() || &raw_programs[reloc.prog_index] != &prog) {
8,794✔
622
            continue;
7,944✔
623
        }
624
        const auto& target_function_name = reloc.target_function_name;
850✔
625
        if (!subprogram_offsets.contains(target_function_name)) {
850✔
626
            subprogram_offsets[target_function_name] = prog.prog.size();
772✔
627
            if (reloc.target_section_index >= reader.sections.size()) {
772✔
628
                throw UnmarshalError("Invalid section index");
×
629
            }
630
            const auto& sub_sec = *reader.sections[reloc.target_section_index];
772✔
631
            if (const auto sub = find_subprogram(raw_programs, sub_sec, target_function_name)) {
772✔
632
                if (sub == &prog) {
772✔
633
                    throw UnmarshalError("Recursive subprogram call");
×
634
                }
635
                const std::string err = append_subprograms(*sub);
772✔
636
                if (!err.empty()) {
772✔
637
                    return err;
×
638
                }
639
                const size_t base = subprogram_offsets[target_function_name];
772✔
640

641
                prog.prog.insert(prog.prog.end(), sub->prog.begin(), sub->prog.end());
772✔
642
                if (parse_params.options.verbosity_opts.print_line_info) {
772✔
643
                    for (const auto& [k, info] : sub->info.line_info) {
×
644
                        prog.info.line_info[base + k] = info;
×
645
                    }
646
                }
647
                for (const size_t builtin_offset : sub->info.builtin_call_offsets) {
772✔
648
                    prog.info.builtin_call_offsets.insert(base + builtin_offset);
×
649
                }
650
            } else {
772✔
651
                return "Subprogram not found: " + target_function_name;
×
652
            }
653
        }
654
        const auto target_offset = gsl::narrow<int64_t>(subprogram_offsets[target_function_name]);
850✔
655
        const auto source_offset = gsl::narrow<int64_t>(reloc.source_offset);
850✔
656
        prog.prog[reloc.source_offset].imm = gsl::narrow<int32_t>(target_offset - source_offset - 1);
850✔
657
    }
658
    currently_visiting.erase(&prog);
30,914✔
659
    resolved_subprograms[&prog] = true;
30,914✔
660
    return {};
30,914✔
661
}
30,914✔
662

663
int ProgramReader::relocate_map(const std::string& name, const ELFIO::Elf_Word index) const {
477,074✔
664
    size_t val{};
477,074✔
665
    if (const auto* record_size = std::get_if<size_t>(&global.map_record_size_or_map_offsets)) {
477,074✔
666
        const auto symbol_value = get_symbol_details(symbols, index).value;
×
667
        if (symbol_value % *record_size != 0) {
×
668
            throw UnmarshalError("Map symbol offset " + std::to_string(symbol_value) +
×
669
                                 " is not aligned to record size " + std::to_string(*record_size));
×
670
        }
671

672
        val = symbol_value / *record_size;
×
673
    } else {
674
        const auto& offsets = std::get<MapOffsets>(global.map_record_size_or_map_offsets);
477,074✔
675
        const auto it = offsets.find(name);
477,074✔
676
        if (it == offsets.end()) {
477,074✔
677
            throw UnmarshalError("Map descriptor not found: " + name);
×
678
        }
679
        val = it->second;
477,074✔
680
    }
681
    if (val >= global.map_descriptors.size()) {
477,074✔
682
        throw UnmarshalError(bad_reloc_value(val));
×
683
    }
684
    return global.map_descriptors.at(val).original_fd;
477,074✔
685
}
686

687
int ProgramReader::relocate_global_variable(const std::string& name) const {
12,208✔
688
    const auto* offsets = std::get_if<MapOffsets>(&global.map_record_size_or_map_offsets);
12,208✔
689
    if (!offsets) {
12,208✔
690
        throw UnmarshalError("Invalid map offsets");
×
691
    }
692
    const auto it = offsets->find(name);
12,208✔
693
    if (it == offsets->end()) {
12,208✔
694
        throw UnmarshalError("Map descriptor not found: " + name);
×
695
    }
696
    const size_t val = it->second;
12,208✔
697
    if (val >= global.map_descriptors.size()) {
12,208✔
698
        throw UnmarshalError(bad_reloc_value(val));
×
699
    }
700
    return global.map_descriptors.at(val).original_fd;
18,312✔
701
}
702

703
int32_t ProgramReader::compute_lddw_reloc_offset_imm(const ELFIO::Elf_Sxword addend, const ELFIO::Elf_Word index,
12,208✔
704
                                                     const std::reference_wrapper<EbpfInst> lo_inst) const {
705
    const auto& sym = get_symbol_details(symbols, index);
12,208✔
706
    if (sym.type == ELFIO::STT_SECTION) {
12,208✔
707
        return addend != 0 ? gsl::narrow<int32_t>(addend) : lo_inst.get().imm;
446✔
708
    }
709
    return gsl::narrow<int32_t>(sym.value + addend);
11,762✔
710
}
12,208✔
711

712
void ProgramReader::build_ksym_function_resolution_cache() {
1,686✔
713
    ksym_function_resolution_cache.clear();
1,686✔
714
    const auto* btf_sec = reader.sections[".BTF"];
1,686✔
715
    if (!btf_sec || !btf_sec->get_data()) {
1,686✔
716
        return;
1,622✔
717
    }
718

719
    const libbtf::btf_type_data btf_data{vector_of<std::byte>(*btf_sec)};
1,114✔
720

721
    const auto ksyms_id = btf_data.get_id(".ksyms");
1,082✔
722
    if (ksyms_id == 0) {
1,082✔
723
        return;
1,034✔
724
    }
725

726
    libbtf::btf_kind_data_section ksyms;
48✔
727
    try {
24✔
728
        ksyms = btf_data.get_kind_type<libbtf::btf_kind_data_section>(ksyms_id);
48✔
729
    } catch (const std::exception& e) {
×
730
        throw UnmarshalError(std::string("Unsupported or invalid BTF .ksyms section (id ") + std::to_string(ksyms_id) +
×
731
                             "): " + e.what());
×
732
    }
×
733

734
    for (const auto& member : ksyms.members) {
176✔
735
        if (btf_data.get_kind_index(member.type) != libbtf::BTF_KIND_FUNCTION) {
128✔
736
            continue;
18✔
737
        }
738
        libbtf::btf_kind_function function;
110✔
739
        try {
55✔
740
            function = btf_data.get_kind_type<libbtf::btf_kind_function>(member.type);
165✔
741
        } catch (const std::exception& e) {
×
742
            throw UnmarshalError(std::string("Unsupported or invalid BTF .ksyms function (type ") +
×
743
                                 std::to_string(member.type) + "): " + e.what());
×
744
        }
×
745
        if (function.name.empty() || ksym_function_resolution_cache.contains(function.name)) {
110✔
746
            continue;
×
747
        }
748

749
        std::optional<KsymBtfId> resolved;
110✔
750
        if (parse_params.platform && parse_params.platform->resolve_ksym_btf_id) {
110✔
751
            resolved = parse_params.platform->resolve_ksym_btf_id(function.name);
110✔
752
        }
753
        ksym_function_resolution_cache.emplace(function.name, resolved);
110✔
754
    }
110✔
755
}
1,082✔
756

757
bool ProgramReader::try_reloc(const std::string& symbol_name, const ELFIO::Elf_Half symbol_section_index,
723,034✔
758
                              const unsigned char symbol_type, const unsigned char symbol_bind,
759
                              std::vector<EbpfInst>& instructions, const size_t location, const ELFIO::Elf_Word index,
760
                              const ELFIO::Elf_Sxword addend) {
761
    EbpfInst& instruction_to_relocate = instructions[location];
723,034✔
762

763
    if (symbol_section_index == ELFIO::SHN_UNDEF) {
723,034✔
764
        if (const auto value = resolve_known_linux_extern_symbol(symbol_name)) {
232,890✔
765
            if (rewrite_extern_constant_load(instructions, location, *value)) {
52✔
766
                return true;
52✔
767
            }
768
        }
769
        if (rewrite_extern_address_load_to_zero(instructions, location)) {
232,838✔
770
            return true;
70✔
771
        }
772
    }
773

774
    // Handle local function calls - queue for post-processing.
775
    if (instruction_to_relocate.opcode == INST_OP_CALL && instruction_to_relocate.src == INST_CALL_LOCAL) {
722,842✔
776
        if (symbol_section_index == ELFIO::SHN_UNDEF) {
233,536✔
777
            if (const auto it = ksym_function_resolution_cache.find(symbol_name);
232,698✔
778
                it != ksym_function_resolution_cache.end()) {
232,698✔
779
                if (it->second.has_value()) {
116✔
780
                    if (!rewrite_extern_kfunc_call(instruction_to_relocate, *it->second)) {
98✔
781
                        throw UnmarshalError("Invalid kfunc call rewrite for symbol " + symbol_name +
×
782
                                             ": instruction encoding or resolver output is invalid");
×
783
                    }
784
                    return true;
50✔
785
                }
786
                if (symbol_bind != ELFIO::STB_WEAK) {
18✔
787
                    return false;
1✔
788
                }
789
            }
790
        }
791
        if (symbol_section_index == ELFIO::SHN_UNDEF && parse_params.platform->resolve_builtin_call) {
233,436✔
792
            if (const auto builtin_id = parse_params.platform->resolve_builtin_call(symbol_name)) {
232,598✔
793
                instruction_to_relocate.src = INST_CALL_STATIC_HELPER;
232,598✔
794
                instruction_to_relocate.imm = *builtin_id;
232,598✔
795
                if (*builtin_id < 0) {
232,598✔
796
                    builtin_offsets_for_current_program.insert(location);
232,592✔
797
                }
798
                return true;
232,598✔
799
            }
800
        }
801
        if (symbol_section_index == ELFIO::SHN_UNDEF) {
838✔
802
            return false;
803
        }
804

805
        std::string target_function_name = symbol_name;
838✔
806
        if (target_function_name.empty() && symbol_type == ELFIO::STT_SECTION) {
838✔
807
            const int64_t target_byte_offset =
190✔
808
                addend != 0 ? addend : (gsl::narrow<int64_t>(instruction_to_relocate.imm) + 1) * sizeof(EbpfInst);
380✔
809
            if (target_byte_offset < 0 || target_byte_offset % sizeof(EbpfInst) != 0) {
380✔
810
                throw UnmarshalError("Invalid section-local call target offset");
×
811
            }
812
            if (const auto target = find_function_symbol_at_offset(symbols, symbol_section_index,
570✔
813
                                                                   gsl::narrow<ELFIO::Elf_Xword>(target_byte_offset))) {
380✔
814
                target_function_name = *target;
380✔
815
            }
380✔
816
        }
817

818
        if (!target_function_name.empty() && !has_function_relocation(raw_programs.size(), location)) {
838✔
819
            record_function_relocation(FunctionRelocation{
838✔
820
                .prog_index = raw_programs.size(),
838✔
821
                .source_offset = location,
822
                .relocation_entry_index = index,
823
                .target_section_index = symbol_section_index,
824
                .target_function_name = target_function_name,
825
            });
826
            return true;
838✔
827
        }
828
        return false;
×
829
    }
838✔
830

831
    // Handle empty symbol names for global variable sections
832
    if (symbol_name.empty()) {
489,306✔
833
        if (global.variable_section_indices.contains(symbol_section_index)) {
470✔
834
            if (!std::holds_alternative<MapOffsets>(global.map_record_size_or_map_offsets)) {
446✔
835
                return false;
836
            }
837

838
            auto [lo_inst, hi_inst] = validate_and_get_lddw_pair(instructions, location, "global variable");
669✔
839

840
            hi_inst.get().imm = compute_lddw_reloc_offset_imm(addend, index, lo_inst);
446✔
841
            lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
446✔
842

843
            const std::string section_name = reader.sections[symbol_section_index]->get_name();
669✔
844
            lo_inst.get().imm = relocate_global_variable(section_name);
446✔
845
            return true;
446✔
846
        }
446✔
847
        return true;
12✔
848
    }
849

850
    if ((instruction_to_relocate.opcode & INST_CLS_MASK) != INST_CLS_LD) {
488,836✔
851
        return false;
852
    }
853

854
    if (global.map_section_indices.contains(symbol_section_index)) {
488,836✔
855
        instruction_to_relocate.src = INST_LD_MODE_MAP_FD;
477,074✔
856
        instruction_to_relocate.imm = relocate_map(symbol_name, index);
477,074✔
857
        return true;
477,074✔
858
    }
859

860
    if (global.variable_section_indices.contains(symbol_section_index)) {
11,762✔
861
        auto [lo_inst, hi_inst] =
17,643✔
862
            validate_and_get_lddw_pair(instructions, location, "global variable '" + symbol_name + "'");
17,643✔
863

864
        hi_inst.get().imm = compute_lddw_reloc_offset_imm(addend, index, lo_inst);
11,762✔
865
        lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
11,762✔
866
        lo_inst.get().imm = relocate_global_variable(reader.sections[symbol_section_index]->get_name());
17,643✔
867
        return true;
11,762✔
868
    }
869

870
    if (symbol_name.rfind("__config_", 0) == 0) {
×
871
        instruction_to_relocate.imm = 0;
×
872
        return true;
×
873
    }
874

875
    return false;
876
}
877

878
void ProgramReader::process_relocations(std::vector<EbpfInst>& instructions, const ELFIO::section& reloc,
29,932✔
879
                                        const std::string& section_name, const ELFIO::Elf_Xword program_offset,
880
                                        const size_t program_size) {
881
    const auto layout = relocation_section_layout(reader, reloc, section_name);
29,932✔
882
    for (ELFIO::Elf_Xword i = 0; i < layout.entries_num; i++) {
1,328,542✔
883
        const auto entry = read_relocation_entry(reloc, i, layout);
1,298,616✔
884
        ELFIO::Elf64_Addr o = entry.offset;
1,298,616✔
885
        const ELFIO::Elf_Word idx = entry.symbol_index;
1,298,616✔
886
        const unsigned type = entry.type;
1,298,616✔
887
        const ELFIO::Elf_Sxword addend = entry.addend;
1,298,616✔
888
        if (!is_supported_bpf_relocation_type(type)) {
1,298,616✔
889
            throw UnmarshalError("Unsupported relocation type " + std::to_string(type) + " in section " + section_name);
4✔
890
        }
891
        if (type == R_BPF_NONE_TYPE && idx == 0) {
1,298,614✔
892
            continue;
575,578✔
893
        }
894
        if (idx >= symbols.get_symbols_num()) {
1,298,614✔
895
            throw UnmarshalError("Invalid relocation symbol index " + std::to_string(idx) + " in section " +
5✔
896
                                 section_name);
6✔
897
        }
898
        if (o < program_offset || o >= program_offset + program_size) {
1,298,612✔
899
            continue;
575,578✔
900
        }
901
        o -= program_offset;
723,034✔
902

903
        if (o % sizeof(EbpfInst) != 0) {
723,034✔
904
            throw UnmarshalError("Unaligned relocation offset");
×
905
        }
906
        const auto loc = o / sizeof(EbpfInst);
723,034✔
907
        if (loc >= instructions.size()) {
723,034✔
908
            throw UnmarshalError("Invalid relocation");
×
909
        }
910
        auto sym = get_symbol_details(symbols, idx);
723,034✔
911

912
        if (!try_reloc(sym.name, sym.section_index, sym.type, sym.bind, instructions, loc, idx, addend)) {
723,034✔
913
            unresolved_symbol_errors.push_back(unresolved_symbol_error_t{
5✔
914
                .section = section_name,
915
                .message = "Unresolved external symbol " + (sym.name.empty() ? "<anonymous>" : sym.name) +
4✔
916
                           " in section " + section_name + " at location " + std::to_string(loc),
9✔
917
            });
918
        }
919
    }
723,034✔
920
}
29,926✔
921

922
const ELFIO::section* ProgramReader::get_relocation_section(const std::string& name) const {
30,924✔
923
    if (name == ".BTF") {
30,924✔
924
        return nullptr;
925
    }
926
    const auto* relocs = reader.sections[".rel" + name];
30,924✔
927
    if (!relocs) {
30,924✔
928
        relocs = reader.sections[".rela" + name];
1,622✔
929
    }
930
    if (!relocs || !relocs->get_data()) {
30,924✔
931
        return nullptr;
992✔
932
    }
933
    return relocs;
14,966✔
934
}
935

936
void ProgramReader::read_programs() {
1,686✔
937
    resolved_subprograms.clear();
1,686✔
938
    build_ksym_function_resolution_cache();
1,686✔
939

940
    for (const auto& sec : reader.sections) {
88,770✔
941
        if (!(sec->get_flags() & ELFIO::SHF_EXECINSTR) || !sec->get_size() || !sec->get_data()) {
87,108✔
942
            continue;
57,382✔
943
        }
944
        auto section_instructions = vector_of<EbpfInst>(*sec);
29,726✔
945
        const auto& sec_name = sec->get_name();
29,726✔
946
        const auto prog_type = parse_params.platform->get_program_type(sec_name, parse_params.path);
29,726✔
947
        for (ELFIO::Elf_Xword offset = 0; offset < sec->get_size();) {
60,644✔
948
            builtin_offsets_for_current_program.clear();
30,926✔
949
            auto [name, symbol_size] = get_program_name_and_size(*sec, offset, symbols);
30,926✔
950
            const auto extracted_size = compute_reachable_program_span(section_instructions, offset, symbol_size);
30,924✔
951
            if (offset + extracted_size > sec->get_size()) {
30,924✔
952
                throw UnmarshalError("Program span exceeds section bounds in section " + sec->get_name());
×
953
            }
954
            auto instructions = vector_of<EbpfInst>(sec->get_data() + offset, extracted_size);
30,924✔
955
            if (const auto reloc_sec = get_relocation_section(sec_name)) {
30,924✔
956
                process_relocations(instructions, *reloc_sec, sec_name, offset, extracted_size);
29,932✔
957
            }
958
            enqueue_synthetic_local_calls(instructions, sec->get_index(), offset);
30,918✔
959
            ProgramInfo program_info{
15,459✔
960
                .platform = parse_params.platform,
30,918✔
961
                .map_descriptors = global.map_descriptors,
30,918✔
962
                .type = prog_type,
963
                .builtin_call_offsets = std::move(builtin_offsets_for_current_program),
30,918✔
964
            };
30,918✔
965
            raw_programs.emplace_back(RawProgram{
77,295✔
966
                parse_params.path,
30,918✔
967
                sec_name,
968
                gsl::narrow<uint32_t>(offset),
30,918✔
969
                name,
970
                std::move(instructions),
15,459✔
971
                std::move(program_info),
15,459✔
972
            });
973
            offset += symbol_size;
30,918✔
974
        }
30,930✔
975
    }
29,738✔
976

977
    if (const auto btf_sec = reader.sections[".BTF"]) {
1,662✔
978
        try {
538✔
979
            process_core_relocations({vector_of<std::byte>(*btf_sec)});
1,079✔
980
        } catch (const std::exception& e) {
2✔
981
            throw UnmarshalError(std::string("Unsupported or invalid CO-RE/BTF relocation data: ") + e.what());
5✔
982
        }
2✔
983
    }
984

985
    bool has_relevant_unresolved_symbols = false;
1,660✔
986
    for (const auto& err : unresolved_symbol_errors) {
1,662✔
987
        if (!parse_params.desired_section.empty() && err.section != parse_params.desired_section) {
2✔
988
            continue;
×
989
        }
990
        has_relevant_unresolved_symbols = true;
2✔
991
        std::cerr << err.message << std::endl;
2✔
992
    }
993
    if (has_relevant_unresolved_symbols) {
1,660✔
994
        throw UnmarshalError("Unresolved symbols found.");
5✔
995
    }
996

997
    if (parse_params.options.verbosity_opts.print_line_info) {
1,658✔
998
        if (const auto btf_sec = reader.sections[".BTF"]) {
×
999
            if (const auto btf_ext = reader.sections[".BTF.ext"]) {
×
1000
                try {
1001
                    update_line_info(raw_programs, btf_sec, btf_ext);
×
1002
                } catch (const std::exception& e) {
×
1003
                    throw UnmarshalError(std::string("Unsupported or invalid BTF line info: ") + e.what());
×
1004
                }
×
1005
            }
1006
        }
1007
    }
1008

1009
    for (auto& prog : raw_programs) {
32,572✔
1010
        const auto err = append_subprograms(prog);
30,914✔
1011
        if (!err.empty() && prog.section_name == parse_params.desired_section) {
30,914✔
1012
            throw UnmarshalError(err);
×
1013
        }
1014
    }
30,914✔
1015

1016
    if (!parse_params.desired_section.empty()) {
1,658✔
1017
        std::erase_if(raw_programs, [&](const auto& p) { return p.section_name != parse_params.desired_section; });
17,115✔
1018
    }
1019

1020
    if (raw_programs.empty()) {
1,658✔
1021
        throw UnmarshalError(parse_params.desired_section.empty() ? "No executable sections" : "Section not found");
×
1022
    }
1023
}
1,658✔
1024

1025
// ---------------------------------------------------------------------------
1026
// Top-level read_elf functions
1027
// ---------------------------------------------------------------------------
1028

1029
std::vector<RawProgram> read_elf(std::istream& input_stream, const std::string& path,
8✔
1030
                                 const std::string& desired_section, const std::string& desired_program,
1031
                                 const VerifierOptions& options, const ebpf_platform_t* platform) {
1032
    try {
4✔
1033
        std::vector<RawProgram> res;
8✔
1034
        parse_params_t params{path, options, platform, desired_section};
8✔
1035
        auto reader = load_elf(input_stream, path);
8✔
1036
        auto symbols = read_and_validate_symbol_section(reader, path);
8✔
1037
        auto global = extract_global_data(params, reader, symbols);
8✔
1038
        ProgramReader program_reader{params, reader, symbols, global};
8✔
1039
        program_reader.read_programs();
8✔
1040

1041
        if (desired_program.empty()) {
6✔
1042
            return std::move(program_reader.raw_programs);
6✔
1043
        }
1044
        for (RawProgram& cur : program_reader.raw_programs) {
×
1045
            if (cur.function_name == desired_program) {
×
1046
                res.emplace_back(std::move(cur));
×
1047
                return res;
×
1048
            }
1049
        }
1050
        return std::move(program_reader.raw_programs);
×
1051
    } catch (const UnmarshalError&) {
17✔
1052
        throw;
2✔
1053
    } catch (const std::exception& e) {
2✔
1054
        throw UnmarshalError(std::string("Unsupported or invalid ELF/BTF data: ") + e.what());
×
1055
    }
×
1056
}
1057

1058
std::vector<RawProgram> read_elf(const std::string& path, const std::string& desired_section,
×
1059
                                 const std::string& desired_program, const VerifierOptions& options,
1060
                                 const ebpf_platform_t* platform) {
1061
    if (std::ifstream stream{path, std::ios::in | std::ios::binary}) {
×
1062
        return read_elf(stream, path, desired_section, desired_program, options, platform);
×
1063
    }
×
1064
    struct stat st; // NOLINT(*-pro-type-member-init)
1065
    if (stat(path.c_str(), &st)) {
×
1066
        throw UnmarshalError(std::string(strerror(errno)) + " opening " + path);
×
1067
    }
1068
    throw UnmarshalError("Can't process ELF file " + path);
×
1069
}
1070

1071
} // namespace prevail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc