blob: ec48ff175b25aa869bec623c8c2a2df00b401080 [file] [log] [blame]
// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: Apache-2.0
// This program reads BPF instructions from stdin and memory contents from
// the first agument. It then executes the BPF program and prints the
// value of %r0 at the end of execution.
// The program is intended to be used with the bpf conformance test suite.
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <sstream>
#include <sys/mman.h>
#include "ubpf_int.h"
extern "C"
{
#include "ebpf.h"
#include "ubpf.h"
}
#include "test_helpers.h"
uint64_t test_helpers_dispatcher(uint64_t p0, uint64_t p1,uint64_t p2,uint64_t p3, uint64_t p4, unsigned int idx, void* cookie) {
UNREFERENCED_PARAMETER(cookie);
return helper_functions[idx](p0, p1, p2, p3, p4);
}
bool test_helpers_validater(unsigned int idx, const struct ubpf_vm *vm) {
UNREFERENCED_PARAMETER(vm);
return helper_functions.contains(idx);
}
/**
* @brief Read in a string of hex bytes and return a vector of bytes.
*
* @param[in] input String containing hex bytes.
* @return Vector of bytes.
*/
std::vector<uint8_t>
base16_decode(const std::string &input)
{
std::vector<uint8_t> output;
std::stringstream ss(input);
std::string value;
output.reserve(input.size() / 3);
while (std::getline(ss, value, ' '))
{
try
{
output.push_back(static_cast<uint8_t>(std::stoi(value, nullptr, 16)));
}
catch (...)
{
// Ignore invalid values.
}
}
return output;
}
/**
* @brief Convert a vector of bytes to a vector of ebpf_inst.
*
* @param[in] bytes Vector of bytes.
* @return Vector of ebpf_inst.
*/
std::vector<ebpf_inst>
bytes_to_ebpf_inst(std::vector<uint8_t> bytes)
{
std::vector<ebpf_inst> instructions(bytes.size() / sizeof(ebpf_inst));
memcpy(instructions.data(), bytes.data(), bytes.size());
return instructions;
}
/**
* @brief This program reads BPF instructions from stdin and memory contents from
* the first agument. It then executes the BPF program and prints the
* value of %r0 at the end of execution.
*/
int main(int argc, char **argv)
{
bool jit = false; // JIT == true, interpreter == false
std::vector<std::string> args(argv, argv + argc);
std::string program_string;
std::string memory_string;
// Remove the first argument which is the program name.
args.erase(args.begin());
// First parameter is optional memory contents.
if (args.size() > 0 && !args[0].starts_with("--"))
{
memory_string = args[0];
args.erase(args.begin());
}
if (args.size() > 0 && args[0] == "--program")
{
args.erase(args.begin());
if (args.size() > 0)
{
program_string = args[0];
args.erase(args.begin());
}
}
if (args.size() > 0 && args[0] == "--jit")
{
jit = true;
args.erase(args.begin());
}
if (args.size() > 0 && args[0] == "--interpret")
{
jit = false;
args.erase(args.begin());
}
if (args.size() > 0 && args[0].size() > 0)
{
std::cerr << "Invalid arguments: " << args[0] << std::endl;
return 1;
}
if (program_string.empty()) {
std::getline(std::cin, program_string);
}
std::vector<ebpf_inst> program = bytes_to_ebpf_inst(base16_decode(program_string));
std::vector<uint8_t> memory = base16_decode(memory_string);
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);
char* error = nullptr;
if (vm == nullptr)
{
std::cerr << "Failed to create VM" << std::endl;
return 1;
}
ubpf_register_external_dispatcher(vm.get(), test_helpers_dispatcher, test_helpers_validater);
if (ubpf_set_unwind_function_index(vm.get(), 5) != 0)
{
std::cerr << "Failed to set unwind function index" << std::endl;
return 1;
}
if (ubpf_load(vm.get(), program.data(), static_cast<uint32_t>(program.size() * sizeof(ebpf_inst)), &error) != 0)
{
std::cout << "Failed to load code: " << error << std::endl;
free(error);
return 1;
}
uint64_t external_dispatcher_result;
if (jit)
{
// Compile the program ...
ubpf_jit_fn fn = ubpf_compile(vm.get(), &error);
if (fn == nullptr)
{
std::cerr << "Failed to compile program: " << error << std::endl;
free(error);
return 1;
}
// ... keep the original program memory safe from being trashed by test program so that
// it can be run again ...
std::vector<uint8_t> usable_program_memory{memory};
uint8_t *usable_program_memory_pointer{nullptr};
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}
// ... execute the original copy of the JIT'd code ...
external_dispatcher_result = fn(usable_program_memory_pointer, usable_program_memory.size());
// ... execute original code but with indexed dispatcher to helper functions ...
ubpf_register_external_dispatcher(vm.get(), nullptr, test_helpers_validater);
for (auto& [key, value] : helper_functions) {
if (ubpf_register(vm.get(), key, "unnamed", value) != 0) {
std::cerr << "Failed to register helper function" << std::endl;
return 1;
}
}
uint64_t index_helper_result;
usable_program_memory = memory;
usable_program_memory_pointer = nullptr;
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}
index_helper_result = fn(usable_program_memory_pointer, usable_program_memory.size());
// ... copy the JIT'd program ...
auto fn_copy_size = vm->jitted_size * sizeof(char);
void *fn_copy = mmap(0, fn_copy_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
fn = ubpf_copy_jit(vm.get(), fn_copy, fn_copy_size, &error);
if (fn == nullptr) {
std::cerr << "Failed to copy JIT'd program: " << error << std::endl;
free(error);
return 1;
}
mprotect(fn_copy, fn_copy_size, PROT_READ | PROT_EXEC);
// ... execute the copy of the JIT'd code ...
uint64_t copy_result;
usable_program_memory = memory;
usable_program_memory_pointer = nullptr;
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}
copy_result = fn(usable_program_memory_pointer, usable_program_memory.size());
// ... and make sure the results are the same.
if (external_dispatcher_result != index_helper_result || index_helper_result != copy_result) {
std::cerr << "Execution of the JIT'd code (with external and indexed helpers) and a copy of "
"the JIT'd code gave different results: 0x" << std::hex << external_dispatcher_result
<< " vs 0x" << std::hex << index_helper_result
<< " vs 0x" << std::hex << copy_result << "." << std::endl;
return 1;
}
}
else
{
// Keep the original program memory safe from being trashed by test program so that
// it can be run again ...
std::vector<uint8_t> usable_program_memory{memory};
uint8_t *usable_program_memory_pointer{nullptr};
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}
if (ubpf_exec(vm.get(), usable_program_memory_pointer, usable_program_memory.size(), &external_dispatcher_result) != 0)
{
std::cerr << "Failed to execute program" << std::endl;
return 1;
}
// ... execute original code but with indexed dispatcher to helper functions ...
ubpf_register_external_dispatcher(vm.get(), nullptr, test_helpers_validater);
for (auto& [key, value] : helper_functions) {
if (ubpf_register(vm.get(), key, "unnamed", value) != 0) {
std::cerr << "Failed to register helper function" << std::endl;
return 1;
}
}
// ... but first reset program memory.
usable_program_memory = memory;
usable_program_memory_pointer = nullptr;
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}
uint64_t index_helper_result;
if (ubpf_exec(vm.get(), usable_program_memory_pointer, usable_program_memory.size(), &index_helper_result) != 0)
{
std::cerr << "Failed to execute program" << std::endl;
return 1;
}
// ... and make sure the results are the same.
if (external_dispatcher_result != index_helper_result) {
std::cerr << "Execution of the interpreted code with external and indexed helpers gave difference results: 0x"
<< std::hex << external_dispatcher_result
<< " vs 0x" << std::hex << index_helper_result << "." << std::endl;
return 1;
}
}
std::cout << std::hex << external_dispatcher_result << std::endl;
return 0;
}