├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cli ├── .gitignore ├── Makefile ├── README.md └── bpfcov.c ├── docs └── assets │ ├── html1.png │ ├── html2.png │ ├── html3.png │ ├── html4.png │ ├── json1.png │ ├── lcov1.png │ ├── mult1.png │ ├── stdo1.png │ └── stdo2.png ├── examples ├── .gitignore ├── README.md ├── src │ ├── Makefile │ ├── commons.c │ ├── fentry.bpf.c │ ├── fentry.c │ ├── lsm.bpf.c │ ├── lsm.c │ ├── raw_enter.bpf.c │ └── raw_enter.c └── tools │ └── .gitignore ├── include └── BPFCov.h └── lib ├── BPFCov.cpp ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "examples/libbpf"] 2 | path = examples/libbpf 3 | url = https://github.com/libbpf/libbpf.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.4) 2 | project(bpfcov) 3 | 4 | #=============================================================================== 5 | # 1. VERIFY LLVM INSTALLATION DIR 6 | # This is just a bit of a sanity checking. 7 | #=============================================================================== 8 | set(LT_LLVM_INSTALL_DIR "" CACHE PATH "LLVM installation directory") 9 | 10 | # 1.1 Check the "include" directory 11 | set(LT_LLVM_INCLUDE_DIR "${LT_LLVM_INSTALL_DIR}/include/llvm") 12 | if(NOT EXISTS "${LT_LLVM_INCLUDE_DIR}") 13 | message(FATAL_ERROR "LT_LLVM_INSTALL_DIR (${LT_LLVM_INCLUDE_DIR}) is invalid.") 14 | endif() 15 | 16 | # 1.2 Check that the LLVMConfig.cmake file exists 17 | set(LT_VALID_INSTALLATION FALSE) 18 | 19 | # Ubuntu + Darwin 20 | if(EXISTS "${LT_LLVM_INSTALL_DIR}/lib/cmake/llvm/LLVMConfig.cmake") 21 | set(LT_VALID_INSTALLATION TRUE) 22 | endif() 23 | 24 | # Fedora 25 | if(EXISTS "${LT_LLVM_INSTALL_DIR}/lib64/cmake/llvm/LLVMConfig.cmake") 26 | set(LT_VALID_INSTALLATION TRUE) 27 | endif() 28 | 29 | if(NOT ${LT_VALID_INSTALLATION}) 30 | message(FATAL_ERROR 31 | "LLVM installation directory, (${LT_LLVM_INSTALL_DIR}), is invalid. Couldn't find LLVMConfig.cmake.") 32 | endif() 33 | 34 | #=============================================================================== 35 | # 2. LOAD LLVM CONFIGURATION 36 | # See: http://llvm.org/docs/CMake.html#embedding-llvm-in-your-project 37 | #=============================================================================== 38 | # Add the location of LLVMConfig.cmake to CMake search paths so that find_package can locate it. 39 | # Note: On Fedora, when using the pre-compiled binaries installed with `dnf`, 40 | # LLVMConfig.cmake is located in "/usr/lib64/cmake/llvm". But this path is 41 | # among other paths that will be checked by default when using 42 | # `find_package(llvm)`. So there's no need to add it here. 43 | list(APPEND CMAKE_PREFIX_PATH "${LT_LLVM_INSTALL_DIR}/lib/cmake/llvm/") 44 | 45 | find_package(LLVM 12.0.0 REQUIRED CONFIG) 46 | 47 | # Another sanity check 48 | if(NOT "12" VERSION_EQUAL "${LLVM_VERSION_MAJOR}") 49 | message(FATAL_ERROR "Found LLVM ${LLVM_VERSION_MAJOR}, but need LLVM 12") 50 | endif() 51 | 52 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 53 | message(STATUS "Using LLVMConfig.cmake in: ${LT_LLVM_INSTALL_DIR}") 54 | 55 | message("LLVM STATUS: 56 | Definitions ${LLVM_DEFINITIONS} 57 | Includes ${LLVM_INCLUDE_DIRS} 58 | Libraries ${LLVM_LIBRARY_DIRS} 59 | Targets ${LLVM_TARGETS_TO_BUILD}" 60 | ) 61 | 62 | # Set the LLVM header and library paths 63 | include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) 64 | link_directories(${LLVM_LIBRARY_DIRS}) 65 | add_definitions(${LLVM_DEFINITIONS}) 66 | 67 | #=============================================================================== 68 | # 3. BUILD CONFIGURATION 69 | #=============================================================================== 70 | # Use the same C++ standard as LLVM does 71 | set(CMAKE_CXX_STANDARD 14 CACHE STRING "") 72 | 73 | # Build type 74 | if (NOT CMAKE_BUILD_TYPE) 75 | set(CMAKE_BUILD_TYPE Debug CACHE 76 | STRING "Build type (default Debug):" FORCE) 77 | endif() 78 | 79 | # Compiler flags 80 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fdiagnostics-color=always") 81 | 82 | # LLVM is normally built without RTTI. Be consistent with that. 83 | if(NOT LLVM_ENABLE_RTTI) 84 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") 85 | endif() 86 | 87 | # -fvisibility-inlines-hidden is set when building LLVM and on Darwin warnings 88 | # are triggered if this is built without this flag (though otherwise it 89 | # builds fine). For consistency, add it here too. 90 | include(CheckCXXCompilerFlag) 91 | check_cxx_compiler_flag("-fvisibility-inlines-hidden" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG) 92 | if (${SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG} EQUAL "1") 93 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") 94 | endif() 95 | 96 | # Set the build directories 97 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") 98 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 99 | 100 | #=============================================================================== 101 | # 4. ADD SUB-TARGETS 102 | # Doing this at the end so that all definitions and link/include paths are 103 | # available for the sub-projects. 104 | #=============================================================================== 105 | add_subdirectory(lib) 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) [year], [fullname] 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpfcov 2 | 3 | > Source-code based coverage for eBPF programs actually running in the Linux kernel 4 | 5 | This project provides 2 main components: 6 | 7 | 1. `libBPFCov.so` - an **out-of-tree LLVM pass** to **instrument** your **eBPF programs** for coverage. 8 | 2. `bpfcov` - a **CLI** to **collect source-based coverage** from your eBPF programs. 9 | 10 | 11 | | | | | 12 | |:-------------------------:|:-------------------------:|:-------------------------:| 13 | | Source-based code coverage for BPF raw tracepoints | Source-based code coverage for BPF LSM programs | HTML coverage index for multiple eBPF programs 14 | | HTML coverage report for eBPF programs | HTML coverage report for eBPF programs | JSON report for multiple eBPF programs 15 | | LCOV info file from multiple eBPF programs | HTML line coverage report for eBPF programs | HTML line coverage report for eBPF programs 16 | 17 | ## Overview 18 | 19 | This section aims to provide a high-level overiew of the steps you need to get started with **bpfcov**. 20 | 21 | 1. [Compile the LLVM pass](#building) obtaining `libBPFCov.so` 22 | 2. Instrument your eBPF program by compiling it and by running the LLVM pass (`libBPFCov.so`) on it 23 | 3. Build the userspace code of your eBPF application 24 | 4. Execute your eBPF application in the kernel through the `bpfcov run ...` command 25 | 5. Generate the `.profraw` file from the run through the `bpfcov gen ...` command 26 | 1. Having a `.profraw` makes this tool fully interoperable 27 | 2. Having a `.profraw` allows you to generate a variety of coverage reports in different formats 28 | 6. Use the LLVM toolchain to create coverage reports as documented in the [LLVM docs](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#creating-coverage-reports) 29 | 30 | In case you are impatient and want to jump straight into getting your hands dirty, then the [examples](examples/) directory contains a few dummy eBPF programs to showcase what **bpfcov** does. 31 | 32 | It basically automates steps 2 and 3. Its [README](examples/README.md) contains more details. 33 | 34 | While the [README of the cli directory](cli/README.md) gives you more details about the steps 4 and 5 (and also 6). 35 | 36 | ## Usage 37 | 38 | Here I will highlight the _manual_ steps to use it. 39 | 40 | I suggest you to automate most of them like I did in the [examples Makefile](examples/src/Makefile). 41 | 42 | Anyway, assuming you have [built](#building) the LLVM pass, you can then use your fresh `libBPFCov.so` to instrument your eBPF programs for coverage (steps 2 and 3 above). 43 | 44 | How to do it? 45 | 46 | First, you need to compile your eBPF program almost as usual but to LLVM IR... 47 | 48 | ```bash 49 | clang -g -O2 \ 50 | -target bpf -D__TARGET_ARCH_x86 -I$(YOUR_INCLUDES) \ 51 | -fprofile-instr-generate -fcoverage-mapping \ 52 | -emit-llvm -S \ 53 | -c program.bpf.c \ 54 | -o program.bpf.ll 55 | ``` 56 | 57 | Notice it doesn't matter if you use the textual (`*.ll`) or the binary form (`*.bc`). 58 | Obviously, the former is more readable. 59 | 60 | The same logic applies to `opt`: by default it generates `*.bc`. 61 | Using the `-S` flag you can obtain the output in textual form (`*.ll`). 62 | 63 | Anyhow, it's time to run the LLVM pass on the LLVM IR we obtained. 64 | 65 | Let's do it: 66 | 67 | ```bash 68 | opt -load-pass-plugin $(BUILD_DIR)/lib/libBPFCov.so -passes="bpf-cov" \ 69 | -S program.bpf.ll \ 70 | -o program.bpf.cov.ll 71 | ``` 72 | 73 | We should have obtained a new LLVM IR that's now valid and loadable from the BPF VM in the Linux kernel. Almost there, YaY! 74 | 75 | From it, we can obtain a valid BPF ELF now: 76 | 77 | ```bash 78 | llc -march=bpf -filetype=obj -o cov/program.bpf.o program.bpf.cov.ll 79 | ``` 80 | 81 | While we are at it, it is also worth running the LLVM pass again (with a flag) to obtain another BPF ELF containing all the **profiling** and **coverage mapping** info. 82 | It will come in handy later with `llvm-cov`. 83 | 84 | ```bash 85 | opt -load $(BUILD_DIR)/lib/libBPFCov.so -strip-initializers-only -bpf-cov \ 86 | program.bpf.ll | \ 87 | llc -march=bpf -filetype=obj -o cov/program.bpf.obj 88 | ``` 89 | 90 | At this point, we can compile our userspace application loading the eBPF instrumented program (`cov/program.bpf.o`). 91 | 92 | Doing this when using `libbpf` and skeletons is very easy. Nothing different from the common steps: `bpftool`, `cc`, etc. 93 | 94 | In the [examples](examples/) directory, you can find further explainations. 95 | 96 | So assuming we got our instrumented binary ready (`cov/program`), we can run it via the `bpfcov` CLI. 97 | 98 | ```bash 99 | sudo ./bpfcov run cov/program 100 | # Wait for it to exit, or stop it with CTRL+C 101 | sudo ./bpfcov gen --unpin cov/program 102 | ``` 103 | 104 | Again, in case you wanna know more about these 2 steps, refer this time to the [CLI README](cli/README.md). 105 | 106 | Now we have a magic `cov/program.profraw` file... 107 | 108 | And we can use the LLVM toolchain to generate very fine-grained coverage reports like those in the screenshots! 109 | 110 | Refer to the [LLVM docs](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#creating-coverage-reports) to learn how to do it. 111 | 112 | But no worries, it's just about invoking `llvm-profdata` and `llvm-cov`: 113 | 114 | ```bash 115 | lvm-profdata merge -sparse cov/program.profraw -o cov/program.profdata 116 | llvm-cov show \ 117 | --format=html \ 118 | --show-line-counts-or-regions --show-region-summary --show-branch-summary \ 119 | --instr-profile=cov/profdata.profdata \ 120 | -object cov/program.bpf.obj \ 121 | --output-dir=cov/html_report 122 | ``` 123 | 124 | Anyayws, **bpfcov** also provides you an opinionated shortcut command to generate HTML, JSON, and LCOV coverage reports: 125 | 126 | ```bash 127 | ./bpfcov out --format=html cov/program.profraw 128 | ``` 129 | 130 | 131 | ## Development Environment 132 | 133 | In order to **build** the BPFCov library (`libBPFCov.so`) you will need: 134 | 135 | - LLVM 12+ 136 | - CMake 3.13.4+ 137 | - C++ compiler that supports C++14 138 | 139 | In order to **use** it, you will need: 140 | 141 | - clang 12 (to generate the input LLVM files) 142 | - its [opt](http://llvm.org/docs/CommandGuide/opt.html) binary to run the LLVM pass 143 | 144 | This project has been tested on 5.15 Linux kernels. 145 | 146 | ## Building 147 | 148 | Build as follows: 149 | 150 | ```console 151 | mkdir -p build && cd build 152 | cmake -DLT_LLVM_INSTALL_DIR=/path/to/llvm/installation .. 153 | make 154 | ``` 155 | 156 | Notice that the `LT_LLVM_INSTALL_DIR` variable should be set to the root of either the installation (usually `/usr`) or the build directory of LLVM. 157 | 158 | It is used to locate the corresponding `LLVMConfig.cmake` script that is used to set the include and the 159 | library paths. 160 | 161 | ## Testing 162 | 163 | **TBD** 164 | 165 | To run the tests you will need to install **llvm-lit**. 166 | 167 | Usually, you can install it with **pip**: 168 | 169 | ```console 170 | pip install lit 171 | ``` 172 | 173 | Running the tests is as simple as: 174 | 175 | ```console 176 | lit build/test 177 | ``` 178 | 179 | 180 | -------------------------------------------------------------------------------- /cli/.gitignore: -------------------------------------------------------------------------------- 1 | bpfcov 2 | *.profdata 3 | *.json 4 | *.lcov 5 | *_html -------------------------------------------------------------------------------- /cli/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= cc 2 | LIBBPF_DIR := $(abspath ../examples/libbpf) 3 | LIBBPF_OBJ := $(abspath ../examples/src/.output/libbpf.a) 4 | CFLAGS := -std=c11 -Wall -Wextra -O3 -g3 5 | 6 | PROGRAM = bpfcov 7 | 8 | ifeq ($(V),1) 9 | Q = 10 | msg = 11 | else 12 | Q = @ 13 | msg = @printf ' %-8s %s%s\n' \ 14 | "$(1)" \ 15 | "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ 16 | "$(if $(3), $(3))"; 17 | MAKEFLAGS += --no-print-directory 18 | endif 19 | 20 | ifeq ($(RELEASE),1) 21 | CFLAGS += -DNDEBUG 22 | endif 23 | 24 | .PHONY: all 25 | all: $(PROGRAM) 26 | 27 | .PHONY: clean 28 | clean: 29 | $(call msg,CLEAN) 30 | $(Q)rm -rf $(PROGRAM) 31 | 32 | %: %.c $(LIBBPF_OBJ) 33 | $(call msg,BIN,$@) 34 | $(Q)$(CC) $(CFLAGS) $^ -lelf -lz -o $@ 35 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | # bpfcov / cli 2 | 3 | > Run your instrumented eBPF programs and obtain coverage from them 4 | 5 | ## Usage 6 | 7 | Once you have [built](#building) it, you can use the **bpfcov** CLI following the next steps. 8 | 9 | First, **run** your eBPF application through it: 10 | 11 | ```bash 12 | $ sudo ./bpfcov -v2 run ../examples/src/.output/cov/raw_enter 13 | ``` 14 | 15 | Notice that you must give to the `run` subcommand the **instrumented eBPF application** (`.output/`**cov**`/raw_enter`). 16 | 17 | To know how to instrument it please [read this section](../README#usage). 18 | Or just take a look at the [examples](../examples) directory... 19 | 20 | When the eBPF application exited, or when you stopped the `run` subcommand, you may want to double-check 21 | that **bpfcov** created **its pinned eBPF maps** in the BPF file system. 22 | 23 | ```bash 24 | $ sudo tree /sys/fs/bpf/cov 25 | 26 | /sys/fs/bpf/cov 27 | └── raw_enter 28 | ├── covmap 29 | ├── profc 30 | ├── profd 31 | └── profn 32 | ``` 33 | 34 | If so, then it is time to **generate** a `.profraw` file by collecting info from those eBPF maps! 35 | 36 | To do so, you need to use the `gen` subcommand: 37 | 38 | ```bash 39 | sudo ./bpfcov -v2 gen ../examples/src/.output/cov/raw_enter 40 | ``` 41 | 42 | This command will create a `raw_enter.profraw` file sibling to the instrumented eBPF application binary (thus, in `../examples/src/.output/cov/raw_enter.profraw`). 43 | 44 | By default, the `gen` subcommand will **not** unpin the eBPF maps that the `run` subcommand created. 45 | 46 | But in case you want to unpin them, and you want to output the `.profraw` file in a different location, you can do the following command: 47 | 48 | ```bash 49 | sudo ./bpfcov -v2 gen --unpin -o hellow.profraw ../examples/src/.output/cov/raw_enter 50 | ``` 51 | 52 | Now that you have a fresh `.profraw` file you can use the **LLVM tools** ([llvm-profdata](https://llvm.org/docs/CommandGuide/llvm-profdata.html), and [llvm-cov](https://llvm.org/docs/CommandGuide/llvm-cov.html)) as usual to get a nice **source-based coverage** report out of it. 53 | 54 | Or you can use `bpfcov out ...`! 55 | 56 | It acts as an opinionated wrapper to the `llvm-profdata` and `llvm-cov` commands you'd need to execute manually otherwise. [This sections](#generating-coverage-reports) shows how it works! 57 | 58 | Anyways, here's how to output a source-based code coverage report to the standard output starting from the `*.profraw` file we just generated. 59 | 60 | First, generate a `*.profdata` file: 61 | 62 | ```bash 63 | $ llvm-profdata merge -sparse hellow.profraw -o hellow.profdata 64 | ``` 65 | 66 | With such a file, plus the `*.bpf.obj` file created while instrumenting your eBPF program ([read this if you haven't](../examples/README.md#key-aspects)), you can now obtain various coverage reports! 67 | 68 | ```bash 69 | $ llvm-cov show \ 70 | --instr-profile=hellow.profdata \ 71 | --show-region-summary --show-branch-summary --show-line-counts-or-regions \ 72 | ../examples/src/.output/cov/raw_enter.bpf.obj 73 | ``` 74 | 75 | The previous command will output the annotated source code to `stdout`: 76 | 77 | ``` 78 | 1| |#include "vmlinux.h" 79 | 2| |#include 80 | 3| |#include 81 | 4| |#include 82 | 5| |#include 83 | 6| | 84 | 7| |char LICENSE[] SEC("license") = "GPL"; 85 | 8| | 86 | 9| |const volatile int count = 0; 87 | 10| | 88 | 11| |SEC("raw_tp/sys_enter") 89 | 12| |int BPF_PROG(hook_sys_enter) 90 | 13| 1|{ 91 | 14| 1| bpf_printk("ciao0"); 92 | 15| | 93 | 16| 1| struct trace_event_raw_sys_enter *x = (struct trace_event_raw_sys_enter *)ctx; 94 | 17| 1| if (x->id != __NR_connect) 95 | 18| 1| { 96 | 19| 0| return 0; 97 | 20| 0| } 98 | 21| | 99 | 22| 10| for (int i = 1; i < count; i++) 100 | ^1 ^9 101 | 23| 9| { 102 | 24| 9| bpf_printk("ciao%d", i); 103 | 25| 9| } 104 | 26| | 105 | 27| 1| return 0; 106 | 28| 1|} 107 | ``` 108 | 109 | You can also output a JSON report, a LCOV, or an HTML one (see [the next section](generating-coverage-reports)). It's your call! 110 | 111 | Feel free to explore the different flags the **bpfcov** CLI and its subcommand supports by reading their manual (more in the [help section](#help)). 112 | 113 | ### Generating coverage reports 114 | 115 | This section shows how to generate code coverage reports for your eBPF programs, either via the **bpfcov** CLI (more straightforward) or manually. 116 | 117 | It shows how to do it for a few eBPF applications in a single report, either HTML, json, or lcov. 118 | 119 | But the same applies if you only have one eBPF application for which you want to generate a report. 120 | 121 | Assuming we have gerenated with `bpfcov gen ...` the `*.profraw` files for 3 examples, 122 | we can generate a **HTML* report for all of them: 123 | 124 | ```bash 125 | ./bpfcov -v2 out -o awesome_html_cov_report \ 126 | ../examples/src/.output/cov/lsm.profraw ../examples/src/.output/cov/fentry.profraw ../examples/src/.output/cov/fentry.profraw 127 | ```` 128 | 129 | Generating a **JSON** report it's just a matter of specifing the format: 130 | 131 | ```bash 132 | ./bpfcov -v2 out --format=json \ 133 | ../examples/src/.output/cov/lsm.profraw ../examples/src/.output/cov/fentry.profraw ../examples/src/.output/cov/fentry.profraw 134 | ``` 135 | 136 | By default, the `out` subcommand will output in `out.json` when the `--output` flag is not specified. 137 | 138 | No need to repeat myself showing the `lcov` format... Right? 139 | 140 | Just in case you need to fine-tune the coverage report by passing different arguments to `llvm-cov`, 141 | here is how to manually do the same things the `bpfcov out` command does. 142 | 143 | 1. Generate the `*.profdata` files from your `*.profraw` ones: 144 | 145 | ```bash 146 | llvm-profdata merge -sparse ../examples/src/.output/cov/lsm.profraw -o lsm.profdata 147 | llvm-profdata merge -sparse ../examples/src/.output/cov/fentry.profraw -o fentry.profdata 148 | llvm-profdata merge -sparse ../examples/src/.output/cov/raw_enter.profraw -o raw_enter.profdata 149 | ``` 150 | 151 | 2. Merge all of them in a single `all.profdata` file: 152 | 153 | ```bash 154 | llvm-profdata merge \ 155 | -sparse lsm.profdata fentry.profdata raw_enter.profdata \ 156 | -o all.profdata 157 | ``` 158 | 159 | 3. Play with `llvm-cov` to outpu your **HTML** coverage report, for example: 160 | 161 | ```bash 162 | llvm-cov show --format=html \ 163 | --show-branches=count --show-line-counts-or-regions --show-region-summary \ 164 | -instr-profile=all.profdata \ 165 | -object ../examples/src/.output/cov/raw_enter.bpf.obj -object ../examples/src/.output/cov/fentry.bpf.obj -object ../examples/src/.output/cov/lsm.bpf.obj \ 166 | --output-dir=../yay 167 | ``` 168 | 169 | Notice that this is the step where you need the `*.bpf.obj` archive files! 170 | 171 | 4. Want to export an **lcov** representation of the coverage and generate a line coverage HTML report only? 172 | 173 | ```bash 174 | llvm-cov export --format=lcov \ 175 | --show-region-summary --show-branch-summary \ 176 | -instr-profile=all.profdata \ 177 | -object ../examples/src/.output/cov/raw_enter.bpf.obj -object ../examples/src/.output/cov/fentry.bpf.obj -object ../examples/src/.output/cov/lsm.bpf.obj > all.info 178 | genhtml all.info --legend --show-details --highlight --output-directory ../lcov_line_coverage 179 | ``` 180 | 181 | ## Help 182 | 183 | The **bpfcov** CLI provides a detailed `--help` flag. 184 | 185 | ```bash 186 | $ ./bpfcov --help 187 | 188 | Usage: bpfcov [OPTION...] [run|gen|out] 189 | 190 | Obtain coverage from your instrumented eBPF applications. 191 | 192 | OPTIONS: 193 | --bpffs=path Set the BPF FS path (defaults to /sys/fs/bpf) 194 | -v, --verbose[=level] Set the verbosity level when not built for release 195 | (defaults to 0) 196 | 197 | 198 | GLOBALS: 199 | -?, --help Give this help list 200 | --usage Give a short usage message 201 | -V, --version Print program version 202 | 203 | Mandatory or optional arguments to long options are also mandatory or optional 204 | for any corresponding short options. 205 | 206 | EXAMPLES: 207 | bpfcov run 208 | bpfcov gen 209 | bpfcov out + 210 | 211 | ... 212 | ``` 213 | 214 | It also provides a specific `--help` flag for each subcommand. 215 | 216 | For example, you can get to know more about the `gen` subcommand by typing: 217 | 218 | ```bash 219 | $ ./bpfcov gen --help 220 | 221 | Usage: bpfcov gen [OPTION...] 222 | 223 | Generate the profraw file for the bpfcov instrumented eBPF applications. 224 | 225 | 226 | OPTIONS: 227 | -o, --output=path Set the output path 228 | (defaults to .profraw) 229 | --unpin Unpin the maps 230 | 231 | 232 | GLOBALS: 233 | -?, --help Give this help list 234 | --usage Give a short usage message 235 | -V, --version Print program version 236 | ``` 237 | 238 | Feel free to explore the other subcommands and their flags. 239 | 240 | ## Building 241 | 242 | I'm not sure this topic requires a whole section on its own: 243 | 244 | ```bash 245 | make 246 | ``` 247 | 248 | 🎈 249 | -------------------------------------------------------------------------------- /cli/bpfcov.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #ifdef NDEBUG 4 | #define DEBUG 0 5 | #else 6 | #define DEBUG 1 7 | #endif 8 | 9 | /* C standard library */ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /* POSIX */ 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | /* Linux */ 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | // -------------------------------------------------------------------------------------------------------------------- 36 | // Global info 37 | // -------------------------------------------------------------------------------------------------------------------- 38 | 39 | #define TOOL_NAME "bpfcov" 40 | const char *argp_program_version = TOOL_NAME " 0.1"; 41 | const char *argp_program_bug_address = "leo"; 42 | error_t argp_err_exit_status = 1; 43 | 44 | // -------------------------------------------------------------------------------------------------------------------- 45 | // Prototypes 46 | // -------------------------------------------------------------------------------------------------------------------- 47 | 48 | const char *argp_key(int key, char *str); 49 | 50 | struct root_args; 51 | typedef int (*callback_t)(struct root_args *args); 52 | 53 | static error_t root_parse(int key, char *arg, struct argp_state *state); 54 | void root(int argc, char **argv); 55 | 56 | void run_cmd(struct argp_state *state); 57 | static error_t run_parse(int key, char *arg, struct argp_state *state); 58 | int run(struct root_args *args); 59 | 60 | void gen_cmd(struct argp_state *state); 61 | static error_t gen_parse(int key, char *arg, struct argp_state *state); 62 | int gen(struct root_args *args); 63 | 64 | void out_cmd(struct argp_state *state); 65 | static error_t out_parse(int key, char *arg, struct argp_state *state); 66 | int out(struct root_args *args); 67 | 68 | static bool is_bpffs(char *bpffs_path); 69 | static void strip_trailing_char(char *str, char c); 70 | static void replace_with(char *str, const char what, const char with); 71 | static void strip_extension(char *str); 72 | static void handle_map_pins(struct root_args *args, struct argp_state *state, bool unpin); 73 | static void wait_or_exit(struct root_args *args, pid_t pid, char *err); 74 | 75 | // -------------------------------------------------------------------------------------------------------------------- 76 | // Logging 77 | // -------------------------------------------------------------------------------------------------------------------- 78 | 79 | void print_log(int level, const char *prefix, struct root_args *args, const char *fmt, ...); 80 | 81 | #define log_erro(args, fmt, ...) \ 82 | do \ 83 | { \ 84 | if (DEBUG) \ 85 | print_log(0, TOOL_NAME ": %s: ", args, fmt, __VA_ARGS__); \ 86 | } while (0) 87 | 88 | #define log_warn(args, fmt, ...) \ 89 | do \ 90 | { \ 91 | if (DEBUG) \ 92 | print_log(1, TOOL_NAME ": %s: ", args, fmt, __VA_ARGS__); \ 93 | } while (0) 94 | 95 | #define log_info(args, fmt, ...) \ 96 | do \ 97 | { \ 98 | if (DEBUG) \ 99 | print_log(2, TOOL_NAME ": %s: ", args, fmt, __VA_ARGS__); \ 100 | } while (0) 101 | 102 | #define log_debu(args, fmt, ...) \ 103 | do \ 104 | { \ 105 | if (DEBUG) \ 106 | print_log(3, TOOL_NAME ": %s: ", args, fmt, __VA_ARGS__); \ 107 | } while (0) 108 | 109 | #define log_fata(args, fmt, ...) \ 110 | log_erro(args, fmt, __VA_ARGS__); \ 111 | exit(EXIT_FAILURE); 112 | 113 | // -------------------------------------------------------------------------------------------------------------------- 114 | // Entrypoint 115 | // -------------------------------------------------------------------------------------------------------------------- 116 | 117 | int main(int argc, char **argv) 118 | { 119 | root(argc, argv); 120 | 121 | return 0; 122 | } 123 | 124 | // -------------------------------------------------------------------------------------------------------------------- 125 | // CLI / bpfcov 126 | // -------------------------------------------------------------------------------------------------------------------- 127 | 128 | #define NUM_PINNED_MAPS 4 129 | 130 | #define FOREACH_FORMAT(FORMAT) \ 131 | FORMAT(FORMAT_, html) \ 132 | FORMAT(FORMAT_, json) \ 133 | FORMAT(FORMAT_, lcov) 134 | 135 | #define GEN_ENUM(PREFIX, ENUM) PREFIX##ENUM, 136 | #define GEN_STRING(PREFIX, STRING) #STRING, 137 | 138 | enum out_format 139 | { 140 | FOREACH_FORMAT(GEN_ENUM) 141 | }; 142 | 143 | static const char *format_string[] = {FOREACH_FORMAT(GEN_STRING)}; 144 | 145 | typedef enum out_format out_format_t; 146 | 147 | struct root_args 148 | { 149 | bool unpin; 150 | char *output; 151 | char *bpffs; 152 | char *cov_root; 153 | char *prog_root; 154 | char *pin[NUM_PINNED_MAPS]; 155 | char **profraw; 156 | char *report_path; 157 | int num_profraw; 158 | out_format_t out_format; 159 | int verbosity; 160 | callback_t command; 161 | char **program; 162 | }; 163 | 164 | const char ROOT_BPFFS_OPT_KEY = 0x80; 165 | const char ROOT_BPFFS_OPT_LONG[] = "bpffs"; 166 | const char ROOT_BPFFS_OPT_ARG[] = "path"; 167 | const char ROOT_VERBOSITY_OPT_KEY = 'v'; 168 | const char ROOT_VERBOSITY_OPT_LONG[] = "verbose"; 169 | const char ROOT_VERBOSITY_OPT_ARG[] = "level"; 170 | 171 | static struct argp_option root_opts[] = { 172 | {"OPTIONS:", 0, 0, OPTION_DOC, 0, 0}, 173 | {ROOT_BPFFS_OPT_LONG, ROOT_BPFFS_OPT_KEY, ROOT_BPFFS_OPT_ARG, 0, "Set the BPF FS path (defaults to /sys/fs/bpf)", 1}, 174 | { 175 | ROOT_VERBOSITY_OPT_LONG, 176 | ROOT_VERBOSITY_OPT_KEY, 177 | ROOT_VERBOSITY_OPT_ARG, 178 | OPTION_ARG_OPTIONAL, 179 | "Set the verbosity level when not built for release (defaults to 0)", 180 | 1, 181 | }, 182 | {"\n", 0, 0, OPTION_DOC, 0, 0}, 183 | {"GLOBALS:", 0, 0, OPTION_DOC, 0, 0}, 184 | {0} // . 185 | }; 186 | 187 | static char root_docs[] = 188 | "\n" 189 | "Obtain source-based code coverage from your instrumented eBPF applications." 190 | "\v" 191 | " EXAMPLES:\n" 192 | " bpfcov run \n" 193 | " bpfcov gen \n" 194 | " bpfcov out +\n"; 195 | 196 | static struct argp root_argp = { 197 | .options = root_opts, 198 | .parser = root_parse, 199 | .args_doc = "[run|gen|out] ", 200 | .doc = root_docs, 201 | }; 202 | 203 | static error_t root_parse(int key, char *arg, struct argp_state *state) 204 | { 205 | struct root_args *args = state->input; 206 | 207 | char str[2]; 208 | log_debu(args, "parsing %s = '%s'\n", argp_key(key, str), arg ? arg : "(null)"); 209 | 210 | switch (key) 211 | { 212 | 213 | // Initialization 214 | case ARGP_KEY_INIT: 215 | args->bpffs = "/sys/fs/bpf"; 216 | // args->verbosity = 0; // It needs to be set before the parsing starts 217 | args->command = NULL; 218 | args->program = calloc(PATH_MAX, sizeof(char *)); 219 | args->output = NULL; 220 | args->unpin = false; 221 | break; 222 | 223 | case ROOT_BPFFS_OPT_KEY: 224 | if (strlen(arg) > 0) 225 | { 226 | strip_trailing_char(arg, '/'); 227 | args->bpffs = arg; 228 | break; 229 | } 230 | argp_error(state, "option '--%s' requires a %s", ROOT_BPFFS_OPT_LONG, ROOT_BPFFS_OPT_ARG); 231 | break; 232 | 233 | case ROOT_VERBOSITY_OPT_KEY: 234 | if (arg) 235 | { 236 | errno = 0; 237 | char *end; 238 | long num = strtol(arg, &end, 10); 239 | if (end == arg) 240 | { 241 | argp_error(state, "option '--%s' requires a numeric %s", ROOT_VERBOSITY_OPT_LONG, ROOT_VERBOSITY_OPT_ARG); 242 | } 243 | if (num < 0 || num > 3) 244 | { 245 | argp_error(state, "option '--%s' requires a %s value in [0,3]", ROOT_VERBOSITY_OPT_LONG, ROOT_VERBOSITY_OPT_ARG); 246 | } 247 | 248 | args->verbosity = (int)num; 249 | } 250 | else 251 | { 252 | args->verbosity++; 253 | } 254 | break; 255 | 256 | case ARGP_KEY_ARG: 257 | assert(arg); 258 | 259 | /**/ if (strncmp(arg, "run", 3) == 0) 260 | { 261 | args->command = &run; 262 | run_cmd(state); 263 | } 264 | else if (strncmp(arg, "gen", 3) == 0) 265 | { 266 | args->command = &gen; 267 | gen_cmd(state); 268 | } 269 | else if (strncmp(arg, "out", 3) == 0) 270 | { 271 | args->command = &out; 272 | out_cmd(state); 273 | } 274 | else 275 | { 276 | args->program[state->arg_num] = arg; 277 | } 278 | 279 | break; 280 | 281 | // Args validation 282 | case ARGP_KEY_END: 283 | if (state->arg_num == 0) 284 | { 285 | argp_state_help(state, state->err_stream, ARGP_HELP_STD_HELP); 286 | } 287 | if (args->command != &out && args->program[0] == NULL) 288 | { 289 | // This should never happen 290 | argp_error(state, "unexpected missing "); 291 | } 292 | break; 293 | 294 | // Final validations, checks, and settings 295 | case ARGP_KEY_FINI: 296 | bool is_run = args->command == &run; 297 | 298 | // When the subcommand is 299 | // - do not validate BPF FS 300 | // - do not generate pinning paths 301 | // - do not clean up () or check () pinned maps 302 | if (args->command == &out) 303 | { 304 | break; 305 | } 306 | 307 | // Check the BPF filesystem 308 | if (!is_bpffs(args->bpffs)) 309 | { 310 | argp_error(state, "the BPF filesystem is not mounted at %s", args->bpffs); 311 | } 312 | 313 | // Create the coverage directory in the BPF filesystem 314 | char cov_root[PATH_MAX]; 315 | int cov_root_len = snprintf(cov_root, PATH_MAX, "%s/%s", args->bpffs, "cov"); 316 | if (cov_root_len >= PATH_MAX) 317 | { 318 | argp_error(state, "coverage root path too long"); 319 | } 320 | if (is_run && mkdir(cov_root, 0700) && errno != EEXIST) 321 | { 322 | argp_error(state, "could not create '%s'", cov_root); 323 | } 324 | args->cov_root = cov_root; 325 | 326 | // Obtain the program name and create a directory in the BPF filesystem for it 327 | char *prog_name = basename(args->program[0]); 328 | char prog_root[PATH_MAX]; 329 | int prog_root_len = snprintf(prog_root, PATH_MAX, "%s/%s", cov_root, prog_name); 330 | if (prog_root_len >= PATH_MAX) 331 | { 332 | argp_error(state, "program root path too long"); 333 | } 334 | char *prog_root_sane = strdup(prog_root); 335 | replace_with(prog_root_sane, '.', '_'); // Sanitize because BPF FS doesn't accept dots 336 | if (is_run && mkdir(prog_root_sane, 0700) && errno != EEXIST) 337 | { 338 | argp_error(state, "could not create '%s'", prog_root_sane); 339 | } 340 | args->prog_root = prog_root_sane; 341 | log_info(args, "root directory for map pins at '%s'\n", prog_root_sane); 342 | 343 | // Create pinning path for the counters map 344 | char pin_profc[PATH_MAX]; 345 | int pin_profc_len = snprintf(pin_profc, PATH_MAX, "%s/%s", prog_root_sane, "profc"); 346 | if (pin_profc_len >= PATH_MAX) 347 | { 348 | argp_error(state, "counters pinning path too long"); 349 | } 350 | args->pin[0] = pin_profc; 351 | 352 | // Create pinning path for the data map 353 | char pin_profd[PATH_MAX]; 354 | int pin_profd_len = snprintf(pin_profd, PATH_MAX, "%s/%s", prog_root_sane, "profd"); 355 | if (pin_profd_len >= PATH_MAX) 356 | { 357 | argp_error(state, "data pinning path too long"); 358 | } 359 | args->pin[1] = pin_profd; 360 | 361 | // Create pinning path for the names map 362 | char pin_profn[PATH_MAX]; 363 | int pin_profn_len = snprintf(pin_profn, PATH_MAX, "%s/%s", prog_root_sane, "profn"); 364 | if (pin_profn_len >= PATH_MAX) 365 | { 366 | argp_error(state, "names pinning path too long"); 367 | } 368 | args->pin[2] = pin_profn; 369 | 370 | // Create pinning path for the coverage mapping header 371 | char pin_covmap[PATH_MAX]; 372 | int pin_covmap_len = snprintf(pin_covmap, PATH_MAX, "%s/%s", prog_root_sane, "covmap"); 373 | if (pin_covmap_len >= PATH_MAX) 374 | { 375 | argp_error(state, "coverage mapping header path too long"); 376 | } 377 | args->pin[3] = pin_covmap; 378 | 379 | // Check whether the map pinning paths already exist: 380 | // - unpin them in case they do exist and the current subcommand is `run` 381 | // - error out in case the do not exist and the current subcommand is `gen` 382 | handle_map_pins(args, state, is_run); 383 | 384 | break; 385 | 386 | default: 387 | log_debu(args, "parsing UNKNOWN = '%s'\n", arg ? arg : "(null)"); 388 | return ARGP_ERR_UNKNOWN; 389 | } 390 | 391 | return 0; 392 | } 393 | 394 | void root(int argc, char **argv) 395 | { 396 | struct root_args this = { 397 | .verbosity = 0, 398 | }; 399 | argp_parse(&root_argp, argc, argv, ARGP_IN_ORDER, NULL, &this); 400 | 401 | if (this.command) 402 | { 403 | this.command(&this); 404 | } 405 | else 406 | { 407 | log_fata(NULL, "%s\n", "not implemented yet"); 408 | // run(&this); 409 | // gen(&this); 410 | } 411 | } 412 | 413 | // -------------------------------------------------------------------------------------------------------------------- 414 | // CLI / bpfcov run 415 | // -------------------------------------------------------------------------------------------------------------------- 416 | 417 | struct run_args 418 | { 419 | struct root_args *parent; 420 | }; 421 | 422 | static struct argp_option run_opts[] = { 423 | {"GLOBALS:", 0, 0, OPTION_DOC, 0, 0}, 424 | {0} // . 425 | }; 426 | 427 | static char run_docs[] = "\n" 428 | "Execute your bpfcov instrumented eBPF applications.\n" 429 | "\n"; 430 | 431 | static struct argp run_argp = { 432 | .options = run_opts, 433 | .parser = run_parse, 434 | .args_doc = "", 435 | .doc = run_docs, 436 | }; 437 | 438 | static error_t 439 | run_parse(int key, char *arg, struct argp_state *state) 440 | { 441 | struct run_args *args = state->input; 442 | 443 | assert(args); 444 | assert(args->parent); 445 | 446 | char str[2]; 447 | log_debu(args->parent, "parsing %s = '%s'\n", argp_key(key, str), arg ? arg : "(null)"); 448 | 449 | switch (key) 450 | { 451 | case ARGP_KEY_ARG: 452 | args->parent->program[state->arg_num] = arg; 453 | break; 454 | 455 | case ARGP_KEY_END: 456 | if (!args->parent->program[0]) 457 | { 458 | argp_error(state, "missing program argument"); 459 | } 460 | if (access(args->parent->program[0], F_OK) != 0) 461 | { 462 | argp_error(state, "program '%s' does not actually exist", args->parent->program[0]); 463 | } 464 | break; 465 | 466 | default: 467 | log_debu(args->parent, "parsing UNKNOWN = '%s'\n", arg ? arg : "(null)"); 468 | return ARGP_ERR_UNKNOWN; 469 | } 470 | 471 | return 0; 472 | } 473 | 474 | void run_cmd(struct argp_state *state) 475 | { 476 | struct run_args args = {}; 477 | int argc = state->argc - state->next + 1; 478 | char **argv = &state->argv[state->next - 1]; 479 | char *argv0 = argv[0]; 480 | 481 | args.parent = state->input; 482 | 483 | log_debu(args.parent, "begin (argc = %d, argv[0] = %s)\n", argc, argv[0]); 484 | 485 | argv[0] = malloc(strlen(state->name) + strlen(" run") + 1); 486 | if (!argv[0]) 487 | { 488 | argp_failure(state, 1, ENOMEM, 0); 489 | } 490 | sprintf(argv[0], "%s run", state->name); 491 | 492 | argp_parse(&run_argp, argc, argv, ARGP_IN_ORDER, &argc, &args); 493 | 494 | free(argv[0]); 495 | 496 | argv[0] = argv0; 497 | 498 | state->next += argc - 1; 499 | 500 | log_debu(args.parent, "end (next = %d, argv[next] = %s)\n", state->next, state->argv[state->next]); 501 | } 502 | 503 | // -------------------------------------------------------------------------------------------------------------------- 504 | // CLI / bpfcov gen 505 | // -------------------------------------------------------------------------------------------------------------------- 506 | 507 | struct gen_args 508 | { 509 | struct root_args *parent; 510 | }; 511 | 512 | const char GEN_OUTPUT_OPT_KEY = 'o'; 513 | const char GEN_OUTPUT_OPT_LONG[] = "output"; 514 | const char GEN_OUTPUT_OPT_ARG[] = "path"; 515 | const char GEN_UNPIN_OPT_KEY = 0x81; 516 | const char GEN_UNPIN_OPT_LONG[] = "unpin"; 517 | 518 | static struct argp_option gen_opts[] = { 519 | {"OPTIONS:", 0, 0, OPTION_DOC, 0, 0}, 520 | {GEN_OUTPUT_OPT_LONG, GEN_OUTPUT_OPT_KEY, GEN_OUTPUT_OPT_ARG, 0, "Set the output path\n(defaults to .profraw)", 1}, 521 | {GEN_UNPIN_OPT_LONG, GEN_UNPIN_OPT_KEY, 0, 0, "Unpin the maps", 1}, 522 | {"\n", 0, 0, OPTION_DOC, 0, 0}, 523 | {"GLOBALS:", 0, 0, OPTION_DOC, 0, 0}, 524 | {0} // . 525 | }; 526 | 527 | static char gen_docs[] = "\n" 528 | "Generate the profraw file for the bpfcov instrumented eBPF applications.\n" 529 | "\n"; 530 | 531 | static struct argp gen_argp = { 532 | .options = gen_opts, 533 | .parser = gen_parse, 534 | .args_doc = "", 535 | .doc = gen_docs, 536 | }; 537 | 538 | static error_t 539 | gen_parse(int key, char *arg, struct argp_state *state) 540 | { 541 | struct gen_args *args = state->input; 542 | 543 | assert(args); 544 | assert(args->parent); 545 | 546 | char str[2]; 547 | log_debu(args->parent, "parsing %s = '%s'\n", argp_key(key, str), arg ? arg : "(null)"); 548 | 549 | switch (key) 550 | { 551 | case GEN_OUTPUT_OPT_KEY: 552 | if (strlen(arg) > 0) 553 | { 554 | args->parent->output = arg; 555 | break; 556 | } 557 | argp_error(state, "option '--%s' requires a %s", GEN_OUTPUT_OPT_LONG, GEN_OUTPUT_OPT_ARG); 558 | break; 559 | 560 | case GEN_UNPIN_OPT_KEY: 561 | args->parent->unpin = true; 562 | break; 563 | 564 | case ARGP_KEY_ARG: 565 | // NOTE > Collecting also other arguments/options even though they are not used to generate the pinning path 566 | args->parent->program[state->arg_num] = arg; 567 | break; 568 | 569 | case ARGP_KEY_END: 570 | if (!args->parent->program[0]) 571 | { 572 | argp_error(state, "missing program argument"); 573 | } 574 | if (access(args->parent->program[0], F_OK) != 0) 575 | { 576 | argp_error(state, "program '%s' does not actually exist", args->parent->program[0]); 577 | } 578 | if (!args->parent->output) 579 | { 580 | char output_path[PATH_MAX]; 581 | int output_path_len = snprintf(output_path, PATH_MAX, "%s.%s", args->parent->program[0], "profraw"); 582 | if (output_path_len >= PATH_MAX) 583 | { 584 | argp_error(state, "default output path too long"); 585 | } 586 | args->parent->output = output_path; 587 | } 588 | break; 589 | 590 | default: 591 | log_debu(args->parent, "parsing UNKNOWN = '%s'\n", arg ? arg : "(null)"); 592 | return ARGP_ERR_UNKNOWN; 593 | } 594 | 595 | return 0; 596 | } 597 | 598 | void gen_cmd(struct argp_state *state) 599 | { 600 | struct gen_args args = {}; 601 | int argc = state->argc - state->next + 1; 602 | char **argv = &state->argv[state->next - 1]; 603 | char *argv0 = argv[0]; 604 | 605 | args.parent = state->input; 606 | 607 | log_debu(args.parent, "begin (argc = %d, argv[0] = %s)\n", argc, argv[0]); 608 | 609 | argv[0] = malloc(strlen(state->name) + strlen(" gen") + 1); 610 | if (!argv[0]) 611 | { 612 | argp_failure(state, 1, ENOMEM, 0); 613 | } 614 | sprintf(argv[0], "%s gen", state->name); 615 | 616 | argp_parse(&gen_argp, argc, argv, ARGP_IN_ORDER, &argc, &args); 617 | 618 | free(argv[0]); 619 | 620 | argv[0] = argv0; 621 | 622 | state->next += argc - 1; 623 | 624 | log_debu(args.parent, "end (next = %d, argv[next] = %s)\n", state->next, state->argv[state->next]); 625 | } 626 | 627 | // -------------------------------------------------------------------------------------------------------------------- 628 | // CLI / bpfcov out 629 | // -------------------------------------------------------------------------------------------------------------------- 630 | 631 | struct out_args 632 | { 633 | struct root_args *parent; 634 | }; 635 | 636 | const char OUT_OUTPUT_OPT_KEY = 'o'; 637 | const char OUT_OUTPUT_OPT_LONG[] = "output"; 638 | const char OUT_OUTPUT_OPT_ARG[] = "path"; 639 | const char OUT_FORMAT_OPT_KEY = 'f'; 640 | const char OUT_FORMAT_OPT_LONG[] = "format"; 641 | const char OUT_FORMAT_OPT_ARG[] = "html|json|lcov"; 642 | 643 | static struct argp_option out_opts[] = { 644 | {"OPTIONS:", 0, 0, OPTION_DOC, 0, 0}, 645 | {OUT_OUTPUT_OPT_LONG, OUT_OUTPUT_OPT_KEY, OUT_OUTPUT_OPT_ARG, 0, " Set the output path\n (defaults to out[_html/|.json|.lcov])", 1}, 646 | {OUT_FORMAT_OPT_LONG, OUT_FORMAT_OPT_KEY, OUT_FORMAT_OPT_ARG, 0, "Set the output format\n (defaults to html)", 1}, 647 | {"\n", 0, 0, OPTION_DOC, 0, 0}, 648 | {"GLOBALS:", 0, 0, OPTION_DOC, 0, 0}, 649 | {0} // . 650 | }; 651 | 652 | static char out_docs[] = "\n" 653 | "Generate the coverage reports from 1 or more profraw files.\n" 654 | "\n"; 655 | 656 | static struct argp out_argp = { 657 | .options = out_opts, 658 | .parser = out_parse, 659 | .args_doc = "+", 660 | .doc = out_docs, 661 | }; 662 | 663 | static error_t 664 | out_parse(int key, char *arg, struct argp_state *state) 665 | { 666 | struct out_args *args = state->input; 667 | 668 | assert(args); 669 | assert(args->parent); 670 | 671 | char str[2]; 672 | log_debu(args->parent, "parsing %s = '%s'\n", argp_key(key, str), arg ? arg : "(null)"); 673 | 674 | switch (key) 675 | { 676 | case ARGP_KEY_INIT: 677 | args->parent->profraw = calloc(PATH_MAX, sizeof(char *)); 678 | args->parent->num_profraw = 0; 679 | break; 680 | case OUT_OUTPUT_OPT_KEY: 681 | if (strlen(arg) > 0) 682 | { 683 | args->parent->report_path = arg; 684 | break; 685 | } 686 | argp_error(state, "option '--%s' requires a %s", OUT_OUTPUT_OPT_LONG, OUT_OUTPUT_OPT_ARG); 687 | break; 688 | 689 | case OUT_FORMAT_OPT_KEY: 690 | if (strlen(arg) > 0) 691 | { 692 | /**/ if (strncmp(arg, "html", 4) == 0) 693 | { 694 | args->parent->out_format = FORMAT_html; 695 | } 696 | else if (strncmp(arg, "json", 4) == 0) 697 | { 698 | args->parent->out_format = FORMAT_json; 699 | } 700 | else if (strncmp(arg, "lcov", 4) == 0) 701 | { 702 | args->parent->out_format = FORMAT_lcov; 703 | } 704 | /**/ else 705 | { 706 | goto out_format_error; 707 | } 708 | break; 709 | } 710 | out_format_error: 711 | argp_error(state, "option '--%s' requires a value (%s)", OUT_FORMAT_OPT_LONG, OUT_FORMAT_OPT_ARG); 712 | break; 713 | 714 | case ARGP_KEY_ARG: 715 | assert(arg); 716 | args->parent->profraw[state->arg_num] = arg; 717 | break; 718 | 719 | case ARGP_KEY_END: 720 | if (args->parent->profraw[0] == NULL) 721 | { 722 | argp_error(state, "at least one profraw input file is required"); 723 | } 724 | char** ptr = args->parent->profraw; 725 | for (char* profraw = *ptr; profraw; profraw = *++ptr) { 726 | if (access(profraw, F_OK) != 0) 727 | { 728 | argp_error(state, "input profraw file '%s' does not actually exist", profraw); 729 | } 730 | // TODO(leodido) > check it really is a profraw file? 731 | args->parent->num_profraw++; 732 | } 733 | if (!args->parent->report_path) 734 | { 735 | char *sep = "."; 736 | switch (args->parent->out_format) 737 | { 738 | case FORMAT_html: 739 | sep = "_"; 740 | break; 741 | default: 742 | break; 743 | } 744 | 745 | char report_path[PATH_MAX]; 746 | int report_path_len = snprintf(report_path, PATH_MAX, "%s%s%s", "out", sep, format_string[args->parent->out_format]); 747 | if (report_path_len >= PATH_MAX) 748 | { 749 | argp_error(state, "default output path too long"); 750 | } 751 | args->parent->report_path = report_path; 752 | } 753 | break; 754 | 755 | default: 756 | log_debu(args->parent, "parsing UNKNOWN = '%s'\n", arg ? arg : "(null)"); 757 | return ARGP_ERR_UNKNOWN; 758 | } 759 | 760 | return 0; 761 | } 762 | 763 | void out_cmd(struct argp_state *state) 764 | { 765 | struct out_args args = {}; 766 | int argc = state->argc - state->next + 1; 767 | char **argv = &state->argv[state->next - 1]; 768 | char *argv0 = argv[0]; 769 | 770 | args.parent = state->input; 771 | 772 | log_debu(args.parent, "begin (argc = %d, argv[0] = %s)\n", argc, argv[0]); 773 | 774 | argv[0] = malloc(strlen(state->name) + strlen(" out") + 1); 775 | if (!argv[0]) 776 | { 777 | argp_failure(state, 1, ENOMEM, 0); 778 | } 779 | sprintf(argv[0], "%s out", state->name); 780 | 781 | argp_parse(&out_argp, argc, argv, ARGP_IN_ORDER, &argc, &args); 782 | 783 | free(argv[0]); 784 | 785 | argv[0] = argv0; 786 | 787 | state->next += argc - 1; 788 | 789 | log_debu(args.parent, "end (next = %d, argv[next] = %s)\n", state->next, state->argv[state->next]); 790 | } 791 | 792 | // -------------------------------------------------------------------------------------------------------------------- 793 | // Miscellaneous 794 | // -------------------------------------------------------------------------------------------------------------------- 795 | 796 | const char *argp_key(int key, char *str) 797 | { 798 | str[0] = key; 799 | str[1] = 0; 800 | 801 | switch (key) 802 | { 803 | case ARGP_KEY_ARG: 804 | return "ARGP_KEY_ARG"; 805 | case ARGP_KEY_ARGS: 806 | return "ARGP_KEY_ARGS"; 807 | case ARGP_KEY_END: 808 | return "ARGP_KEY_END"; 809 | case ARGP_KEY_NO_ARGS: 810 | return "ARGP_KEY_NO_ARGS"; 811 | case ARGP_KEY_INIT: 812 | return "ARGP_KEY_INIT"; 813 | case ARGP_KEY_SUCCESS: 814 | return "ARGP_KEY_SUCCESS"; 815 | case ARGP_KEY_ERROR: 816 | return "ARGP_KEY_ERROR"; 817 | case ARGP_KEY_FINI: 818 | return "ARGP_KEY_FINI"; 819 | } 820 | 821 | return str; 822 | }; 823 | 824 | void print_log(int level, const char *prefix, struct root_args *args, const char *fmt, ...) 825 | { 826 | if (args->verbosity < level) 827 | { 828 | return; 829 | } 830 | 831 | FILE *f = level == 0 ? stderr : stdout; 832 | va_list argptr; 833 | va_start(argptr, fmt); 834 | 835 | if (!prefix || (prefix && !*prefix)) 836 | { 837 | goto without_prefix; 838 | } 839 | 840 | char *category = "unkn"; 841 | switch (level) 842 | { 843 | case 0: 844 | category = "erro"; 845 | break; 846 | case 1: 847 | category = "warn"; 848 | break; 849 | case 2: 850 | category = "info"; 851 | break; 852 | case 3: 853 | category = "debu"; 854 | break; 855 | } 856 | 857 | fprintf(f, "%s: %s: ", TOOL_NAME, category); 858 | 859 | without_prefix: 860 | vfprintf(f, fmt, argptr); 861 | va_end(argptr); 862 | } 863 | 864 | static bool is_bpffs(char *bpffs_path) 865 | { 866 | struct statfs st_fs; 867 | 868 | if (statfs(bpffs_path, &st_fs) < 0) 869 | return false; 870 | 871 | return st_fs.f_type == BPF_FS_MAGIC; 872 | } 873 | 874 | static void strip_trailing_char(char *str, char c) 875 | { 876 | int last = strlen(str) - 1; 877 | while (last > 0 && str[last] == c) 878 | { 879 | str[last--] = '\0'; 880 | } 881 | } 882 | 883 | static void replace_with(char *str, const char what, const char with) 884 | { 885 | while (*str) 886 | { 887 | if (*str == what) 888 | { 889 | *str = with; 890 | } 891 | str++; 892 | } 893 | } 894 | 895 | static void strip_extension(char *str) 896 | { 897 | char *end = str + strlen(str); 898 | while (end > str && *end != '.' && *end != '\\' && *end != '/') { 899 | --end; 900 | } 901 | if ((end > str && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { 902 | *end = '\0'; 903 | } 904 | } 905 | 906 | static void handle_map_pins(struct root_args *args, struct argp_state *state, bool unpin) 907 | { 908 | int p; 909 | for (p = 0; p < NUM_PINNED_MAPS; p++) 910 | { 911 | if (access(args->pin[p], F_OK) == 0) 912 | { 913 | if (unpin) 914 | { 915 | log_warn(args, "unpinning existing map '%s'\n", args->pin[p]); 916 | if (unlink(args->pin[p]) != 0) 917 | { 918 | if (state) 919 | { 920 | argp_error(state, "could not unpin map '%s'", args->pin[p]); 921 | } 922 | else 923 | { 924 | log_fata(args, "could not unpin map '%s'\n", args->pin[p]); 925 | } 926 | } 927 | } 928 | } 929 | else 930 | { 931 | if (!unpin) 932 | { 933 | if (state) 934 | { 935 | argp_error(state, "could not access map '%s'", args->pin[p]); 936 | } 937 | else 938 | { 939 | log_fata(args, "could not access map '%s'\n", args->pin[p]); 940 | } 941 | } 942 | } 943 | } 944 | } 945 | 946 | static int get_pin_path(struct root_args *args, char *suffix, char **pin_path) 947 | { 948 | if (!suffix) { 949 | return 0; 950 | } 951 | 952 | if (strncmp(suffix, "profc", 5) == 0) 953 | { 954 | *pin_path = args->pin[0]; 955 | return 1; 956 | } 957 | else if (strncmp(suffix, "profd", 5) == 0) 958 | { 959 | *pin_path = args->pin[1]; 960 | return 1; 961 | } 962 | else if (strncmp(suffix, "profn", 5) == 0) 963 | { 964 | *pin_path = args->pin[2]; 965 | return 1; 966 | } 967 | else if (strncmp(suffix, "covmap", 6) == 0) 968 | { 969 | *pin_path = args->pin[3]; 970 | return 1; 971 | } 972 | 973 | return 0; 974 | } 975 | 976 | static int get_map_info(int fd, struct bpf_map_info *info) 977 | { 978 | int err; 979 | unsigned int size = sizeof(*info); 980 | memset(info, 0, size); 981 | err = bpf_obj_get_info_by_fd(fd, info, &size); 982 | if (err) 983 | { 984 | close(fd); 985 | } 986 | return err; 987 | } 988 | 989 | static int get_global_data(int fd, struct bpf_map_info *info, void *data) 990 | { 991 | int err; 992 | void *k, *v; 993 | k = malloc(info->key_size); 994 | v = malloc(info->value_size); 995 | if (!k || !v) 996 | { 997 | err = -1; 998 | goto error_out; 999 | } 1000 | if (!info) 1001 | { 1002 | err = -1; 1003 | goto error_out; 1004 | } 1005 | if (info->max_entries > 1) 1006 | { 1007 | err = -1; 1008 | goto error_out; 1009 | } 1010 | 1011 | err = bpf_map_get_next_key(fd, NULL, k); 1012 | if (err) 1013 | { 1014 | goto error_out; 1015 | } 1016 | err = bpf_map_lookup_elem(fd, k, v); 1017 | if (err) 1018 | { 1019 | goto error_out; 1020 | } 1021 | memcpy(data, v, info->value_size); 1022 | 1023 | error_out: 1024 | free(k); 1025 | free(v); 1026 | close(fd); 1027 | return err; 1028 | } 1029 | 1030 | static void wait_or_exit(struct root_args *args, pid_t pid, char *err) { 1031 | if (!err) { 1032 | err = "exited with status"; 1033 | } 1034 | int status; 1035 | waitpid(pid, &status, 0); 1036 | if (WIFEXITED(status)) 1037 | { 1038 | int exit_status = WEXITSTATUS(status); 1039 | if (exit_status != 0) 1040 | { 1041 | log_fata(args, "%s %d\n", err, exit_status); 1042 | } 1043 | } 1044 | } 1045 | 1046 | // -------------------------------------------------------------------------------------------------------------------- 1047 | // Implementation 1048 | // -------------------------------------------------------------------------------------------------------------------- 1049 | 1050 | int run(struct root_args *args) 1051 | { 1052 | log_info(args, "executing program '%s'\n", args->program[0]); 1053 | 1054 | pid_t pid = fork(); 1055 | switch (pid) 1056 | { 1057 | case -1: /* Error */ 1058 | log_fata(args, "%s\n", strerror(errno)); 1059 | case 0: /* Child */ 1060 | if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) 1061 | { 1062 | log_fata(args, "%s\n", strerror(errno)); 1063 | } 1064 | execvp(args->program[0], args->program); 1065 | log_fata(args, "%s\n", strerror(errno)); 1066 | } 1067 | 1068 | /* Parent */ 1069 | waitpid(pid, 0, 0); // sync with PTRACE_TRACEME 1070 | ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL); 1071 | 1072 | int is_map = 0; 1073 | for (;;) 1074 | { 1075 | /* Enter next system call */ 1076 | if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) 1077 | { 1078 | log_fata(args, "%s\n", strerror(errno)); 1079 | } 1080 | 1081 | /* Waiting for PID to die */ 1082 | if (waitpid(pid, 0, 0) == -1) 1083 | { 1084 | log_fata(args, "%s\n", strerror(errno)); 1085 | } 1086 | 1087 | /* Gather system call arguments */ 1088 | struct user_regs_struct regs; 1089 | if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) 1090 | { 1091 | log_fata(args, "%s\n", strerror(errno)); 1092 | } 1093 | 1094 | /* Mark bpf(BPF_MAP_CREATE, ...) */ 1095 | const unsigned int sysc = regs.orig_rax; 1096 | const unsigned int comm = regs.rdi; 1097 | is_map = (sysc == SYS_bpf && comm == BPF_MAP_CREATE); 1098 | 1099 | /* Print a representation of the system call */ 1100 | log_debu(args, 1101 | "%d(%d, %ld, %ld, %ld, %ld, %ld)", 1102 | sysc, 1103 | comm, (long)regs.rsi, (long)regs.rdx, (long)regs.r10, (long)regs.r8, (long)regs.r9); 1104 | 1105 | /* Run system call and stop on exit */ 1106 | if (ptrace(PTRACE_SYSCALL, pid, 0, 0) == -1) 1107 | { 1108 | log_fata(args, "%s\n", strerror(errno)); 1109 | } 1110 | 1111 | /* Waiting for PID to die */ 1112 | if (waitpid(pid, 0, 0) == -1) 1113 | { 1114 | log_fata(args, "%s\n", strerror(errno)); 1115 | } 1116 | 1117 | /* Get system call result */ 1118 | if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) 1119 | { 1120 | if (DEBUG) { 1121 | print_log(3, NULL, args, "%s\n", " = ?"); 1122 | } 1123 | if (errno == ESRCH) 1124 | { 1125 | exit(regs.rdi); // _exit(2) or similar 1126 | } 1127 | log_fata(args, "%s\n", strerror(errno)); 1128 | } 1129 | 1130 | /* Print system call result */ 1131 | long result = regs.rax; 1132 | if (DEBUG) { 1133 | print_log(3, NULL, args, " = %ld\n", result); 1134 | } 1135 | 1136 | /* Pin the bpfcov maps */ 1137 | if (is_map && result) 1138 | { 1139 | int pidfd = syscall(SYS_pidfd_open, pid, 0); 1140 | if (pidfd < 0) 1141 | { 1142 | continue; 1143 | } 1144 | int curfd = syscall(SYS_pidfd_getfd, pidfd, result, 0); 1145 | if (curfd < 0) 1146 | { 1147 | continue; 1148 | } 1149 | close(pidfd); 1150 | 1151 | struct bpf_map_info map_info = {}; 1152 | int err; 1153 | err = get_map_info(curfd, &map_info); 1154 | if (!err && strlen(map_info.name) > 0) 1155 | { 1156 | log_info(args, "got info about map '%s'\n", map_info.name); 1157 | 1158 | char map_name[BPF_OBJ_NAME_LEN]; 1159 | strcpy(map_name, map_info.name); 1160 | 1161 | const char *sep = "."; 1162 | strtok(map_info.name, sep); 1163 | char *suffix = strtok(NULL, sep); 1164 | 1165 | char *pin_path = ""; 1166 | if (get_pin_path(args, suffix, &pin_path)) 1167 | { 1168 | err = bpf_obj_pin(curfd, pin_path); 1169 | if (err) 1170 | { 1171 | if (errno == EEXIST) 1172 | { 1173 | log_warn(args, "pin '%s' already exists for map '%s'\n", pin_path, map_name); 1174 | continue; 1175 | } 1176 | log_fata(args, "%s\n", "could not pin map"); 1177 | } 1178 | log_warn(args, "pin map '%s' to '%s'\n", map_name, pin_path); 1179 | } 1180 | } 1181 | } 1182 | } 1183 | 1184 | return 0; 1185 | } 1186 | 1187 | int gen(struct root_args *args) 1188 | { 1189 | log_info(args, "generating '%s' for program '%s'\n", args->output, args->program[0]); 1190 | 1191 | /* Get maps info */ 1192 | struct bpf_map_info profc_info = {}; 1193 | if (get_map_info(bpf_obj_get(args->pin[0]), &profc_info)) 1194 | { 1195 | log_fata(args, "could not get info about pinned map '%s'\n", args->pin[0]); 1196 | } 1197 | struct bpf_map_info profd_info = {}; 1198 | if (get_map_info(bpf_obj_get(args->pin[1]), &profd_info)) 1199 | { 1200 | log_fata(args, "could not get info about pinned map '%s'\n", args->pin[1]); 1201 | } 1202 | struct bpf_map_info profn_info = {}; 1203 | if (get_map_info(bpf_obj_get(args->pin[2]), &profn_info)) 1204 | { 1205 | log_fata(args, "could not get info about pinned map '%s'\n", args->pin[2]); 1206 | } 1207 | struct bpf_map_info covmap_info = {}; 1208 | if (get_map_info(bpf_obj_get(args->pin[3]), &covmap_info)) 1209 | { 1210 | log_fata(args, "could not get info about pinned map '%s'\n", args->pin[3]); 1211 | } 1212 | 1213 | /* Time to write binary data to the output file */ 1214 | FILE *outfp = fopen(args->output, "wb"); 1215 | if (!outfp) 1216 | { 1217 | log_fata(args, "could not open the output file '%s'\n", args->output); 1218 | } 1219 | 1220 | /* Write the header */ 1221 | log_info(args, "%s\n", "about to write the profraw header..."); 1222 | // Magic number 1223 | char magic[8] = {0x81, 0x72, 0x66, 0x6F, 0x72, 0x70, 0x6C, 0xFF}; 1224 | fwrite(magic, 1, sizeof(magic), outfp); 1225 | // Version 1226 | void *covmap_data = malloc(covmap_info.value_size); 1227 | if (get_global_data(bpf_obj_get(args->pin[3]), &covmap_info, covmap_data)) 1228 | { 1229 | fclose(outfp); 1230 | log_fata(args, "could not get global data from map '%s'\n", args->pin[3]); 1231 | } 1232 | long long int version = 0; 1233 | memcpy(&version, &((char *)covmap_data)[12], 4); // Version is the 3rd int in the coverage mapping header 1234 | version += 1; // Version is 0 indexed 1235 | fwrite(&version, 1, sizeof(version), outfp); 1236 | free(covmap_data); 1237 | // Data size 1238 | long long int func_num = profd_info.value_size / 48; // 5 x i64 + 2 x i32 for each function 1239 | fwrite(&func_num, 1, sizeof(func_num), outfp); 1240 | // Padding before counters 1241 | long long int pad_bef = 0; 1242 | fwrite(&pad_bef, 1, sizeof(pad_bef), outfp); 1243 | // Counters size 1244 | long long int counters_num = profc_info.value_size / 8; // 1 x i64 for each counter element 1245 | fwrite(&counters_num, 1, sizeof(counters_num), outfp); 1246 | // Padding after counters 1247 | long long int pad_aft = 0; 1248 | fwrite(&pad_aft, 1, sizeof(pad_aft), outfp); 1249 | // Names size 1250 | long long int names_sz = profn_info.value_size; 1251 | fwrite(&names_sz, 1, sizeof(names_sz), outfp); 1252 | // Counters delta (nulled) 1253 | long long int counters_delta = 0; 1254 | fwrite(&counters_delta, 1, sizeof(counters_delta), outfp); 1255 | // Names delta (nulled) 1256 | long long int names_delta = 0; 1257 | fwrite(&names_delta, 1, sizeof(names_delta), outfp); 1258 | // IPVK last 1259 | long long int ipvk_last = 1; 1260 | fwrite(&ipvk_last, 1, sizeof(ipvk_last), outfp); 1261 | 1262 | /* Write the data part */ 1263 | log_info(args, "%s\n", "about to write the data in the profraw..."); 1264 | void *profd_data = malloc(profd_info.value_size); 1265 | if (get_global_data(bpf_obj_get(args->pin[1]), &profd_info, profd_data)) 1266 | { 1267 | fclose(outfp); 1268 | log_fata(args, "could not get global data from map '%s'\n", args->pin[1]); 1269 | } 1270 | fwrite(profd_data, profd_info.value_size, 1, outfp); 1271 | 1272 | /* Write the counters part */ 1273 | log_info(args, "%s\n", "about to write the counters in the profraw.."); 1274 | void *profc_data = malloc(profc_info.value_size); 1275 | if (get_global_data(bpf_obj_get(args->pin[0]), &profc_info, profc_data)) 1276 | { 1277 | fclose(outfp); 1278 | log_fata(args, "could not get global data from map '%s'\n", args->pin[0]); 1279 | } 1280 | fwrite(profc_data, profc_info.value_size, 1, outfp); 1281 | 1282 | /* Write the names part */ 1283 | log_info(args, "%s\n", "about to write the names in the profraw..."); 1284 | void *profn_data = malloc(profn_info.value_size); 1285 | if (get_global_data(bpf_obj_get(args->pin[2]), &profn_info, profn_data)) 1286 | { 1287 | fclose(outfp); 1288 | log_fata(args, "could not get global data from map '%s'\n", args->pin[2]); 1289 | } 1290 | fwrite(profn_data, profn_info.value_size, 1, outfp); 1291 | 1292 | /* Align to 8 bytes */ 1293 | unsigned int b = 0; 1294 | for (unsigned int p = b; p < (7 & (16 - profn_info.value_size % 16)); p++) 1295 | { 1296 | fwrite(&b, 1, 1, outfp); 1297 | } 1298 | 1299 | /* Close */ 1300 | fclose(outfp); 1301 | 1302 | /* Unpin the maps */ 1303 | handle_map_pins(args, NULL, args->unpin); 1304 | 1305 | return 0; 1306 | } 1307 | 1308 | int out(struct root_args *args) 1309 | { 1310 | log_info(args, "%s\n", "generating coverage visualization..."); 1311 | 1312 | char report_path[PATH_MAX]; 1313 | strcpy(report_path, args->report_path); 1314 | 1315 | // Generating a *.profdata for each input *.profraw 1316 | char profdata[args->num_profraw][PATH_MAX]; 1317 | memset(profdata, 0, args->num_profraw * PATH_MAX * sizeof(char)); 1318 | 1319 | char bpfobjs[args->num_profraw][PATH_MAX]; 1320 | memset(bpfobjs, 0, args->num_profraw * PATH_MAX * sizeof(char)); 1321 | 1322 | int c = 0; 1323 | char** ptr = args->profraw; 1324 | for (char* profraw = *ptr; profraw; profraw = *++ptr) { 1325 | // Looking up for *.bpf.obj file sibling to the current input *.profraw file 1326 | char *profraw_wo_ext = strdup(profraw); 1327 | strip_extension(profraw_wo_ext); 1328 | 1329 | char bpfobj_path[PATH_MAX]; 1330 | int bpfobj_path_len = snprintf(bpfobj_path, PATH_MAX, "%s.bpf.obj", profraw_wo_ext); 1331 | if (bpfobj_path_len >= PATH_MAX) 1332 | { 1333 | log_fata(args, "%s\n", "bpf.obj output path too long"); 1334 | } 1335 | log_info(args, "looking for BPF coverage object at '%s'\n", bpfobj_path); 1336 | if (access(bpfobj_path, F_OK) != 0) 1337 | { 1338 | log_fata(args, "could not find the BPF coverage object at '%s'", bpfobj_path); 1339 | } 1340 | free(profraw_wo_ext); 1341 | 1342 | // Storing the *.bpf.obj file for later 1343 | strncpy(bpfobjs[c], bpfobj_path, PATH_MAX); 1344 | 1345 | // Creating the *.profdata path (relative to the execution directory) 1346 | char *profraw_name = strdup(basename(profraw)); 1347 | strtok(profraw_name, "."); 1348 | 1349 | char profdata_path[PATH_MAX]; 1350 | int profdata_path_len = snprintf(profdata_path, PATH_MAX, "%s.profdata", profraw_name); 1351 | if (profdata_path_len >= PATH_MAX) 1352 | { 1353 | log_fata(args, "%s\n", "profdata output path too long"); 1354 | } 1355 | free(profraw_name); 1356 | 1357 | // Storing the output *.profdata file for later 1358 | strncpy(profdata[c], profdata_path, PATH_MAX); 1359 | log_info(args, "generating '%s'\n", profdata[c]); 1360 | 1361 | // Generating the single *.profdata file 1362 | pid_t data_pid; 1363 | switch ((data_pid = fork())) 1364 | { 1365 | case -1: 1366 | log_fata(args, "%s\n", "could not fork"); 1367 | break; 1368 | case 0: 1369 | log_debu(args, "llvm-profdata merge -sparse %s -o %s\n", profraw, profdata[c]); 1370 | 1371 | int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); 1372 | dup2(devnull, STDERR_FILENO); 1373 | execlp("llvm-profdata", "llvm-profdata", "merge", "-sparse", profraw, "-o", profdata[c], NULL); 1374 | close(devnull); 1375 | log_fata(args, "%s\n", "could not exec llvm-profdata"); 1376 | break; 1377 | } 1378 | wait_or_exit(args, data_pid, "llvm-profdata: exited with status"); 1379 | 1380 | c++; 1381 | } 1382 | 1383 | // Merge all the *.profdata into one 1384 | char target_profdata[PATH_MAX]; 1385 | int num_profdata = sizeof(profdata) / PATH_MAX; 1386 | if (num_profdata > 1) { 1387 | strncpy(target_profdata, "all.profdata", PATH_MAX); 1388 | 1389 | log_info(args, "merging into '%s'\n", target_profdata); 1390 | 1391 | pid_t merge_pid; 1392 | switch ((merge_pid = fork())) 1393 | { 1394 | case -1: 1395 | log_fata(args, "%s\n", "could not fork"); 1396 | break; 1397 | case 0: 1398 | char *arguments[num_profdata + 6]; 1399 | arguments[0] = "llvm-profdata"; 1400 | arguments[1] = "merge"; 1401 | arguments[2] = "-sparse"; 1402 | arguments[3] = "-o"; 1403 | arguments[4] = target_profdata; 1404 | for (int i = 0; i < num_profdata; i++) { 1405 | arguments[i + 5] = profdata[i]; 1406 | } 1407 | arguments[num_profdata + 5] = NULL; 1408 | 1409 | log_debu(args, "%s ", arguments[0]); 1410 | for (int a = 1; a < num_profdata + 5; a++) { 1411 | if (DEBUG) { 1412 | print_log(3, NULL, args, "%s ", arguments[a]); 1413 | } 1414 | } 1415 | if (DEBUG) { 1416 | print_log(3, NULL, args, "%s", "\n"); 1417 | } 1418 | 1419 | int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); 1420 | dup2(devnull, STDERR_FILENO); 1421 | execvp("llvm-profdata", arguments); 1422 | close(devnull); 1423 | log_fata(args, "%s\n", "could not exec llvm-profdata"); 1424 | break; 1425 | } 1426 | wait_or_exit(args, merge_pid, "llvm-profdata: exited with status"); 1427 | } 1428 | else { 1429 | strncpy(target_profdata, profdata[0], PATH_MAX); 1430 | } 1431 | 1432 | int num_bpfobj_params = num_profdata * 2; 1433 | int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); 1434 | if (devnull == -1) { 1435 | log_fata(args, "could not open %s\n", "/dev/null"); 1436 | } 1437 | log_info(args, "about to generate the %s coverage report in '%s'\n", format_string[args->out_format], report_path); 1438 | switch (args->out_format) 1439 | { 1440 | case FORMAT_html: 1441 | pid_t html_pid; 1442 | switch ((html_pid = fork())) 1443 | { 1444 | case -1: 1445 | log_fata(args, "%s\n", "could not fork"); 1446 | break; 1447 | case 0: 1448 | char *arguments[num_bpfobj_params + 11]; 1449 | arguments[0] = "llvm-cov"; 1450 | arguments[1] = "show"; 1451 | arguments[2] = "--format=html"; 1452 | arguments[3] = "--show-branches=count"; 1453 | arguments[4] = "--show-line-counts-or-regions"; 1454 | arguments[5] = "--show-region-summary"; 1455 | arguments[6] = "--output-dir"; 1456 | arguments[7] = report_path; 1457 | arguments[8] = "-instr-profile"; 1458 | arguments[9] = target_profdata; 1459 | for (int i = 0; i < num_profdata; i++) { 1460 | int off = i * 2; 1461 | arguments[off + 10] = "-object"; 1462 | arguments[off + 11] = bpfobjs[i]; 1463 | } 1464 | arguments[num_bpfobj_params + 10] = NULL; 1465 | 1466 | log_debu(args, "%s ", arguments[0]); 1467 | for (int a = 1; a < num_bpfobj_params + 10; a++) { 1468 | if (DEBUG) { 1469 | print_log(3, NULL, args, "%s ", arguments[a]); 1470 | } 1471 | } 1472 | if (DEBUG) { 1473 | print_log(3, NULL, args, "%s", "\n"); 1474 | } 1475 | 1476 | dup2(devnull, STDERR_FILENO); 1477 | execvp("llvm-cov", arguments); 1478 | break; 1479 | } 1480 | close(devnull); 1481 | wait_or_exit(args, html_pid, "llvm-cov exited with status"); 1482 | break; 1483 | case FORMAT_lcov: 1484 | /* Fallthrough */ 1485 | case FORMAT_json: 1486 | int outfile = open(report_path, O_RDWR | O_CREAT, 0600); 1487 | if (outfile == -1) { 1488 | log_fata(args, "could not open %s\n", report_path); 1489 | } 1490 | 1491 | pid_t export_pid; 1492 | switch ((export_pid = fork())) 1493 | { 1494 | case -1: 1495 | log_fata(args, "%s\n", "could not fork"); 1496 | break; 1497 | case 0: 1498 | char *arguments[num_bpfobj_params + 8]; 1499 | arguments[0] = "llvm-cov"; 1500 | arguments[1] = "export"; 1501 | arguments[2] = "--format=text"; 1502 | arguments[3] = "--show-branch-summary"; 1503 | arguments[4] = "--show-region-summary"; 1504 | arguments[5] = "-instr-profile"; 1505 | arguments[6] = target_profdata; 1506 | for (int i = 0; i < num_profdata; i++) { 1507 | int off = i > 0 ? (i + 1) : i; 1508 | arguments[off + 7] = "-object"; 1509 | arguments[off + 8] = bpfobjs[i]; 1510 | } 1511 | arguments[num_bpfobj_params + 7] = NULL; 1512 | 1513 | log_debu(args, "%s ", arguments[0]); 1514 | for (int a = 1; a < num_bpfobj_params + 7; a++) { 1515 | if (DEBUG) { 1516 | print_log(3, NULL, args, "%s ", arguments[a]); 1517 | } 1518 | } 1519 | if (DEBUG) { 1520 | print_log(3, NULL, args, "%s", "\n"); 1521 | } 1522 | 1523 | dup2(devnull, STDERR_FILENO); 1524 | dup2(outfile, STDOUT_FILENO); 1525 | execvp("llvm-cov", arguments); 1526 | break; 1527 | } 1528 | close(devnull); 1529 | close(outfile); 1530 | wait_or_exit(args, export_pid, "llvm-cov exited with status"); 1531 | break; 1532 | } 1533 | 1534 | return 0; 1535 | } -------------------------------------------------------------------------------- /docs/assets/html1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/html1.png -------------------------------------------------------------------------------- /docs/assets/html2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/html2.png -------------------------------------------------------------------------------- /docs/assets/html3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/html3.png -------------------------------------------------------------------------------- /docs/assets/html4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/html4.png -------------------------------------------------------------------------------- /docs/assets/json1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/json1.png -------------------------------------------------------------------------------- /docs/assets/lcov1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/lcov1.png -------------------------------------------------------------------------------- /docs/assets/mult1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/mult1.png -------------------------------------------------------------------------------- /docs/assets/stdo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/stdo1.png -------------------------------------------------------------------------------- /docs/assets/stdo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/bpfcov/f06a65b84477dfebc9cff58ae4e4662beed4c411/docs/assets/stdo2.png -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | .output/ -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # bpfcov / examples 2 | 3 | Here (in the `src/` directory) you will find some eBPF programs to demonstrate the usage of `libBPFCov.so`. 4 | 5 | ## Overview 6 | 7 | Every example is composed by: 8 | 9 | 1. a `*.bpf.c` file that contains the eBPF program 10 | 2. a `*.c` file that contains the userspace code 11 | 12 | The [Makefile](src/Makefile) generates 2 targets for each word of the [EXAMPLES](src/Makefile#L20) variable in it. 13 | 14 | So, assuming the `EXAMPLES` variable contains a word `program`, then the following targets will be generated: 15 | 16 | 1. `make program` 17 | 18 | It outputs a your eBPF application binary in `.output/program` 19 | 20 | 2. `make cov/program` 21 | 22 | It outputs an eBPF application binary **instrumented for source-based code coverage** in `.output/cov/program` 23 | 24 | In case you wanna try **bpfcov** on another example, doing it is just a matter of putting its source code in the `src/` directory and appending its name into the `EXAMPLES variable in the [Makefile](src/Makefile). 25 | 26 | The [Makefile](src/Makefile) takes care of everything... But I suggest you to take a look at it in case you are interested into getting to know the details of the steps. Or at least, read the following [section](#key-aspects). 27 | 28 | ## Key aspects 29 | 30 | The key aspects of building an instrumented eBPF application are the following ones. 31 | 32 | 1. Compile you eBPF program (`*.bpf.c`) to LLVM IR instrumenting it with profile and coverage mapping information (`-fprofile-instr-generate -fcoverage-mapping`) 33 | 34 | 2. Run the pass (`libBPFCov.so`) on the LLVM IR to fix it for the BPF VM 35 | 36 | 1. Use it to compile a valid BPF ELF that loads successfully in the Linux kernel 37 | 38 | 3. Run _again_ the pass (`libBPFCov.so`) with the `-strip-initializers-only` flag on the LLVM IR 39 | 40 | 1. Use the resulting LLVM IR to compile a (valid but not loading) BPF ELF (`*.bpf.obj`) 41 | 2. It will serve you as one of the inputs for `llvm-cov` 42 | 43 | 4. Userspace code compilation as usual but using the instrumted ELF from step 2 44 | 45 | ## Usage 46 | 47 | Did you already take a look at the [requirements](#requirements) section? 48 | 49 | Do you only wanna build a specific eBPF example application as is? 50 | 51 | Good! 52 | 53 | ```bash 54 | make fentry 55 | ``` 56 | 57 | The binary will end up in `.output/fentry`. 58 | 59 | Do you only wanna compile a specific eBPF example application instrumented for code coverage? 60 | 61 | Even better! 62 | 63 | ```bash 64 | make cov/fentry 65 | ``` 66 | 67 | At this point, you should be able to run it: 68 | 69 | ```shell 70 | sudo .output/cov/fentry 71 | ``` 72 | 73 | Wanna build every eBPF application as is? 74 | 75 | ```bash 76 | make 77 | ``` 78 | 79 | Wanna instrument every eBPF application here for code coverage? 80 | 81 | ```bash 82 | make cov 83 | ``` 84 | 85 | Wanna start over but not recompile the dependencies too? 86 | 87 | ```bash 88 | make clean 89 | ``` 90 | 91 | Wanna start from scratch? 92 | 93 | ```bash 94 | make distclean 95 | ``` 96 | 97 | ## Requirements 98 | 99 | 1. `libBPFCov.so` 100 | 101 | First [obtain](../README.md#Building) the LLVM pass library, please. 102 | 103 | 2. `bpftool` 104 | 105 | You can provide your own `bpftool` by putting it in the `tools` directory. 106 | 107 | Otherwise the [Makefile](src/Makefile) will try to find it in the system path and symlink it into the `tools/` directory. 108 | 109 | In any case you will need a `bpftool` that supports the **skeletons** feature becase this tool uses **custom eBPF sections** for storing the instrumentation and coverage mapping data. 110 | 111 | 3. `libbpf` 112 | 113 | Ensure you have the git submodule in the `libbpf/` directory. 114 | -------------------------------------------------------------------------------- /examples/src/Makefile: -------------------------------------------------------------------------------- 1 | OUTPUT := .output 2 | CLANG ?= clang 3 | OPT ?= opt 4 | LLC ?= llc 5 | JQ ?= jq 6 | SED ?= sed 7 | ARCH := $(shell uname -m | $(SED) 's/x86_64/x86/' | $(SED) 's/aarch64/arm64/' | $(SED) 's/ppc64le/powerpc/' | $(SED) 's/mips.*/mips/') 8 | BPFCOVLIB_DIR ?= $(abspath ../../build/lib) 9 | BPFTOOL_LOCAL ?= ../tools/bpftool 10 | BPFTOOL = $(abspath $(BPFTOOL_LOCAL)) 11 | LIBBPF_DIR := $(abspath ../libbpf) 12 | LIBBPF_SRC := $(abspath $(LIBBPF_DIR)/src) 13 | LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) 14 | VMLINUX := $(abspath $(OUTPUT)/vmlinux/vmlinux.h) 15 | INCLUDES := -I$(LIBBPF_DIR)/include/uapi -I$(dir $(VMLINUX)) 16 | CLANG_BPF_SYS_INCLUDES = $(shell $(CLANG) -v -E - &1 \ 17 | | $(SED) -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') 18 | 19 | # List your examples here 20 | EXAMPLES = raw_enter fentry lsm 21 | 22 | ifeq ($(V),1) 23 | Q = 24 | msg = 25 | else 26 | Q = @ 27 | msg = @printf ' %-8s %s%s\n' \ 28 | "$(1)" \ 29 | "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ 30 | "$(if $(3), $(3))"; 31 | MAKEFLAGS += --no-print-directory 32 | endif 33 | 34 | .PHONY: all 35 | all: $(EXAMPLES) 36 | 37 | .PHONY: cov 38 | cov: $(patsubst %,cov/%,$(EXAMPLES)) 39 | 40 | .PHONY: distclean 41 | distclean: 42 | $(call msg,DISTCLEAN) 43 | $(Q)rm -rf $(OUTPUT) 44 | $(Q)unlink $(BPFTOOL) 45 | 46 | .PHONY: clean 47 | clean: 48 | $(call msg,CLEAN) 49 | $(Q)rm -rf $(OUTPUT)/*.{o,bpf.o,skel.h} 50 | $(Q)rm -rf $(OUTPUT)/cov 51 | $(Q)rm -rf $(patsubst %,$(OUTPUT)/%,$(EXAMPLES)) 52 | 53 | # Create output directory 54 | $(OUTPUT) $(OUTPUT)/cov $(OUTPUT)/libbpf: 55 | $(call msg,MKDIR,$@) 56 | $(Q)mkdir -p $@ 57 | 58 | # Build libbpf 59 | $(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf 60 | $(call msg,LIB,$@) 61 | $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ 62 | OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ 63 | INCLUDEDIR= LIBDIR= UAPIDIR= \ 64 | install 65 | 66 | # Link bpftool 67 | $(BPFTOOL): $(shell command -v bpftool) 68 | $(call msg,BPFTOOL,$(BPFTOOL_LOCAL)) 69 | $(Q)$(shell ln -s $< $@) 70 | $(Q)$(BPFTOOL) version -j | $(JQ) -e '.features.skeletons' || $(error "A bptfool with the skeletons feature is required") 71 | 72 | # Generate vmlinux.h 73 | $(VMLINUX): $(BPFTOOL) /sys/kernel/btf/vmlinux 74 | $(call msg,MKDIR,$(dir $@)) 75 | $(Q)mkdir -p $(dir $@) 76 | $(call msg,VMLINUX,$@) 77 | $(Q)$(BPFTOOL) btf dump file $(word 2,$^) format c > $@ 78 | 79 | # Compile the eBPF example as is 80 | $(OUTPUT)/%.bpf.o: %.bpf.c $(wildcard %.h) $(VMLINUX) $(LIBBPF_OBJ) | $(OUTPUT) 81 | $(call msg,OBJ,$@) 82 | $(Q)$(CLANG) -g -O2 \ 83 | -target bpf -D__TARGET_ARCH_$(ARCH) -I$(OUTPUT) $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ 84 | -c $(filter %.c,$^) \ 85 | -o $@ 86 | 87 | # Obtain the LLVM IR instrumenting profiling and coverage mappings 88 | $(OUTPUT)/cov/%.bpf.ll: %.bpf.c $(wildcard %.h) $(VMLINUX) $(LIBBPF_OBJ) | $(OUTPUT)/cov 89 | $(call msg,LL,$@) 90 | $(Q)$(CLANG) -g -O2 \ 91 | -target bpf -D__TARGET_ARCH_$(ARCH) -I$(OUTPUT)/cov $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ 92 | -fprofile-instr-generate -fcoverage-mapping \ 93 | -emit-llvm -S \ 94 | -c $(filter %.c,$^) \ 95 | -o $@ 96 | 97 | # Create the object file for coverage (not for loading, only for llvm-cov) 98 | $(OUTPUT)/cov/%.bpf.obj: $(OUTPUT)/cov/%.bpf.ll | $(OUTPUT)/cov 99 | $(call msg,ARCHIVE,$@) 100 | $(Q)$(OPT) \ 101 | -load $(BPFCOVLIB_DIR)/libBPFCov.so -strip-initializers-only -bpf-cov $< \ 102 | | $(LLC) -march=bpf -filetype=obj -o $@ 103 | 104 | # Make the LLVM IR valid for eBPF 105 | $(OUTPUT)/cov/%.bpf.cov.ll: $(OUTPUT)/cov/%.bpf.ll | $(OUTPUT)/cov 106 | $(call msg,COV,$@) 107 | $(Q)$(OPT) \ 108 | -load-pass-plugin $(BPFCOVLIB_DIR)/libBPFCov.so -passes="bpf-cov" \ 109 | -S $< -o $@ 110 | 111 | # Build the instrumented ELF 112 | $(patsubst %,$(OUTPUT)/cov/%.bpf.o,$(EXAMPLES)): %.bpf.o: %.bpf.cov.ll %.bpf.obj 113 | $(OUTPUT)/cov/%.bpf.o: $(OUTPUT)/cov/%.bpf.cov.ll | $(OUTPUT)/cov 114 | $(call msg,OBJ,$@) 115 | $(Q)$(LLC) -march=bpf -filetype=obj -o $@ $< 116 | 117 | # Generate the skeleton for the eBPF example as is 118 | $(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o $(BPFTOOL) | $(OUTPUT) 119 | $(call msg,SKEL,$@) 120 | $(Q)$(BPFTOOL) gen skeleton $< name $* > $@ 121 | 122 | # Generate the skeleton for the instrumented ELF 123 | $(OUTPUT)/cov/%.skel.h: $(OUTPUT)/cov/%.bpf.o $(BPFTOOL) | $(OUTPUT)/cov 124 | $(call msg,SKEL,$@) 125 | $(Q)$(BPFTOOL) gen skeleton $< name $* > $@ 126 | 127 | # Build userspace code 128 | $(patsubst %,$(OUTPUT)/%.o,$(EXAMPLES)): %.o: %.skel.h 129 | 130 | # Build the eBPF application as is 131 | $(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) 132 | $(call msg,CC,$@) 133 | $(Q)$(CC) -g -Wall -I$(OUTPUT) $(INCLUDES) -c $(filter %.c,$^) -o $@ 134 | 135 | # Build userspace code using the instrumented skeleton 136 | $(patsubst %,$(OUTPUT)/cov/%.o,$(EXAMPLES)): %.o: %.skel.h 137 | 138 | # Build the instrumented eBPF application 139 | $(OUTPUT)/cov/%.o: %.c $(wildcard %.h) | $(OUTPUT)/cov 140 | $(call msg,CC,$@) 141 | $(Q)$(CC) -g -Wall -I$(OUTPUT)/cov $(INCLUDES) -c $(filter %.c,$^) -o $@ 142 | 143 | # Build the binary as is 144 | %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) 145 | $(call msg,BIN,$(OUTPUT)/$@) 146 | $(Q)$(CC) -g -Wall $^ -lelf -lz -o $(OUTPUT)/$@ 147 | 148 | # Build the instrumented eBPF binary 149 | cov/%: $(OUTPUT)/cov/%.o $(LIBBPF_OBJ) | $(OUTPUT)/cov 150 | $(call msg,BIN,$(OUTPUT)/$@) 151 | $(Q)$(CC) -g -Wall $^ -lelf -lz -o $(OUTPUT)/$@ 152 | 153 | # Delete failed targets 154 | .DELETE_ON_ERROR: 155 | 156 | # Keep intermediate (.bpf.o, etc.) targets 157 | .SECONDARY: 158 | -------------------------------------------------------------------------------- /examples/src/commons.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 11 | { 12 | return vfprintf(stdout, format, args); 13 | } 14 | 15 | static void bump_memlock_rlimit(void) 16 | { 17 | struct rlimit rlim_new = { 18 | .rlim_cur = RLIM_INFINITY, 19 | .rlim_max = RLIM_INFINITY, 20 | }; 21 | 22 | if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) 23 | { 24 | fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n"); 25 | exit(1); 26 | } 27 | } 28 | 29 | static volatile sig_atomic_t stop; 30 | 31 | void sig_int(int signo) 32 | { 33 | stop = signo; 34 | } 35 | 36 | int bpf_trace_pipe(int out) 37 | { 38 | // todo > find mount -> use mnt/trace_pipe (making strong assumptions atm) 39 | 40 | int inp = STDERR_FILENO; 41 | inp = open("/sys/kernel/debug/tracing/trace_pipe", O_RDONLY); 42 | if (inp < 0) 43 | { 44 | return inp; 45 | } 46 | 47 | while (!stop) 48 | { 49 | static char buf[4096]; 50 | ssize_t ret; 51 | 52 | ret = read(inp, buf, sizeof(buf)); 53 | if (ret > 0 && write(out, buf, ret) == ret) 54 | { 55 | continue; 56 | } 57 | } 58 | 59 | close(inp); 60 | return 0; 61 | } -------------------------------------------------------------------------------- /examples/src/fentry.bpf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include "vmlinux.h" 3 | #include 4 | #include 5 | 6 | char LICENSE[] SEC("license") = "GPL"; 7 | 8 | SEC("fentry/do_unlinkat") 9 | int BPF_PROG(do_unlinkat, int dfd, struct filename *name) 10 | { 11 | pid_t pid; 12 | 13 | pid = bpf_get_current_pid_tgid() >> 32; 14 | bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name); 15 | return 0; 16 | } 17 | 18 | SEC("fexit/do_unlinkat") 19 | int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret) 20 | { 21 | pid_t pid; 22 | 23 | pid = bpf_get_current_pid_tgid() >> 32; 24 | bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /examples/src/fentry.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include "commons.c" 3 | #include "fentry.skel.h" 4 | 5 | int main(int argc, char **argv) 6 | { 7 | struct fentry *skel; 8 | int err; 9 | 10 | /* Set up libbpf errors and debug info callback */ 11 | libbpf_set_print(libbpf_print_fn); 12 | 13 | /* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */ 14 | bump_memlock_rlimit(); 15 | 16 | /* Open load and verify BPF application */ 17 | skel = fentry__open_and_load(); 18 | if (!skel) 19 | { 20 | fprintf(stderr, "Failed to open BPF skeleton\n"); 21 | return 1; 22 | } 23 | 24 | /* Attach tracepoint handler */ 25 | err = fentry__attach(skel); 26 | if (err) 27 | { 28 | fprintf(stderr, "Failed to attach BPF skeleton\n"); 29 | goto cleanup; 30 | } 31 | 32 | if (signal(SIGINT, sig_int) == SIG_ERR) 33 | { 34 | fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); 35 | goto cleanup; 36 | } 37 | 38 | printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` " 39 | "to see output of the BPF programs.\n"); 40 | 41 | while (!stop) 42 | { 43 | fprintf(stderr, "."); 44 | sleep(1); 45 | } 46 | 47 | cleanup: 48 | fentry__destroy(skel); 49 | return -err; 50 | } 51 | -------------------------------------------------------------------------------- /examples/src/lsm.bpf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | /* 4 | * Adapted from: 5 | * https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/progs/lsm.c 6 | */ 7 | 8 | #include "vmlinux.h" 9 | #include 10 | #include 11 | 12 | #define EPERM 1 /* Operation not permitted */ 13 | #define EFAULT 14 /* Bad address */ 14 | 15 | struct { 16 | __uint(type, BPF_MAP_TYPE_ARRAY); 17 | __uint(max_entries, 1); 18 | __type(key, __u32); 19 | __type(value, __u64); 20 | } array SEC(".maps"); 21 | 22 | struct { 23 | __uint(type, BPF_MAP_TYPE_HASH); 24 | __uint(max_entries, 1); 25 | __type(key, __u32); 26 | __type(value, __u64); 27 | } hash SEC(".maps"); 28 | 29 | struct { 30 | __uint(type, BPF_MAP_TYPE_LRU_HASH); 31 | __uint(max_entries, 1); 32 | __type(key, __u32); 33 | __type(value, __u64); 34 | } lru_hash SEC(".maps"); 35 | 36 | struct { 37 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 38 | __uint(max_entries, 1); 39 | __type(key, __u32); 40 | __type(value, __u64); 41 | } percpu_array SEC(".maps"); 42 | 43 | struct { 44 | __uint(type, BPF_MAP_TYPE_PERCPU_HASH); 45 | __uint(max_entries, 1); 46 | __type(key, __u32); 47 | __type(value, __u64); 48 | } percpu_hash SEC(".maps"); 49 | 50 | struct { 51 | __uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH); 52 | __uint(max_entries, 1); 53 | __type(key, __u32); 54 | __type(value, __u64); 55 | } lru_percpu_hash SEC(".maps"); 56 | 57 | struct inner_map { 58 | __uint(type, BPF_MAP_TYPE_ARRAY); 59 | __uint(max_entries, 1); 60 | __type(key, int); 61 | __type(value, __u64); 62 | } inner_map SEC(".maps"); 63 | 64 | struct outer_arr { 65 | __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); 66 | __uint(max_entries, 1); 67 | __uint(key_size, sizeof(int)); 68 | __uint(value_size, sizeof(int)); 69 | __array(values, struct inner_map); 70 | } outer_arr SEC(".maps") = { 71 | .values = { [0] = &inner_map }, 72 | }; 73 | 74 | struct outer_hash { 75 | __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); 76 | __uint(max_entries, 1); 77 | __uint(key_size, sizeof(int)); 78 | __array(values, struct inner_map); 79 | } outer_hash SEC(".maps") = { 80 | .values = { [0] = &inner_map }, 81 | }; 82 | 83 | char _license[] SEC("license") = "GPL"; 84 | 85 | int monitored_pid = 0; 86 | int mprotect_count = 0; 87 | int bprm_count = 0; 88 | 89 | SEC("lsm/file_mprotect") 90 | int BPF_PROG(test_int_hook, struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot, int ret) 91 | { 92 | if (ret != 0) 93 | return ret; 94 | 95 | __u32 pid = bpf_get_current_pid_tgid() >> 32; 96 | int is_stack = 0; 97 | 98 | is_stack = (vma->vm_start <= vma->vm_mm->start_stack && vma->vm_end >= vma->vm_mm->start_stack); 99 | 100 | if (is_stack && monitored_pid == pid) { 101 | mprotect_count++; 102 | ret = -EPERM; 103 | } 104 | 105 | return ret; 106 | } 107 | 108 | SEC("lsm.s/bprm_committed_creds") 109 | int BPF_PROG(test_void_hook, struct linux_binprm *bprm) 110 | { 111 | __u32 pid = bpf_get_current_pid_tgid() >> 32; 112 | struct inner_map *inner_map; 113 | char args[64]; 114 | __u32 key = 0; 115 | __u64 *value; 116 | 117 | if (monitored_pid == pid) 118 | bprm_count++; 119 | 120 | bpf_copy_from_user(args, sizeof(args), (void *)bprm->vma->vm_mm->arg_start); 121 | bpf_copy_from_user(args, sizeof(args), (void *)bprm->mm->arg_start); 122 | 123 | value = bpf_map_lookup_elem(&array, &key); 124 | if (value) 125 | *value = 0; 126 | value = bpf_map_lookup_elem(&hash, &key); 127 | if (value) 128 | *value = 0; 129 | value = bpf_map_lookup_elem(&lru_hash, &key); 130 | if (value) 131 | *value = 0; 132 | value = bpf_map_lookup_elem(&percpu_array, &key); 133 | if (value) 134 | *value = 0; 135 | value = bpf_map_lookup_elem(&percpu_hash, &key); 136 | if (value) 137 | *value = 0; 138 | value = bpf_map_lookup_elem(&lru_percpu_hash, &key); 139 | if (value) 140 | *value = 0; 141 | inner_map = bpf_map_lookup_elem(&outer_arr, &key); 142 | if (inner_map) { 143 | value = bpf_map_lookup_elem(inner_map, &key); 144 | if (value) 145 | *value = 0; 146 | } 147 | inner_map = bpf_map_lookup_elem(&outer_hash, &key); 148 | if (inner_map) { 149 | value = bpf_map_lookup_elem(inner_map, &key); 150 | if (value) 151 | *value = 0; 152 | } 153 | 154 | return 0; 155 | } 156 | 157 | SEC("lsm/task_free") /* lsm/ is ok, lsm.s/ fails */ 158 | int BPF_PROG(test_task_free, struct task_struct *task) 159 | { 160 | return 0; 161 | } 162 | 163 | int copy_test = 0; 164 | 165 | SEC("fentry.s/__x64_sys_setdomainname") 166 | int BPF_PROG(test_sys_setdomainname, struct pt_regs *regs) 167 | { 168 | void *ptr = (void *)PT_REGS_PARM1(regs); 169 | int len = PT_REGS_PARM2(regs); 170 | int buf = 0; 171 | long ret; 172 | 173 | ret = bpf_copy_from_user(&buf, sizeof(buf), ptr); 174 | if (len == -2 && ret == 0 && buf == 1234) 175 | copy_test++; 176 | if (len == -3 && ret == -EFAULT) 177 | copy_test++; 178 | if (len == -4 && ret == -EFAULT) 179 | copy_test++; 180 | return 0; 181 | } 182 | -------------------------------------------------------------------------------- /examples/src/lsm.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | /* 4 | * Adapted from: 5 | * https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/prog_tests/test_lsm.c 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "lsm.skel.h" 15 | 16 | void test__fail(void) {} 17 | 18 | #define _CHECK(condition, tag, duration, format...) ({ \ 19 | int __ret = !!(condition); \ 20 | int __save_errno = errno; \ 21 | if (__ret) { \ 22 | test__fail(); \ 23 | fprintf(stdout, "%s:FAIL:%s ", __func__, tag); \ 24 | fprintf(stdout, ##format); \ 25 | } else { \ 26 | fprintf(stdout, "%s:PASS:%s %d nsec\n", \ 27 | __func__, tag, duration); \ 28 | } \ 29 | errno = __save_errno; \ 30 | __ret; \ 31 | }) 32 | 33 | #define CHECK(condition, tag, format...) \ 34 | _CHECK(condition, tag, duration, format) 35 | 36 | #define ASSERT_EQ(actual, expected, name) ({ \ 37 | static int duration = 0; \ 38 | typeof(actual) ___act = (actual); \ 39 | typeof(expected) ___exp = (expected); \ 40 | bool ___ok = ___act == ___exp; \ 41 | CHECK(!___ok, (name), \ 42 | "unexpected %s: actual %lld != expected %lld\n", \ 43 | (name), (long long)(___act), (long long)(___exp)); \ 44 | ___ok; \ 45 | }) 46 | 47 | #define ASSERT_OK(res, name) ({ \ 48 | static int duration = 0; \ 49 | long long ___res = (res); \ 50 | bool ___ok = ___res == 0; \ 51 | CHECK(!___ok, (name), "unexpected error: %lld (errno %d)\n", \ 52 | ___res, errno); \ 53 | ___ok; \ 54 | }) 55 | 56 | #define ASSERT_ERR_PTR(ptr, name) ({ \ 57 | static int duration = 0; \ 58 | const void *___res = (ptr); \ 59 | int ___err = libbpf_get_error(___res); \ 60 | bool ___ok = ___err != 0; \ 61 | CHECK(!___ok, (name), "unexpected pointer: %p\n", ___res); \ 62 | ___ok; \ 63 | }) 64 | 65 | #define ASSERT_OK_PTR(ptr, name) ({ \ 66 | static int duration = 0; \ 67 | const void *___res = (ptr); \ 68 | int ___err = libbpf_get_error(___res); \ 69 | bool ___ok = ___err == 0; \ 70 | CHECK(!___ok, (name), "unexpected error: %d\n", ___err); \ 71 | ___ok; \ 72 | }) 73 | 74 | char *CMD_ARGS[] = {"true", NULL}; 75 | 76 | #define GET_PAGE_ADDR(ADDR, PAGE_SIZE) \ 77 | (char *)(((unsigned long) (ADDR + PAGE_SIZE)) & ~(PAGE_SIZE-1)) 78 | 79 | int stack_mprotect(void) 80 | { 81 | void *buf; 82 | long sz; 83 | int ret; 84 | 85 | sz = sysconf(_SC_PAGESIZE); 86 | if (sz < 0) 87 | return sz; 88 | 89 | buf = alloca(sz * 3); 90 | ret = mprotect(GET_PAGE_ADDR(buf, sz), sz, PROT_READ | PROT_WRITE | PROT_EXEC); 91 | return ret; 92 | } 93 | 94 | int exec_cmd(int *monitored_pid) 95 | { 96 | int child_pid, child_status; 97 | 98 | child_pid = fork(); 99 | if (child_pid == 0) { 100 | *monitored_pid = getpid(); 101 | execvp(CMD_ARGS[0], CMD_ARGS); 102 | return -EINVAL; 103 | } else if (child_pid > 0) { 104 | waitpid(child_pid, &child_status, 0); 105 | return child_status; 106 | } 107 | 108 | return -EINVAL; 109 | } 110 | 111 | static int test_lsm(struct lsm *skel) 112 | { 113 | struct bpf_link *link; 114 | int buf = 1234; 115 | int err; 116 | 117 | err = lsm__attach(skel); 118 | if (!ASSERT_OK(err, "attach")) 119 | return err; 120 | 121 | /* Check that already linked program can't be attached again. */ 122 | link = bpf_program__attach(skel->progs.test_int_hook); 123 | if (!ASSERT_ERR_PTR(link, "attach_link")) 124 | return -1; 125 | 126 | err = exec_cmd(&skel->bss->monitored_pid); 127 | if (!ASSERT_OK(err, "exec_cmd")) 128 | return err; 129 | 130 | ASSERT_EQ(skel->bss->bprm_count, 1, "bprm_count"); 131 | 132 | skel->bss->monitored_pid = getpid(); 133 | 134 | err = stack_mprotect(); 135 | if (!ASSERT_EQ(errno, EPERM, "stack_mprotect")) 136 | return err; 137 | 138 | ASSERT_EQ(skel->bss->mprotect_count, 1, "mprotect_count"); 139 | 140 | syscall(__NR_setdomainname, &buf, -2L); 141 | syscall(__NR_setdomainname, 0, -3L); 142 | syscall(__NR_setdomainname, ~0L, -4L); 143 | 144 | ASSERT_EQ(skel->bss->copy_test, 3, "copy_test"); 145 | 146 | lsm__detach(skel); 147 | 148 | skel->bss->copy_test = 0; 149 | skel->bss->bprm_count = 0; 150 | skel->bss->mprotect_count = 0; 151 | return 0; 152 | } 153 | 154 | int main(int argc, char **argv) 155 | { 156 | struct lsm *skel = NULL; 157 | int err; 158 | 159 | skel = lsm__open_and_load(); 160 | if (!ASSERT_OK_PTR(skel, "lsm_skel_load")) 161 | goto close_prog; 162 | 163 | err = test_lsm(skel); 164 | if (!ASSERT_OK(err, "test_lsm_first_attach")) 165 | goto close_prog; 166 | 167 | err = test_lsm(skel); 168 | ASSERT_OK(err, "test_lsm_second_attach"); 169 | 170 | close_prog: 171 | lsm__destroy(skel); 172 | } 173 | -------------------------------------------------------------------------------- /examples/src/raw_enter.bpf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include "vmlinux.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | char LICENSE[] SEC("license") = "GPL"; 9 | 10 | const volatile int count = 0; 11 | 12 | SEC("raw_tp/sys_enter") 13 | int BPF_PROG(hook_sys_enter) 14 | { 15 | bpf_printk("ciao0"); 16 | 17 | struct trace_event_raw_sys_enter *x = (struct trace_event_raw_sys_enter *)ctx; 18 | if (x->id != __NR_connect) 19 | { 20 | return 0; 21 | } 22 | 23 | for (int i = 1; i < count; i++) 24 | { 25 | bpf_printk("ciao%d", i); 26 | } 27 | 28 | return 0; 29 | } -------------------------------------------------------------------------------- /examples/src/raw_enter.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include "commons.c" 5 | #include "raw_enter.skel.h" 6 | 7 | struct trace_entry 8 | { 9 | short unsigned int type; 10 | unsigned char flags; 11 | unsigned char preempt_count; 12 | int pid; 13 | }; 14 | 15 | struct trace_event_raw_sys_enter 16 | { 17 | struct trace_entry ent; 18 | long int id; 19 | long unsigned int args[6]; 20 | char __data[0]; 21 | }; 22 | 23 | int main(int argc, char **argv) 24 | { 25 | struct raw_enter *skel; 26 | int err; 27 | 28 | /* Set up libbpf errors and debug info callback */ 29 | libbpf_set_print(libbpf_print_fn); 30 | 31 | /* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */ 32 | bump_memlock_rlimit(); 33 | 34 | if (signal(SIGINT, sig_int) == SIG_ERR || signal(SIGTERM, sig_int) == SIG_ERR) 35 | { 36 | fprintf(stderr, "Can't handle Ctrl-C: %s\n", strerror(errno)); 37 | goto cleanup; 38 | } 39 | 40 | /* Open load and verify BPF application */ 41 | skel = raw_enter__open(); 42 | if (!skel) 43 | { 44 | fprintf(stderr, "Can't open the BPF skeleton\n"); 45 | return 1; 46 | } 47 | 48 | // Set the counter 49 | skel->rodata->count = 10; 50 | 51 | err = raw_enter__load(skel); 52 | if (err) 53 | { 54 | fprintf(stderr, "Can't load the BPF skeleton\n"); 55 | goto cleanup; 56 | } 57 | fprintf(stdout, "BPF skeleton OK\n"); 58 | 59 | int prog_fd = bpf_program__fd(skel->progs.hook_sys_enter); 60 | fprintf(stdout, "BPF program FD: %d\n", prog_fd); 61 | struct trace_event_raw_sys_enter ctx = { 62 | .id = __NR_connect, 63 | }; 64 | 65 | struct bpf_prog_test_run_attr tattr = {}; 66 | tattr.prog_fd = prog_fd; 67 | tattr.repeat = 0; 68 | tattr.ctx_in = &ctx; 69 | tattr.ctx_size_in = sizeof(ctx); 70 | 71 | err = bpf_prog_test_run_xattr(&tattr); 72 | 73 | cleanup: 74 | raw_enter__destroy(skel); 75 | return -err; 76 | } -------------------------------------------------------------------------------- /examples/tools/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /include/BPFCov.h: -------------------------------------------------------------------------------- 1 | #ifndef LLVM_BPFCOV_H 2 | #define LLVM_BPFCOV_H 3 | 4 | #include "llvm/IR/PassManager.h" 5 | #include "llvm/Pass.h" 6 | #include "llvm/Support/raw_ostream.h" 7 | 8 | //------------------------------------------------------------------------------ 9 | // New PM / Interface 10 | //------------------------------------------------------------------------------ 11 | struct BPFCov : public llvm::PassInfoMixin 12 | { 13 | llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM); 14 | 15 | static bool isRequired() { return true; } 16 | 17 | virtual bool runOnModule(llvm::Module &M); 18 | }; 19 | 20 | //------------------------------------------------------------------------------ 21 | // Legacy PM / Interface 22 | //------------------------------------------------------------------------------ 23 | struct LegacyBPFCov : public llvm::ModulePass 24 | { 25 | static char ID; 26 | LegacyBPFCov() : llvm::ModulePass(ID) {} 27 | bool runOnModule(llvm::Module &M) override; 28 | void getAnalysisUsage(llvm::AnalysisUsage &AU) const override; 29 | void print(llvm::raw_ostream &OutS, llvm::Module const *M) const override; 30 | 31 | BPFCov Impl; 32 | }; 33 | 34 | #endif // LLVM_BPFCOV_H -------------------------------------------------------------------------------- /lib/BPFCov.cpp: -------------------------------------------------------------------------------- 1 | //===================================================================================================================== 2 | // FILE: 3 | // BPFCov.cpp 4 | // 5 | // AUTHOR: 6 | // Leonardo Di Donato (leodido) 7 | // 8 | // DESCRIPTION: 9 | // Patch the IR of eBPF programs instrumented for source-code based coverage (-fprofile-instr-generate -fcoverage-mapping). 10 | // 11 | // USAGE: 12 | // 1. Legacy LLVM Pass Manager 13 | // opt --load libBPFCov.{so,dylib} [--strip-initializers-only] --bpf-cov 14 | // 15 | // 2. New LLVM Pass Manager 16 | // opt --load-pass-plugin libBPFCov.{so,dylib} --passes='bpf-cov' 17 | // 18 | // OR 19 | // 20 | // opt --load-pass-plugin libBPFCov.{so,dylib} --passes='default' 21 | // 22 | // NOTICE: CLI options not available when using the new Pass Manager. 23 | // 24 | // LICENSE: 25 | // ... 26 | //===================================================================================================================== 27 | #include "BPFCov.h" 28 | #include "llvm/IR/DIBuilder.h" 29 | #include "llvm/IR/DebugInfo.h" 30 | #include "llvm/IR/DebugInfoMetadata.h" 31 | #include "llvm/IR/Function.h" 32 | #include "llvm/IR/IRBuilder.h" 33 | #include "llvm/IR/LegacyPassManager.h" 34 | #include "llvm/PassRegistry.h" 35 | #include "llvm/Passes/PassBuilder.h" 36 | #include "llvm/Passes/PassPlugin.h" 37 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 38 | #include "llvm/Transforms/Utils/ModuleUtils.h" 39 | #include "llvm/Support/CommandLine.h" 40 | 41 | static constexpr char PassArg[] = "bpf-cov"; 42 | static constexpr char PassName[] = "BPF Coverage Pass"; 43 | static constexpr char PluginName[] = "BPFCov"; 44 | 45 | #define DEBUG_TYPE ::PassArg 46 | 47 | // NOTE > LLVM_DEBUG requires a LLVM built with NDEBUG unset 48 | // NOTE > Then use with opt -debug 49 | 50 | using namespace llvm; 51 | 52 | //--------------------------------------------------------------------------------------------------------------------- 53 | // CLI options 54 | //--------------------------------------------------------------------------------------------------------------------- 55 | 56 | // This cause the resulting BPF ELF to be readable by llvm-cov for coverage output, 57 | // but it does not output a valid BPF program. 58 | static cl::opt 59 | StripInitializersOnly( 60 | "strip-initializers-only", 61 | cl::desc("Stop the pass after the initializers have been removed"), 62 | cl::init(false)); 63 | 64 | //--------------------------------------------------------------------------------------------------------------------- 65 | // Utility functions 66 | //--------------------------------------------------------------------------------------------------------------------- 67 | 68 | namespace 69 | { 70 | 71 | bool deleteGVarByName(Module &M, StringRef Name) 72 | { 73 | auto GV = M.getNamedGlobal(Name); 74 | if (!GV) 75 | { 76 | return false; 77 | } 78 | errs() << "erasing " << Name << "\n"; 79 | GV->eraseFromParent(); 80 | return true; 81 | } 82 | 83 | bool deleteFuncByName(Module &M, StringRef Name) 84 | { 85 | auto F = M.getFunction(Name); 86 | if (!F) 87 | { 88 | return false; 89 | } 90 | errs() << "erasing " << Name << "()\n"; 91 | F->replaceAllUsesWith(UndefValue::get(F->getType())); 92 | F->eraseFromParent(); 93 | 94 | return true; 95 | } 96 | 97 | bool fixupUsedGlobals(Module &M) 98 | { 99 | auto U = M.getNamedGlobal("llvm.used"); 100 | if (!U || !U->hasInitializer()) 101 | { 102 | return false; 103 | } 104 | 105 | SmallVector UsedGlobals; 106 | auto UArray = dyn_cast(U->getInitializer()); 107 | auto NElems = UArray->getNumOperands(); 108 | for (unsigned int i = 0; i < NElems; i++) 109 | { 110 | if (ConstantExpr *CE = dyn_cast(UArray->getOperand(i))) 111 | { 112 | auto OC = CE->getOpcode(); 113 | if (OC == Instruction::BitCast || OC == Instruction::GetElementPtr) 114 | { 115 | if (GlobalValue *GV = dyn_cast(CE->getOperand(0))) 116 | { 117 | auto Name = GV->getName(); 118 | if (!Name.startswith("__llvm_profile_runtime") && !Name.startswith("__profd") && !Name.startswith("__covrec") && !Name.startswith("__llvm_coverage")) 119 | { 120 | UsedGlobals.push_back(UArray->getOperand(i)); 121 | } 122 | } 123 | } 124 | } 125 | // TODO(leodido) > almost certainly the following doesn't make sense for "llvm.used" array 126 | else if (GlobalValue *GV = dyn_cast(UArray->getOperand(i))) 127 | { 128 | auto Name = GV->getName(); 129 | if (!Name.startswith("__llvm_profile_runtime") && !Name.startswith("__profd") && !Name.startswith("__covrec") && !Name.startswith("__llvm_coverage")) 130 | { 131 | UsedGlobals.push_back(UArray->getOperand(i)); 132 | } 133 | } 134 | } 135 | 136 | if (UsedGlobals.size() < NElems) 137 | { 138 | errs() << "fixing llvm.used\n"; 139 | U->eraseFromParent(); 140 | ArrayType *AType = ArrayType::get(Type::getInt8PtrTy(M.getContext()), UsedGlobals.size()); 141 | U = new GlobalVariable(M, AType, false, GlobalValue::AppendingLinkage, ConstantArray::get(AType, UsedGlobals), "llvm.used"); 142 | U->setSection("llvm.metadata"); 143 | 144 | return true; 145 | } 146 | 147 | return false; 148 | } 149 | 150 | bool swapSectionWithPrefix(Module &M, StringRef Prefix, StringRef New) 151 | { 152 | bool Changed = false; 153 | for (auto gv_iter = M.global_begin(); gv_iter != M.global_end(); gv_iter++) 154 | { 155 | GlobalVariable *GV = &*gv_iter; 156 | if (GV->hasSection() && GV->getSection().startswith(Prefix)) 157 | { 158 | errs() << "swapping " << GV->getName() << " section with " << New << " \n"; 159 | GV->setSection(New); 160 | Changed = true; 161 | } 162 | } 163 | return Changed; 164 | } 165 | 166 | bool convertStructs(Module &M) 167 | { 168 | bool Changed = false; 169 | 170 | auto &CTX = M.getContext(); 171 | SmallVector ToDelete; 172 | auto CountersSizeAcc = 0; 173 | 174 | for (auto gv_iter = M.global_begin(); gv_iter != M.global_end(); gv_iter++) 175 | { 176 | GlobalVariable *GV = &*gv_iter; 177 | if (GV->hasName()) 178 | { 179 | auto Name = GV->getName(); 180 | if (Name.startswith("__profd") && GV->getValueType()->isStructTy()) 181 | { 182 | errs() << "converting " << Name << " struct to globals\n"; 183 | 184 | // Translate the function ID to a global scalar 185 | ConstantInt *C0 = dyn_cast(GV->getInitializer()->getOperand(0)); 186 | if (!C0) 187 | { 188 | // TODO(leodido) > bail out 189 | errs() << Name << ": cast failed\n"; 190 | } 191 | auto Ty = C0->getType(); 192 | if (!Ty->isIntegerTy(64)) 193 | { 194 | // TODO(leodido) > bail out 195 | errs() << Name << ": wrong type bandwidth\n"; 196 | } 197 | auto *GV0 = new GlobalVariable( 198 | M, 199 | /*Ty=*/Ty, 200 | /*isConstant=*/true, 201 | /*Linkage=*/GlobalVariable::ExternalLinkage, 202 | /*Initializer=*/ConstantInt::get(Ty, C0->getSExtValue(), true), 203 | /*Name=*/Name + ".0", 204 | /*InsertBefore=*/GV); 205 | GV0->setDSOLocal(true); 206 | GV0->setAlignment(MaybeAlign(8)); 207 | GV0->setSection(GV->getSection()); 208 | 209 | appendToUsed(M, GV0); 210 | 211 | Changed = true; 212 | 213 | // Translate the function hash to a global scalar 214 | ConstantInt *C1 = dyn_cast(GV->getInitializer()->getOperand(1)); 215 | if (!C1) 216 | { 217 | // TODO(leodido) > bail out 218 | errs() << Name << ": cast failed\n"; 219 | } 220 | auto Ty1 = C1->getType(); 221 | if (!Ty1->isIntegerTy(64)) 222 | { 223 | // TODO(leodido) > bail out 224 | errs() << Name << ": wrong type bandwidth\n"; 225 | } 226 | auto *GV1 = new GlobalVariable( 227 | M, 228 | /*Ty=*/Ty1, 229 | /*isConstant=*/true, 230 | /*Linkage=*/GlobalVariable::ExternalLinkage, 231 | /*Initializer=*/ConstantInt::get(Ty1, C1->getSExtValue(), true), 232 | /*Name=*/Name + ".1", 233 | /*InsertBefore=*/GV); 234 | GV1->setDSOLocal(true); 235 | GV1->setAlignment(MaybeAlign(8)); 236 | GV1->setSection(GV->getSection()); 237 | 238 | appendToUsed(M, GV1); 239 | 240 | // Get the number of counters for current __profd_* 241 | ConstantInt *C5 = dyn_cast(GV->getInitializer()->getOperand(5)); 242 | if (!C5) 243 | { 244 | // TODO(leodido) > bail out 245 | errs() << Name << ": cast failed\n"; 246 | } 247 | auto Ty5 = C5->getType(); 248 | if (!Ty5->isIntegerTy(32)) 249 | { 250 | // TODO(leodido) > bail out 251 | errs() << Name << ": wrong type bandwidth\n"; 252 | } 253 | 254 | auto NumCounters = C5->getSExtValue(); 255 | 256 | // Translate the address of the counter to a global scalar containing the relative offset 257 | auto *GV2 = new GlobalVariable( 258 | M, 259 | /*Ty=*/Ty1, 260 | /*isConstant=*/true, 261 | /*Linkage=*/GlobalVariable::ExternalLinkage, 262 | /*Initializer=*/ConstantInt::get(Ty1, CountersSizeAcc, true), 263 | /*Name=*/Name + ".2", 264 | /*InsertBefore=*/GV); 265 | GV2->setDSOLocal(true); 266 | GV2->setAlignment(MaybeAlign(8)); 267 | GV2->setSection(GV->getSection()); 268 | appendToUsed(M, GV2); 269 | 270 | // Increment the counter offset for the next __profd_* 271 | CountersSizeAcc += NumCounters * 8; 272 | 273 | // Create fake (zero) global scalars for 4th and 5th field of __profd_* structs 274 | auto *GV3 = new GlobalVariable( 275 | M, 276 | /*Ty=*/Ty1, 277 | /*isConstant=*/true, 278 | /*Linkage=*/GlobalVariable::ExternalLinkage, 279 | /*Initializer=*/ConstantInt::get(Ty1, 0, true), // TODO > we want this or zeroinitializer? 280 | /*Name=*/Name + ".3", 281 | /*InsertBefore=*/GV); 282 | GV3->setDSOLocal(true); 283 | GV3->setAlignment(MaybeAlign(8)); 284 | GV3->setSection(GV->getSection()); 285 | appendToUsed(M, GV3); 286 | 287 | auto *GV4 = new GlobalVariable( 288 | M, 289 | /*Ty=*/Ty1, 290 | /*isConstant=*/true, 291 | /*Linkage=*/GlobalVariable::ExternalLinkage, 292 | /*Initializer=*/ConstantInt::get(Ty1, 0, true), // TODO > we want this or zeroinitializer? 293 | /*Name=*/Name + ".4", 294 | /*InsertBefore=*/GV); 295 | GV4->setDSOLocal(true); 296 | GV4->setAlignment(MaybeAlign(8)); 297 | GV4->setSection(GV->getSection()); 298 | appendToUsed(M, GV4); 299 | 300 | // Translate the number of counters (that this data refers to) to a global scalar 301 | auto NumCountersC = ConstantInt::get(Ty5, NumCounters, true); 302 | auto *GV5 = new GlobalVariable( 303 | M, 304 | /*Ty=*/Ty5, 305 | /*isConstant=*/true, 306 | /*Linkage=*/GlobalVariable::ExternalLinkage, 307 | /*Initializer=*/NumCountersC, 308 | /*Name=*/Name + ".5", 309 | /*InsertBefore=*/GV); 310 | GV5->setDSOLocal(true); 311 | GV5->setAlignment(MaybeAlign(4)); 312 | GV5->setSection(GV->getSection()); 313 | appendToUsed(M, GV5); 314 | 315 | // Translate the value sites [2 x i16] into a single i32 316 | auto *GV6 = new GlobalVariable( 317 | M, 318 | /*Ty=*/Ty5, 319 | /*isConstant=*/true, 320 | /*Linkage=*/GlobalVariable::ExternalLinkage, 321 | /*Initializer=*/ConstantInt::get(Ty5, 0, true), // TODO > obtain from the array values 322 | /*Name=*/Name + ".6", 323 | /*InsertBefore=*/GV); 324 | GV6->setDSOLocal(true); 325 | GV6->setAlignment(MaybeAlign(4)); 326 | GV6->setSection(GV->getSection()); 327 | appendToUsed(M, GV6); 328 | 329 | ToDelete.push_back(GV); 330 | } 331 | else if (Name.startswith("__covrec") && GV->getValueType()->isStructTy()) 332 | { 333 | ToDelete.push_back(GV); 334 | } 335 | else if (Name.startswith("__llvm_coverage") && GV->getValueType()->isStructTy()) 336 | { 337 | errs() << "converting " << Name << " struct to globals\n"; 338 | 339 | ConstantStruct *C0 = dyn_cast(GV->getInitializer()->getOperand(0)); 340 | if (!C0) 341 | { 342 | // TODO(leodido) > bail out 343 | errs() << Name << ": cast failed\n"; 344 | } 345 | auto Ty0 = C0->getType(); 346 | if (!Ty0->isStructTy()) 347 | { 348 | // TODO(leodido) > bail out 349 | errs() << Name << ": wrong type\n"; 350 | } 351 | 352 | SmallVector Vals; 353 | for (unsigned int i = 0; i < C0->getNumOperands(); i++) 354 | { 355 | ConstantInt *C = dyn_cast(C0->getOperand(i)); 356 | if (!C) 357 | { 358 | // TODO(leodido) > bail out 359 | errs() << Name << ": cast failed\n"; 360 | } 361 | if (!C->getType()->isIntegerTy(32)) 362 | { 363 | // TODO(leodido) > bail out 364 | errs() << Name << ": wrong type\n"; 365 | } 366 | Vals.push_back(C); 367 | } 368 | 369 | ArrayType *ATy = ArrayType::get(Type::getInt32Ty(CTX), Vals.size()); 370 | 371 | auto *GV0 = new GlobalVariable( 372 | M, 373 | /*Ty=*/ATy, 374 | /*isConstant=*/true, 375 | /*Linkage=*/GlobalVariable::ExternalLinkage, 376 | /*Initializer=*/ConstantArray::get(ATy, Vals), 377 | /*Name=*/Name + ".0", 378 | /*InsertBefore=*/GV); 379 | GV0->setDSOLocal(true); 380 | GV0->setSection(GV->getSection()); 381 | GV0->setAlignment(MaybeAlign(4)); 382 | 383 | Changed = true; 384 | 385 | appendToUsed(M, GV0); 386 | 387 | ConstantDataArray *C1 = dyn_cast(GV->getInitializer()->getOperand(1)); 388 | if (!C1) 389 | { 390 | // TODO(leodido) > bail out 391 | errs() << Name << ": cast failed\n"; 392 | } 393 | auto Ty1 = C1->getType(); 394 | if (!Ty1->isArrayTy()) 395 | { 396 | // TODO(leodido) > bail out 397 | errs() << Name << ": wrong type\n"; 398 | } 399 | 400 | auto *GV1 = new GlobalVariable( 401 | M, 402 | /*Ty=*/Ty1, 403 | /*isConstant=*/true, 404 | /*Linkage=*/GlobalVariable::ExternalLinkage, 405 | /*Initializer=*/ConstantDataArray::getString(CTX, C1->getRawDataValues(), false), 406 | /*Name=*/Name + ".1", 407 | /*InsertBefore=*/GV); 408 | GV1->setDSOLocal(true); 409 | GV1->setAlignment(MaybeAlign(1)); 410 | GV1->setSection(GV->getSection()); 411 | 412 | appendToUsed(M, GV1); 413 | 414 | ToDelete.push_back(GV); 415 | } 416 | } 417 | } 418 | 419 | for (auto *GV : ToDelete) 420 | { 421 | errs() << "erasing " << GV->getName() << "\n"; 422 | GV->eraseFromParent(); 423 | } 424 | 425 | return Changed; 426 | } 427 | 428 | bool annotateCounters(Module &M) 429 | { 430 | bool Annotated = false; 431 | 432 | DIBuilder DIB(M); 433 | 434 | Module::debug_compile_units_iterator CUIterator = M.debug_compile_units_begin(); 435 | auto *DebugCU = *CUIterator; 436 | auto *DebugFile = DebugCU->getFile(); 437 | 438 | // Save the current list of globals from the CU debug info 439 | SmallVector DebugGlobals; 440 | for (auto *DG : DebugCU->getGlobalVariables()) 441 | { 442 | DebugGlobals.push_back(DG); 443 | } 444 | 445 | for (auto gv_iter = M.global_begin(); gv_iter != M.global_end(); gv_iter++) 446 | { 447 | GlobalVariable *GV = &*gv_iter; 448 | if (GV->hasName()) 449 | { 450 | if (GV->getName().startswith("__profc") && GV->getValueType()->isArrayTy()) 451 | { 452 | // Change to DSO local 453 | GV->setLinkage(GlobalValue::LinkageTypes::ExternalLinkage); 454 | GV->setDSOLocal(true); 455 | 456 | auto N = GV->getValueType()->getArrayNumElements(); 457 | 458 | auto *S64Ty = DIB.createBasicType("long long int", 64, dwarf::DW_ATE_signed); 459 | 460 | auto *DebugArrayTy = DIB.createArrayType( 461 | /*Size=*/N * 64, 462 | /*AlignInBits=*/0, 463 | /*Ty=*/S64Ty, 464 | /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); 465 | 466 | auto *DebugGVE = DIB.createGlobalVariableExpression( 467 | /*Context=*/DebugCU, 468 | /*Name=*/GV->getName(), 469 | /*LinkageName=*/"", 470 | /*File=*/DebugFile, 471 | /*LineNo=*/0, 472 | /*Ty=*/DebugArrayTy, 473 | /*IsLocalToUnit=*/GV->hasLocalLinkage(), 474 | /*IsDefinition=*/true, 475 | /*Expr=*/nullptr, 476 | /*Decl=*/nullptr, 477 | /*TemplateParams=*/nullptr, 478 | /*AlignInBits=*/0); 479 | 480 | GV->addDebugInfo(DebugGVE); 481 | DebugGlobals.push_back(DebugGVE); 482 | 483 | Annotated = true; 484 | } 485 | else if (GV->getName() == "__llvm_prf_nm" && GV->getValueType()->isArrayTy()) 486 | { 487 | // Change to DSO local 488 | GV->setLinkage(GlobalValue::LinkageTypes::ExternalLinkage); 489 | GV->setDSOLocal(true); 490 | 491 | auto N = GV->getValueType()->getArrayNumElements(); 492 | 493 | auto *S8Ty = DIB.createBasicType("char", 8, dwarf::DW_ATE_signed_char); 494 | 495 | auto *ConstS8Ty = DIB.createQualifiedType(dwarf::DW_TAG_const_type, S8Ty); 496 | 497 | auto *DebugArrayTy = DIB.createArrayType( 498 | /*Size=*/N * 8, 499 | /*AlignInBits=*/0, 500 | /*Ty=*/ConstS8Ty, 501 | /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); 502 | 503 | auto *DebugGVE = DIB.createGlobalVariableExpression( 504 | /*Context=*/DebugCU, 505 | /*Name=*/GV->getName(), 506 | /*LinkageName=*/"", 507 | /*File=*/DebugFile, 508 | /*LineNo=*/0, 509 | /*Ty=*/DebugArrayTy, 510 | /*IsLocalToUnit=*/GV->hasLocalLinkage(), 511 | /*IsDefinition=*/true, 512 | /*Expr=*/nullptr, 513 | /*Decl=*/nullptr, 514 | /*TemplateParams=*/nullptr, 515 | /*AlignInBits=*/0); 516 | 517 | GV->addDebugInfo(DebugGVE); 518 | DebugGlobals.push_back(DebugGVE); 519 | 520 | Annotated = true; 521 | } 522 | else if (GV->getName().startswith("__profd")) 523 | { 524 | DIBasicType *Ty; 525 | if (GV->getName().endswith(".0") || GV->getName().endswith(".1") || GV->getName().endswith(".2") || GV->getName().endswith(".3") || GV->getName().endswith(".4")) 526 | { 527 | Ty = DIB.createBasicType("long long int", 64, dwarf::DW_ATE_signed); 528 | } 529 | else if (GV->getName().endswith(".5") || GV->getName().endswith(".6")) 530 | { 531 | Ty = DIB.createBasicType("int", 32, dwarf::DW_ATE_signed); 532 | } 533 | 534 | auto *DebugGVE = DIB.createGlobalVariableExpression( 535 | /*Context=*/DebugCU, 536 | /*Name=*/GV->getName(), 537 | /*LinkageName=*/"", 538 | /*File=*/DebugFile, 539 | /*LineNo=*/0, 540 | /*Ty=*/Ty, 541 | /*IsLocalToUnit=*/GV->hasLocalLinkage(), 542 | /*IsDefinition=*/true, 543 | /*Expr=*/nullptr, 544 | /*Decl=*/nullptr, 545 | /*TemplateParams=*/nullptr, 546 | /*AlignInBits=*/0); 547 | 548 | GV->addDebugInfo(DebugGVE); 549 | DebugGlobals.push_back(DebugGVE); 550 | 551 | Annotated = true; 552 | } 553 | else if (GV->getName().startswith("__covrec")) 554 | { 555 | DIType *GVTy; 556 | if (GV->getName().endswith(".0")) 557 | { 558 | auto *Ty = DIB.createBasicType("long long int", 64, dwarf::DW_ATE_signed); 559 | GVTy = DIB.createQualifiedType(dwarf::DW_TAG_const_type, Ty); 560 | } 561 | if (GV->getName().endswith(".4")) 562 | { 563 | auto *Ty = DIB.createBasicType("char", 8, dwarf::DW_ATE_signed_char); 564 | auto N = GV->getValueType()->getArrayNumElements(); 565 | GVTy = DIB.createArrayType( 566 | /*Size=*/N * 8, 567 | /*AlignInBits=*/0, 568 | /*Ty=*/DIB.createQualifiedType(dwarf::DW_TAG_const_type, Ty), 569 | /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); 570 | } 571 | 572 | auto *DebugGVE = DIB.createGlobalVariableExpression( 573 | /*Context=*/DebugCU, 574 | /*Name=*/GV->getName(), 575 | /*LinkageName=*/"", 576 | /*File=*/DebugFile, 577 | /*LineNo=*/0, 578 | /*Ty=*/GVTy, 579 | /*IsLocalToUnit=*/GV->hasLocalLinkage(), 580 | /*IsDefinition=*/true, 581 | /*Expr=*/nullptr, 582 | /*Decl=*/nullptr, 583 | /*TemplateParams=*/nullptr, 584 | /*AlignInBits=*/0); 585 | 586 | GV->addDebugInfo(DebugGVE); 587 | DebugGlobals.push_back(DebugGVE); 588 | 589 | Annotated = true; 590 | } 591 | else if (GV->getName().startswith("__llvm_coverage")) 592 | { 593 | auto N = GV->getValueType()->getArrayNumElements(); 594 | 595 | auto Size = N; 596 | DIBasicType *Ty; 597 | if (GV->getName().endswith(".0")) 598 | { 599 | Ty = DIB.createBasicType("int", 32, dwarf::DW_ATE_signed); 600 | Size *= 32; 601 | } 602 | if (GV->getName().endswith(".1")) 603 | { 604 | Ty = DIB.createBasicType("char", 8, dwarf::DW_ATE_signed_char); 605 | Size *= 8; 606 | } 607 | 608 | auto *ConstTy = DIB.createQualifiedType(dwarf::DW_TAG_const_type, Ty); 609 | 610 | auto *DebugArrayTy = DIB.createArrayType( 611 | /*Size=*/Size, 612 | /*AlignInBits=*/0, 613 | /*Ty=*/ConstTy, 614 | /*Subscripts=*/DIB.getOrCreateArray({DIB.getOrCreateSubrange(0, N)})); 615 | 616 | auto *DebugGVE = DIB.createGlobalVariableExpression( 617 | /*Context=*/DebugCU, 618 | /*Name=*/GV->getName(), 619 | /*LinkageName=*/"", 620 | /*File=*/DebugFile, 621 | /*LineNo=*/0, 622 | /*Ty=*/DebugArrayTy, 623 | /*IsLocalToUnit=*/GV->hasLocalLinkage(), 624 | /*IsDefinition=*/true, 625 | /*Expr=*/nullptr, 626 | /*Decl=*/nullptr, 627 | /*TemplateParams=*/nullptr, 628 | /*AlignInBits=*/0); 629 | 630 | GV->addDebugInfo(DebugGVE); 631 | DebugGlobals.push_back(DebugGVE); 632 | 633 | Annotated = true; 634 | } 635 | } 636 | } 637 | 638 | if (Annotated) 639 | { 640 | errs() << "updating compile unit's globals debug info\n"; 641 | DebugCU->replaceGlobalVariables(MDTuple::get(M.getContext(), DebugGlobals)); 642 | 643 | DIB.finalize(); 644 | } 645 | 646 | return Annotated; 647 | } 648 | } 649 | 650 | //--------------------------------------------------------------------------------------------------------------------- 651 | // Implementation 652 | //--------------------------------------------------------------------------------------------------------------------- 653 | 654 | PreservedAnalyses BPFCov::run(Module &M, ModuleAnalysisManager &MAM) 655 | { 656 | bool changed = runOnModule(M); 657 | return (changed ? PreservedAnalyses::none() : PreservedAnalyses::all()); 658 | } 659 | 660 | bool BPFCov::runOnModule(Module &M) 661 | { 662 | errs() << "module: " << M.getName() << "\n"; // LLVM_DEBUG(dbgs() << ""); 663 | 664 | bool instrumented = false; 665 | 666 | // Bail out when missing debug info 667 | if (M.debug_compile_units().empty()) 668 | { 669 | errs() << "Missing debug info\n"; 670 | return instrumented; 671 | } 672 | 673 | // This sequence is not random at all 674 | instrumented |= deleteGVarByName(M, "llvm.global_ctors"); 675 | instrumented |= deleteFuncByName(M, "__llvm_profile_init"); 676 | instrumented |= deleteFuncByName(M, "__llvm_profile_register_function"); 677 | instrumented |= deleteFuncByName(M, "__llvm_profile_register_names_function"); 678 | instrumented |= deleteFuncByName(M, "__llvm_profile_runtime_user"); 679 | instrumented |= deleteGVarByName(M, "__llvm_profile_runtime"); 680 | instrumented |= fixupUsedGlobals(M); 681 | // Stop here to avoid rewriting the profiling and coverage structs 682 | if (StripInitializersOnly) 683 | { 684 | return instrumented; 685 | } 686 | instrumented |= swapSectionWithPrefix(M, "__llvm_prf_cnts", ".data.profc"); 687 | instrumented |= swapSectionWithPrefix(M, "__llvm_prf_names", ".rodata.profn"); 688 | instrumented |= convertStructs(M); 689 | instrumented |= annotateCounters(M); 690 | instrumented |= swapSectionWithPrefix(M, "__llvm_prf_data", ".rodata.profd"); 691 | instrumented |= swapSectionWithPrefix(M, "__llvm_covmap", ".rodata.covmap"); 692 | 693 | return instrumented; 694 | } 695 | 696 | //--------------------------------------------------------------------------------------------------------------------- 697 | // Legacy PM / Implementation 698 | //--------------------------------------------------------------------------------------------------------------------- 699 | 700 | char LegacyBPFCov::ID = 0; 701 | 702 | bool LegacyBPFCov::runOnModule(llvm::Module &M) 703 | { 704 | if (skipModule(M)) 705 | { 706 | errs() << "legacy: skipping\n"; 707 | return false; 708 | } 709 | 710 | errs() << "legacy: running\n"; 711 | return Impl.runOnModule(M); 712 | } 713 | 714 | void LegacyBPFCov::print(raw_ostream &OutStream, const Module *) const 715 | { 716 | OutStream << "BPFCov (Legacy Pass Manager)\n"; 717 | } 718 | 719 | void LegacyBPFCov::getAnalysisUsage(AnalysisUsage &AU) const 720 | { 721 | // This pass does not transform the control flow graph 722 | AU.setPreservesCFG(); 723 | } 724 | 725 | //--------------------------------------------------------------------------------------------------------------------- 726 | // New PM / Registration 727 | //--------------------------------------------------------------------------------------------------------------------- 728 | PassPluginLibraryInfo getBPFCovPluginInfo() 729 | { 730 | return {LLVM_PLUGIN_API_VERSION, PluginName, LLVM_VERSION_STRING, 731 | [](PassBuilder &PB) 732 | { 733 | // #1 Regiser "opt -passes=bpf-cov" 734 | PB.registerPipelineParsingCallback( 735 | [&](StringRef Name, ModulePassManager &MPM, ArrayRef) 736 | { 737 | if (Name.equals(PassArg)) 738 | { 739 | errs() << "strip-initializers-only: " << (StripInitializersOnly.getValue() ? "true" : "false") << "\n"; 740 | MPM.addPass(BPFCov()); 741 | return true; 742 | } 743 | return false; 744 | }); 745 | // #2 Register for running at "default" // TODO > double-check 746 | PB.registerPipelineStartEPCallback( 747 | [&](ModulePassManager &MPM, ArrayRef OLevels) 748 | { 749 | if (OLevels.size() == 1 && 750 | OLevels[0] == PassBuilder::OptimizationLevel::O2) 751 | { 752 | MPM.addPass(BPFCov()); 753 | } 754 | }); 755 | }}; 756 | } 757 | 758 | extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo 759 | llvmGetPassPluginInfo() 760 | { 761 | return getBPFCovPluginInfo(); 762 | } 763 | 764 | //--------------------------------------------------------------------------------------------------------------------- 765 | // Legacy PM / Registration 766 | //--------------------------------------------------------------------------------------------------------------------- 767 | 768 | static RegisterPass X(/*PassArg=*/PassArg, 769 | /*Name=*/PassName, 770 | /*CFGOnly=*/false, 771 | /*is_analysis=*/false); 772 | 773 | static RegisterStandardPasses RegisterBPFCov( 774 | PassManagerBuilder::EP_EarlyAsPossible, 775 | [](const PassManagerBuilder &, legacy::PassManagerBase &PM) 776 | { 777 | errs() << "legacy: strip-initializers-only: " << (StripInitializersOnly.getValue() ? "true" : "false") << "\n"; 778 | PM.add(new LegacyBPFCov()); 779 | }); -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # THE LIST OF PLUGINS AND THE CORRESPONDING SOURCE FILES 2 | # ====================================================== 3 | set(BPFCOV_PLUGINS 4 | BPFCov 5 | ) 6 | 7 | set(BPFCov_SOURCES BPFCov.cpp) 8 | 9 | # CONFIGURE THE PLUGIN LIBRARIES 10 | # ============================== 11 | foreach( plugin ${BPFCOV_PLUGINS} ) 12 | # Create a library corresponding to 'plugin' 13 | add_library( 14 | ${plugin} 15 | SHARED 16 | ${${plugin}_SOURCES} 17 | ) 18 | 19 | # Configure include directories for 'plugin' 20 | target_include_directories( 21 | ${plugin} 22 | PRIVATE 23 | "${CMAKE_CURRENT_SOURCE_DIR}/../include" 24 | ) 25 | 26 | # On Darwin (unlike on Linux), undefined symbols in shared objects are not 27 | # allowed at the end of the link-edit. The plugins defined here: 28 | # - _are_ shared objects 29 | # - reference symbols from LLVM shared libraries, i.e. symbols which are 30 | # undefined until those shared objects are loaded in memory (and hence 31 | # _undefined_ during static linking) 32 | # The build will fail with errors like this: 33 | # "Undefined symbols for architecture x86_64" 34 | # with various LLVM symbols being undefined. Since those symbols are later 35 | # loaded and resolved at runtime, these errors are false positives. 36 | # This behaviour can be modified via the '-undefined' OS X linker flag. 37 | target_link_libraries( 38 | ${plugin} 39 | "$<$:-undefined dynamic_lookup>" 40 | ) 41 | endforeach() 42 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # bpfcov / lib 2 | 3 | To know how to build this LLVM pass and obtain `libBPFCov.so` refer to [this section](../README.md#building). 4 | 5 | To get to know how to use it, refer to the [main usage guide](../README.md#usage) or to the [examples](../examples/README.md#key-aspects). 6 | --------------------------------------------------------------------------------