Skip to content

Commit

Permalink
Make the JIT'd code completely portable.
Browse files Browse the repository at this point in the history
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
  • Loading branch information
hawkinsw committed May 3, 2024
1 parent 04e04fa commit 62e5f21
Show file tree
Hide file tree
Showing 38 changed files with 2,149 additions and 383 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Expand Up @@ -29,6 +29,7 @@ add_subdirectory("vm")
STEP_TARGETS build)

if(UBPF_ENABLE_TESTS)
add_subdirectory("custom_tests")
add_subdirectory("ubpf_plugin")
if (NOT UBPF_SKIP_EXTERNAL)
endif()
Expand Down
3 changes: 2 additions & 1 deletion cmake/settings.cmake
Expand Up @@ -17,7 +17,8 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
-Wall
-Werror
-Iinc
-O2
-O0
-g
-Wunused-parameter
-fPIC
)
Expand Down
62 changes: 62 additions & 0 deletions custom_tests/CMakeLists.txt
@@ -0,0 +1,62 @@
# Copyright (c) Microsoft Corporation
# SPDX-License-Identifier: Apache-2.0

set(CMAKE_CXX_STANDARD 20)

file(GLOB test_descr_files ${CMAKE_SOURCE_DIR}/custom_tests/descrs/*.md)

add_library(ubpf_custom_test_support srcs/ubpf_custom_test_support.cc)

target_link_libraries(
ubpf_custom_test_support
ubpf
ubpf_settings
)

target_include_directories(ubpf_custom_test_support PUBLIC ".srcs/")
target_include_directories(ubpf_custom_test_support PRIVATE
"${CMAKE_SOURCE_DIR}/vm"
"${CMAKE_BINARY_DIR}/vm"
"${CMAKE_SOURCE_DIR}/vm/inc"
"${CMAKE_BINARY_DIR}/vm/inc"
)

set(QEMU_RUNNER "")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 AND (NOT CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL aarch64))
set(QEMU_RUNNER qemu-aarch64 -L /usr/aarch64-linux-gnu)
endif()

foreach(test_file ${test_descr_files})
get_filename_component(test_name ${test_file} NAME_WE)
set(test_source_path "${CMAKE_SOURCE_DIR}/custom_tests/srcs/${test_name}.cc")

add_executable(
${test_name}
${test_source_path}
)
target_include_directories(${test_name} PRIVATE
"${CMAKE_SOURCE_DIR}/vm"
"${CMAKE_BINARY_DIR}/vm"
"${CMAKE_SOURCE_DIR}/vm/inc"
"${CMAKE_BINARY_DIR}/vm/inc"
)
target_link_libraries(
${test_name}
ubpf
ubpf_custom_test_support
ubpf_settings
)
set(potential_input_file ${CMAKE_SOURCE_DIR}/custom_tests/data/${test_name}.input)
if (EXISTS ${potential_input_file})
list(JOIN QEMU_RUNNER " " QEMU_RUNNER_STR)
add_test(
NAME ${test_name}-Custom
COMMAND sh -c "cat ${potential_input_file} | ${QEMU_RUNNER_STR} $<TARGET_FILE:${test_name}>"
)
else()
add_test(
NAME ${test_name}-Custom
COMMAND ${QEMU_RUNNER} $<TARGET_FILE:${test_name}>
)
endif()
endforeach()
35 changes: 35 additions & 0 deletions custom_tests/README.md
@@ -0,0 +1,35 @@
## Writing a uBPF Custom Tests

Custom tests are enabled by creating two (2) or three (3) different files in the `custom_tests` directory.

### Files Of a uBPF Custom Test

#### Description Files

The first file to create is the Description File. The Description File is a file with a `.md` extension that resides in the `descrs` directory. The purpose of this file is to identify the name of the test (everything before the `.md` extension) and provide a place to document the purpose of the test.

#### Source Files

The second file to create is the Source File. The Source file should reside in the `srcs` directory and have a name that matches its Description File (with the `.cc` extension rather than the `.md` extension).

#### Input Files

The final file is optional. The Input File resides in the `data` directory and should have the same name as the other two (2) files but with an `.input` extension rather than `.cc` or `.md` for the Source and Description File respectively. If present, the contents of this file will be given to the executed custom test over standard input.

### Building

The Source Files for a custom test are compiled using C++20 and are saved as an executable named according to the name of the test in the CMake build directory.

### Return Values

All successful tests should return `0`. All failing tests should return something other than `0`.

### Supporting Libraries

To reduce the boilerplate needed to write custom tests, there is a custom test library with several helpful functions. These functions are documented in the library's header file (`custom_tests/srcs/ubpf_custom_test_support.h`).

### Putting It Together

After describing the test's purpose in a Markdown syntax in a file named, say, `test_example.md` and stored in the `descrs` directory, you can write the test's Source Code (in C++20) and give it the name `test_example.cc` in the `srcs` directory. If the test needs input, you can save that input in the tests Input File (`test_input.input`) in the `data` directory.

Because all the files are present, this test will be run when the CTest target is invoked. Because there the optional `test_input.input` file is present, the contents of that file will be given to the executable via standard input.
@@ -0,0 +1 @@
b7 01 00 00 01 02 03 04 85 00 00 00 01 00 00 00 95 00 00 00 00 00 00 00
@@ -0,0 +1 @@
85 00 00 00 01 00 00 00 95 00 00 00 00 00 00 00
@@ -0,0 +1 @@
8f 00 00 00 01 00 00 00
1 change: 1 addition & 0 deletions custom_tests/data/ubpf_test_update_dispatcher.input
@@ -0,0 +1 @@
85 00 00 00 01 00 00 00 95 00 00 00 00 00 00 00
1 change: 1 addition & 0 deletions custom_tests/data/ubpf_test_update_helpers.input
@@ -0,0 +1 @@
85 00 00 00 01 00 00 00 95 00 00 00 00 00 00 00
@@ -0,0 +1,7 @@
## Test Description

This custom test program tests whether JIT'd eBPF programs properly pass the original context
to external helper dispatcher even when (eBPF) register r0 has been modified. The original
context to the eBPF program is passed in (eBPF) register r0. Subsequent changes to that
register by the eBPF program should *not* affect that context (which is given to the
helper function external dispatcher).
@@ -0,0 +1,4 @@
## Test Description

This custom test program tests whether JIT'd eBPF programs properly pass the context
to external helper dispatcher.
4 changes: 4 additions & 0 deletions custom_tests/descrs/ubpf_test_jit_buffer_too_small.md
@@ -0,0 +1,4 @@
## Test Description

This custom test program tests whether compilation fails (with the proper error message) when
the user gives a buffer that is too small to accommodate the size of the JIT'd code.
4 changes: 4 additions & 0 deletions custom_tests/descrs/ubpf_test_jit_unexpected_instruction.md
@@ -0,0 +1,4 @@
## Test Description

This custom test program tests that an eBPF program fails to load (with the proper error) in
the presence of a program with an invalid instruction opcode.
4 changes: 4 additions & 0 deletions custom_tests/descrs/ubpf_test_update_dispatcher.md
@@ -0,0 +1,4 @@
## Test Description

This custom test program tests whether it is possible to update the external helper dispatcher
after an eBPF program has been compiled.
4 changes: 4 additions & 0 deletions custom_tests/descrs/ubpf_test_update_helpers.md
@@ -0,0 +1,4 @@
## Test Description

This custom test program tests whether it is possible to update the external helper
functions for an eBPF program that has already been JIT'd.
225 changes: 225 additions & 0 deletions custom_tests/srcs/test_helpers.h
@@ -0,0 +1,225 @@
// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once
#include "ubpf.h"
#include <string.h>
#include <cmath>
#include <cstdint>
#include <map>

#if !defined(UNREFERENCED_PARAMETER)
#define UNREFERENCED_PARAMETER(P) (void)(P)
#endif

static uint64_t
gather_bytes(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
return ((uint64_t)(a & 0xff) << 32) | ((uint64_t)(b & 0xff) << 24) | ((uint64_t)(c & 0xff) << 16) |
((uint64_t)(d & 0xff) << 8) | (e & 0xff);
};

static uint64_t
memfrob(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);

uint8_t* p = reinterpret_cast<uint8_t*>(a);
for (uint64_t i = 0; i < b; i++) {
p[i] ^= 42;
}
return 0;
};

;

static uint64_t
no_op(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);

return 0;
}

static uint64_t
sqrti(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);

return static_cast<uint64_t>(std::sqrt(a));
}

static uint64_t
strcmp_ext(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return strcmp(reinterpret_cast<char*>(a), reinterpret_cast<char*>(b));
}

static uint64_t
unwind(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return a;
}

static std::map<uint32_t, external_function_t> helper_functions = {
{0, gather_bytes},
{1, memfrob},
{2, no_op},
{3, sqrti},
{4, strcmp_ext},
{5, unwind},
};

static uint64_t
dispatcher_test_memfrob(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 42;

}

static uint64_t
updated_dispatcher_test_memfrob(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 43;
}

static uint64_t
dispatcher_gather_bytes(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 44;

}

static uint64_t
updated_dispatcher_gather_bytes(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 45;
}

static uint64_t
dispatcher_no_op(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 46;

}

static uint64_t
updated_dispatcher_no_op(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 47;
}

static uint64_t
dispatcher_sqrti(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 48;

}

static uint64_t
updated_dispatcher_sqrti(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 49;
}

static uint64_t
dispatcher_strcmp_ext(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 50;

}

static uint64_t
updated_dispatcher_strcmp_ext(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 51;
}

static uint64_t
dispatcher_unwind(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 52;

}

static uint64_t
updated_dispatcher_unwind(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)
{
UNREFERENCED_PARAMETER(a);
UNREFERENCED_PARAMETER(b);
UNREFERENCED_PARAMETER(c);
UNREFERENCED_PARAMETER(d);
UNREFERENCED_PARAMETER(e);
return 53;
}

0 comments on commit 62e5f21

Please sign in to comment.