Skip to content
Draft
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
20 changes: 20 additions & 0 deletions custom_tests/descrs/ubpf_test_large_program.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Large Program Test

This test validates that programs with more than 65,536 instructions can be loaded, JIT compiled, and executed correctly when `ubpf_set_max_instructions()` is used to increase the limit.

The test performs the following:
1. Creates a VM and sets max_instructions to 100,000
2. Generates a program with 66,000 instructions (NOP-like JA instructions with offset 0) plus an EXIT
3. Loads the program into the VM
4. JIT compiles the program
5. Executes the JIT-compiled program

This validates that:
- The type change from uint16_t to uint32_t for num_insts works correctly
- Programs beyond the old 65,536 limit can be loaded
- The validation path handles large instruction counts correctly
- The JIT compiler can handle large programs (> 65,536 instructions)
- JIT-compiled large programs execute correctly

Note: The test skips interpreter execution because executing 66,000 sequential NOP-like
instructions via the interpreter would take prohibitively long for a test.
14 changes: 14 additions & 0 deletions custom_tests/descrs/ubpf_test_max_insts_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Boundary and API Test

This test validates the boundary conditions and API for configurable instruction limits:

1. Test that 65,535 instructions (just under the default limit) load successfully
2. Test that 65,536 instructions (at the default limit) fail to load
3. Test that ubpf_set_max_instructions() works correctly
4. Test that ubpf_set_max_instructions() fails if code is already loaded
5. Test setting a lower limit than default

This ensures:
- The default limit of 65,536 is preserved for backward compatibility
- The API correctly validates and enforces limits
- Programs at the boundary behave as expected
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extern "C"
#include "ubpf_custom_test_support.h"

int
stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
stack_usage_calculator(const struct ubpf_vm* vm, uint32_t pc, void* cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extern "C"
#include "ubpf_custom_test_support.h"

int
stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
stack_usage_calculator(const struct ubpf_vm* vm, uint32_t pc, void* cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extern "C"
#include "ubpf_custom_test_support.h"

int
stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
stack_usage_calculator(const struct ubpf_vm* vm, uint32_t pc, void* cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
Expand Down
4 changes: 2 additions & 2 deletions custom_tests/srcs/ubpf_test_frame_pointer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extern "C"
#include "ubpf_custom_test_support.h"

int
stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
stack_usage_calculator(const struct ubpf_vm* vm, uint32_t pc, void* cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
Expand All @@ -25,7 +25,7 @@ stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
}

int
overwrite_stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
overwrite_stack_usage_calculator(const struct ubpf_vm* vm, uint32_t pc, void* cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
Expand Down
108 changes: 108 additions & 0 deletions custom_tests/srcs/ubpf_test_large_program.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) GitHub Copilot
// SPDX-License-Identifier: Apache-2.0

#include <cstdint>
#include <cstring>
#include <iostream>
#include <memory>
#include <vector>

extern "C"
{
#include "ubpf.h"
#include "ebpf.h"
}

int
main(int argc, char** argv)
{
(void)argc;
(void)argv;

// Create a program with more than 65,536 instructions
// Use a reasonable number that's large enough to demonstrate the feature
// but small enough to execute in reasonable time
const uint32_t num_instructions = 66000;
std::vector<struct ebpf_inst> program(num_instructions);

// First instruction: set r0 = 0
program[0].opcode = EBPF_OP_MOV_IMM;
program[0].dst = 0; // r0
program[0].src = 0;
program[0].offset = 0;
program[0].imm = 0; // value 0

// Fill rest with NOP-like JA instructions (jump with offset 0)
for (uint32_t i = 1; i < num_instructions - 1; i++) {
program[i].opcode = EBPF_OP_JA;
program[i].dst = 0;
program[i].src = 0;
program[i].offset = 0;
program[i].imm = 0;
}

// Last instruction is EXIT
program[num_instructions - 1].opcode = EBPF_OP_EXIT;
program[num_instructions - 1].dst = 0;
program[num_instructions - 1].src = 0;
program[num_instructions - 1].offset = 0;
program[num_instructions - 1].imm = 0;

std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);
if (!vm) {
std::cerr << "Failed to create VM" << std::endl;
return 1;
}

// Set max instructions to allow this large program
if (ubpf_set_max_instructions(vm.get(), 100000) != 0) {
std::cerr << "Failed to set max instructions" << std::endl;
return 1;
}

// Set a larger JIT buffer size for large programs
// Estimate: ~50 bytes per instruction + overhead
size_t jit_buffer_size = num_instructions * 50 + 4096;
if (ubpf_set_jit_code_size(vm.get(), jit_buffer_size) != 0) {
std::cerr << "Failed to set JIT buffer size" << std::endl;
return 1;
}

// Load the program
char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), num_instructions * sizeof(struct ebpf_inst), &errmsg);
if (result != 0) {
std::cerr << "Failed to load program: " << (errmsg ? errmsg : "unknown error") << std::endl;
free(errmsg);
return 1;
}

std::cout << "Successfully loaded program with " << num_instructions << " instructions" << std::endl;

// Test JIT compilation with large program
errmsg = nullptr;
ubpf_jit_fn jit_fn = ubpf_compile(vm.get(), &errmsg);
if (!jit_fn) {
std::cerr << "Failed to JIT compile: " << (errmsg ? errmsg : "unknown error") << std::endl;
free(errmsg);
return 1;
}

std::cout << "Successfully JIT compiled program with " << num_instructions << " instructions" << std::endl;

// Execute via JIT - the program sets r0 to 0 and exits
uint64_t jit_result = jit_fn(nullptr, 0);

if (jit_result != 0) {
std::cerr << "JIT execution returned unexpected value: " << jit_result << " (expected 0)" << std::endl;
return 1;
}

std::cout << "JIT execution result: " << jit_result << " (correct)" << std::endl;

// Note: We skip interpreter execution because executing 66,000 sequential NOP-like
// instructions via the interpreter would take too long for a test

std::cout << "Test passed!" << std::endl;
return 0;
}
185 changes: 185 additions & 0 deletions custom_tests/srcs/ubpf_test_max_insts_api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c) GitHub Copilot
// SPDX-License-Identifier: Apache-2.0

#include <cstdint>
#include <cstring>
#include <iostream>
#include <memory>
#include <vector>

extern "C"
{
#include "ubpf.h"
#include "ebpf.h"
}

static std::vector<struct ebpf_inst>
generate_program(uint32_t num_instructions)
{
std::vector<struct ebpf_inst> program(num_instructions);

// Fill with NOP-like JA instructions
for (uint32_t i = 0; i < num_instructions - 1; i++) {
program[i].opcode = EBPF_OP_JA;
program[i].dst = 0;
program[i].src = 0;
program[i].offset = 0;
program[i].imm = 0;
}

// Last instruction is EXIT
program[num_instructions - 1].opcode = EBPF_OP_EXIT;
program[num_instructions - 1].dst = 0;
program[num_instructions - 1].src = 0;
program[num_instructions - 1].offset = 0;
program[num_instructions - 1].imm = 0;

return program;
}

int
main(int argc, char** argv)
{
(void)argc;
(void)argv;

// Test 1: 65,535 instructions (just under default limit) should work
{
std::cout << "Test 1: Loading 65,535 instructions (just under default limit)..." << std::endl;
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);
auto program = generate_program(65535);

char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), program.size() * sizeof(struct ebpf_inst), &errmsg);
if (result != 0) {
std::cerr << "Test 1 FAILED: Could not load 65,535 instructions: "
<< (errmsg ? errmsg : "unknown error") << std::endl;
free(errmsg);
return 1;
}
std::cout << "Test 1 PASSED" << std::endl;
}

// Test 2: 65,536 instructions (at default limit) should fail
{
std::cout << "Test 2: Loading 65,536 instructions (at default limit - should fail)..." << std::endl;
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);
auto program = generate_program(65536);

char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), program.size() * sizeof(struct ebpf_inst), &errmsg);
if (result == 0) {
std::cerr << "Test 2 FAILED: Should not be able to load 65,536 instructions with default limit" << std::endl;
return 1;
}

// Check that error message mentions instruction limit
if (errmsg == nullptr || strstr(errmsg, "too many instructions") == nullptr) {
std::cerr << "Test 2 FAILED: Expected 'too many instructions' error, got: "
<< (errmsg ? errmsg : "null") << std::endl;
free(errmsg);
return 1;
}
free(errmsg);
std::cout << "Test 2 PASSED" << std::endl;
}

// Test 3: ubpf_set_max_instructions() allows loading larger programs
{
std::cout << "Test 3: Loading 70,000 instructions after setting max to 100,000..." << std::endl;
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);

if (ubpf_set_max_instructions(vm.get(), 100000) != 0) {
std::cerr << "Test 3 FAILED: Could not set max instructions" << std::endl;
return 1;
}

auto program = generate_program(70000);
char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), program.size() * sizeof(struct ebpf_inst), &errmsg);
if (result != 0) {
std::cerr << "Test 3 FAILED: Could not load 70,000 instructions: "
<< (errmsg ? errmsg : "unknown error") << std::endl;
free(errmsg);
return 1;
}
std::cout << "Test 3 PASSED" << std::endl;
}

// Test 4: ubpf_set_max_instructions() fails if code is already loaded
{
std::cout << "Test 4: Setting max instructions after loading code (should fail)..." << std::endl;
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);

auto program = generate_program(100);
char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), program.size() * sizeof(struct ebpf_inst), &errmsg);
if (result != 0) {
std::cerr << "Test 4 FAILED: Could not load program: "
<< (errmsg ? errmsg : "unknown error") << std::endl;
free(errmsg);
return 1;
}

// Now try to set max instructions - should fail
result = ubpf_set_max_instructions(vm.get(), 200000);
if (result == 0) {
std::cerr << "Test 4 FAILED: Should not be able to set max instructions after loading code" << std::endl;
return 1;
}
std::cout << "Test 4 PASSED" << std::endl;
}

// Test 5: Setting a lower limit than default
{
std::cout << "Test 5: Setting max instructions to 1,000 and loading 1,001 instructions (should fail)..." << std::endl;
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);

if (ubpf_set_max_instructions(vm.get(), 1000) != 0) {
std::cerr << "Test 5 FAILED: Could not set max instructions" << std::endl;
return 1;
}

auto program = generate_program(1001);
char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), program.size() * sizeof(struct ebpf_inst), &errmsg);
if (result == 0) {
std::cerr << "Test 5 FAILED: Should not be able to load 1,001 instructions with limit of 1,000" << std::endl;
return 1;
}

if (errmsg == nullptr || strstr(errmsg, "too many instructions") == nullptr) {
std::cerr << "Test 5 FAILED: Expected 'too many instructions' error" << std::endl;
free(errmsg);
return 1;
}
free(errmsg);
std::cout << "Test 5 PASSED" << std::endl;
}

// Test 6: Setting max_insts to 0 uses default
{
std::cout << "Test 6: Setting max instructions to 0 (should use default 65536)..." << std::endl;
std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);

if (ubpf_set_max_instructions(vm.get(), 0) != 0) {
std::cerr << "Test 6 FAILED: Could not set max instructions to 0" << std::endl;
return 1;
}

// Should be able to load just under default limit
auto program = generate_program(65535);
char* errmsg = nullptr;
int result = ubpf_load(vm.get(), program.data(), program.size() * sizeof(struct ebpf_inst), &errmsg);
if (result != 0) {
std::cerr << "Test 6 FAILED: Could not load 65,535 instructions with default limit: "
<< (errmsg ? errmsg : "unknown error") << std::endl;
free(errmsg);
return 1;
}
std::cout << "Test 6 PASSED" << std::endl;
}

std::cout << "All tests passed!" << std::endl;
return 0;
}
Loading
Loading