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

Alan-Jowett / ebpf-verifier / 19340586454

08 Nov 2025 03:47PM UTC coverage: 86.605% (-0.3%) from 86.936%
19340586454

push

github

web-flow
Implement checks for the --line-info flag and move program selection into read_elf (#946)

* Check when --line-info is used, and avoid computing btf_line_info in read_elf and unmarshall
* Modify main and read_elf to avoid returning multiple RawProgram when desired_program is specified
* Use thread_local_options instead of {} in test_verify
* Avoid using brace initialization when returning the selected RawProgram in read_elf

Signed-off-by: Maxime Derri <maxime.derri@orange.com>

33 of 42 new or added lines in 6 files covered. (78.57%)

27 existing lines in 4 files now uncovered.

9019 of 10414 relevant lines covered (86.6%)

3908138.07 hits per line

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

70.09
/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 "ir/program.hpp"
28
#include "platform.hpp"
29

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

43
namespace prevail {
44

45
namespace {
46

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

227
constexpr int DEFAULT_MAP_FD = -1;
228

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

930
bool ProgramReader::try_reloc(const std::string& symbol_name, const ELFIO::Elf_Half symbol_section_index,
217,830✔
931
                              std::vector<EbpfInst>& instructions, const size_t location, const ELFIO::Elf_Word index,
932
                              const ELFIO::Elf_Sxword addend) {
933
    // Handle empty symbol names for global variable sections
934
    // These occur in legacy ELF files where relocations reference
935
    // section symbols rather than named variable symbols
936
    if (symbol_name.empty()) {
217,830✔
937
        if (global.variable_section_indices.contains(symbol_section_index)) {
10✔
938
            if (!std::holds_alternative<MapOffsets>(global.map_record_size_or_map_offsets)) {
8✔
939
                return false; // Legacy path without MapOffsets; let caller handle
940
            }
941

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

944
            const auto symbol_details = get_symbol_details(symbols, index);
8✔
945
            hi_inst.get().imm = gsl::narrow<int32_t>(symbol_details.value);
8✔
946
            lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
8✔
947

948
            const std::string section_name = reader.sections[symbol_section_index]->get_name();
12✔
949
            lo_inst.get().imm = relocate_global_variable(section_name);
8✔
950
            return true;
8✔
951
        }
8✔
952
        // Empty symbol name in non-variable section - skip it
953
        return true;
1✔
954
    }
955

956
    EbpfInst& instruction_to_relocate = instructions[location];
217,820✔
957

958
    // Handle local function calls - queue for post-processing
959
    if (instruction_to_relocate.opcode == INST_OP_CALL && instruction_to_relocate.src == INST_CALL_LOCAL) {
217,820✔
960
        function_relocations.emplace_back(FunctionRelocation{raw_programs.size(), location, index, symbol_name});
29,402✔
961
        return true;
29,402✔
962
    }
963

964
    // Only LD-class instructions can be map/global loads
965
    if ((instruction_to_relocate.opcode & INST_CLS_MASK) != INST_CLS_LD) {
188,418✔
966
        return false;
967
    }
968

969
    // Handle map relocations (BTF or legacy)
970
    if (global.map_section_indices.contains(symbol_section_index)) {
188,418✔
971
        instruction_to_relocate.src = INST_LD_MODE_MAP_FD;
178,506✔
972
        instruction_to_relocate.imm = relocate_map(symbol_name, index);
178,506✔
973
        return true;
178,506✔
974
    }
975

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

981
        const int32_t offset = addend != 0 ? gsl::narrow<int32_t>(addend) : lo_inst.get().imm;
9,912✔
982
        hi_inst.get().imm = offset;
9,912✔
983
        lo_inst.get().src = INST_LD_MODE_MAP_VALUE;
9,912✔
984
        lo_inst.get().imm = relocate_global_variable(reader.sections[symbol_section_index]->get_name());
14,868✔
985
        return true;
9,912✔
986
    }
987

988
    // Legacy fallback: zero out __config_* symbols not in a variable section
989
    // (for compatibility with older toolchains)
990
    if (symbol_name.rfind("__config_", 0) == 0) {
×
991
        instruction_to_relocate.imm = 0;
×
992
        return true;
×
993
    }
994

995
    return false;
996
}
997

998
void ProgramReader::process_relocations(std::vector<EbpfInst>& instructions,
7,560✔
999
                                        const ELFIO::const_relocation_section_accessor& reloc,
1000
                                        const std::string& section_name, const ELFIO::Elf_Xword program_offset,
1001
                                        const size_t program_size) {
1002
    for (ELFIO::Elf_Xword i = 0; i < reloc.get_entries_num(); i++) {
856,082✔
1003
        ELFIO::Elf64_Addr o{};
848,522✔
1004
        ELFIO::Elf_Word idx{};
848,522✔
1005
        unsigned type{};
848,522✔
1006
        ELFIO::Elf_Sxword addend{};
848,522✔
1007
        if (reloc.get_entry(i, o, idx, type, addend)) {
848,522✔
1008
            if (o < program_offset || o >= program_offset + program_size) {
848,522✔
1009
                continue;
630,692✔
1010
            }
1011
            o -= program_offset;
217,830✔
1012

1013
            if (o % sizeof(EbpfInst) != 0) {
217,830✔
1014
                throw UnmarshalError("Unaligned relocation offset");
×
1015
            }
1016
            const auto loc = o / sizeof(EbpfInst);
217,830✔
1017
            if (loc >= instructions.size()) {
217,830✔
1018
                throw UnmarshalError("Invalid relocation");
×
1019
            }
1020
            auto sym = get_symbol_details(symbols, idx);
217,830✔
1021

1022
            if (!try_reloc(sym.name, sym.section_index, instructions, loc, idx, addend)) {
217,830✔
1023
                unresolved_symbol_errors.push_back("Unresolved external symbol " + sym.name + " in section " +
×
1024
                                                   section_name + " at location " + std::to_string(loc));
×
1025
            }
1026
        }
217,830✔
1027
    }
1028
}
7,560✔
1029

1030
const ELFIO::section* ProgramReader::get_relocation_section(const std::string& name) const {
8,000✔
1031
    if (name == ".BTF") {
8,000✔
1032
        return nullptr;
1033
    }
1034
    const auto* relocs = reader.sections[".rel" + name];
8,000✔
1035
    if (!relocs) {
8,000✔
1036
        relocs = reader.sections[".rela" + name];
1,070✔
1037
    }
1038
    if (!relocs || !relocs->get_data()) {
8,000✔
1039
        return nullptr;
440✔
1040
    }
1041
    return relocs;
3,780✔
1042
}
1043

1044
void ProgramReader::read_programs() {
812✔
1045
    // Clear cycle detection state for this batch
1046
    resolved_subprograms.clear();
812✔
1047

1048
    for (const auto& sec : reader.sections) {
26,060✔
1049
        if (!(sec->get_flags() & ELFIO::SHF_EXECINSTR) || !sec->get_size() || !sec->get_data()) {
25,248✔
1050
            continue;
17,940✔
1051
        }
1052
        const auto& sec_name = sec->get_name();
7,308✔
1053
        const auto prog_type = parse_params.platform->get_program_type(sec_name, parse_params.path);
7,308✔
1054
        for (ELFIO::Elf_Xword offset = 0; offset < sec->get_size();) {
15,308✔
1055
            auto [name, size] = get_program_name_and_size(*sec, offset, symbols);
8,000✔
1056
            auto instructions = vector_of<EbpfInst>(sec->get_data() + offset, size);
8,000✔
1057
            if (const auto reloc_sec = get_relocation_section(sec_name)) {
8,000✔
1058
                process_relocations(instructions, ELFIO::const_relocation_section_accessor{reader, reloc_sec}, sec_name,
7,560✔
1059
                                    offset, size);
1060
            }
1061
            raw_programs.emplace_back(RawProgram{
20,000✔
1062
                parse_params.path,
8,000✔
1063
                sec_name,
1064
                gsl::narrow<uint32_t>(offset),
8,000✔
1065
                name,
1066
                std::move(instructions),
4,000✔
1067
                {parse_params.platform, global.map_descriptors, prog_type},
8,000✔
1068
            });
1069
            offset += size;
8,000✔
1070
        }
8,000✔
1071
    }
7,308✔
1072

1073
    if (const auto btf_sec = reader.sections[".BTF"]) {
812✔
1074
        process_core_relocations({vector_of<std::byte>(*btf_sec)});
246✔
1075
    }
1076

1077
    if (!unresolved_symbol_errors.empty()) {
812✔
1078
        for (const auto& err : unresolved_symbol_errors) {
×
1079
            std::cerr << err << std::endl;
×
1080
        }
1081
        throw UnmarshalError("Unresolved symbols found.");
×
1082
    }
1083

1084
    if (parse_params.options.verbosity_opts.print_line_info) {
812✔
NEW
1085
        if (const auto btf_sec = reader.sections[".BTF"]) {
×
NEW
1086
            if (const auto btf_ext = reader.sections[".BTF.ext"]) {
×
NEW
1087
                update_line_info(raw_programs, btf_sec, btf_ext);
×
1088
            }
1089
        }
1090
    }
1091

1092
    for (auto& prog : raw_programs) {
8,810✔
1093
        const auto err = append_subprograms(prog);
8,000✔
1094
        if (!err.empty() && prog.section_name == parse_params.desired_section) {
8,000✔
1095
            throw UnmarshalError(err);
2✔
1096
        }
1097
    }
8,000✔
1098

1099
    if (!parse_params.desired_section.empty()) {
810✔
1100
        std::erase_if(raw_programs, [&](const auto& p) { return p.section_name != parse_params.desired_section; });
4,750✔
1101
    }
1102

1103
    if (raw_programs.empty()) {
810✔
1104
        throw UnmarshalError(parse_params.desired_section.empty() ? "No executable sections" : "Section not found");
6✔
1105
    }
1106
}
808✔
1107
} // namespace
1108

1109
int create_map_crab(const EbpfMapType& map_type, const uint32_t key_size, const uint32_t value_size,
5,800✔
1110
                    const uint32_t max_entries, ebpf_verifier_options_t) {
1111
    const EquivalenceKey equiv{map_type.value_type, key_size, value_size, map_type.is_array ? max_entries : 0};
5,800✔
1112
    if (!thread_local_program_info->cache.contains(equiv)) {
5,800✔
1113
        // +1 so 0 is the null FD
1114
        thread_local_program_info->cache[equiv] = gsl::narrow<int>(thread_local_program_info->cache.size()) + 1;
5,022✔
1115
    }
1116
    return thread_local_program_info->cache.at(equiv);
8,700✔
1117
}
1118

1119
EbpfMapDescriptor* find_map_descriptor(const int map_fd) {
18,882✔
1120
    for (EbpfMapDescriptor& map : thread_local_program_info->map_descriptors) {
154,074✔
1121
        if (map.original_fd == map_fd) {
154,074✔
1122
            return &map;
18,882✔
1123
        }
1124
    }
1125
    return nullptr;
1126
}
1127

1128
std::vector<RawProgram> read_elf(std::istream& input_stream, const std::string& path,
814✔
1129
                                 const std::string& desired_section, const std::string& desired_program,
1130
                                 const ebpf_verifier_options_t& options, const ebpf_platform_t* platform) {
1131
    std::vector<RawProgram> res;
814✔
1132
    parse_params_t params{path, options, platform, desired_section};
814✔
1133
    auto reader = load_elf(input_stream, path);
814✔
1134
    auto symbols = read_and_validate_symbol_section(reader, path);
814✔
1135
    auto global = extract_global_data(params, reader, symbols);
812✔
1136
    ProgramReader program_reader{params, reader, symbols, global};
812✔
1137
    program_reader.read_programs();
812✔
1138

1139
    // Return the desired_program, or raw_programs
1140
    if (desired_program.empty()) {
808✔
1141
        return std::move(program_reader.raw_programs);
808✔
1142
    }
NEW
1143
    for (RawProgram& cur : program_reader.raw_programs) {
×
NEW
1144
        if (cur.function_name == desired_program) {
×
NEW
1145
            res.emplace_back(std::move(cur));
×
NEW
1146
            return res;
×
1147
        }
1148
    }
UNCOV
1149
    return std::move(program_reader.raw_programs);
×
1150
}
831✔
1151

1152
std::vector<RawProgram> read_elf(const std::string& path, const std::string& desired_section,
816✔
1153
                                 const std::string& desired_program, const ebpf_verifier_options_t& options,
1154
                                 const ebpf_platform_t* platform) {
1155
    if (std::ifstream stream{path, std::ios::in | std::ios::binary}) {
816✔
1156
        return read_elf(stream, path, desired_section, desired_program, options, platform);
1,622✔
1157
    }
816✔
1158
    struct stat st; // NOLINT(*-pro-type-member-init)
1✔
1159
    if (stat(path.c_str(), &st)) {
2✔
1160
        throw UnmarshalError(std::string(strerror(errno)) + " opening " + path);
6✔
1161
    }
1162
    throw UnmarshalError("Can't process ELF file " + path);
×
1163
}
1164

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