Skip to content

Commit

Permalink
Support multiple programs per section (vbpf#638)
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Thaler <[email protected]>
  • Loading branch information
dthaler authored May 15, 2024
1 parent f7f871d commit e3e1efa
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 92 deletions.
179 changes: 109 additions & 70 deletions src/asm_files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,20 @@ using std::string;
using std::vector;

template <typename T>
static vector<T> vector_of(const ELFIO::section& sec) {
auto data = sec.get_data();
auto size = sec.get_size();
static vector<T> vector_of(const char* data, ELFIO::Elf_Xword size) {
if ((size % sizeof(T) != 0) || size > UINT32_MAX || !data) {
throw std::runtime_error("Invalid argument to vector_of");
}
return {(T*)data, (T*)(data + size)};
}

template <typename T>
static vector<T> vector_of(const ELFIO::section& sec) {
auto data = sec.get_data();
auto size = sec.get_size();
return vector_of<T>(data, size);
}

int create_map_crab(const EbpfMapType& map_type, uint32_t key_size, uint32_t value_size, uint32_t max_entries, ebpf_verifier_options_t options) {
EquivalenceKey equiv{map_type.value_type, key_size, value_size, map_type.is_array ? max_entries : 0};
if (!global_program_info->cache.count(equiv)) {
Expand Down Expand Up @@ -66,7 +71,7 @@ std::tuple<string, ELFIO::Elf_Half> get_symbol_name_and_section_index(ELFIO::con
return {symbol_name, section_index};
}

ELFIO::Elf64_Addr get_value(ELFIO::const_symbol_section_accessor& symbols, ELFIO::Elf_Word index) {
std::tuple<ELFIO::Elf64_Addr, unsigned char> get_value(ELFIO::const_symbol_section_accessor& symbols, ELFIO::Elf_Word index) {
string symbol_name;
ELFIO::Elf64_Addr value{};
ELFIO::Elf_Xword size{};
Expand All @@ -75,7 +80,7 @@ ELFIO::Elf64_Addr get_value(ELFIO::const_symbol_section_accessor& symbols, ELFIO
ELFIO::Elf_Half section_index{};
unsigned char other{};
symbols.get_symbol(index, symbol_name, value, size, bind, type, section_index, other);
return value;
return {value, type};
}

// parse_maps_sections processes all maps sections in the provided ELF file by calling the platform-specific maps
Expand Down Expand Up @@ -123,6 +128,30 @@ vector<raw_program> read_elf(const std::string& path, const std::string& desired
throw std::runtime_error(string("Can't process ELF file ") + path);
}

std::tuple<string, ELFIO::Elf_Xword> get_program_name_and_size(ELFIO::section& sec, ELFIO::Elf_Xword start, ELFIO::const_symbol_section_accessor& symbols) {
ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num();
ELFIO::Elf_Half section_index = sec.get_index();
string program_name = sec.get_name();
ELFIO::Elf_Xword size = sec.get_size() - start;
for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) {
auto [symbol_name, symbol_section_index] = get_symbol_name_and_section_index(symbols, index);
if (symbol_section_index == section_index && !symbol_name.empty()) {
auto [relocation_offset, relocation_type] = get_value(symbols, index);
if (relocation_type != ELFIO::STT_FUNC) {
continue;
}
if (relocation_offset == start) {
// We found the program name for this program.
program_name = symbol_name;
} else if (relocation_offset > start && relocation_offset < start + size) {
// We found another program that follows, so truncate the size of this program.
size = relocation_offset - start;
}
}
}
return {program_name, size};
}

vector<raw_program> read_elf(std::istream& input_stream, const std::string& path, const std::string& desired_section, const ebpf_verifier_options_t* options, const ebpf_platform_t* platform) {
if (options == nullptr)
options = &ebpf_verifier_default_options;
Expand Down Expand Up @@ -225,80 +254,90 @@ vector<raw_program> read_elf(std::istream& input_stream, const std::string& path
if ((section->get_size() == 0) || (section->get_data() == nullptr))
continue;
info.type = platform->get_program_type(name, path);
raw_program prog{path, name, vector_of<ebpf_inst>(*section), info};
auto prelocs = reader.sections[string(".rel") + name];
if (!prelocs)
prelocs = reader.sections[string(".rela") + name];

if (prelocs) {
if (!prelocs->get_data()) {
throw std::runtime_error("Malformed relocation data");
}
ELFIO::const_relocation_section_accessor reloc{reader, prelocs};

// Fetch and store relocation count locally to permit static
// analysis tools to correctly reason about the code below.
ELFIO::Elf_Xword relocation_count = reloc.get_entries_num();

for (ELFIO::Elf_Xword i = 0; i < relocation_count; i++) {
ELFIO::Elf64_Addr offset{};
ELFIO::Elf_Word index{};
unsigned type{};
ELFIO::Elf_Sxword addend{};
if (!reloc.get_entry(i, offset, index, type, addend)) {
continue;
}
if ((offset / sizeof(ebpf_inst)) >= prog.prog.size()) {
throw std::runtime_error("Invalid relocation data");
}
ebpf_inst& inst = prog.prog[offset / sizeof(ebpf_inst)];

auto [symbol_name, symbol_section_index] = get_symbol_name_and_section_index(symbols, index);
for (ELFIO::Elf_Xword program_offset = 0; program_offset < section->get_size();) {
auto [program_name, program_size] = get_program_name_and_size(*section, program_offset, symbols);
raw_program prog{path, name, program_name, vector_of<ebpf_inst>(section->get_data() + program_offset, program_size), info};
auto prelocs = reader.sections[string(".rel") + name];
if (!prelocs)
prelocs = reader.sections[string(".rela") + name];

// Only perform relocation for symbols located in the maps section.
if (!map_section_indices.contains(symbol_section_index)) {
std::string unresolved_symbol = "Unresolved external symbol " + symbol_name +
" in section " + name + " at location " + std::to_string(offset / sizeof(ebpf_inst));
unresolved_symbols.push_back(unresolved_symbol);
continue;
if (prelocs) {
if (!prelocs->get_data()) {
throw std::runtime_error("Malformed relocation data");
}
ELFIO::const_relocation_section_accessor reloc{reader, prelocs};

// Fetch and store relocation count locally to permit static
// analysis tools to correctly reason about the code below.
ELFIO::Elf_Xword relocation_count = reloc.get_entries_num();

for (ELFIO::Elf_Xword i = 0; i < relocation_count; i++) {
ELFIO::Elf64_Addr offset{};
ELFIO::Elf_Word index{};
unsigned type{};
ELFIO::Elf_Sxword addend{};
if (!reloc.get_entry(i, offset, index, type, addend)) {
continue;
}
if (offset < program_offset || offset >= program_offset + program_size) {
// Relocation is not for this program.
continue;
}
offset -= program_offset;
if ((offset / sizeof(ebpf_inst)) >= prog.prog.size()) {
throw std::runtime_error("Invalid relocation data");
}
ebpf_inst& inst = prog.prog[offset / sizeof(ebpf_inst)];

// Only permit loading the address of the map.
if ((inst.opcode & INST_CLS_MASK) != INST_CLS_LD) {
throw std::runtime_error("Illegal operation on symbol " + symbol_name +
" at location " + std::to_string(offset / sizeof(ebpf_inst)));
}
inst.src = 1; // magic number for LoadFd

// Relocation value is an offset into the "maps" or ".maps" section.
size_t relocation_offset = get_value(symbols, index);
if (map_record_size_or_map_offsets.index() == 0) {
// The older maps section format uses a single map_record_size value, so we can
// calculate the map descriptor index directly.
size_t reloc_value = relocation_offset / std::get<0>(map_record_size_or_map_offsets);
if (reloc_value >= info.map_descriptors.size()) {
throw std::runtime_error("Bad reloc value (" + std::to_string(reloc_value) + "). "
+ "Make sure to compile with -O2.");
auto [symbol_name, symbol_section_index] = get_symbol_name_and_section_index(symbols, index);

// Only perform relocation for symbols located in the maps section.
if (!map_section_indices.contains(symbol_section_index)) {
std::string unresolved_symbol = "Unresolved external symbol " + symbol_name +
" in section " + name + " at location " + std::to_string(offset / sizeof(ebpf_inst));
unresolved_symbols.push_back(unresolved_symbol);
continue;
}

inst.imm = info.map_descriptors.at(reloc_value).original_fd;
}
else {
// The newer .maps section format uses a variable-length map descriptor array,
// so we need to look up the map descriptor index in a map.
auto& map_descriptors_offsets = std::get<1>(map_record_size_or_map_offsets);
auto it = map_descriptors_offsets.find(symbol_name);

if (it == map_descriptors_offsets.end()) {
throw std::runtime_error("Bad reloc value (" + std::to_string(index) + "). "
+ "Make sure to compile with -O2.");
// Only permit loading the address of the map.
if ((inst.opcode & INST_CLS_MASK) != INST_CLS_LD) {
throw std::runtime_error("Illegal operation on symbol " + symbol_name +
" at location " + std::to_string(offset / sizeof(ebpf_inst)));
}
inst.src = 1; // magic number for LoadFd

// Relocation value is an offset into the "maps" or ".maps" section.
auto [relocation_offset, relocation_type] = get_value(symbols, index);
if (map_record_size_or_map_offsets.index() == 0) {
// The older maps section format uses a single map_record_size value, so we can
// calculate the map descriptor index directly.
size_t reloc_value = relocation_offset / std::get<0>(map_record_size_or_map_offsets);
if (reloc_value >= info.map_descriptors.size()) {
throw std::runtime_error("Bad reloc value (" + std::to_string(reloc_value) + "). "
+ "Make sure to compile with -O2.");
}

inst.imm = info.map_descriptors.at(reloc_value).original_fd;
}
else {
// The newer .maps section format uses a variable-length map descriptor array,
// so we need to look up the map descriptor index in a map.
auto& map_descriptors_offsets = std::get<1>(map_record_size_or_map_offsets);
auto it = map_descriptors_offsets.find(symbol_name);

if (it == map_descriptors_offsets.end()) {
throw std::runtime_error("Bad reloc value (" + std::to_string(index) + "). "
+ "Make sure to compile with -O2.");
}
inst.imm = info.map_descriptors.at(it->second).original_fd;
}
inst.imm = info.map_descriptors.at(it->second).original_fd;
}
}
prog.line_info.resize(prog.prog.size());
res.push_back(prog);
program_offset += program_size;
}
prog.line_info.resize(prog.prog.size());
res.push_back(prog);
}

// Below, only relocations of symbols located in the map sections are allowed,
Expand All @@ -314,7 +353,7 @@ vector<raw_program> read_elf(std::istream& input_stream, const std::string& path
if (btf != nullptr && btf_ext != nullptr) {
std::map<std::string, raw_program&> segment_to_program;
for (auto& program : res) {
segment_to_program.insert({program.section, program});
segment_to_program.insert({program.function_name, program});
}

auto visitor = [&](const std::string& section, uint32_t instruction_offset, const std::string& file_name,
Expand Down
36 changes: 26 additions & 10 deletions src/main/check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ static std::vector<std::string> get_string_vector(std::string list) {
return string_vector;
}

static std::optional<raw_program> find_program(vector<raw_program>& raw_progs, std::string desired_program) {
if (desired_program.empty() && raw_progs.size() == 1) {
// Select the last program section.
return raw_progs.back();
}
for (raw_program current_program : raw_progs) {
if (current_program.function_name == desired_program) {
return current_program;
}
}
return {};
}

int main(int argc, char** argv) {
// Always call ebpf_verifier_clear_thread_local_state on scope exit.
at_scope_exit<ebpf_verifier_clear_thread_local_state> clear_thread_local_state;
Expand All @@ -86,9 +99,13 @@ int main(int argc, char** argv) {

std::string desired_section;

app.add_option("section", desired_section, "Section to analyze")->type_name("SECTION");
app.add_option("--section,section", desired_section, "Section to analyze")->type_name("SECTION");

std::string desired_program;

app.add_option("--function,function", desired_program, "Function to analyze")->type_name("FUNCTION");
bool list = false;
app.add_flag("-l", list, "List sections");
app.add_flag("-l", list, "List programs");

std::string domain = "zoneCrab";
std::set<string> doms{"stats", "linux", "zoneCrab", "cfg"};
Expand Down Expand Up @@ -184,25 +201,24 @@ int main(int argc, char** argv) {
return 1;
}

if (list || raw_progs.size() != 1) {
std::optional<raw_program> found_prog = find_program(raw_progs, desired_program);
if (list || !found_prog) {
if (!list) {
std::cout << "please specify a section\n";
std::cout << "available sections:\n";
std::cout << "please specify a program\n";
std::cout << "available programs:\n";
}
if (!desired_section.empty() && raw_progs.empty()) {
// We could not find the desired section, so get the full list
// We could not find the desired program, so get the full list
// of possibilities.
raw_progs = read_elf(filename, string(), &ebpf_verifier_options, &platform);
}
for (const raw_program& raw_prog : raw_progs) {
std::cout << raw_prog.section << " ";
std::cout << "section=" << raw_prog.section_name << " function=" << raw_prog.function_name << std::endl;
}
std::cout << "\n";
return list ? 0 : 64;
}

// Select the last program section.
raw_program raw_prog = raw_progs.back();
raw_program raw_prog = *found_prog;

// Convert the raw program section to a set of instructions.
std::variant<InstructionSeq, std::string> prog_or_error = unmarshal(raw_prog);
Expand Down
3 changes: 2 additions & 1 deletion src/spec_type_descriptors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ struct btf_line_info_t {

struct raw_program {
std::string filename{};
std::string section{};
std::string section_name{};
std::string function_name{};
std::vector<ebpf_inst> prog{};
program_info info{};
std::vector<btf_line_info_t> line_info{};
Expand Down
Loading

0 comments on commit e3e1efa

Please sign in to comment.