Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bpf_object__open_mem #3416

Merged
merged 1 commit into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Implement bpf_object__open_mem
Signed-off-by: Alan Jowett <alan.jowett@microsoft.com>
  • Loading branch information
Alan Jowett committed Apr 1, 2024
commit bc7da9e2c35b9917af1e79ba57b227cfecb8b7b8
1 change: 1 addition & 0 deletions ebpfapi/Source.def
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ EXPORTS
bpf_object__next_program
bpf_object__open
bpf_object__open_file
bpf_object__open_mem
bpf_object__pin
bpf_object__pin_maps
bpf_object__pin_programs
Expand Down
35 changes: 28 additions & 7 deletions libs/api/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,24 @@ _get_map_names(

static void
_get_program_and_map_names(
_In_ const string& path,
std::variant<std::string, std::vector<uint8_t>>& file_or_buffer,
_Inout_ vector<section_program_map_t>& section_to_program_map,
_Inout_ vector<section_offset_to_map_t>& map_names) noexcept(false)
{
ELFIO::elfio reader;
size_t symbols_count = 0;

if (!reader.load(path)) {
throw std::runtime_error(string("Can't process ELF file ") + path);
if (std::holds_alternative<std::string>(file_or_buffer)) {
if (!reader.load(std::get<std::string>(file_or_buffer))) {
throw std::runtime_error("Can't process ELF file " + std::get<std::string>(file_or_buffer));
}
} else {
std::stringstream buffer_stream(std::string(
std::get<std::vector<uint8_t>>(file_or_buffer).begin(),
std::get<std::vector<uint8_t>>(file_or_buffer).end()));
if (!reader.load(buffer_stream)) {
throw std::runtime_error("Can't process ELF file from memory");
}
}

ELFIO::const_symbol_section_accessor symbols{reader, reader.sections[".symtab"]};
Expand Down Expand Up @@ -317,7 +326,7 @@ _get_program_and_map_names(

_Must_inspect_result_ ebpf_result_t
load_byte_code(
_In_z_ const char* filename,
std::variant<std::string, std::vector<uint8_t>>& file_or_buffer,
_In_opt_z_ const char* section_name,
_In_ const ebpf_verifier_options_t* verifier_options,
_In_z_ const char* pin_root_path,
Expand All @@ -335,13 +344,25 @@ load_byte_code(

try {
const ebpf_platform_t* platform = &g_ebpf_platform_windows;
std::string file_name(filename);
std::string section_name_string;
if (section_name != nullptr) {
section_name_string = std::string(section_name);
}

auto raw_programs = read_elf(file_name, section_name_string, verifier_options, platform);
std::vector<raw_program> raw_programs;

// If file_or_buffer is a string, it is a file name.
if (std::holds_alternative<std::string>(file_or_buffer)) {
raw_programs =
read_elf(std::get<std::string>(file_or_buffer), section_name_string, verifier_options, platform);
} else {
std::stringstream buffer_stream;
// If file_or_buffer is a vector, it is a buffer.
auto& buffer = std::get<std::vector<uint8_t>>(file_or_buffer);
buffer_stream = std::stringstream(std::string(buffer.begin(), buffer.end()));
raw_programs = read_elf(buffer_stream, "memory", section_name_string, verifier_options, platform);
}

if (raw_programs.size() == 0) {
result = EBPF_ELF_PARSING_FAILED;
goto Exit;
Expand Down Expand Up @@ -423,7 +444,7 @@ load_byte_code(
section_to_program_map.emplace_back(iterator->section_name, std::string());
}

_get_program_and_map_names(file_name, section_to_program_map, map_names);
_get_program_and_map_names(file_or_buffer, section_to_program_map, map_names);

auto map_descriptors = get_all_map_descriptors();
size_t anonymous_map_count = 0;
Expand Down
5 changes: 4 additions & 1 deletion libs/api/Verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
#include "ebpf_program_types.h"
#include "ebpf_result.h"
#include "platform.hpp"

#include <variant>

typedef int (*map_create_fp)(
uint32_t map_type, uint32_t key_size, uint32_t value_size, uint32_t max_entries, ebpf_verifier_options_t options);

_Must_inspect_result_ ebpf_result_t
load_byte_code(
_In_z_ const char* filename,
std::variant<std::string, std::vector<uint8_t>>& file_or_buffer,
_In_opt_z_ const char* section_name,
_In_ const ebpf_verifier_options_t* verifier_options,
_In_z_ const char* pin_root_path,
Expand Down
32 changes: 32 additions & 0 deletions libs/api/api_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,38 @@ ebpf_object_open(
_Outptr_ struct bpf_object** object,
_Outptr_result_maybenull_z_ const char** error_message) noexcept;

/**
* @brief Open a ELF file from memory without loading the programs.
*
* @param[in] buffer Pointer to the buffer containing the ELF file.
* @param[in] buffer_size Size of the buffer containing the ELF file.
* @param[in] object_name Optional object name to override file name
* as the object name.
* @param[in] pin_root_path Optional root path for automatic pinning of maps.
* @param[in] program_type Optional program type for all programs.
* If NULL, the program type is derived from the section names.
* @param[in] attach_type Default attach type for all programs.
* If NULL, the attach type is derived from the section names.
* @param[out] object Returns a pointer to the object created.
* @param[out] error_message Error message string, which
* the caller must free using ebpf_free_string().
*
* @retval EBPF_SUCCESS The operation was successful.
* @retval EBPF_INVALID_ARGUMENT One or more parameters are wrong.
* @retval EBPF_NO_MEMORY Out of memory.
*/
EBPF_API_LOCKING
_Must_inspect_result_ ebpf_result_t
ebpf_object_open_memory(
_In_reads_(buffer_size) const uint8_t* buffer,
size_t buffer_size,
_In_opt_z_ const char* object_name,
_In_opt_z_ const char* pin_root_path,
_In_opt_ const ebpf_program_type_t* program_type,
_In_opt_ const ebpf_attach_type_t* attach_type,
_Outptr_ struct bpf_object** object,
_Outptr_result_maybenull_z_ const char** error_message) noexcept;

/**
* @brief Load all the programs in a given object.
*
Expand Down
93 changes: 84 additions & 9 deletions libs/api/ebpf_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2279,20 +2279,19 @@ CATCH_NO_MEMORY_EBPF_RESULT

static ebpf_result_t
_initialize_ebpf_object_from_elf(
_In_z_ const char* file_name,
std::variant<std::string, std::vector<uint8_t>>& file_or_data,
_In_opt_z_ const char* pin_root_path,
_Inout_ ebpf_object_t& object,
_Outptr_result_maybenull_z_ const char** error_message) NO_EXCEPT_TRY
{
EBPF_LOG_ENTRY();
ebpf_assert(file_name);
ebpf_assert(error_message);

ebpf_result_t result = EBPF_SUCCESS;

ebpf_verifier_options_t verifier_options{false, false, false, false, false};
result = load_byte_code(
file_name,
file_or_data,
nullptr,
&verifier_options,
pin_root_path ? pin_root_path : DEFAULT_PIN_ROOT_PATH,
Expand All @@ -2314,7 +2313,7 @@ CATCH_NO_MEMORY_EBPF_RESULT

static ebpf_result_t
_initialize_ebpf_object_from_file(
Alan-Jowett marked this conversation as resolved.
Show resolved Hide resolved
_In_z_ const char* path,
std::variant<std::string, std::vector<uint8_t>> file_or_data,
_In_opt_z_ const char* object_name,
_In_opt_z_ const char* pin_root_path,
_Out_ ebpf_object_t* new_object,
Expand All @@ -2323,22 +2322,32 @@ _initialize_ebpf_object_from_file(
EBPF_LOG_ENTRY();
ebpf_result_t result = EBPF_SUCCESS;

new_object->file_name = cxplat_duplicate_string(path);
if (std::holds_alternative<std::string>(file_or_data)) {
Alan-Jowett marked this conversation as resolved.
Show resolved Hide resolved
std::string path = std::get<std::string>(file_or_data);
new_object->file_name = cxplat_duplicate_string(path.c_str());
} else {
new_object->file_name = cxplat_duplicate_string("memory");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe make this more unique? eg. EBPF_PROG_ID_MEMORY_BUF or similar

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like libbpf uses the name from the opts file, so I will switch it.

}
if (new_object->file_name == nullptr) {
result = EBPF_NO_MEMORY;
goto Done;
}

new_object->object_name = cxplat_duplicate_string(object_name ? object_name : path);
new_object->object_name = cxplat_duplicate_string(object_name ? object_name : new_object->file_name);
if (new_object->object_name == nullptr) {
result = EBPF_NO_MEMORY;
goto Done;
}

if (Platform::_is_native_program(path)) {
result = _initialize_ebpf_object_from_native_file(path, pin_root_path, *new_object, error_message);
// If file_or_data is a string, it is a file path.
// If it is a vector, it is the ELF data.

if (std::holds_alternative<std::string>(file_or_data) &&
Platform::_is_native_program(std::get<std::string>(file_or_data).c_str())) {
result = _initialize_ebpf_object_from_native_file(
std::get<std::string>(file_or_data).c_str(), pin_root_path, *new_object, error_message);
} else {
result = _initialize_ebpf_object_from_elf(path, pin_root_path, *new_object, error_message);
result = _initialize_ebpf_object_from_elf(file_or_data, pin_root_path, *new_object, error_message);
}
if (result != EBPF_SUCCESS) {
goto Done;
Expand Down Expand Up @@ -2865,6 +2874,72 @@ _Requires_lock_not_held_(_ebpf_state_mutex) _Must_inspect_result_ ebpf_result_t
}
CATCH_NO_MEMORY_EBPF_RESULT

EBPF_API_LOCKING
_Must_inspect_result_ ebpf_result_t
ebpf_object_open_memory(
_In_reads_(buffer_size) const uint8_t* buffer,
size_t buffer_size,
_In_opt_z_ const char* object_name,
_In_opt_z_ const char* pin_root_path,
_In_opt_ const ebpf_program_type_t* program_type,
_In_opt_ const ebpf_attach_type_t* attach_type,
_Outptr_ struct bpf_object** object,
_Outptr_result_maybenull_z_ const char** error_message) NO_EXCEPT_TRY
{
EBPF_LOG_ENTRY();
ebpf_assert(buffer);
ebpf_assert(object);
ebpf_assert(error_message);
*error_message = nullptr;

EBPF_LOG_MESSAGE(
EBPF_TRACELOG_LEVEL_INFO, EBPF_TRACELOG_KEYWORD_API, "ebpf_object_open_memory: loading from memory");

ebpf_object_t* new_object = new (std::nothrow) ebpf_object_t();
if (new_object == nullptr) {
EBPF_RETURN_RESULT(EBPF_NO_MEMORY);
}

set_global_program_and_attach_type(program_type, attach_type);

std::vector<uint8_t> data(buffer, buffer + buffer_size);

ebpf_result_t result =
_initialize_ebpf_object_from_file(data, object_name, pin_root_path, new_object, error_message);
if (result != EBPF_SUCCESS) {
std::string log_message = "ebpf_object_open_memory: error loading memory:";
log_message += ", error message:";
Alan-Jowett marked this conversation as resolved.
Show resolved Hide resolved
log_message += *error_message != NULL ? *error_message : "none";
EBPF_LOG_MESSAGE_STRING(
EBPF_TRACELOG_LEVEL_ERROR, EBPF_TRACELOG_KEYWORD_API, "*** ERROR *** ", log_message.c_str());
goto Done;
}

*object = new_object;
{
std::unique_lock lock(_ebpf_state_mutex);
_ebpf_objects.emplace_back(*object);
}

Done:
clear_map_descriptors();
if (result != EBPF_SUCCESS) {
_clean_up_ebpf_object(new_object);

// Libbpf API absorbs the error message string.
// Print it here for debugging purposes.
if (*error_message) {
EBPF_LOG_MESSAGE_STRING(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_API,
"ebpf_object_open_memory failed (error_message)",
*error_message);
}
}
EBPF_RETURN_RESULT(result);
}
CATCH_NO_MEMORY_EBPF_RESULT

static inline bool
_ebpf_is_map_in_map(_In_ const ebpf_map_t* map) noexcept
{
Expand Down
23 changes: 23 additions & 0 deletions libs/api/libbpf_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,29 @@ bpf_object__open_file(const char* path, const struct bpf_object_open_opts* opts)
return object;
}

struct bpf_object*
bpf_object__open_mem(const void* buffer, size_t buffer_size, const struct bpf_object_open_opts* opts)
{
if (!buffer) {
return (struct bpf_object*)libbpf_err_ptr(-EINVAL);
}

struct bpf_object* object = nullptr;
const char* error_message;
ebpf_result_t result = ebpf_object_open_memory(
reinterpret_cast<const uint8_t*>(buffer),
buffer_size,
((opts) ? opts->object_name : nullptr),
((opts) ? opts->pin_root_path : nullptr),
nullptr,
nullptr,
&object,
&error_message);
ebpf_free_string(error_message);
libbpf_result_err(result); // Set errno.
return object;
}

int
bpf_object__load_xattr(struct bpf_object_load_attr* attr)
{
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/libbpf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "test_helper.hpp"

#include <chrono>
#include <fstream>
#include <stop_token>
#include <thread>

Expand Down Expand Up @@ -2574,6 +2575,79 @@ TEST_CASE("bpf_object__load with .o", "[libbpf]")
bpf_object__close(object);
}

TEST_CASE("bpf_object__load with .o from memory", "[libbpf]")
{
_test_helper_libbpf test_helper;
test_helper.initialize();

const char* my_object_name = "my_object_name";
struct bpf_object_open_opts opts = {0};
opts.object_name = my_object_name;

// Read droppacket.o into a std::vector.
std::vector<uint8_t> object_data;
std::fstream file("droppacket.o", std::ios::in | std::ios::binary);
REQUIRE(file.is_open());
file.seekg(0, std::ios::end);
object_data.resize(file.tellg());
file.seekg(0, std::ios::beg);
file.read((char*)object_data.data(), object_data.size());
file.close();

struct bpf_object* object = bpf_object__open_mem(object_data.data(), object_data.size(), &opts);
REQUIRE(object != nullptr);

REQUIRE(strcmp(bpf_object__name(object), my_object_name) == 0);

struct bpf_program* program = bpf_object__find_program_by_name(object, "DropPacket");
REQUIRE(program != nullptr);

REQUIRE(bpf_program__fd(program) == ebpf_fd_invalid);
REQUIRE(bpf_program__type(program) == BPF_PROG_TYPE_XDP);

// Make sure we can override the program type if desired.
REQUIRE(bpf_program__set_type(program, BPF_PROG_TYPE_BIND) == 0);
REQUIRE(bpf_program__type(program) == BPF_PROG_TYPE_BIND);

REQUIRE(bpf_program__set_type(program, BPF_PROG_TYPE_XDP) == 0);

struct bpf_map* map = bpf_object__next_map(object, nullptr);
REQUIRE(map != nullptr);
REQUIRE(strcmp(bpf_map__name(map), "dropped_packet_map") == 0);
REQUIRE(bpf_map__fd(map) == ebpf_fd_invalid);
map = bpf_object__next_map(object, map);
REQUIRE(strcmp(bpf_map__name(map), "interface_index_map") == 0);
REQUIRE(bpf_map__fd(map) == ebpf_fd_invalid);
map = bpf_object__next_map(object, map);
REQUIRE(map == nullptr);

// Trying to attach the program should fail since it's not loaded yet.
bpf_link_ptr link(bpf_program__attach(program));
REQUIRE(link == nullptr);
REQUIRE(libbpf_get_error(link.get()) == -EINVAL);

// Load the program.
REQUIRE(bpf_object__load(object) == 0);

// Attach should now succeed.
link.reset(bpf_program__attach(program));
REQUIRE(link != nullptr);

// The maps should now have FDs.
map = bpf_object__next_map(object, nullptr);
REQUIRE(map != nullptr);
REQUIRE(strcmp(bpf_map__name(map), "dropped_packet_map") == 0);
REQUIRE(bpf_map__fd(map) != ebpf_fd_invalid);
map = bpf_object__next_map(object, map);
REQUIRE(strcmp(bpf_map__name(map), "interface_index_map") == 0);
REQUIRE(bpf_map__fd(map) != ebpf_fd_invalid);
map = bpf_object__next_map(object, map);
REQUIRE(map == nullptr);

REQUIRE(bpf_link__destroy(link.release()) == 0);
bpf_object__close(object);
}

// Test bpf() with the following command ids:
// BPF_PROG_LOAD, BPF_OBJ_GET_INFO_BY_FD, BPF_PROG_GET_NEXT_ID,
// BPF_MAP_CREATE, BPF_MAP_GET_NEXT_ID, BPF_PROG_BIND_MAP,
Expand Down
Loading