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

Alan-Jowett / ebpf-verifier / 20938922363

10 Jan 2026 05:49PM UTC coverage: 86.61% (+0.005%) from 86.605%
20938922363

push

github

elazarg
Bump external/bpf_conformance from `35b1eb1` to `3203c1f`

Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `35b1eb1` to `3203c1f`.
- [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases)
- [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/35b1eb1bc...<a class=hub.com/Alan-Jowett/ebpf-verifier/commit/3203c1fa9863bd28b6d57c03433b834aa0556ad7">3203c1fa9)

---
updated-dependencies:
- dependency-name: external/bpf_conformance
  dependency-version: 3203c1fa9863bd28b6d57c03433b834aa0556ad7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

9023 of 10418 relevant lines covered (86.61%)

3906755.72 hits per line

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

70.31
/src/elf_loader.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <cerrno>
5
#include <cstddef>
6
#include <cstring>
7
#include <functional>
8
#include <iostream>
9
#include <limits>
10
#include <map>
11
#include <optional>
12
#include <set>
13
#include <sstream>
14
#include <string>
15
#include <sys/stat.h>
16
#include <variant>
17
#include <vector>
18

19
#include <elfio/elfio.hpp>
20
#include <libbtf/btf_c_type.h>
21
#include <libbtf/btf_json.h>
22
#include <libbtf/btf_map.h>
23
#include <libbtf/btf_parse.h>
24

25
#include "crab_utils/num_safety.hpp"
26
#include "elf_loader.hpp"
27
#include "platform.hpp"
28

29
/// @brief ELF file parser for BPF programs with support for legacy and BTF-based formats.
30
///
31
/// This file implements a complete BPF ELF loader that handles:
32
/// - Legacy map definitions (struct bpf_elf_map in "maps" sections)
33
/// - BTF-based map definitions (parsed from .BTF section metadata)
34
/// - Global variables in .data/.rodata/.bss sections (as implicit array maps)
35
/// - CO-RE (Compile Once - Run Everywhere) relocations
36
/// - Subprogram linking and function call relocations
37
/// - Mixed-mode files (BTF metadata + legacy map sections)
38
///
39
/// The loader performs ELF parsing, symbol resolution, relocation processing,
40
/// and produces fully-linked RawProgram objects ready for verification.
41

42
namespace prevail {
43

44
namespace {
45

46
/// @brief Validate and return a LDDW instruction pair for relocation.
47
///
48
/// LDDW (Load Double Word) is a two-slot instruction used for 64-bit immediate loads.
49
/// Encoding: first slot has opcode 0x18, second slot has opcode 0x00.
50
///
51
/// @param instructions Instruction vector
52
/// @param location Index of the first instruction
53
/// @param context Description for error messages (e.g., "global variable 'foo'")
54
/// @return Pair of references to the low and high instruction slots
55
/// @throws UnmarshalError if validation fails
56
std::pair<std::reference_wrapper<EbpfInst>, std::reference_wrapper<EbpfInst>>
57
validate_and_get_lddw_pair(std::vector<EbpfInst>& instructions, size_t location, const std::string& context) {
9,920✔
58
    constexpr uint8_t BPF_LDDW = 0x18;
9,920✔
59
    constexpr uint8_t BPF_LDDW_HI = 0x00;
9,920✔
60

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

65
    auto& lo_inst = instructions[location];
9,920✔
66
    auto& hi_inst = instructions[location + 1];
9,920✔
67

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

77
    return {std::ref(lo_inst), std::ref(hi_inst)};
9,920✔
78
}
79

80
template <typename T>
81
    requires std::is_trivially_copyable_v<T>
82
std::vector<T> vector_of(const char* data, ELFIO::Elf_Xword size) {
8,422✔
83
    if (!data || size % sizeof(T) != 0 || size > std::numeric_limits<uint32_t>::max()) {
8,422✔
84
        throw UnmarshalError("Invalid argument to vector_of");
×
85
    }
86
    const size_t n = size / sizeof(T);
8,422✔
87
    std::vector<T> v(n);
8,422✔
88
    std::memcpy(v.data(), data, n * sizeof(T));
8,422✔
89
    return v;
8,422✔
90
}
91

92
template <typename T>
93
    requires std::is_trivially_copyable_v<T>
94
std::vector<T> vector_of(const ELFIO::section& sec) {
422✔
95
    return vector_of<T>(sec.get_data(), sec.get_size());
422✔
96
}
97

98
bool is_map_section(const std::string& name) {
40,754✔
99
    const std::string maps_prefix = "maps/";
40,754✔
100
    return name == "maps" || (name.length() > 5 && name.compare(0, maps_prefix.length(), maps_prefix) == 0);
81,508✔
101
}
40,754✔
102

103
bool is_global_section(const std::string& name) {
25,248✔
104
    return name == ".data" || name == ".rodata" || name == ".bss" || name.starts_with(".data.") ||
62,970✔
105
           name.starts_with(".rodata.") || name.starts_with(".bss.");
75,444✔
106
}
107

108
struct symbol_details_t {
596,350✔
109
    std::string name;
110
    ELFIO::Elf64_Addr value{};
111
    ELFIO::Elf_Xword size{};
112
    unsigned char bind{};
113
    unsigned char type{};
114
    ELFIO::Elf_Half section_index{};
115
    unsigned char other{};
116
};
117

118
symbol_details_t get_symbol_details(const ELFIO::const_symbol_section_accessor& symbols, const ELFIO::Elf_Xword index) {
30,917,558✔
119
    symbol_details_t details;
30,917,558✔
120
    symbols.get_symbol(index, details.name, details.value, details.size, details.bind, details.type,
30,917,558✔
121
                       details.section_index, details.other);
30,917,558✔
122
    return details;
30,917,558✔
123
}
×
124

125
struct parse_params_t {
3✔
126
    const std::string& path;
127
    const ebpf_verifier_options_t& options;
128
    const ebpf_platform_t* platform;
129
    const std::string desired_section;
130
};
131

132
std::tuple<std::string, ELFIO::Elf_Xword>
133
get_program_name_and_size(const ELFIO::section& sec, const ELFIO::Elf_Xword start,
8,000✔
134
                          const ELFIO::const_symbol_section_accessor& symbols) {
135
    const ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num();
8,000✔
136
    const ELFIO::Elf_Half section_index = sec.get_index();
8,000✔
137
    std::string program_name = sec.get_name();
8,000✔
138
    ELFIO::Elf_Xword size = sec.get_size() - start;
8,000✔
139
    for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) {
29,727,046✔
140
        auto symbol_details = get_symbol_details(symbols, index);
29,719,046✔
141
        if (symbol_details.section_index == section_index && !symbol_details.name.empty()) {
29,719,046✔
142
            if (symbol_details.type != ELFIO::STT_FUNC) {
274,546✔
143
                continue;
255,140✔
144
            }
145
            const auto relocation_offset = symbol_details.value;
19,406✔
146
            if (relocation_offset == start) {
19,406✔
147
                program_name = symbol_details.name;
14,735,807✔
148
            } else if (relocation_offset > start && relocation_offset < start + size) {
15,552✔
149
                size = relocation_offset - start;
942✔
150
            }
151
        }
152
    }
29,719,046✔
153
    return {program_name, size};
16,000✔
154
}
8,000✔
155

156
std::string bad_reloc_value(const size_t reloc_value) {
×
157
    return "Bad reloc value (" + std::to_string(reloc_value) + "). " + "Make sure to compile with -O2.";
×
158
}
159

160
struct FunctionRelocation {
93,249✔
161
    size_t prog_index{};
162
    ELFIO::Elf_Xword source_offset{};
163
    ELFIO::Elf_Xword relocation_entry_index{};
164
    std::string target_function_name;
165
};
166

167
RawProgram* find_subprogram(std::vector<RawProgram>& programs, const ELFIO::section& subprogram_section,
1,958✔
168
                            const std::string& symbol_name) {
169
    for (auto& subprog : programs) {
186,646✔
170
        if (subprog.section_name == subprogram_section.get_name() && subprog.function_name == symbol_name) {
277,050✔
171
            return &subprog;
12✔
172
        }
173
    }
174
    return nullptr;
973✔
175
}
176

177
using MapOffsets = std::map<std::string, size_t>;
178

179
/// @brief EBPF-Global data extracted from an ELF file during parsing.
180
///
181
/// This structure aggregates all map descriptors and metadata about sections
182
/// containing maps and global variables. It uses a variant to support both
183
/// legacy and BTF-based map resolution strategies.
184
struct ElfGlobalData {
406✔
185
    /// Section indices containing map definitions (e.g., "maps", "maps/xyz")
186
    std::set<ELFIO::Elf_Half> map_section_indices;
187

188
    /// All map descriptors extracted from the file
189
    std::vector<EbpfMapDescriptor> map_descriptors;
190

191
    /// Strategy for resolving map symbols to descriptors:
192
    /// - size_t: Legacy mode - fixed record size, use offset/size arithmetic
193
    /// - MapOffsets: BTF mode - name-based lookup from map name to descriptor index
194
    std::variant<size_t, MapOffsets> map_record_size_or_map_offsets;
195

196
    /// Section indices containing global variables (.data, .rodata, .bss)
197
    std::set<ELFIO::Elf_Half> variable_section_indices;
198
};
199

200
/// @brief Collect all global variable sections from the ELF file.
201
///
202
/// @param reader The ELF file reader
203
/// @return Vector of pointers to global variable sections (can be empty)
204
std::vector<ELFIO::section*> global_sections(const ELFIO::elfio& reader) {
812✔
205
    std::vector<ELFIO::section*> result;
812✔
206
    for (auto& section : reader.sections) {
26,060✔
207
        if (!section || !is_global_section(section->get_name())) {
37,872✔
208
            continue;
24,900✔
209
        }
210

211
        const auto type = section->get_type();
348✔
212

213
        // Global variables in eBPF are stored in special sections:
214
        // - .data, .data.*     -> initialized read-write globals (SHT_PROGBITS)
215
        // - .rodata, .rodata.* -> constants (SHT_PROGBITS)
216
        // - .bss, .bss.*       -> uninitialized globals (SHT_NOBITS, zero-initialized at load)
217
        // .bss sections have type SHT_NOBITS and contain no file data, but still
218
        // have a non-zero size representing the memory allocation needed at runtime.
219
        if (type == ELFIO::SHT_NOBITS || (type == ELFIO::SHT_PROGBITS && section->get_size() != 0)) {
348✔
220
            result.push_back(section.get());
348✔
221
        }
222
    }
223
    return result;
812✔
224
}
×
225

226
constexpr int DEFAULT_MAP_FD = -1;
227

228
/// @brief Add implicit map descriptors for global variable sections.
229
///
230
/// Creates single-entry array maps for .data/.rodata/.bss sections.
231
/// Each section becomes a map where the entire section content is the map value.
232
///
233
/// @param reader ELF file reader
234
/// @param global Global data to populate with map descriptors
235
/// @param map_offsets Map name to descriptor index mapping
236
void add_global_variable_maps(const ELFIO::elfio& reader, ElfGlobalData& global, MapOffsets& map_offsets) {
176✔
237
    for (const auto section : global_sections(reader)) {
244✔
238
        map_offsets[section->get_name()] = global.map_descriptors.size();
68✔
239

240
        global.map_descriptors.push_back(EbpfMapDescriptor{
34✔
241
            .original_fd = gsl::narrow<int>(global.map_descriptors.size() + 1),
68✔
242
            .type = 0,
243
            .key_size = sizeof(uint32_t),
244
            .value_size = gsl::narrow<uint32_t>(section->get_size()),
68✔
245
            .max_entries = 1,
246
            .inner_map_fd = DEFAULT_MAP_FD,
247
        });
248

249
        global.variable_section_indices.insert(section->get_index());
68✔
250
    }
176✔
251
}
176✔
252

253
ELFIO::const_symbol_section_accessor read_and_validate_symbol_section(const ELFIO::elfio& reader,
814✔
254
                                                                      const std::string& path) {
255
    const ELFIO::section* symbol_section = reader.sections[".symtab"];
814✔
256
    if (!symbol_section) {
814✔
257
        throw UnmarshalError("No symbol section found in ELF file " + path);
×
258
    }
259
    const auto expected_entry_size =
407✔
260
        reader.get_class() == ELFIO::ELFCLASS32 ? sizeof(ELFIO::Elf32_Sym) : sizeof(ELFIO::Elf64_Sym);
814✔
261
    if (symbol_section->get_entry_size() != expected_entry_size) {
814✔
262
        throw UnmarshalError("Invalid symbol section in ELF file " + path);
3✔
263
    }
264
    return ELFIO::const_symbol_section_accessor{reader, symbol_section};
812✔
265
}
266

267
ELFIO::elfio load_elf(std::istream& input_stream, const std::string& path) {
814✔
268
    ELFIO::elfio reader;
814✔
269
    if (!reader.load(input_stream)) {
814✔
270
        throw UnmarshalError("Can't process ELF file " + path);
×
271
    }
272
    return reader;
814✔
273
}
×
274

275
void dump_btf_types(const libbtf::btf_type_data& btf_data, const std::string& path) {
×
276
    std::stringstream output;
×
277
    std::cout << "Dumping BTF data for " << path << std::endl;
×
278
    btf_data.to_json(output);
×
279
    std::cout << libbtf::pretty_print_json(output.str()) << std::endl;
×
280
}
×
281

282
void update_line_info(std::vector<RawProgram>& raw_programs, const ELFIO::section* btf_section,
×
283
                      const ELFIO::section* btf_ext) {
284
    auto visitor = [&raw_programs](const std::string& section, const uint32_t instruction_offset,
×
285
                                   const std::string& file_name, const std::string& source, const uint32_t line_number,
286
                                   const uint32_t column_number) {
287
        for (auto& program : raw_programs) {
×
288
            if (program.section_name == section && instruction_offset >= program.insn_off &&
×
289
                instruction_offset < program.insn_off + program.prog.size() * sizeof(EbpfInst)) {
×
290
                const size_t inst_index = (instruction_offset - program.insn_off) / sizeof(EbpfInst);
×
291
                if (inst_index >= program.prog.size()) {
×
292
                    throw UnmarshalError("Invalid BTF data");
×
293
                }
294
                program.info.line_info.insert_or_assign(inst_index,
×
295
                                                        btf_line_info_t{file_name, source, line_number, column_number});
×
296
            }
297
        }
298
    };
×
299
    libbtf::btf_parse_line_information(vector_of<std::byte>(*btf_section), vector_of<std::byte>(*btf_ext), visitor);
×
300
    for (auto& program : raw_programs) {
×
301
        std::optional<btf_line_info_t> last;
×
302
        for (size_t i = 0; i < program.prog.size(); ++i) {
×
303
            auto it = program.info.line_info.find(i);
×
304
            if (it != program.info.line_info.end()) {
×
305
                if (it->second.line_number != 0) {
×
306
                    last = it->second;
×
307
                }
308
            } else if (last) {
×
309
                program.info.line_info[i] = *last;
×
310
            }
311
        }
312
    }
×
313
}
×
314

315
std::map<int, int> map_typeid_to_fd(const std::vector<EbpfMapDescriptor>& map_descriptors) {
176✔
316
    std::map<int, int> type_id_to_fd_map;
176✔
317
    int pseudo_fd = 1;
176✔
318
    for (const auto& map_descriptor : map_descriptors) {
2,546✔
319
        if (!type_id_to_fd_map.contains(map_descriptor.original_fd)) {
2,370✔
320
            type_id_to_fd_map[map_descriptor.original_fd] = pseudo_fd++;
2,370✔
321
        }
322
    }
323
    return type_id_to_fd_map;
176✔
324
}
×
325

326
ElfGlobalData parse_btf_section(const parse_params_t& parse_params, const ELFIO::elfio& reader) {
176✔
327
    const auto btf_section = reader.sections[".BTF"];
176✔
328
    if (!btf_section) {
176✔
329
        return {};
×
330
    }
331

332
    const libbtf::btf_type_data btf_data(vector_of<std::byte>(*btf_section));
176✔
333
    if (parse_params.options.verbosity_opts.dump_btf_types_json) {
176✔
334
        dump_btf_types(btf_data, parse_params.path);
×
335
    }
336

337
    ElfGlobalData global;
176✔
338
    MapOffsets map_offsets;
176✔
339

340
    // Parse BTF-defined maps from the .maps DATASEC
341
    for (const auto& map : parse_btf_map_section(btf_data)) {
2,546✔
342
        map_offsets.emplace(map.name, global.map_descriptors.size());
2,370✔
343
        global.map_descriptors.push_back(EbpfMapDescriptor{
2,370✔
344
            .original_fd = gsl::narrow<int>(map.type_id), // Temporary: stores BTF type ID
2,370✔
345
            .type = map.map_type,
2,370✔
346
            .key_size = map.key_size,
2,370✔
347
            .value_size = map.value_size,
2,370✔
348
            .max_entries = map.max_entries,
2,370✔
349
            .inner_map_fd = map.inner_map_type_id == 0 ? DEFAULT_MAP_FD : gsl::narrow<int>(map.inner_map_type_id),
2,370✔
350
        });
351
    }
176✔
352

353
    // Remap BTF type IDs to pseudo file descriptors
354
    // Only remap values that are actually set (not the sentinel)
355
    const auto type_id_to_fd_map = map_typeid_to_fd(global.map_descriptors);
176✔
356
    for (auto& desc : global.map_descriptors) {
2,546✔
357
        // Remap the outer map's type ID to a pseudo-FD
358
        if (auto it = type_id_to_fd_map.find(desc.original_fd); it != type_id_to_fd_map.end()) {
2,370✔
359
            desc.original_fd = it->second;
2,370✔
360
        } else {
361
            throw UnmarshalError("Unknown map type ID in BTF: " + std::to_string(desc.original_fd));
×
362
        }
363

364
        // Only remap inner_map_fd if it's not the sentinel value
365
        if (desc.inner_map_fd != DEFAULT_MAP_FD) {
2,370✔
366
            auto inner_it = type_id_to_fd_map.find(desc.inner_map_fd);
240✔
367
            if (inner_it == type_id_to_fd_map.end()) {
240✔
368
                throw UnmarshalError("Unknown inner map type ID in BTF: " + std::to_string(desc.inner_map_fd));
×
369
            }
370
            desc.inner_map_fd = inner_it->second;
240✔
371
        }
372
    }
373

374
    // Remember the .maps section index if present (used for relocation classification)
375
    if (const auto maps_section = reader.sections[".maps"]) {
176✔
376
        global.map_section_indices.insert(maps_section->get_index());
130✔
377
    }
378

379
    // Create implicit maps for all global variable sections
380
    add_global_variable_maps(reader, global, map_offsets);
176✔
381

382
    global.map_record_size_or_map_offsets = std::move(map_offsets);
176✔
383
    return global;
176✔
384
}
176✔
385

386
/// @brief Create implicit map descriptors for global variable sections.
387
///
388
/// In eBPF, global variables are implemented as single-entry array maps:
389
/// - .data section -> read-write array map (initialized globals)
390
/// - .rodata section -> read-only array map (constants)
391
/// - .bss section -> zero-initialized array map (uninitialized globals)
392
///
393
/// Each section becomes a map descriptor with:
394
/// - key_size = 4 (uint32_t index, always 0)
395
/// - value_size = section size (entire section is the map value)
396
/// - max_entries = 1 (single entry containing all variables)
397
///
398
/// Access pattern: `r0 = *(type *)(map_value_ptr + offset_within_section)`
399
///
400
/// @param reader ELF file reader
401
/// @return Global data with map descriptors for each non-empty variable section
402
ElfGlobalData create_global_variable_maps(const ELFIO::elfio& reader) {
72✔
403
    ElfGlobalData global;
72✔
404
    MapOffsets offsets;
72✔
405

406
    // For legacy (non-BTF) files without map sections, create implicit map descriptors
407
    // for global variable sections only
408
    for (const auto section : global_sections(reader)) {
104✔
409
        offsets[section->get_name()] = global.map_descriptors.size();
32✔
410

411
        global.map_descriptors.push_back(EbpfMapDescriptor{
16✔
412
            .original_fd = gsl::narrow<int>(global.map_descriptors.size() + 1),
32✔
413
            .type = 0,
414
            .key_size = sizeof(uint32_t),
415
            .value_size = gsl::narrow<uint32_t>(section->get_size()),
32✔
416
            .max_entries = 1,
417
            .inner_map_fd = DEFAULT_MAP_FD,
418
        });
419

420
        global.variable_section_indices.insert(section->get_index());
32✔
421
    }
72✔
422

423
    global.map_record_size_or_map_offsets = std::move(offsets);
72✔
424
    return global;
108✔
425
}
72✔
426

427
/// @brief Parse legacy map sections with per-section validation.
428
///
429
/// Legacy BPF ELF files define maps using struct bpf_elf_map in "maps" or "maps/*"
430
/// sections. This function:
431
/// 1. Identifies all legacy map sections
432
/// 2. Calculates the per-section record size (section_size / symbol_count)
433
/// 3. Validates symbol offsets are aligned and within bounds
434
/// 4. Builds a name-to-descriptor mapping for relocation resolution
435
///
436
/// Note: Different map sections may have different record sizes, so validation
437
/// must be done per-section, not globally.
438
///
439
/// @param parse_params Parsing parameters including platform callbacks
440
/// @param reader The ELF file reader
441
/// @param symbols Symbol table accessor
442
/// @return Global data structure with map descriptors and metadata
443
ElfGlobalData parse_map_sections(const parse_params_t& parse_params, const ELFIO::elfio& reader,
564✔
444
                                 const ELFIO::const_symbol_section_accessor& symbols) {
445
    ElfGlobalData global;
564✔
446
    std::map<ELFIO::Elf_Half, size_t> section_record_sizes; // Per-section record size
564✔
447
    std::map<ELFIO::Elf_Half, size_t> section_base_index;   // Starting descriptor index per section
564✔
448

449
    // Parse each legacy map section
450
    for (ELFIO::Elf_Half i = 0; i < reader.sections.size(); ++i) {
20,532✔
451
        const auto s = reader.sections[i];
19,968✔
452
        if (!s || !is_map_section(s->get_name())) {
29,952✔
453
            continue;
19,402✔
454
        }
455

456
        // Count map symbols in this section
457
        int map_count = 0;
283✔
458
        for (ELFIO::Elf_Xword index = 0; index < symbols.get_symbols_num(); ++index) {
484,974✔
459
            const auto symbol_details = get_symbol_details(symbols, index);
484,408✔
460
            if (symbol_details.section_index == i && !symbol_details.name.empty()) {
484,408✔
461
                map_count++;
5,800✔
462
            }
463
        }
484,408✔
464

465
        // Track this as a map section even if empty
466
        global.map_section_indices.insert(s->get_index());
566✔
467

468
        if (map_count <= 0) {
566✔
469
            continue;
×
470
        }
471

472
        const size_t base_index = global.map_descriptors.size();
566✔
473
        const size_t map_record_size = s->get_size() / map_count;
566✔
474

475
        // Validate section structure
476
        if (s->get_data() == nullptr || map_record_size == 0 || s->get_size() % map_record_size != 0) {
566✔
477
            throw UnmarshalError("Malformed legacy maps section: " + s->get_name());
×
478
        }
479

480
        section_record_sizes[i] = map_record_size;
566✔
481
        section_base_index[i] = base_index;
566✔
482

483
        // Platform-specific parsing of map definitions
484
        parse_params.platform->parse_maps_section(global.map_descriptors, s->get_data(), map_record_size, map_count,
566✔
485
                                                  parse_params.platform, parse_params.options);
566✔
486
    }
487

488
    // Resolve inner map references (platform-specific logic)
489
    parse_params.platform->resolve_inner_map_references(global.map_descriptors);
564✔
490

491
    // Build name-to-index mapping with per-section validation
492
    MapOffsets map_offsets;
564✔
493
    for (ELFIO::Elf_Xword index = 0; index < symbols.get_symbols_num(); ++index) {
484,960✔
494
        const auto sym_details = get_symbol_details(symbols, index);
484,396✔
495

496
        // Skip symbols not in map sections or without names
497
        if (!global.map_section_indices.contains(sym_details.section_index) || sym_details.name.empty()) {
484,396✔
498
            continue;
478,596✔
499
        }
500

501
        // Look up the per-section metadata
502
        const auto record_size_it = section_record_sizes.find(sym_details.section_index);
5,800✔
503
        const auto base_index_it = section_base_index.find(sym_details.section_index);
5,800✔
504
        if (record_size_it == section_record_sizes.end() || base_index_it == section_base_index.end()) {
5,800✔
505
            continue; // Section was not parsed (empty)
×
506
        }
507

508
        const auto* section = reader.sections[sym_details.section_index];
5,800✔
509
        const size_t record_size = record_size_it->second;
5,800✔
510
        if (!section) {
5,800✔
511
            continue;
×
512
        }
513

514
        // Validate alignment and bounds before calculating index.
515
        // A malformed ELF could have symbol offsets that don't align to record boundaries
516
        // or that exceed the section size, leading to incorrect descriptor lookups
517
        if (sym_details.value % record_size != 0 || sym_details.value >= section->get_size()) {
5,800✔
518
            throw UnmarshalError("Legacy map symbol '" + sym_details.name + "' has invalid offset: not aligned to " +
×
519
                                 std::to_string(record_size) + "-byte boundary or out of section bounds");
×
520
        }
521

522
        const size_t local_index = sym_details.value / record_size;
5,800✔
523
        const size_t descriptor_index = base_index_it->second + local_index;
5,800✔
524

525
        if (descriptor_index >= global.map_descriptors.size()) {
5,800✔
526
            throw UnmarshalError("Legacy map symbol index out of range for: " + sym_details.name);
×
527
        }
528

529
        map_offsets[sym_details.name] = descriptor_index;
5,800✔
530
    }
484,396✔
531

532
    // Add implicit maps for global variable sections
533
    for (const auto section : global_sections(reader)) {
812✔
534
        map_offsets[section->get_name()] = global.map_descriptors.size();
248✔
535
        global.map_descriptors.push_back(EbpfMapDescriptor{
124✔
536
            .original_fd = gsl::narrow<int>(global.map_descriptors.size() + 1),
248✔
537
            .type = 0,
538
            .key_size = sizeof(uint32_t),
539
            .value_size = gsl::narrow<uint32_t>(section->get_size()),
248✔
540
            .max_entries = 1,
541
            .inner_map_fd = DEFAULT_MAP_FD,
542
        });
543
        global.variable_section_indices.insert(section->get_index());
248✔
544
    }
564✔
545

546
    global.map_record_size_or_map_offsets = std::move(map_offsets);
564✔
547
    return global;
846✔
548
}
564✔
549

550
/// @brief Extract maps and global variable metadata from an ELF file.
551
///
552
/// This function determines the appropriate parsing strategy based on the file's format:
553
/// 1. **Legacy maps** (priority): If a "maps" section exists, use struct bpf_elf_map parsing
554
///    - The .BTF section (if present) contains only type information, not map definitions
555
/// 2. **BTF-only**: If no legacy maps but .BTF exists, parse map definitions from BTF
556
///    - Modern format where maps are defined as BTF VAR types in a DATASEC
557
/// 3. **No maps**: If neither exists, create implicit maps for global variable sections only
558
///
559
/// @param params Parsing parameters including path, options, and platform
560
/// @param reader The loaded ELF file reader
561
/// @param symbols Symbol table accessor for the ELF file
562
/// @return Global data structure containing all extracted metadata
563
ElfGlobalData extract_global_data(const parse_params_t& params, const ELFIO::elfio& reader,
812✔
564
                                  const ELFIO::const_symbol_section_accessor& symbols) {
565
    const bool has_legacy_maps =
406✔
566
        std::ranges::any_of(reader.sections, [](const auto& s) { return is_map_section(s->get_name()); });
21,598✔
567
    // If we have legacy maps section, always use legacy parser regardless of BTF.
568
    // The BTF in these files is just for type info, not map definitions
569
    if (has_legacy_maps) {
812✔
570
        return parse_map_sections(params, reader, symbols);
564✔
571
    }
572

573
    // Only use BTF for maps if there's no legacy maps section
574
    if (reader.sections[".BTF"]) {
248✔
575
        return parse_btf_section(params, reader);
176✔
576
    }
577

578
    // No maps or BTF, but might still have global variables
579
    return create_global_variable_maps(reader);
72✔
580
}
581

582
enum bpf_core_relo_kind {
583
    BPF_CORE_FIELD_BYTE_OFFSET = 0,
584
    BPF_CORE_FIELD_BYTE_SIZE = 1,
585
    BPF_CORE_FIELD_EXISTS = 2,
586
    BPF_CORE_FIELD_SIGNED = 3,
587
    BPF_CORE_FIELD_LSHIFT_U64 = 4,
588
    BPF_CORE_FIELD_RSHIFT_U64 = 5,
589
    BPF_CORE_TYPE_ID_LOCAL = 6,
590
    BPF_CORE_TYPE_ID_TARGET = 7,
591
    BPF_CORE_TYPE_EXISTS = 8,
592
    BPF_CORE_TYPE_SIZE = 9,
593
    BPF_CORE_ENUMVAL_EXISTS = 10,
594
    BPF_CORE_ENUMVAL_VALUE = 11,
595
    BPF_CORE_TYPE_MATCHES = 12,
596
};
597

598
struct bpf_core_relo {
599
    uint32_t insn_off;
600
    uint32_t type_id;
601
    uint32_t access_str_off;
602
    bpf_core_relo_kind kind;
603
};
604

605
std::vector<uint32_t> parse_core_access_string(const std::string& s) {
×
606
    std::vector<uint32_t> indices;
×
607
    std::stringstream ss(s);
×
608
    std::string item;
×
609
    while (std::getline(ss, item, ':')) {
×
610
        if (!item.empty()) {
×
611
            try {
612
                indices.push_back(std::stoul(item));
×
613
            } catch (const std::exception&) {
×
614
                throw UnmarshalError("Invalid CO-RE access string: " + s);
×
615
            }
×
616
        }
617
    }
618
    return indices;
×
619
}
×
620

621
class ProgramReader {
622
    const parse_params_t& parse_params;
623
    const ELFIO::elfio& reader;
624
    const ELFIO::const_symbol_section_accessor& symbols;
625
    const ElfGlobalData& global;
626
    std::vector<FunctionRelocation> function_relocations;
627
    std::vector<std::string> unresolved_symbol_errors;
628

629
    // loop detection for recursive subprogram resolution
630
    std::map<const RawProgram*, bool> resolved_subprograms;
631
    std::set<const RawProgram*> currently_visiting;
632

633
    /// @brief Apply a single CO-RE relocation to an instruction.
634
    ///
635
    /// CO-RE (Compile Once - Run Everywhere) relocations allow BPF programs to access
636
    /// kernel data structures in a portable way. The loader resolves these relocations
637
    /// by traversing the BTF type graph to calculate field offsets, type sizes, etc.
638
    ///
639
    /// Supported relocation kinds:
640
    /// - FIELD_BYTE_OFFSET: Calculate offset of a struct field (with nested access)
641
    /// - TYPE_ID_LOCAL/TARGET: Replace with type ID
642
    /// - TYPE_SIZE: Replace with sizeof(type)
643
    ///
644
    /// @param prog RawProgram containing the instruction to relocate
645
    /// @param relo CO-RE relocation descriptor (from .BTF.ext section)
646
    /// @param btf_data BTF type information for offset calculations
647
    /// @throws UnmarshalError if relocation is invalid or unsupported
648
    void apply_core_relocation(RawProgram& prog, const bpf_core_relo& relo,
649
                               const libbtf::btf_type_data& btf_data) const;
650
    void process_core_relocations(const libbtf::btf_type_data& btf_data);
651

652
    int32_t compute_lddw_reloc_offset_imm(ELFIO::Elf_Sxword addend, ELFIO::Elf_Word index, std::reference_wrapper<EbpfInst> lo_inst) const;
653

654
  public:
655
    std::vector<RawProgram> raw_programs;
656

657
    ProgramReader(const parse_params_t& p, const ELFIO::elfio& r, const ELFIO::const_symbol_section_accessor& s,
812✔
658
                  const ElfGlobalData& g)
659
        : parse_params{p}, reader{r}, symbols{s}, global{g} {}
812✔
660

661
    std::string append_subprograms(RawProgram& prog);
662
    [[nodiscard]]
663
    int relocate_map(const std::string& name, ELFIO::Elf_Word index) const;
664
    [[nodiscard]]
665
    int relocate_global_variable(const std::string& name) const;
666

667
    /// @brief Attempt to relocate a symbol reference in an instruction.
668
    ///
669
    /// Handles multiple relocation types:
670
    /// - **Function calls**: Queue for later resolution when all programs are loaded
671
    /// - **Map references**: Resolve to map file descriptor
672
    /// - **Global variables**: Resolve to implicit map FD + offset within section
673
    /// - **Config symbols**: Zero out (compile-time configuration)
674
    ///
675
    /// Global variable relocations MUST be applied to LDDW instruction pairs
676
    /// (opcode 0x18 followed by opcode 0x00). This function validates the instruction
677
    /// structure before patching to prevent corruption of non-LDDW instructions.
678
    ///
679
    /// @param symbol_name Name of the symbol (maybe empty for unnamed relocations)
680
    /// @param symbol_section_index Section containing the symbol
681
    /// @param instructions Instruction vector to modify
682
    /// @param location Instruction index to relocate
683
    /// @param index Symbol table index for additional lookup
684
    /// @param addend Additional offset to apply
685
    /// @return true if relocation succeeded or should be skipped, false if unresolved
686
    bool try_reloc(const std::string& symbol_name, ELFIO::Elf_Half symbol_section_index,
687
                   std::vector<EbpfInst>& instructions, size_t location, ELFIO::Elf_Word index,
688
                   ELFIO::Elf_Sxword addend);
689
    void process_relocations(std::vector<EbpfInst>& instructions, const ELFIO::const_relocation_section_accessor& reloc,
690
                             const std::string& section_name, ELFIO::Elf_Xword program_offset, size_t program_size);
691
    [[nodiscard]]
692
    const ELFIO::section* get_relocation_section(const std::string& name) const;
693
    void read_programs();
694
};
695

696
void ProgramReader::apply_core_relocation(RawProgram& prog, const bpf_core_relo& relo,
×
697
                                          const libbtf::btf_type_data& btf_data) const {
698
    const size_t inst_idx = (relo.insn_off - prog.insn_off) / sizeof(EbpfInst);
×
699
    if (inst_idx >= prog.prog.size()) {
×
700
        throw UnmarshalError("CO-RE relocation offset out of bounds");
×
701
    }
702
    EbpfInst& inst = prog.prog[inst_idx];
×
703

704
    switch (relo.kind) {
×
705
    case BPF_CORE_FIELD_BYTE_OFFSET: {
×
706
        const auto* btf_section = reader.sections[".BTF"];
×
707
        const auto* hdr = reinterpret_cast<const btf_header_t*>(btf_section->get_data());
×
708
        const char* base = btf_section->get_data() + hdr->hdr_len;
×
709
        const char* str_base = base + hdr->str_off;
×
710
        const std::string access_string(str_base + relo.access_str_off);
×
711

712
        const auto indices = parse_core_access_string(access_string);
×
713
        uint32_t current_type_id = relo.type_id;
×
714
        uint32_t final_offset_bits = 0;
×
715

716
        for (const uint32_t index : indices) {
×
717
            int depth = 0;
×
718
            while (true) {
719
                if (++depth > 255) {
×
720
                    throw UnmarshalError("CO-RE type resolution exceeded depth limit (possible corrupt BTF)");
×
721
                }
722
                const auto kind_index = btf_data.get_kind_index(current_type_id);
×
723
                if (kind_index == libbtf::BTF_KIND_TYPEDEF) {
×
724
                    current_type_id = btf_data.get_kind_type<libbtf::btf_kind_typedef>(current_type_id).type;
×
725
                } else if (kind_index == libbtf::BTF_KIND_CONST) {
×
726
                    current_type_id = btf_data.get_kind_type<libbtf::btf_kind_const>(current_type_id).type;
×
727
                } else if (kind_index == libbtf::BTF_KIND_VOLATILE) {
×
728
                    current_type_id = btf_data.get_kind_type<libbtf::btf_kind_volatile>(current_type_id).type;
×
729
                } else if (kind_index == libbtf::BTF_KIND_RESTRICT) {
×
730
                    current_type_id = btf_data.get_kind_type<libbtf::btf_kind_restrict>(current_type_id).type;
×
731
                } else {
732
                    break;
733
                }
734
            }
735
            const auto kind_index = btf_data.get_kind_index(current_type_id);
×
736
            if (kind_index == libbtf::BTF_KIND_STRUCT) {
×
737
                auto s = btf_data.get_kind_type<libbtf::btf_kind_struct>(current_type_id);
×
738
                if (index < s.members.size()) {
×
739
                    final_offset_bits += s.members[index].offset_from_start_in_bits;
×
740
                    current_type_id = s.members[index].type;
×
741
                } else {
742
                    throw UnmarshalError("CO-RE: member index out of bounds");
×
743
                }
744
            } else if (kind_index == libbtf::BTF_KIND_ARRAY) {
×
745
                const auto a = btf_data.get_kind_type<libbtf::btf_kind_array>(current_type_id);
×
746
                final_offset_bits += index * btf_data.get_size(a.element_type) * 8;
×
747
                current_type_id = a.element_type;
×
748
            } else {
749
                throw UnmarshalError("CO-RE: indexing into non-aggregate type");
×
750
            }
751
        }
752
        inst.imm = gsl::narrow<int32_t>(final_offset_bits) / 8;
×
753
        break;
×
754
    }
×
755
    case BPF_CORE_TYPE_ID_LOCAL:
×
756
    case BPF_CORE_TYPE_ID_TARGET: inst.imm = gsl::narrow<int>(relo.type_id); break;
×
757
    case BPF_CORE_TYPE_SIZE: inst.imm = gsl::narrow<int>(btf_data.get_size(relo.type_id)); break;
×
758
    default: throw UnmarshalError("Unsupported CO-RE relocation kind: " + std::to_string(relo.kind));
×
759
    }
760
}
×
761

762
void ProgramReader::process_core_relocations(const libbtf::btf_type_data& btf_data) {
246✔
763
    const ELFIO::section* relo_sec = reader.sections[".rel.BTF"];
246✔
764
    if (!relo_sec) {
246✔
765
        relo_sec = reader.sections[".rela.BTF"];
40✔
766
    }
767
    if (!relo_sec) {
143✔
768
        return;
40✔
769
    }
770

771
    const ELFIO::section* btf_ext_sec = reader.sections[".BTF.ext"];
206✔
772
    if (!btf_ext_sec) {
206✔
773
        throw UnmarshalError(".BTF.ext section missing for CO-RE relocations");
×
774
    }
775

776
    const char* btf_ext_data = btf_ext_sec->get_data();
206✔
777
    const ELFIO::const_relocation_section_accessor relocs(reader, relo_sec);
206✔
778

779
    // R_BPF_64_NODYLD32 from the kernel UAPI (linux/bpf.h)
780
    // This relocation type is specifically for CO-RE field access relocations.
781
    // The value 19 is stable across kernel versions as part of the BPF ELF ABI.
782
    constexpr unsigned int R_BPF_64_NODYLD32 = 19;
206✔
783

784
    for (ELFIO::Elf_Xword i = 0; i < relocs.get_entries_num(); i++) {
5,372✔
785
        ELFIO::Elf64_Addr offset{};
5,166✔
786
        ELFIO::Elf_Word sym_idx{};
5,166✔
787
        unsigned type{};
5,166✔
788
        ELFIO::Elf_Sxword addend{};
5,166✔
789
        if (relocs.get_entry(i, offset, sym_idx, type, addend)) {
5,166✔
790
            // Only process relocations that are specifically for CO-RE.
791
            // Ignore other relocation types like function calls (308).
792
            if (type != R_BPF_64_NODYLD32) {
5,166✔
793
                continue;
5,166✔
794
            }
795

796
            const auto sym = get_symbol_details(symbols, sym_idx);
×
797
            if (sym.value + sizeof(bpf_core_relo) > btf_ext_sec->get_size()) {
×
798
                throw UnmarshalError("CO-RE relocation offset out of BTF.ext bounds");
×
799
            }
800
            const auto* relo = reinterpret_cast<const bpf_core_relo*>(btf_ext_data + sym.value);
×
801
            bool applied = false;
×
802

803
            for (auto& prog : raw_programs) {
×
804
                // Find the right program based on the instruction offset from the CO-RE struct.
805
                if (relo->insn_off >= prog.insn_off &&
×
806
                    relo->insn_off < prog.insn_off + prog.prog.size() * sizeof(EbpfInst)) {
×
807
                    apply_core_relocation(prog, *relo, btf_data);
×
808
                    applied = true;
809
                    break;
810
                }
811
            }
812

813
            if (!applied) {
×
814
                throw UnmarshalError("Failed to find program for CO-RE relocation at instruction offset " +
×
815
                                     std::to_string(relo->insn_off));
×
816
            }
817
        }
×
818
    }
819
}
820

821
/// @brief Recursively append subprograms to a main program.
822
///
823
/// BPF programs can call local functions (subprograms). The linker must:
824
/// 1. Identify all CallLocal instructions in the program
825
/// 2. Find the target subprogram by name
826
/// 3. Append the subprogram's instructions to the caller
827
/// 4. Update the CallLocal immediate with the correct PC-relative offset
828
/// 5. Recursively process any subprograms called by the subprogram
829
///
830
/// Note: Recursive calls are detected and rejected.
831
///
832
/// @param prog RawProgram to process
833
/// @return Empty string on success, error message on failure
834
std::string ProgramReader::append_subprograms(RawProgram& prog) {
8,012✔
835
    if (resolved_subprograms[&prog]) {
8,012✔
836
        return {};
12✔
837
    }
838

839
    if (currently_visiting.contains(&prog)) {
8,000✔
840
        throw UnmarshalError("Mutual recursion in subprogram calls");
×
841
    }
842
    currently_visiting.insert(&prog);
8,000✔
843

844
    std::map<std::string, ELFIO::Elf_Xword> subprogram_offsets;
8,000✔
845
    for (const auto& reloc : function_relocations) {
1,881,776✔
846
        if (reloc.prog_index >= raw_programs.size() ||
2,813,583✔
847
            raw_programs[reloc.prog_index].function_name != prog.function_name) {
1,875,722✔
848
            continue;
1,873,752✔
849
        }
850
        if (!subprogram_offsets.contains(reloc.target_function_name)) {
1,970✔
851
            subprogram_offsets[reloc.target_function_name] = prog.prog.size();
1,958✔
852
            auto sym = get_symbol_details(symbols, reloc.relocation_entry_index);
1,958✔
853
            if (sym.section_index >= reader.sections.size()) {
1,958✔
854
                throw UnmarshalError("Invalid section index");
×
855
            }
856
            const auto& sub_sec = *reader.sections[sym.section_index];
1,958✔
857
            if (const auto sub = find_subprogram(raw_programs, sub_sec, sym.name)) {
1,958✔
858
                if (sub == &prog) {
12✔
859
                    throw UnmarshalError("Recursive subprogram call");
×
860
                }
861
                const std::string err = append_subprograms(*sub);
12✔
862
                if (!err.empty()) {
12✔
863
                    return err;
×
864
                }
865
                const size_t base = subprogram_offsets[reloc.target_function_name];
12✔
866

867
                // Append subprogram to program
868
                prog.prog.insert(prog.prog.end(), sub->prog.begin(), sub->prog.end());
12✔
869
                if (parse_params.options.verbosity_opts.print_line_info) {
12✔
870
                    for (const auto& [k, info] : sub->info.line_info) {
×
871
                        prog.info.line_info[base + k] = info;
×
872
                    }
873
                }
874
            } else {
12✔
875
                return "Subprogram not found: " + sym.name;
1,946✔
876
            }
877
        }
1,958✔
878
        // BPF uses signed 32-bit immediates: offset = target - (source + 1)
879
        const auto target_offset = gsl::narrow<int64_t>(subprogram_offsets[reloc.target_function_name]);
24✔
880
        const auto source_offset = gsl::narrow<int64_t>(reloc.source_offset);
24✔
881
        prog.prog[reloc.source_offset].imm = gsl::narrow<int32_t>(target_offset - source_offset - 1);
24✔
882
    }
883
    currently_visiting.erase(&prog);
6,054✔
884
    resolved_subprograms[&prog] = true;
6,054✔
885
    return {};
6,054✔
886
}
8,000✔
887

888
int ProgramReader::relocate_map(const std::string& name, const ELFIO::Elf_Word index) const {
178,506✔
889
    size_t val{};
178,506✔
890
    if (const auto* record_size = std::get_if<size_t>(&global.map_record_size_or_map_offsets)) {
178,506✔
891
        // Legacy path: map symbol value is byte offset into maps section
892
        // Divide by struct size to get descriptor index
893
        const auto symbol_value = get_symbol_details(symbols, index).value;
×
894
        if (symbol_value % *record_size != 0) {
×
895
            throw UnmarshalError("Map symbol offset " + std::to_string(symbol_value) +
×
896
                                 " is not aligned to record size " + std::to_string(*record_size));
×
897
        }
898

899
        val = symbol_value / *record_size;
×
900
    } else {
901
        // BTF path: use map name to look up descriptor index
902
        const auto& offsets = std::get<MapOffsets>(global.map_record_size_or_map_offsets);
178,506✔
903
        const auto it = offsets.find(name);
178,506✔
904
        if (it == offsets.end()) {
178,506✔
905
            throw UnmarshalError("Map descriptor not found: " + name);
×
906
        }
907
        val = it->second;
178,506✔
908
    }
909
    if (val >= global.map_descriptors.size()) {
178,506✔
910
        throw UnmarshalError(bad_reloc_value(val));
×
911
    }
912
    return global.map_descriptors.at(val).original_fd;
178,506✔
913
}
914

915
int ProgramReader::relocate_global_variable(const std::string& name) const {
9,920✔
916
    const auto* offsets = std::get_if<MapOffsets>(&global.map_record_size_or_map_offsets);
9,920✔
917
    if (!offsets) {
9,920✔
918
        throw UnmarshalError("Invalid map offsets");
×
919
    }
920
    const auto it = offsets->find(name);
9,920✔
921
    if (it == offsets->end()) {
9,920✔
922
        throw UnmarshalError("Map descriptor not found: " + name);
×
923
    }
924
    const size_t val = it->second;
9,920✔
925
    if (val >= global.map_descriptors.size()) {
9,920✔
926
        throw UnmarshalError(bad_reloc_value(val));
×
927
    }
928
    return global.map_descriptors.at(val).original_fd;
14,880✔
929
}
930

931
/// Compute the 32-bit offset to store in the *high* LDDW imm for a global-variable relocation.
932
///
933
/// The encoding rules differ depending on the relocation kind:
934
/// - For relocations against a _section_ symbol (sym.type == STT_SECTION):
935
///   * In RELA ELFs, the relocation addend holds the section-relative offset.
936
///   * In REL ELFs, the addend is zero and the compiler encodes the offset in the
937
///     low LDDW instruction's imm field (`lo_inst_imm`).
938
///   In both cases, we interpret:
939
///       offset = (addend != 0) ? addend : lo_inst_imm
940
///
941
/// - For relocations against a _data_ symbol (e.g., `global_var4`):
942
///   The symbol value is already section-relative, so the offset is:
943
///       offset = sym.value + addend
944
///
945
/// The result is narrowed to int32_t, matching the 32-bit imm field of a BPF instruction.
946
///
947
/// This function is only used for global-variable LDDW relocations.
948
int32_t ProgramReader::compute_lddw_reloc_offset_imm(const ELFIO::Elf_Sxword addend, const ELFIO::Elf_Word index,
9,920✔
949
                                                     const std::reference_wrapper<EbpfInst> lo_inst) const {
950
    const auto& sym = get_symbol_details(symbols, index);
9,920✔
951
    if (sym.type == ELFIO::STT_SECTION) {
9,920✔
952
        return addend != 0 ? gsl::narrow<int32_t>(addend) : lo_inst.get().imm;
8✔
953
    }
954
    return gsl::narrow<int32_t>(sym.value + addend);
9,912✔
955
}
9,920✔
956

957
bool ProgramReader::try_reloc(const std::string& symbol_name, const ELFIO::Elf_Half symbol_section_index,
217,830✔
958
                              std::vector<EbpfInst>& instructions, const size_t location, const ELFIO::Elf_Word index,
959
                              const ELFIO::Elf_Sxword addend) {
960
    // Handle empty symbol names for global variable sections
961
    // These occur in legacy ELF files where relocations reference
962
    // section symbols rather than named variable symbols
963
    if (symbol_name.empty()) {
217,830✔
964
        if (global.variable_section_indices.contains(symbol_section_index)) {
10✔
965
            if (!std::holds_alternative<MapOffsets>(global.map_record_size_or_map_offsets)) {
8✔
966
                return false; // Legacy path without MapOffsets; let caller handle
967
            }
968

969
            auto [lo_inst, hi_inst] = validate_and_get_lddw_pair(instructions, location, "global variable");
12✔
970

971
            hi_inst.get().imm = compute_lddw_reloc_offset_imm(addend, index, lo_inst);
8✔
972
            lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
8✔
973

974
            const std::string section_name = reader.sections[symbol_section_index]->get_name();
12✔
975
            lo_inst.get().imm = relocate_global_variable(section_name);
8✔
976
            return true;
8✔
977
        }
8✔
978
        // Empty symbol name in non-variable section - skip it
979
        return true;
1✔
980
    }
981

982
    EbpfInst& instruction_to_relocate = instructions[location];
217,820✔
983

984
    // Handle local function calls - queue for post-processing
985
    if (instruction_to_relocate.opcode == INST_OP_CALL && instruction_to_relocate.src == INST_CALL_LOCAL) {
217,820✔
986
        function_relocations.emplace_back(FunctionRelocation{raw_programs.size(), location, index, symbol_name});
29,402✔
987
        return true;
29,402✔
988
    }
989

990
    // Only LD-class instructions can be map/global loads
991
    if ((instruction_to_relocate.opcode & INST_CLS_MASK) != INST_CLS_LD) {
188,418✔
992
        return false;
993
    }
994

995
    // Handle map relocations (BTF or legacy)
996
    if (global.map_section_indices.contains(symbol_section_index)) {
188,418✔
997
        instruction_to_relocate.src = INST_LD_MODE_MAP_FD;
178,506✔
998
        instruction_to_relocate.imm = relocate_map(symbol_name, index);
178,506✔
999
        return true;
178,506✔
1000
    }
1001

1002
    // Handle named global variables (including __config_* symbols in .rodata.config)
1003
    if (global.variable_section_indices.contains(symbol_section_index)) {
9,912✔
1004
        auto [lo_inst, hi_inst] =
14,868✔
1005
            validate_and_get_lddw_pair(instructions, location, "global variable '" + symbol_name + "'");
14,868✔
1006

1007
        hi_inst.get().imm = compute_lddw_reloc_offset_imm(addend, index, lo_inst);
9,912✔
1008
        lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
9,912✔
1009
        lo_inst.get().imm = relocate_global_variable(reader.sections[symbol_section_index]->get_name());
14,868✔
1010
        return true;
9,912✔
1011
    }
1012

1013
    // Legacy fallback: zero out __config_* symbols not in a variable section
1014
    // (for compatibility with older toolchains)
1015
    if (symbol_name.rfind("__config_", 0) == 0) {
×
1016
        instruction_to_relocate.imm = 0;
×
1017
        return true;
×
1018
    }
1019

1020
    return false;
1021
}
1022

1023
void ProgramReader::process_relocations(std::vector<EbpfInst>& instructions,
7,560✔
1024
                                        const ELFIO::const_relocation_section_accessor& reloc,
1025
                                        const std::string& section_name, const ELFIO::Elf_Xword program_offset,
1026
                                        const size_t program_size) {
1027
    for (ELFIO::Elf_Xword i = 0; i < reloc.get_entries_num(); i++) {
856,082✔
1028
        ELFIO::Elf64_Addr o{};
848,522✔
1029
        ELFIO::Elf_Word idx{};
848,522✔
1030
        unsigned type{};
848,522✔
1031
        ELFIO::Elf_Sxword addend{};
848,522✔
1032
        if (reloc.get_entry(i, o, idx, type, addend)) {
848,522✔
1033
            if (o < program_offset || o >= program_offset + program_size) {
848,522✔
1034
                continue;
630,692✔
1035
            }
1036
            o -= program_offset;
217,830✔
1037

1038
            if (o % sizeof(EbpfInst) != 0) {
217,830✔
1039
                throw UnmarshalError("Unaligned relocation offset");
×
1040
            }
1041
            const auto loc = o / sizeof(EbpfInst);
217,830✔
1042
            if (loc >= instructions.size()) {
217,830✔
1043
                throw UnmarshalError("Invalid relocation");
×
1044
            }
1045
            auto sym = get_symbol_details(symbols, idx);
217,830✔
1046

1047
            if (!try_reloc(sym.name, sym.section_index, instructions, loc, idx, addend)) {
217,830✔
1048
                unresolved_symbol_errors.push_back("Unresolved external symbol " + sym.name + " in section " +
×
1049
                                                   section_name + " at location " + std::to_string(loc));
×
1050
            }
1051
        }
217,830✔
1052
    }
1053
}
7,560✔
1054

1055
const ELFIO::section* ProgramReader::get_relocation_section(const std::string& name) const {
8,000✔
1056
    if (name == ".BTF") {
8,000✔
1057
        return nullptr;
1058
    }
1059
    const auto* relocs = reader.sections[".rel" + name];
8,000✔
1060
    if (!relocs) {
8,000✔
1061
        relocs = reader.sections[".rela" + name];
1,070✔
1062
    }
1063
    if (!relocs || !relocs->get_data()) {
8,000✔
1064
        return nullptr;
440✔
1065
    }
1066
    return relocs;
3,780✔
1067
}
1068

1069
void ProgramReader::read_programs() {
812✔
1070
    // Clear cycle detection state for this batch
1071
    resolved_subprograms.clear();
812✔
1072

1073
    for (const auto& sec : reader.sections) {
26,060✔
1074
        if (!(sec->get_flags() & ELFIO::SHF_EXECINSTR) || !sec->get_size() || !sec->get_data()) {
25,248✔
1075
            continue;
17,940✔
1076
        }
1077
        const auto& sec_name = sec->get_name();
7,308✔
1078
        const auto prog_type = parse_params.platform->get_program_type(sec_name, parse_params.path);
7,308✔
1079
        for (ELFIO::Elf_Xword offset = 0; offset < sec->get_size();) {
15,308✔
1080
            auto [name, size] = get_program_name_and_size(*sec, offset, symbols);
8,000✔
1081
            auto instructions = vector_of<EbpfInst>(sec->get_data() + offset, size);
8,000✔
1082
            if (const auto reloc_sec = get_relocation_section(sec_name)) {
8,000✔
1083
                process_relocations(instructions, ELFIO::const_relocation_section_accessor{reader, reloc_sec}, sec_name,
7,560✔
1084
                                    offset, size);
1085
            }
1086
            raw_programs.emplace_back(RawProgram{
20,000✔
1087
                parse_params.path,
8,000✔
1088
                sec_name,
1089
                gsl::narrow<uint32_t>(offset),
8,000✔
1090
                name,
1091
                std::move(instructions),
4,000✔
1092
                {parse_params.platform, global.map_descriptors, prog_type},
8,000✔
1093
            });
1094
            offset += size;
8,000✔
1095
        }
8,000✔
1096
    }
7,308✔
1097

1098
    if (const auto btf_sec = reader.sections[".BTF"]) {
812✔
1099
        process_core_relocations({vector_of<std::byte>(*btf_sec)});
246✔
1100
    }
1101

1102
    if (!unresolved_symbol_errors.empty()) {
812✔
1103
        for (const auto& err : unresolved_symbol_errors) {
×
1104
            std::cerr << err << std::endl;
×
1105
        }
1106
        throw UnmarshalError("Unresolved symbols found.");
×
1107
    }
1108

1109
    if (parse_params.options.verbosity_opts.print_line_info) {
812✔
1110
        if (const auto btf_sec = reader.sections[".BTF"]) {
×
1111
            if (const auto btf_ext = reader.sections[".BTF.ext"]) {
×
1112
                update_line_info(raw_programs, btf_sec, btf_ext);
×
1113
            }
1114
        }
1115
    }
1116

1117
    for (auto& prog : raw_programs) {
8,810✔
1118
        const auto err = append_subprograms(prog);
8,000✔
1119
        if (!err.empty() && prog.section_name == parse_params.desired_section) {
8,000✔
1120
            throw UnmarshalError(err);
2✔
1121
        }
1122
    }
8,000✔
1123

1124
    if (!parse_params.desired_section.empty()) {
810✔
1125
        std::erase_if(raw_programs, [&](const auto& p) { return p.section_name != parse_params.desired_section; });
4,750✔
1126
    }
1127

1128
    if (raw_programs.empty()) {
810✔
1129
        throw UnmarshalError(parse_params.desired_section.empty() ? "No executable sections" : "Section not found");
6✔
1130
    }
1131
}
808✔
1132
} // namespace
1133

1134
int create_map_crab(const EbpfMapType& map_type, const uint32_t key_size, const uint32_t value_size,
5,800✔
1135
                    const uint32_t max_entries, ebpf_verifier_options_t) {
1136
    const EquivalenceKey equiv{map_type.value_type, key_size, value_size, map_type.is_array ? max_entries : 0};
5,800✔
1137
    if (!thread_local_program_info->cache.contains(equiv)) {
5,800✔
1138
        // +1 so 0 is the null FD
1139
        thread_local_program_info->cache[equiv] = gsl::narrow<int>(thread_local_program_info->cache.size()) + 1;
5,020✔
1140
    }
1141
    return thread_local_program_info->cache.at(equiv);
8,700✔
1142
}
1143

1144
EbpfMapDescriptor* find_map_descriptor(const int map_fd) {
18,882✔
1145
    for (EbpfMapDescriptor& map : thread_local_program_info->map_descriptors) {
154,074✔
1146
        if (map.original_fd == map_fd) {
154,074✔
1147
            return &map;
18,882✔
1148
        }
1149
    }
1150
    return nullptr;
1151
}
1152

1153
std::vector<RawProgram> read_elf(std::istream& input_stream, const std::string& path,
814✔
1154
                                 const std::string& desired_section, const std::string& desired_program,
1155
                                 const ebpf_verifier_options_t& options, const ebpf_platform_t* platform) {
1156
    std::vector<RawProgram> res;
814✔
1157
    parse_params_t params{path, options, platform, desired_section};
814✔
1158
    auto reader = load_elf(input_stream, path);
814✔
1159
    auto symbols = read_and_validate_symbol_section(reader, path);
814✔
1160
    auto global = extract_global_data(params, reader, symbols);
812✔
1161
    ProgramReader program_reader{params, reader, symbols, global};
812✔
1162
    program_reader.read_programs();
812✔
1163

1164
    // Return the desired_program, or raw_programs
1165
    if (desired_program.empty()) {
808✔
1166
        return std::move(program_reader.raw_programs);
808✔
1167
    }
1168
    for (RawProgram& cur : program_reader.raw_programs) {
×
1169
        if (cur.function_name == desired_program) {
×
1170
            res.emplace_back(std::move(cur));
×
1171
            return res;
×
1172
        }
1173
    }
1174
    return std::move(program_reader.raw_programs);
×
1175
}
831✔
1176

1177
std::vector<RawProgram> read_elf(const std::string& path, const std::string& desired_section,
816✔
1178
                                 const std::string& desired_program, const ebpf_verifier_options_t& options,
1179
                                 const ebpf_platform_t* platform) {
1180
    if (std::ifstream stream{path, std::ios::in | std::ios::binary}) {
816✔
1181
        return read_elf(stream, path, desired_section, desired_program, options, platform);
1,622✔
1182
    }
816✔
1183
    struct stat st; // NOLINT(*-pro-type-member-init)
1✔
1184
    if (stat(path.c_str(), &st)) {
2✔
1185
        throw UnmarshalError(std::string(strerror(errno)) + " opening " + path);
6✔
1186
    }
1187
    throw UnmarshalError("Can't process ELF file " + path);
×
1188
}
1189

1190
} // 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