├── .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 | |
|
|
14 | |
|
|
15 | |
|
|
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 |
--------------------------------------------------------------------------------