diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..f4bd0306 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "Ubuntu", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/base:noble" + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f33a02cd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/test-attach.yml b/.github/workflows/test-attach.yml index 4a556fc6..a925f767 100644 --- a/.github/workflows/test-attach.yml +++ b/.github/workflows/test-attach.yml @@ -26,24 +26,76 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - + - name: Install dependencies + if: "matrix.container == 'ubuntu-2204'" + run: | + apt-get install -y lcov libzstd-dev libboost-all-dev gpg + - name: Install lcov + if: "matrix.container == 'fedora-39'" + run: | + dnf install -y lcov + - name: Build for frida uprobe attach tests run: | - cmake -B build + cmake -DTEST_LCOV=ON -B build cmake --build build --config Debug --target bpftime_frida_uprobe_attach_tests -j$(nproc) - name: Run frida uprobe attach tests run: | ./build/attach/frida_uprobe_attach_impl/bpftime_frida_uprobe_attach_tests - + - name: Generate frida uprobe attach test coverage (Fedora) + if: "matrix.container == 'fedora-39'" + run: | + lcov --capture --directory . --output-file coverage-uprobe.info + lcov --remove coverage-uprobe.info '/usr/*' --output-file coverage-uprobe.info + lcov --list coverage-uprobe.info + - name: Generate frida uprobe attach test coverage (Ubuntu) + if: "matrix.container == 'ubuntu-2204'" + run: | + lcov --capture --directory . --output-file coverage-uprobe.info --gcov-tool $(which gcov-12) + lcov --remove coverage-uprobe.info '/usr/*' --output-file coverage-uprobe.info + lcov --list coverage-uprobe.info + - name: Upload uprobe coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-uprobe-${{matrix.container}} + include-hidden-files: false + path: | + ./coverage-uprobe.info - name: Remove the build run: rm -rf build - name: Build syscall trace uprobe attach tests run: | - cmake -B build + cmake -DTEST_LCOV=ON -B build cmake --build build --config Debug --target bpftime_syscall_trace_attach_tests -j$(nproc) - name: Run syscall trace uprobe attach tests run: | ./build/attach/syscall_trace_attach_impl/bpftime_syscall_trace_attach_tests + - name: Generate syscall trace uprobe attach coverage (Ubuntu) + if: "matrix.container == 'ubuntu-2204'" + run: | + lcov --capture --directory . --output-file coverage-syscall-trace.info --gcov-tool $(which gcov-12) + lcov --remove coverage-syscall-trace.info '/usr/*' --output-file coverage-syscall-trace.info + lcov --list coverage-syscall-trace.info + - name: Generate syscall trace uprobe attach coverage (Fedora) + if: "matrix.container == 'fedora-39'" + run: | + lcov --capture --directory . --output-file coverage-syscall-trace.info + lcov --remove coverage-syscall-trace.info '/usr/*' --output-file coverage-syscall-trace.info + lcov --list coverage-syscall-trace.info + - name: Upload uprobe coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-syscall-trace-${{matrix.container}} + include-hidden-files: false + path: | + ./coverage-syscall-trace.info + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true # optional (default = false) + files: ./coverage-syscall-trace.info, ./coverage-uprobe.info # optional + flags: attach tests (uprobe & syscall trace) + token: ${{ secrets.CODECOV_TOKEN }} # required + verbose: true # optional (default = false) diff --git a/.github/workflows/test-examples.yml b/.github/workflows/test-examples.yml index 5c27063e..ea38cfcb 100644 --- a/.github/workflows/test-examples.yml +++ b/.github/workflows/test-examples.yml @@ -44,18 +44,23 @@ jobs: -DBUILD_BPFTIME_DAEMON=1 \ -DCMAKE_CXX_FLAGS="-DDEFAULT_LOGGER_OUTPUT_PATH='\"console\"'" cmake --build build --config RelWithDebInfo --target install -j + - name: Build basic examples + run: | + make -C example -j - name: Upload build results (without jit) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{!matrix.enable_jit}} with: name: runtime-package-no-jit-${{matrix.container.name}} + include-hidden-files: true path: | ~/.bpftime - name: Upload build results (with jit) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{matrix.enable_jit}} with: name: runtime-package-jit-${{matrix.container.name}} + include-hidden-files: true path: | ~/.bpftime build-and-test: @@ -63,7 +68,7 @@ jobs: needs: [build-runtime] container: image: "manjusakalza/bpftime-base-image:${{matrix.container.image}}" - options: " ${{matrix.privilege_options.options}}" + options: --privileged -v /sys/kernel/debug/:/sys/kernel/debug:rw -v /sys/kernel/tracing:/sys/kernel/tracing:rw strategy: fail-fast: true matrix: @@ -159,13 +164,13 @@ jobs: steps: - name: Download prebuilt runtime (with jit) if: ${{matrix.enable_jit}} - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: runtime-package-jit-${{matrix.container.name}} path: ~/.bpftime - name: Download prebuilt runtime (without jit) if: ${{!matrix.enable_jit}} - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: runtime-package-no-jit-${{matrix.container.name}} path: ~/.bpftime diff --git a/.github/workflows/test-runtime.yml b/.github/workflows/test-runtime.yml index eafcf6a1..832a8969 100644 --- a/.github/workflows/test-runtime.yml +++ b/.github/workflows/test-runtime.yml @@ -24,19 +24,101 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' + - name: Install lcov + if: "matrix.container == 'ubuntu-2204'" + run: | + apt-get install -y lcov libzstd-dev libboost-all-dev + - name: Install lcov + if: "matrix.container == 'fedora-39'" + run: | + dnf install -y lcov + - name: Build run: | - cmake -DBPFTIME_LLVM_JIT=YES -DBPFTIME_ENABLE_UNIT_TESTING=YES -DCMAKE_BUILD_TYPE=Debug -B build + cmake -DTEST_LCOV=ON -DBPFTIME_LLVM_JIT=YES -DBPFTIME_ENABLE_UNIT_TESTING=YES -DCMAKE_BUILD_TYPE=Debug -B build cmake --build build --config Debug --target bpftime_runtime_tests -j$(nproc) - name: Test Runtime run: ./build/runtime/unit-test/bpftime_runtime_tests + - name: Generate runtime coverage (Ubuntu) + if: "matrix.container == 'ubuntu-2204'" + run: | + lcov --capture --directory . --output-file coverage-runtime.info --gcov-tool $(which gcov-12) + lcov --remove coverage-runtime.info '/usr/*' --output-file coverage-runtime.info + lcov --list coverage-runtime.info + - name: Generate runtime coverage (Fedora) + if: "matrix.container == 'fedora-39'" + run: | + lcov --capture --directory . --output-file coverage-runtime.info + lcov --remove coverage-runtime.info '/usr/*' --output-file coverage-runtime.info + lcov --list coverage-runtime.info + - name: Generate runtime coverage + run: | + lcov --capture --directory . --output-file coverage-runtime.info --gcov-tool $(which gcov-12) + lcov --remove coverage-runtime.info '/usr/*' --output-file coverage-runtime.info + lcov --list coverage-runtime.info + - name: Upload runtime coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-runtime-${{matrix.container}} + include-hidden-files: false + path: | + ./coverage-runtime.info + - name: build runtime with mpk enable run: | rm -rf build - cmake -Bbuild -DBPFTIME_LLVM_JIT=YES -DBPFTIME_ENABLE_UNIT_TESTING=YES -DBPFTIME_ENABLE_MPK=YES -DCMAKE_BUILD_TYPE=Debug + cmake -Bbuild -DTEST_LCOV=ON -DBPFTIME_LLVM_JIT=YES -DBPFTIME_ENABLE_UNIT_TESTING=YES -DBPFTIME_ENABLE_MPK=YES -DCMAKE_BUILD_TYPE=Debug cmake --build build --config Debug --target bpftime_runtime_tests -j$(nproc) + - name: test runtime with mpk + run: ./build/runtime/unit-test/bpftime_runtime_tests + + - name: build runtime with ubpf and verifier enable + run: | + rm -rf build + cmake -Bbuild -DBPFTIME_LLVM_JIT=0 -DBPFTIME_ENABLE_UNIT_TESTING=1 -DCMAKE_BUILD_TYPE=Release -DBPFTIME_ENABLE_LTO=1 \ + -DBUILD_BPFTIME_DAEMON=1 + cmake --build build --config Release + - name: build runtime without libbpf + run: | + rm -rf build + cmake -Bbuild -DBPFTIME_BUILD_WITH_LIBBPF=0 + cmake --build build --config Release + + - name: build runtime static lib + run: | + rm -rf build + cmake -Bbuild -DBPFTIME_BUILD_STATIC_LIB=1 + cmake --build build --config Release - name: test runtime with mpk run: ./build/runtime/unit-test/bpftime_runtime_tests + - name: Generate runtime with mpk enable coverage (Ubuntu) + if: "matrix.container == 'ubuntu-2204'" + run: | + lcov --capture --directory . --output-file coverage-runtime-mpk.info --gcov-tool $(which gcov-12) + lcov --remove coverage-runtime-mpk.info '/usr/*' --output-file coverage-runtime-mpk.info + lcov --list coverage-runtime-mpk.info + - name: Generate runtime with mpk enable coverage (Fedora) + if: "matrix.container == 'fedora-39'" + run: | + lcov --capture --directory . --output-file coverage-runtime-mpk.info + lcov --remove coverage-runtime-mpk.info '/usr/*' --output-file coverage-runtime-mpk.info + lcov --list coverage-runtime-mpk.info + - name: Upload runtime-mpk coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-runtime-mpk-${{matrix.container}} + include-hidden-files: false + path: | + ./coverage-runtime-mpk.info + + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true # optional (default = false) + files: ./coverage-runtime.info + flags: runtime tests + token: ${{ secrets.CODECOV_TOKEN }} # required + verbose: true # optional (default = false) + diff --git a/.github/workflows/test-verifier.yml b/.github/workflows/test-verifier.yml index 66862ebf..b2d2d602 100644 --- a/.github/workflows/test-verifier.yml +++ b/.github/workflows/test-verifier.yml @@ -19,10 +19,19 @@ jobs: run: | sudo apt-get update -y sudo apt-get install binutils-dev libboost1.74-all-dev libelf-dev zlib1g-dev ninja-build libyaml-cpp-dev -y + - name: Install lcov + run: | + sudo apt install -y lcov libzstd-dev - name: Build test target run: | - cmake -DBPFTIME_ENABLE_UNIT_TESTING=YES -DBPFTIME_LLVM_JIT=NO -DENABLE_EBPF_VERIFIER=YES -DCMAKE_BUILD_TYPE:STRING=Release -S . -B build -G Ninja + cmake -DTEST_LCOV=ON -DBPFTIME_ENABLE_UNIT_TESTING=YES -DBPFTIME_LLVM_JIT=NO -DENABLE_EBPF_VERIFIER=YES -DCMAKE_BUILD_TYPE:STRING=Release -S . -B build -G Ninja cmake --build build --config Release --target bpftime_verifier_tests - name: Run tests run: | ./build/bpftime-verifier/bpftime_verifier_tests + - name: upload runtime coverage + run: | + lcov --capture --directory . --output-file coverage.info + lcov --remove coverage.info '/usr/*' --output-file coverage.info + lcov --list coverage.info + diff --git a/.github/workflows/test-vm.yml b/.github/workflows/test-vm.yml index e4eb7604..bee5fb23 100644 --- a/.github/workflows/test-vm.yml +++ b/.github/workflows/test-vm.yml @@ -17,7 +17,7 @@ jobs: - ubuntu-2204 - fedora-39 container: - image: "manjusakalza/bpftime-base-image:${{matrix.container}}" + image: "hp77creator/bpftime-base-image:${{matrix.container}}" options: --privileged steps: diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 22e9f418..3ea52ef5 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:24.04 RUN apt-get update && apt-get install -y g++-aarch64-linux-gnu make libyaml-cpp-dev wget pkg-config zlib1g-dev gcc-arm-linux-gnueabi clang llvm-15-dev gcc-12 libelf-dev python3-pytest g++ systemtap-sdt-dev llvm git qemu-user cmake binutils-dev g++-12 gcc-aarch64-linux-gnu libboost1.74-all-dev python3 gcc diff --git a/README.md b/README.md index a03479b4..c32338f6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# bpftime: Userspace eBPF runtime for fast Uprobe & Syscall Hook & Extensions +# bpftime: Userspace eBPF runtime for Observability, Network & General extensions Framework [![Build and Test VM](https://github.com/eunomia-bpf/bpftime/actions/workflows/test-vm.yml/badge.svg)](https://github.com/eunomia-bpf/bpftime/actions/workflows/test-vm.yml) [![Build and test runtime](https://github.com/eunomia-bpf/bpftime/actions/workflows/test-runtime.yml/badge.svg)](https://github.com/eunomia-bpf/bpftime/actions/workflows/test-runtime.yml) [![DOI](https://zenodo.org/badge/676866666.svg)](https://doi.org/10.48550/arXiv.2311.07923) -`bpftime`, a full-featured, high-performance eBPF runtime designed to operate in userspace. It offers fast Uprobe and Syscall hook capabilities: Userspace uprobe can be **10x faster than kernel uprobe!** and can programmatically **hook all syscalls of a process** safely and efficiently. +`bpftime` is a High-Performance userspace eBPF runtime and General Extension Framework designed for userspace. It enables faster Uprobe, USDT, Syscall hooks, XDP, and more event sources by bypassing the kernel and utilizing an optimized compiler like `LLVM`. 📦 [Key Features](#key-features) \ 🔨 [Quick Start](#quick-start) \ @@ -15,23 +15,35 @@ [**Checkout our documents in eunomia.dev!**](https://eunomia.dev/bpftime/) +bpftime is not `userspace eBPF VM`, it's a userspace runtime framework includes everything to run eBPF in userspace: `loader`, `verifier`, `helpers`, `maps`, `ufunc` and multiple `events` such as Observability, Network, Policy or Access Control. It has multiple VM backend options support. For eBPF VM only, please see [llvmbpf](https://github.com/eunomia-bpf/llvmbpf). + +> ⚠️ **Note**: `bpftime` is currently under active development and may contain bugs or unstable API. Please use it with caution. For more details, check our [roadmap](#roadmap). We'd love to hear your feedback and suggestions! Feel free to open an issue or [Contact us](#contact-and-citations). + +## Why bpftime? What's the design Goal? + +- **Performance Gains**: Achieve better performance by `bypassing the kernel` (e.g., via `Userspace DBI` or `Network Drivers`), with more configurable, optimized and more arch supported JIT/AOT options like `LLVM`, while maintaining compatibility with Linux kernel eBPF. +- **Cross-Platform Compatibility**: Enables `eBPF functionality and large ecosystem` where kernel eBPF is unavailable, such as on older or alternative operating systems, or where kernel-level permissions are restricted, without changing your tool. +- **Flexible and General Extension Language & Runtime for Innovation**: eBPF is designed for innovation, evolving into a General Extension Language & Runtime in production that supports very diverse use cases. `bpftime`'s modular design allows easy integration as a library for adding new events and program types without touching kernel. Wishing it could enable rapid prototyping and exploration of new features! + ## Key Features -- **Uprobe and Syscall hooks based on binary rewriting**: Run eBPF programs in userspace, attaching them to Uprobes and Syscall tracepoints: **No manual instrumentation or restart required!**. It can `trace` or `change` the execution of a function, `hook` or `filter` all syscalls of a process safely, and efficiently with an eBPF userspace runtime. Can inject eBPF runtime into any running process without the need for a restart or manual recompilation. +- **Dynamic Binary rewriting**: Run eBPF programs in userspace, attaching them to `Uprobes` and `Syscall tracepoints`: **No manual instrumentation or restart required!**. It can `trace` or `change` the execution of a function, `hook` or `filter` all syscalls of a process safely, and efficiently with an eBPF userspace runtime. Can inject eBPF runtime into any running process without the need for a restart or manual recompilation. - **Performance**: Experience up to a `10x` speedup in Uprobe overhead compared to kernel uprobe and uretprobe. Read/Write userspace memory is also faster than kernel eBPF. - **Interprocess eBPF Maps**: Implement userspace `eBPF maps` in shared userspace memory for summary aggregation or control plane communication. - **Compatibility**: use `existing eBPF toolchains` like clang, libbpf and bpftrace to develop userspace eBPF application without any modifications. Supporting CO-RE via BTF, and offering userspace `ufunc` access. - **Multi JIT Support**: Support [llvmbpf](https://github.com/eunomia-bpf/llvmbpf), a high-speed `JIT/AOT` compiler powered by LLVM, or using `ubpf JIT` and INTERPRETER. The vm can be built as `a standalone library` like ubpf. - **Run with kernel eBPF**: Can load userspace eBPF from kernel, and using kernel eBPF maps to cooperate with kernel eBPF programs like kprobes and network filters. +- **Integrate with AF_XDP or DPDK**: Run your `XDP` network applications with better performance in userspace just like in kernel!(experimental) ## Components - [`vm`](https://github.com/eunomia-bpf/bpftime/tree/master/vm): The eBPF VM and JIT compiler for bpftime, you can choose from [bpftime LLVM JIT/AOT compiler](https://github.com/eunomia-bpf/llvmbpf) and [ubpf](https://github.com/iovisor/ubpf). The [llvm-based vm](https://github.com/eunomia-bpf/llvmbpf) in bpftime can also be built as a standalone library and integrated into other projects, similar to ubpf. -- [`runtime`](https://github.com/eunomia-bpf/bpftime/tree/master/runtime): The userspace runtime for eBPF, including the bpf-syscall loader(`syscall-server`) and agent, support attaching eBPF programs to Uprobes, Syscall tracepoints and other events, as well as eBPF maps in shared memory. -- [verifier](https://github.com/eunomia-bpf/bpftime/tree/master/bpftime-verifier): Support using [PREVAIL](https://github.com/vbpf/ebpf-verifier) as userspace verifier, or using Linux kernel verifier as an option. -- [`daemon`](https://github.com/eunomia-bpf/bpftime/tree/master/daemon): A daemon to make userspace eBPF working with kernel and compatible with kernel uprobe. Monitor and modify kernel eBPF events and syscalls, load eBPF in userspace from kernel. +- [`runtime`](https://github.com/eunomia-bpf/bpftime/tree/master/runtime): The userspace runtime for eBPF, including the maps, helpers, ufuncs and other runtime safety features. +- [`Attach events`](https://github.com/eunomia-bpf/bpftime/tree/master/attach): support attaching eBPF programs to `Uprobes`, `Syscall tracepoints`, `XDP` and other events with bpf_link, and also the driver event sources. +- [`verifier`](https://github.com/eunomia-bpf/bpftime/tree/master/bpftime-verifier): Support using [PREVAIL](https://github.com/vbpf/ebpf-verifier) as userspace verifier, or using `Linux kernel verifier` for better results. +- [`Loader`](https://github.com/eunomia-bpf/bpftime/tree/master/runtime/syscall-server): Includes a `LD_PRELOAD` loader library in userspace can work with current eBPF toolchain and library without involving any kernel, Another option is [daemon](https://github.com/eunomia-bpf/bpftime/tree/master/daemon) when Linux eBPF is available. -## Quick Start +## Quick Start: Uprobe With `bpftime`, you can build eBPF applications using familiar tools like clang and libbpf, and execute them in userspace. For instance, the [`malloc`](https://github.com/eunomia-bpf/bpftime/tree/master/example/malloc) eBPF program traces malloc calls using uprobe and aggregates the counts using a hash map. @@ -41,6 +53,7 @@ To get started, you can build and run a libbpf based eBPF program starts with `b ```console make -C example/malloc # Build the eBPF program example +export PATH=$PATH:~/.bpftime/ bpftime load ./example/malloc/malloc ``` @@ -97,8 +110,6 @@ See [eunomia.dev/bpftime/documents/usage](https://eunomia.dev/bpftime/documents/ ## Examples & Use Cases -> ⚠️ **Note**: `bpftime` is actively under development, and it's not yet recommended for production use. See our [roadmap](#roadmap) for details. We'd love to hear your feedback and suggestions! Please feel free to open an issue or [Contact us](#contact-and-citations). - For more examples and details, please refer to [eunomia.dev/bpftime/documents/examples/](https://eunomia.dev/bpftime/documents/examples/) webpage. Examples including: @@ -111,7 +122,7 @@ Examples including: - Run with [bpftrace](https://github.com/eunomia-bpf/bpftime/tree/master/example/bpftrace) commands or scripts. - [error injection](https://github.com/eunomia-bpf/bpftime/tree/master/example/error-injection): change function behavior with `bpf_override_return`. - Use the eBPF LLVM JIT/AOT vm as [a standalone library](https://github.com/eunomia-bpf/bpftime/tree/master/vm/llvm-jit/example). -- Userspace [XDP eBPF with DPDK](https://github.com/eunomia-bpf/XDP-eBPF-in-DPDK) +- Userspace [XDP with DPDK and AF_XDP](https://github.com/userspace-xdp/userspace-xdp) ## In-Depth @@ -171,7 +182,7 @@ See [benchmark](https://github.com/eunomia-bpf/bpftime/tree/master/benchmark) di ### Comparing with Kernel eBPF Runtime -- `bpftime` allows you to use `clang` and `libbpf` to build eBPF programs, and run them directly in this runtime. We have tested it with a libbpf version in [third_party/libbpf](https://github.com/eunomia-bpf/bpftime/tree/master/third_party/libbpf). No specify libbpf or clang version needed. +- `bpftime` allows you to use `clang` and `libbpf` to build eBPF programs, and run them directly in this runtime, just like normal kernel eBPF. We have tested it with a libbpf version in [third_party/libbpf](https://github.com/eunomia-bpf/bpftime/tree/master/third_party/libbpf). No specify libbpf or clang version needed. - Some kernel helpers and kfuncs may not be available in userspace. - It does not support direct access to kernel data structures or functions like `task_struct`. @@ -185,15 +196,16 @@ See [eunomia.dev/bpftime/documents/build-and-test](https://eunomia.dev/bpftime/d `bpftime` is continuously evolving with more features in the pipeline: -- [X] An AOT compiler for eBPF based on the LLVM. +- [ ] Keep compatibility with the evolving kernel +- [ ] Refactor for General Extension Framework +- [ ] Trying to refactor, bug fixing for `Production`. - [ ] More examples and usecases: - - [ ] Network on userspace eBPF + - [X] Userspace Network Driver on userspace eBPF - [X] Hotpatch userspace application - [X] Error injection and filter syscall - - [X] Hotpatch and use iouring to batch syscall + - [X] Syscall bypassing, batching + - [X] Userspace Storage Driver on userspace eBPF - [ ] etc... -- [ ] More map types and distribution maps support. -- [ ] More program types support. Stay tuned for more developments from this promising project! You can find `bpftime` on [GitHub](https://github.com/eunomia-bpf/bpftime). diff --git a/attach/CMakeLists.txt b/attach/CMakeLists.txt index aa570841..cb72ea9a 100644 --- a/attach/CMakeLists.txt +++ b/attach/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(base_attach_impl) add_subdirectory(frida_uprobe_attach_impl) -if(UNIX AND NOT APPLE) +# disable syscall trace when not build libbpf +if(BPFTIME_BUILD_WITH_LIBBPF) add_subdirectory(syscall_trace_attach_impl) add_subdirectory(text_segment_transformer) endif() diff --git a/attach/base_attach_impl/CMakeLists.txt b/attach/base_attach_impl/CMakeLists.txt index 27778442..cc065a92 100644 --- a/attach/base_attach_impl/CMakeLists.txt +++ b/attach/base_attach_impl/CMakeLists.txt @@ -10,5 +10,3 @@ target_include_directories(bpftime_base_attach_impl add_dependencies(bpftime_base_attach_impl spdlog::spdlog) target_link_libraries(bpftime_base_attach_impl INTERFACE spdlog::spdlog) - -set_property(TARGET bpftime_base_attach_impl PROPERTY CXX_STANDARD 20) diff --git a/attach/frida_uprobe_attach_impl/CMakeLists.txt b/attach/frida_uprobe_attach_impl/CMakeLists.txt index dba219c1..ccb44b4c 100644 --- a/attach/frida_uprobe_attach_impl/CMakeLists.txt +++ b/attach/frida_uprobe_attach_impl/CMakeLists.txt @@ -14,7 +14,7 @@ set(FRIDA_UPROBE_ATTACH_IMPL_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE S target_include_directories(bpftime_frida_uprobe_attach_impl PRIVATE ${SPDLOG_INCLUDE} ${FRIDA_UPROBE_ATTACH_IMPL_INCLUDE} PUBLIC ${FRIDA_GUM_INSTALL_DIR} ${BASE_ATTACH_IMPL_INCLUDE}) -target_link_libraries(bpftime_frida_uprobe_attach_impl PRIVATE ${FRIDA_GUM_INSTALL_DIR}/libfrida-gum.a PUBLIC bpftime_base_attach_impl spdlog::spdlog) +target_link_libraries(bpftime_frida_uprobe_attach_impl PRIVATE ${FRIDA_GUM_INSTALL_DIR}/libfrida-gum.a PUBLIC bpftime_base_attach_impl spdlog::spdlog dl) set(TEST_SOURCES test/test_uprobe_uretprobe.cpp @@ -23,8 +23,13 @@ set(TEST_SOURCES test/test_replace_attach_with_override.cpp test/test_attach_with_unified_interface.cpp ) +option(TEST_LCOV "option for lcov" OFF) add_executable(bpftime_frida_uprobe_attach_tests ${TEST_SOURCES}) +if (${TEST_LCOV}) + target_compile_options(bpftime_frida_uprobe_attach_tests PRIVATE -fprofile-arcs -ftest-coverage) +endif() + if(${ENABLE_EBPF_VERIFIER} AND NOT TARGET Catch2) message(STATUS "Adding Catch2 by FetchContent for frida_uprobe_attach_impl") Include(FetchContent) @@ -39,7 +44,12 @@ if(${ENABLE_EBPF_VERIFIER} AND NOT TARGET Catch2) endif() add_dependencies(bpftime_frida_uprobe_attach_tests Catch2 bpftime_frida_uprobe_attach_impl spdlog::spdlog) -target_link_libraries(bpftime_frida_uprobe_attach_tests PRIVATE Catch2::Catch2WithMain bpftime_frida_uprobe_attach_impl spdlog::spdlog) +if (${TEST_LCOV}) + target_link_options(bpftime_frida_uprobe_attach_tests PRIVATE -lgcov) + target_link_libraries(bpftime_frida_uprobe_attach_tests PRIVATE Catch2::Catch2WithMain bpftime_frida_uprobe_attach_impl spdlog::spdlog gcov) +else () + target_link_libraries(bpftime_frida_uprobe_attach_tests PRIVATE Catch2::Catch2WithMain bpftime_frida_uprobe_attach_impl spdlog::spdlog) +endif() target_include_directories(bpftime_frida_uprobe_attach_tests PRIVATE ${FRIDA_UPROBE_ATTACH_IMPL_INCLUDE} ${Catch2_INCLUDE} ${SPDLOG_INCLUDE}) add_test(NAME bpftime_frida_uprobe_attach_tests COMMAND bpftime_frida_uprobe_attach_tests) diff --git a/attach/syscall_trace_attach_impl/CMakeLists.txt b/attach/syscall_trace_attach_impl/CMakeLists.txt index e94da71e..2241cab2 100644 --- a/attach/syscall_trace_attach_impl/CMakeLists.txt +++ b/attach/syscall_trace_attach_impl/CMakeLists.txt @@ -28,8 +28,13 @@ set(TEST_SOURCES test/test_private_data_parsing.cpp test/test_syscall_dispatch.cpp ) +option(TEST_LCOV "option for lcov" OFF) add_executable(bpftime_syscall_trace_attach_tests ${TEST_SOURCES}) +if (${TEST_LCOV}) + target_compile_options(bpftime_syscall_trace_attach_tests PRIVATE -fprofile-arcs -ftest-coverage) +endif() + if(${ENABLE_EBPF_VERIFIER} AND NOT TARGET Catch2) message(STATUS "Adding Catch2 by FetchContent at syscall_trace_attach_impl") Include(FetchContent) @@ -44,7 +49,12 @@ if(${ENABLE_EBPF_VERIFIER} AND NOT TARGET Catch2) endif() add_dependencies(bpftime_syscall_trace_attach_tests Catch2 bpftime_syscall_trace_attach_impl spdlog::spdlog) -target_link_libraries(bpftime_syscall_trace_attach_tests PRIVATE Catch2::Catch2WithMain bpftime_syscall_trace_attach_impl spdlog::spdlog) +if (${TEST_LCOV}) + target_link_options(bpftime_syscall_trace_attach_tests PRIVATE -lgcov) + target_link_libraries(bpftime_syscall_trace_attach_tests PRIVATE Catch2::Catch2WithMain bpftime_syscall_trace_attach_impl spdlog::spdlog gcov) +else () + target_link_libraries(bpftime_syscall_trace_attach_tests PRIVATE Catch2::Catch2WithMain bpftime_syscall_trace_attach_impl spdlog::spdlog) +endif() target_include_directories(bpftime_syscall_trace_attach_tests PRIVATE ${SYSCALL_TRACE_ATTACH_IMPL_INCLUDE} ${Catch2_INCLUDE} ${SPDLOG_INCLUDE}) add_test(NAME bpftime_syscall_trace_attach_tests COMMAND bpftime_syscall_trace_attach_tests) diff --git a/attach/syscall_trace_attach_impl/src/syscall_trace_attach_impl.cpp b/attach/syscall_trace_attach_impl/src/syscall_trace_attach_impl.cpp index a7db98cd..518a8a99 100644 --- a/attach/syscall_trace_attach_impl/src/syscall_trace_attach_impl.cpp +++ b/attach/syscall_trace_attach_impl/src/syscall_trace_attach_impl.cpp @@ -5,6 +5,10 @@ #include #include +#ifdef __linux__ +#include // For architecture-specific syscall numbers +#endif + namespace bpftime { namespace attach @@ -16,8 +20,11 @@ int64_t syscall_trace_attach_impl::dispatch_syscall(int64_t sys_nr, int64_t arg3, int64_t arg4, int64_t arg5, int64_t arg6) { +// Exit syscall may cause bugs since it's not return to userspace +#ifdef __linux__ if (sys_nr == __NR_exit_group || sys_nr == __NR_exit) return orig_syscall(sys_nr, arg1, arg2, arg3, arg4, arg5, arg6); +#endif SPDLOG_DEBUG("Syscall callback {} {} {} {} {} {} {}", sys_nr, arg1, arg2, arg3, arg4, arg5, arg6); // Indicate whether the return value is overridden diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 7ef2c10d..cf5c7d3d 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -4,7 +4,7 @@ add_dependencies(simple-benchmark-with-embed-ebpf-calling bpftime_vm libbpf) target_compile_definitions(simple-benchmark-with-embed-ebpf-calling PRIVATE ) -target_link_libraries(simple-benchmark-with-embed-ebpf-calling bpftime_vm ${LIBBPF_LIBRARIES} elf z) +target_link_libraries(simple-benchmark-with-embed-ebpf-calling bpftime_vm ${LIBBPF_LIBRARIES} elf z dl) target_include_directories(simple-benchmark-with-embed-ebpf-calling PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}../vm/include diff --git a/benchmark/uprobe/uprobe.bpf.c b/benchmark/uprobe/uprobe.bpf.c index a91e0ef9..6b7fa23c 100644 --- a/benchmark/uprobe/uprobe.bpf.c +++ b/benchmark/uprobe/uprobe.bpf.c @@ -58,7 +58,9 @@ SEC("uprobe/benchmark/test:__bench_write") int BPF_UPROBE(__bench_write, char *a, int b, uint64_t c) { char buffer[5] = "text"; - bpf_probe_write_user(a, buffer, sizeof(buffer)); + for (int i = 0; i < 1000; i++) { + bpf_probe_write_user(a, buffer, sizeof(buffer)); + } return b + c; } @@ -66,7 +68,10 @@ SEC("uprobe/benchmark/test:__bench_read") int BPF_UPROBE(__bench_read, char *a, int b, uint64_t c) { char buffer[5]; - int res = bpf_probe_read_user(buffer, sizeof(buffer), a); + int res; + for (int i = 0; i < 1000; i++) { + bpf_probe_read_user(buffer, sizeof(buffer), a); + } return b + c + res + buffer[1]; } diff --git a/bpftime-verifier/CMakeLists.txt b/bpftime-verifier/CMakeLists.txt index 2b93d5d4..5ab5be9e 100644 --- a/bpftime-verifier/CMakeLists.txt +++ b/bpftime-verifier/CMakeLists.txt @@ -34,8 +34,20 @@ if(NOT TARGET Catch2) FetchContent_MakeAvailable(Catch2) endif() +option(TEST_LCOV "option for lcov" OFF) add_executable(bpftime_verifier_tests ${TEST_SOURCES}) + +if (${TEST_LCOV}) + target_compile_options(bpftime_verifier_tests PRIVATE -fprofile-arcs -ftest-coverage) +endif() + add_dependencies(bpftime_verifier_tests bpftime-verifier) -target_link_libraries(bpftime_verifier_tests PRIVATE bpftime-verifier Catch2::Catch2WithMain) + +if (${TEST_LCOV}) + target_link_options(bpftime_verifier_tests PRIVATE -lgcov) + target_link_libraries(bpftime_verifier_tests PRIVATE bpftime-verifier Catch2::Catch2WithMain gcov) +else () + target_link_libraries(bpftime_verifier_tests PRIVATE bpftime-verifier Catch2::Catch2WithMain) +endif() target_include_directories(bpftime_verifier_tests PRIVATE ${BPFTIME_VERIFIER_INCLUDE}) add_test(NAME bpftime_verifier_tests COMMAND bpftime_verifier_tests) diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 00000000..4486fc42 --- /dev/null +++ b/example/Makefile @@ -0,0 +1,5 @@ +.PHONY: all xdp-counter +all: xdp-counter + +xdp-counter: + $(MAKE) -C xdp-counter diff --git a/example/xdp-counter/.gitignore b/example/xdp-counter/.gitignore new file mode 100644 index 00000000..c778108c --- /dev/null +++ b/example/xdp-counter/.gitignore @@ -0,0 +1,11 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml +ecli +malloc +.output +test +xdp-counter diff --git a/example/xdp-counter/Makefile b/example/xdp-counter/Makefile new file mode 100644 index 00000000..b3dc5756 --- /dev/null +++ b/example/xdp-counter/Makefile @@ -0,0 +1,138 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +LIBBPF_SRC := $(abspath ../../third_party/libbpf/src) +BPFTOOL_SRC := $(abspath ../../third_party/bpftool/src) +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../third_party/vmlinux/$(ARCH)/vmlinux.h +# Use our own libbpf API headers and Linux UAPI headers distributed with +# libbpf to avoid dependency on system-wide headers, which could be missing or +# outdated +INCLUDES := -I$(OUTPUT) -I../../third_party/libbpf/include/uapi -I$(dir $(VMLINUX)) +CFLAGS := -g -Wall +ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) + +APPS = xdp-counter + +CARGO ?= $(shell which cargo) +ifeq ($(strip $(CARGO)),) +BZS_APPS := +else +BZS_APPS := # profile +APPS += $(BZS_APPS) +# Required by libblazesym +ALL_LDFLAGS += -lrt -ldl -lpthread -lm +endif + +# Get Clang's default includes on this system. We'll explicitly add these dirs +# to the includes list when compiling with `-target bpf` because otherwise some +# architecture-specific dirs will be "missing" on some architectures/distros - +# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, +# sys/cdefs.h etc. might be missing. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all +all: $(APPS) + +.PHONY: clean +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(APPS) + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# Build libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIB,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= \ + install + +# Build bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + + +$(LIBBLAZESYM_SRC)/target/release/libblazesym.a:: + $(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release + +$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB, $@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@ + +$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT) + $(call msg,LIB,$@) + $(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@ + +# Build BPF code +$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,BPF,$@) + $(Q)$(CLANG) -Xlinker --export-dynamic -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + $(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@) + +# Generate BPF skeletons +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# Build user-space code +$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h + +$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER) + +$(BZS_APPS): $(LIBBLAZESYM_OBJ) + +# Build application binary +$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) + $(call msg,BINARY,$@) + $(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/example/xdp-counter/README.md b/example/xdp-counter/README.md new file mode 100644 index 00000000..eb7e9e9d --- /dev/null +++ b/example/xdp-counter/README.md @@ -0,0 +1,47 @@ +# xdp in bpftime + +You can load the xdp program into userspace eBPF runtime, and exec it with DPDK or AF_XDP. This allows: + +- `Faster than kernel eBPF`: DPDK can be faster than XDP driver mode, and bpftime can be faster than kernel in some cases. +- Compare to the ubpf in DPDK, this enable: + - `Control plane application support`: enable the control library to operate the eBPF map, control load and unload the eBPF program dynamically. + - `More maps and helpers compatible with kernel`: + +## load the XDP into the userspace eBPF runtime + +Create a virtual network device for test: + +```sh +sudo ip link add veth0 type veth peer name veth1 +``` + +Attach the netdev: + +```sh +LD_PRELOAD=build/runtime/syscall-server/libbpftime-syscall-server.so example/xdp-counter/xdp-counter example/xdp-counter/.output/xdp-counter.bpf.o veth1 example/xdp-counter/base.btf +``` + +- The `example/xdp-counter/base.btf` is for relocation on userspace xdp. See [runtime/extension/userspace_xdp.h](../../runtime/extension/userspace_xdp.h) for how the userspace `xdp_md` looks like. + +## Run XDP program in userspace + +See the driver progam in + +## TODO: support old XDP attach + +Currently bpftime only support attach XDP program with `bpf_link`. We need to handle the old attahc approach later. + +```sh +newfstatat(1, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0 +socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 7 +ioctl(7, SIOCGIFINDEX, {ifr_name="veth1", ifr_ifindex=5}) = 0 +close(7) = 0 +socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 7 +setsockopt(7, SOL_NETLINK, NETLINK_EXT_ACK, [1], 4) = 0 +bind(7, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0 +getsockname(7, {sa_family=AF_NETLINK, nl_pid=405177, nl_groups=00000000}, [12]) = 0 +sendto(7, [{nlmsg_len=52, nlmsg_type=RTM_SETLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK, nlmsg_seq=1724212008, nlmsg_pid=0}, {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=if_nametoindex("veth1"), ifi_flags=0, ifi_change=0}, [{nla_len=20, nla_type=NLA_F_NESTED|IFLA_XDP}, [[{nla_len=8, nla_type=IFLA_XDP_FD}, 6], [{nla_len=8, nla_type=IFLA_XDP_FLAGS}, XDP_FLAGS_UPDATE_IF_NOEXIST]]]], 52, 0, NULL, 0) = 52 +recvmsg(7, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base=[{nlmsg_len=36, nlmsg_type=NLMSG_ERROR, nlmsg_flags=NLM_F_CAPPED, nlmsg_seq=1724212008, nlmsg_pid=405177}, {error=0, msg={nlmsg_len=52, nlmsg_type=RTM_SETLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK, nlmsg_seq=1724212008, nlmsg_pid=0}}], iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_PEEK|MSG_TRUNC) = 36 +recvmsg(7, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base=[{nlmsg_len=36, nlmsg_type=NLMSG_ERROR, nlmsg_flags=NLM_F_CAPPED, nlmsg_seq=1724212008, nlmsg_pid=405177}, {error=0, msg={nlmsg_len=52, nlmsg_type=RTM_SETLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK, nlmsg_seq=1724212008, nlmsg_pid=0}}], iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 36 +close(7) = 0 +``` diff --git a/example/xdp-counter/base.btf b/example/xdp-counter/base.btf new file mode 100755 index 00000000..635e88aa Binary files /dev/null and b/example/xdp-counter/base.btf differ diff --git a/example/xdp-counter/xdp-counter.bpf.c b/example/xdp-counter/xdp-counter.bpf.c new file mode 100755 index 00000000..42d902ce --- /dev/null +++ b/example/xdp-counter/xdp-counter.bpf.c @@ -0,0 +1,72 @@ +/* Copyright (C) 2018-present, Facebook, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "vmlinux.h" +#include +#include + +#define CTRL_ARRAY_SIZE 2 +#define CNTRS_ARRAY_SIZE 512 + +// use map type define +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u32); + __uint(max_entries, CTRL_ARRAY_SIZE); +} ctl_array SEC(".maps"); + +// use global variable define +__u64 cntrs_array[CNTRS_ARRAY_SIZE]; + +static void swap_src_dst_mac(void *data) +{ + unsigned short *p = data; + unsigned short dst[3]; + + dst[0] = p[0]; + dst[1] = p[1]; + dst[2] = p[2]; + p[0] = p[3]; + p[1] = p[4]; + p[2] = p[5]; + p[3] = dst[0]; + p[4] = dst[1]; + p[5] = dst[2]; +} + +SEC("xdp") +int xdp_pass(struct xdp_md* ctx) { + void* data_end = (void*)(long)ctx->data_end; + void* data = (void*)(long)ctx->data; + __u32 ctl_flag_pos = 0; + __u32 cntr_pos = 0; + + // access maps with helpers + __u32* flag = bpf_map_lookup_elem(&ctl_array, &ctl_flag_pos); + if (!flag || (*flag != 0)) { + return XDP_PASS; + }; + + // access maps with global variables + cntrs_array[cntr_pos]++; + + if (data + sizeof(struct ethhdr) > data_end) + return XDP_DROP; + swap_src_dst_mac(data); + return XDP_TX; +} + +char _license[] SEC("license") = "GPL"; diff --git a/example/xdp-counter/xdp-counter.c b/example/xdp-counter/xdp-counter.c new file mode 100755 index 00000000..93c75627 --- /dev/null +++ b/example/xdp-counter/xdp-counter.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + return vfprintf(stderr, format, args); +} + +int main(int argc, char **argv) +{ + if (argc < 3) { + printf("ERROR - Usage is: ./loader" + " [btf_file]\n" + "\n"); + return 1; + } + libbpf_set_print(libbpf_print_fn); + LIBBPF_OPTS(bpf_object_open_opts , opts, + ); + if (argc == 4) + opts.btf_custom_path = argv[3]; + // Open and load the BPF program + struct bpf_object* obj = bpf_object__open_file(argv[1], &opts); + + if (bpf_object__load(obj)) { + printf( "Failed to load program\n"); + return 1; + } + + struct bpf_program* prog = bpf_object__find_program_by_name(obj, "xdp_pass"); + if (!prog) { + printf("Failed to find program\n"); + return 1; + } + bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); + int prog_fd = bpf_program__fd(prog); + if (prog_fd < 0) { + printf("Failed to get prog FD\n"); + return 1; + } + const char* progName = bpf_program__name(prog); + if (!progName) { + printf("Failed to get progName\n"); + return 1; + } + printf("load prog %s\n", progName); + + // Attach the XDP program to the interface + int ifindex = if_nametoindex(argv[2]); + if (!ifindex) { + printf("failed to if_nametoindex\n"); + return 1; + } + + struct bpf_link *link = bpf_program__attach_xdp(prog, ifindex); + if (!link) { + printf("attach error\n"); + return 1; + } + printf("Attach XDP success\n"); + while (1) { + sleep(1); + printf("wait...\n"); + } + return 0; +} diff --git a/runtime/src/bpf_map/shared/perf_event_array_kernel_user.cpp b/runtime/src/bpf_map/shared/perf_event_array_kernel_user.cpp index 47f94eb6..244e147b 100644 --- a/runtime/src/bpf_map/shared/perf_event_array_kernel_user.cpp +++ b/runtime/src/bpf_map/shared/perf_event_array_kernel_user.cpp @@ -9,6 +9,7 @@ #include "libbpf/include/linux/filter.h" #include #include +#include #include #include #include diff --git a/runtime/src/bpf_map/userspace/prog_array.cpp b/runtime/src/bpf_map/userspace/prog_array.cpp index 22ae84ec..d05e0cce 100644 --- a/runtime/src/bpf_map/userspace/prog_array.cpp +++ b/runtime/src/bpf_map/userspace/prog_array.cpp @@ -4,11 +4,14 @@ * All rights reserved. */ -#if __linux__ +#if __linux__ +#if BPFTIME_BUILD_WITH_LIBBPF #include "bpf/bpf.h" -#include "linux/bpf.h" #include +#endif +#include "linux/bpf.h" #include +#include #elif __APPLE__ #include "bpftime_epoll.h" #endif @@ -26,6 +29,7 @@ #if __linux__ +#if BPFTIME_BUILD_WITH_LIBBPF // syscall() function was hooked by syscall server, direct call to it will lead // to a result provided by bpftime. So if we want to get things from kernel, we @@ -75,6 +79,7 @@ int my_bpf_prog_get_fd_by_id(__u32 id) return fd; } +#endif #endif namespace bpftime @@ -94,6 +99,7 @@ prog_array_map_impl::prog_array_map_impl( } #if __linux__ +#if BPFTIME_BUILD_WITH_LIBBPF void *prog_array_map_impl::elem_lookup(const void *key) { @@ -134,7 +140,7 @@ long prog_array_map_impl::elem_update(const void *key, const void *value, info.id); return 0; } - +#endif #endif long prog_array_map_impl::elem_delete(const void *key) diff --git a/runtime/syscall-server/syscall_context.cpp b/runtime/syscall-server/syscall_context.cpp index 95feefff..bfa4a326 100644 --- a/runtime/syscall-server/syscall_context.cpp +++ b/runtime/syscall-server/syscall_context.cpp @@ -12,12 +12,16 @@ #include #include #if __linux__ -#include "linux/perf_event.h" +#if BPFTIME_BUILD_WITH_LIBBPF +#include +#endif +#include #include +#include "linux/perf_event.h" #include -#include #include #include +#include #elif __APPLE__ #include "bpftime_epoll.h" #endif @@ -49,6 +53,33 @@ .imm = IMM }) #endif +#if __linux__ && !BPFTIME_BUILD_WITH_LIBBPF +#define offsetofend(type, member) (offsetof(type, member) + sizeof(((type *)0)->member)) +static inline __u64 ptr_to_u64(const void *ptr) +{ + return (__u64) (unsigned long) ptr; +} + +inline int bpf_obj_get_info_by_fd(int bpf_fd, void *info, __u32 *info_len) +{ + const size_t attr_sz = offsetofend(union bpf_attr, info); + union bpf_attr attr; + int err; + + memset(&attr, 0, attr_sz); + attr.info.bpf_fd = bpf_fd; + attr.info.info_len = *info_len; + attr.info.info = ptr_to_u64(info); + + err = this->orig_syscall_fn(__NR_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, attr_sz); + if (!err) + *info_len = attr.info.info_len; + // no-op stub + // return libbpf_err_errno(err); + return 1; +} +#endif + using namespace bpftime; #if __APPLE__ using namespace bpftime_epoll; @@ -336,7 +367,15 @@ long syscall_context::handle_sysbpf(int cmd, union bpf_attr *attr, size_t size) attr->btf_id, attr->btf_key_type_id, attr->btf_value_type_id, - attr->map_extra, + #if __linux__ + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + attr->map_extra, + #else + 0, // or some appropriate default value + #endif + #elif __APPLE__ + attr->map_extra + #endif }); SPDLOG_DEBUG( "Created map {}, type={}, name={}, key_size={}, value_size={}", @@ -538,7 +577,13 @@ long syscall_context::handle_sysbpf(int cmd, union bpf_attr *attr, size_t size) ptr->key_size = map_attr.key_size; ptr->id = attr->info.bpf_fd; ptr->ifindex = map_attr.ifindex; + #if __linux__ + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) + ptr->map_extra = map_attr.map_extra; + #endif + #elif __APPLE__ ptr->map_extra = map_attr.map_extra; + #endif // TODO: handle the rest info ptr->max_entries = map_attr.max_ents; ptr->map_flags = map_attr.flags; diff --git a/runtime/unit-test/CMakeLists.txt b/runtime/unit-test/CMakeLists.txt index 95560844..6f7bf94c 100644 --- a/runtime/unit-test/CMakeLists.txt +++ b/runtime/unit-test/CMakeLists.txt @@ -31,10 +31,23 @@ set(TEST_SOURCES tailcall/test_user_to_kernel_tailcall.cpp ) +option(TEST_LCOV "option for lcov" OFF) add_executable(bpftime_runtime_tests ${TEST_SOURCES}) + +if (${TEST_LCOV}) + target_compile_options(bpftime_runtime_tests PRIVATE -fprofile-arcs -ftest-coverage) +endif() + set_property(TARGET bpftime_runtime_tests PROPERTY CXX_STANDARD 20) add_dependencies(bpftime_runtime_tests runtime bpftime-object bpftime_frida_uprobe_attach_impl) -target_link_libraries(bpftime_runtime_tests PRIVATE runtime bpftime-object Catch2::Catch2WithMain bpftime_frida_uprobe_attach_impl) + +if (${TEST_LCOV}) + target_link_options(bpftime_runtime_tests PRIVATE -lgcov) + target_link_libraries(bpftime_runtime_tests PRIVATE runtime bpftime-object Catch2::Catch2WithMain bpftime_frida_uprobe_attach_impl gcov) +else () + target_link_libraries(bpftime_runtime_tests PRIVATE runtime bpftime-object Catch2::Catch2WithMain bpftime_frida_uprobe_attach_impl) +endif() + target_include_directories(bpftime_runtime_tests PRIVATE ${BPFTIME_RUNTIME_INCLUDE} ${BPFTIME_OBJECT_INCLUDE_DIRS} ${Catch2_INCLUDE} ${Boost_INCLUDE} ${FRIDA_UPROBE_ATTACH_IMPL_INCLUDE}) add_test(NAME bpftime_runtime_tests COMMAND bpftime_runtime_tests) diff --git a/vm/CMakeLists.txt b/vm/CMakeLists.txt index 071e5a1b..0559bb85 100644 --- a/vm/CMakeLists.txt +++ b/vm/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.16) # # Project details # diff --git a/vm/llvm-jit b/vm/llvm-jit index 1acd8e52..f6a726c9 160000 --- a/vm/llvm-jit +++ b/vm/llvm-jit @@ -1 +1 @@ -Subproject commit 1acd8e52a47cfd6532ac7bed5361d6cd795f9846 +Subproject commit f6a726c92674e40208ac2fdeeb400902d99ac380