diff --git a/.github/assets/sum.bpf.o b/.github/assets/sum.bpf.o new file mode 100644 index 0000000..f565c6b Binary files /dev/null and b/.github/assets/sum.bpf.o differ diff --git a/.github/workflows/test-aot-cli.yml b/.github/workflows/test-aot-cli.yml index 0b8b52f..74fc7c7 100644 --- a/.github/workflows/test-aot-cli.yml +++ b/.github/workflows/test-aot-cli.yml @@ -2,9 +2,9 @@ name: Build and test AOT cli on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: true @@ -26,17 +26,16 @@ jobs: - name: Build and install everything run: | - cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 + cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBPFTIME_ENABLE_UNIT_TESTING=1 -DBUILD_LLVM_AOT_CLI=1 cmake --build build --target all -j - name: Do compilation & run run: | export PATH=$PATH:~/.bpftime - cd .github/assets - bpftime-vm build sum.bpf.o + ./build/cli/bpftime-vm build .github/assets/sum.bpf.o echo "AwAAAAEAAAACAAAAAwAAAA==" | base64 -d > test.bin - program_output=$(bpftime-vm run test.o test.bin) + program_output=$(./build/cli/bpftime-vm run test.o test.bin) echo $program_output - if echo $program_output | grep "Output: 6"; then + if echo $program_output | grep "Return value: 6"; then echo "Successful!" exit 0 else diff --git a/CMakeLists.txt b/CMakeLists.txt index fe11449..ea11aee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,8 @@ if (${LLVM_PACKAGE_VERSION} VERSION_LESS 15) message(FATAL_ERROR "LLVM version must be >=15") endif() -option(ENABLE_LLVM_SHARED "Link shared library of LLVM" YES) +option(ENABLE_LLVM_SHARED "Link shared library of LLVM" NO) +option(BUILD_LLVM_AOT_CLI "Build AOT cli, which rely on libbpf" NO) if(ENABLE_LLVM_SHARED) set(LLVM_LIBS LLVM) @@ -75,9 +76,12 @@ endif() # if BPFTIME_LLVM_JIT is set, then it's built in the bpftime project. # If not, it's built as a standalone library. if(${BPFTIME_LLVM_JIT}) - # only build cli in the main project because it relies on libbpf + # build cli in the main project because it relies on libbpf add_subdirectory(cli) else() + if(${BUILD_LLVM_AOT_CLI}) + add_subdirectory(cli) + endif() if(${BPFTIME_ENABLE_UNIT_TESTING}) message(INFO " Adding Catch2 seperately..") FetchContent_Declare( diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 305ef8a..b7020fd 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -15,9 +15,31 @@ target_include_directories(bpftime-vm-cli ${LIBBPF_INCLUDE_DIRS} ) -add_dependencies(bpftime-vm-cli spdlog::spdlog argparse bpftime_llvm_jit_vm libbpf) +include(ExternalProject) +ExternalProject_Add( + libbpf_project + PREFIX ${CMAKE_BINARY_DIR}/libbpf + GIT_REPOSITORY https://github.com/libbpf/libbpf.git + GIT_TAG v1.2.0 # Replace with the version you need + CONFIGURE_COMMAND "" + BUILD_COMMAND make -C src -j + BUILD_IN_SOURCE TRUE + INSTALL_COMMAND "" + BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/libbpf/src/libbpf.a +) + +ExternalProject_Get_Property(libbpf_project source_dir) +set(LIBBPF_INCLUDE_DIRS ${source_dir}/src) +set(LIBBPF_LIBRARIES ${source_dir}/src/libbpf.a) + +# Ensure libbpf is built before your target +add_dependencies(bpftime-vm-cli libbpf_project) + +include_directories(${LIBBPF_INCLUDE_DIRS}) + +add_dependencies(bpftime-vm-cli spdlog::spdlog bpftime_llvm_jit_vm) target_link_libraries(bpftime-vm-cli - PRIVATE spdlog::spdlog argparse bpftime_llvm_jit_vm + PRIVATE spdlog::spdlog bpftime_llvm_jit_vm ${LIBBPF_LIBRARIES} elf z diff --git a/cli/main.cpp b/cli/main.cpp index 2ecfef6..6c28c4e 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -1,7 +1,5 @@ -#include #include "spdlog/spdlog.h" #include "spdlog/cfg/env.h" -#include #include #include #include @@ -9,171 +7,184 @@ #include #include #include -#include -#include -#include #include #include #include #include +#include "llvmbpf.hpp" -using namespace llvm; -using namespace llvm::orc; +using namespace bpftime; -extern "C" int _libbpf_print(libbpf_print_level level, const char *fmt, - va_list ap) +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) { - char buf[2048]; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" - int ret = vsnprintf(buf, sizeof(buf), fmt, ap); -#pragma GCC diagnostic pop - std::string out(buf); - if (out.back() == '\n') - out.pop_back(); - if (level == LIBBPF_WARN) { - SPDLOG_WARN("{}", out); - } else if (level == LIBBPF_INFO) { - SPDLOG_INFO("{}", out); - } else if (level == LIBBPF_DEBUG) { - SPDLOG_INFO("{}", out); - } - return ret; + return vfprintf(stderr, format, args); +} + +static void print_usage(const std::string &program_name) +{ + std::cerr << "Usage: " << program_name << " [options]\n" + << "Commands:\n" + << " build [-o ]\n" + << " Build native ELF(s) from eBPF ELF. Each program in the eBPF ELF will be built into a single native ELF.\n" + << " run [MEMORY]\n" + << " Run a native eBPF program.\n"; +} + +static std::optional parse_optional_argument(int argc, const char **argv, int &i, const std::string &option) +{ + if (std::string(argv[i]) == option && i + 1 < argc) { + return argv[++i]; + } + return std::nullopt; } static int build_ebpf_program(const std::string &ebpf_elf, - const std::filesystem::path &output) + const std::filesystem::path &output) { - bpf_object *obj = bpf_object__open(ebpf_elf.c_str()); - if (!obj) { - SPDLOG_CRITICAL("Unable to open BPF elf: {}", errno); - return 1; - } - std::unique_ptr elf( - obj, bpf_object__close); - bpf_program *prog; - bpf_object__for_each_program(prog, elf.get()) - { - auto name = bpf_program__name(prog); - SPDLOG_INFO("Processing program {}", name); - bpftime::vm::llvm::bpftime_llvm_jit_vm vm; - - if (vm.load_code((const void *)bpf_program__insns(prog), - (uint32_t)bpf_program__insn_cnt(prog) * 8) < - 0) { - SPDLOG_ERROR( - "Unable to load instruction of program {}: ", - name, vm.get_error_message()); - return 1; - } - llvm_bpf_jit_context ctx(&vm); - auto result = ctx.do_aot_compile(); - auto out_path = output / (std::string(name) + ".o"); - std::ofstream ofs(out_path, std::ios::binary); - ofs.write((const char *)result.data(), result.size()); - SPDLOG_INFO("Program {} written to {}", name, out_path.c_str()); - } - return 0; + bpf_object *obj = bpf_object__open(ebpf_elf.c_str()); + if (!obj) { + SPDLOG_CRITICAL("Unable to open BPF ELF: {}", errno); + return 1; + } + std::unique_ptr elf( + obj, bpf_object__close); + bpf_program *prog; + bpf_object__for_each_program(prog, elf.get()) + { + auto name = bpf_program__name(prog); + SPDLOG_INFO("Processing program {}", name); + bpftime_llvm_jit_vm vm; + + if (vm.load_code((const void *)bpf_program__insns(prog), + (uint32_t)bpf_program__insn_cnt(prog) * 8) < 0) { + SPDLOG_ERROR("Unable to load instructions of program {}: {}", + name, vm.get_error_message()); + return 1; + } + auto result = vm.do_aot_compile(false); + auto out_path = output / (std::string(name) + ".o"); + std::ofstream ofs(out_path, std::ios::binary); + ofs.write((const char *)result.data(), result.size()); + SPDLOG_INFO("Program {} written to {}", name, out_path.c_str()); + } + return 0; } -static ExitOnError exit_on_error; using bpf_func = uint64_t (*)(const void *, uint64_t); static int run_ebpf_program(const std::filesystem::path &elf, - std::optional memory) + std::optional memory) { - auto jit = exit_on_error(LLJITBuilder().create()); - - if (auto file_buf = MemoryBuffer::getFile(elf.c_str()); file_buf) { - exit_on_error(jit->addObjectFile(std::move(*file_buf))); - } else { - SPDLOG_ERROR("Unable to read elf file: {}", - file_buf.getError().message()); - return 1; - } - if (auto ret = jit->lookup("bpf_main"); ret) { - auto func = ret->toPtr(); - uint64_t result; - if (memory.has_value()) { - std::ifstream ifs(*memory, - std::ios::binary | std::ios::ate); - if (!ifs.is_open()) { - SPDLOG_ERROR("Unable to open memory file"); - return 1; - } - std::streamsize size = ifs.tellg(); - ifs.seekg(0, std::ios::beg); - std::vector buffer(size); - if (!ifs.read((char *)buffer.data(), size)) { - SPDLOG_ERROR("Unable to read memory"); - return 1; - } - SPDLOG_INFO("Memory size: {}", size); - result = func(buffer.data(), buffer.size()); - } else { - result = func(nullptr, 0); - } - SPDLOG_INFO("Output: {}", result); - return 0; - } else { - std::string buf; - raw_string_ostream os(buf); - os << ret.takeError(); - SPDLOG_ERROR("Unable to lookup bpf_main: {}", buf); - return 1; - } + std::ifstream file(elf, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + SPDLOG_CRITICAL("Unable to open ELF file: {}", elf.string()); + return 1; + } + + auto size = file.tellg(); + std::vector file_buffer(size); + + file.seekg(0, std::ios::beg); + if (!file.read((char *)file_buffer.data(), size)) { + SPDLOG_CRITICAL("Failed to read ELF file: {}", elf.string()); + return 1; + } + + file.close(); + + bpftime_llvm_jit_vm vm; + auto func = vm.load_aot_object(file_buffer); + if (!func) { + SPDLOG_CRITICAL("Failed to load AOT object from ELF file: {}", + vm.get_error_message()); + return 1; + } + + uint64_t return_val; + if (memory) { + std::ifstream mem_file(*memory, std::ios::binary | std::ios::ate); + if (!mem_file.is_open()) { + SPDLOG_CRITICAL("Unable to open memory file: {}", *memory); + return 1; + } + + auto mem_size = mem_file.tellg(); + std::vector mem_buffer(mem_size); + + mem_file.seekg(0, std::ios::beg); + if (!mem_file.read((char *)mem_buffer.data(), mem_size)) { + SPDLOG_CRITICAL("Failed to read memory file: {}", *memory); + return 1; + } + + mem_file.close(); + + int res = vm.exec(mem_buffer.data(), mem_buffer.size(), return_val); + if (res < 0) { + SPDLOG_CRITICAL("Execution failed: {}", vm.get_error_message()); + return 1; + } + } else { + int res = vm.exec(nullptr, 0, return_val); + if (res < 0) { + SPDLOG_CRITICAL("Execution failed: {}", vm.get_error_message()); + return 1; + } + } + + SPDLOG_INFO("Program executed successfully. Return value: {}", return_val); + return 0; } int main(int argc, const char **argv) { - spdlog::cfg::load_env_levels(); - libbpf_set_print(_libbpf_print); - InitializeNativeTarget(); - InitializeNativeTargetAsmPrinter(); - argparse::ArgumentParser program(argv[0]); - - argparse::ArgumentParser build_command("build"); - build_command.add_description( - "Build native ELF(s) from eBPF ELF. Each program in the eBPF ELF will be built into a single native ELF"); - build_command.add_argument("-o", "--output") - .default_value(".") - .help("Output directory (There might be multiple output files for a single input)"); - build_command.add_argument("EBPF_ELF") - .help("Path to an eBPF ELF executable"); - - argparse::ArgumentParser run_command("run"); - run_command.add_description("Run an native eBPF program"); - run_command.add_argument("PATH").help("Path to the ELF file"); - run_command.add_argument("MEMORY") - .help("Path to the memory file") - .nargs(0, 1); - program.add_subparser(build_command); - program.add_subparser(run_command); - - try { - program.parse_args(argc, argv); - } catch (const std::exception &err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); - } - if (!program) { - std::cerr << program; - std::exit(1); - } - if (program.is_subcommand_used(build_command)) { - return build_ebpf_program( - build_command.get("EBPF_ELF"), - build_command.get("output")); - } else if (program.is_subcommand_used(run_command)) { - if (run_command.is_used("MEMORY")) { - return run_ebpf_program( - run_command.get("PATH"), - run_command.get("MEMORY")); - } else { - return run_ebpf_program( - run_command.get("PATH"), {}); - } - } - return 0; + spdlog::cfg::load_env_levels(); + libbpf_set_print(libbpf_print_fn); + + // Check for at least one argument (the command) + if (argc < 2) { + print_usage(argv[0]); + return 1; + } + + std::string command = argv[1]; + + if (command == "build") { + if (argc < 3) { + print_usage(argv[0]); + return 1; + } + + std::string ebpf_elf = argv[2]; + std::string output = "."; + + // Parse optional output argument + for (int i = 3; i < argc; ++i) { + auto opt_output = parse_optional_argument(argc, argv, i, "-o"); + if (opt_output) { + output = *opt_output; + } + } + + return build_ebpf_program(ebpf_elf, output); + } else if (command == "run") { + if (argc < 3) { + print_usage(argv[0]); + return 1; + } + + std::filesystem::path elf_path = argv[2]; + std::optional memory_file; + + if (argc > 3) { + memory_file = argv[3]; + } + + return run_ebpf_program(elf_path, memory_file); + } else { + std::cerr << "Unknown command: " << command << "\n"; + print_usage(argv[0]); + return 1; + } }