Skip to content

Commit

Permalink
Adds BPF program iterator (C++)
Browse files Browse the repository at this point in the history
  • Loading branch information
Stringy committed Jan 2, 2025
1 parent e301c70 commit 323c063
Show file tree
Hide file tree
Showing 17 changed files with 502 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.15)
project(collector)

LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

enable_testing()

add_subdirectory(collector)
190 changes: 190 additions & 0 deletions cmake/FindBpfObject.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause

#[=======================================================================[.rst:
FindBpfObject
--------
Find BpfObject
This module finds if all the dependencies for eBPF Compile-Once-Run-Everywhere
programs are available and where all the components are located.
The caller may set the following variables to disable automatic
search/processing for the associated component:
``BPFOBJECT_BPFTOOL_EXE``
Path to ``bpftool`` binary
``BPFOBJECT_CLANG_EXE``
Path to ``clang`` binary
``LIBBPF_INCLUDE_DIRS``
Path to ``libbpf`` development headers
``LIBBPF_LIBRARIES``
Path to `libbpf` library
``BPFOBJECT_VMLINUX_H``
Path to ``vmlinux.h`` generated by ``bpftool``. If unset, this module will
attempt to automatically generate a copy.
This module sets the following result variables:
::
BpfObject_FOUND = TRUE if all components are found
This module also provides the ``bpf_object()`` macro. This macro generates a
cmake interface library for the BPF object's generated skeleton as well
as the associated dependencies.
.. code-block:: cmake
bpf_object(<name> <source> [<header> ...])
Given an abstract ``<name>`` for a BPF object and the associated ``<source>``
file, generates an interface library target, ``<name>_skel``, that may be
linked against by other cmake targets. Additional headers may be provided to
the macro to ensure that the generated skeleton is up-to-date.
Example Usage:
::
find_package(BpfObject REQUIRED)
bpf_object(myobject myobject.bpf.c myobject.h)
add_executable(myapp myapp.c)
target_link_libraries(myapp myobject_skel)
#]=======================================================================]

if(NOT BPFOBJECT_BPFTOOL_EXE)
find_program(BPFOBJECT_BPFTOOL_EXE NAMES bpftool DOC "Path to bpftool executable")
endif()

if(NOT BPFOBJECT_CLANG_EXE)
find_program(BPFOBJECT_CLANG_EXE NAMES clang DOC "Path to clang executable")

execute_process(COMMAND ${BPFOBJECT_CLANG_EXE} --version
OUTPUT_VARIABLE CLANG_version_output
ERROR_VARIABLE CLANG_version_error
RESULT_VARIABLE CLANG_version_result
OUTPUT_STRIP_TRAILING_WHITESPACE)

# Check that clang is new enough
if(${CLANG_version_result} EQUAL 0)
if("${CLANG_version_output}" MATCHES "clang version ([^\n]+)\n")
# Transform X.Y.Z into X;Y;Z which can then be interpreted as a list
set(CLANG_VERSION "${CMAKE_MATCH_1}")
string(REPLACE "." ";" CLANG_VERSION_LIST ${CLANG_VERSION})
list(GET CLANG_VERSION_LIST 0 CLANG_VERSION_MAJOR)

# Anything older than clang 10 doesn't really work
string(COMPARE LESS ${CLANG_VERSION_MAJOR} 10 CLANG_VERSION_MAJOR_LT10)
if(${CLANG_VERSION_MAJOR_LT10})
message(FATAL_ERROR "clang ${CLANG_VERSION} is too old for BPF CO-RE")
endif()

message(STATUS "Found clang version: ${CLANG_VERSION}")
else()
message(FATAL_ERROR "Failed to parse clang version string: ${CLANG_version_output}")
endif()
else()
message(FATAL_ERROR "Command \"${BPFOBJECT_CLANG_EXE} --version\" failed with output:\n${CLANG_version_error}")
endif()
endif()

if(NOT LIBBPF_INCLUDE_DIRS OR NOT LIBBPF_LIBRARIES)
find_package(LibBpf)
endif()

if(BPFOBJECT_VMLINUX_H)
get_filename_component(GENERATED_VMLINUX_DIR ${BPFOBJECT_VMLINUX_H} DIRECTORY)
elseif(BPFOBJECT_BPFTOOL_EXE)
# Generate vmlinux.h
set(GENERATED_VMLINUX_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(BPFOBJECT_VMLINUX_H ${GENERATED_VMLINUX_DIR}/vmlinux.h)
execute_process(COMMAND ${BPFOBJECT_BPFTOOL_EXE} btf dump file /sys/kernel/btf/vmlinux format c
OUTPUT_FILE ${BPFOBJECT_VMLINUX_H}
ERROR_VARIABLE VMLINUX_error
RESULT_VARIABLE VMLINUX_result)
if(${VMLINUX_result} EQUAL 0)
set(VMLINUX ${BPFOBJECT_VMLINUX_H})
else()
message(FATAL_ERROR "Failed to dump vmlinux.h from BTF: ${VMLINUX_error}")
endif()
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(BpfObject
REQUIRED_VARS
BPFOBJECT_BPFTOOL_EXE
BPFOBJECT_CLANG_EXE
LIBBPF_INCLUDE_DIRS
LIBBPF_LIBRARIES
GENERATED_VMLINUX_DIR)

# Get clang bpf system includes
execute_process(
COMMAND bash -c "${BPFOBJECT_CLANG_EXE} -v -E - < /dev/null 2>&1 |
sed -n '/<...> search starts here:/,/End of search list./{ s| \\(/.*\\)|-idirafter \\1|p }'"
OUTPUT_VARIABLE CLANG_SYSTEM_INCLUDES_output
ERROR_VARIABLE CLANG_SYSTEM_INCLUDES_error
RESULT_VARIABLE CLANG_SYSTEM_INCLUDES_result
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(${CLANG_SYSTEM_INCLUDES_result} EQUAL 0)
separate_arguments(CLANG_SYSTEM_INCLUDES UNIX_COMMAND ${CLANG_SYSTEM_INCLUDES_output})
message(STATUS "BPF system include flags: ${CLANG_SYSTEM_INCLUDES}")
else()
message(FATAL_ERROR "Failed to determine BPF system includes: ${CLANG_SYSTEM_INCLUDES_error}")
endif()

# Get target arch
execute_process(COMMAND uname -m
COMMAND sed -e "s/x86_64/x86/" -e "s/aarch64/arm64/" -e "s/ppc64le/powerpc/" -e "s/mips.*/mips/" -e "s/riscv64/riscv/"
OUTPUT_VARIABLE ARCH_output
ERROR_VARIABLE ARCH_error
RESULT_VARIABLE ARCH_result
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(${ARCH_result} EQUAL 0)
set(ARCH ${ARCH_output})
message(STATUS "BPF target arch: ${ARCH}")
else()
message(FATAL_ERROR "Failed to determine target architecture: ${ARCH_error}")
endif()

# Public macro
macro(bpf_object name input)
set(BPF_C_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${input})
foreach(arg ${ARGN})
list(APPEND BPF_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${arg})
endforeach()
set(BPF_O_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.bpf.o)
set(BPF_SKEL_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.skel.h)
set(OUTPUT_TARGET ${name}_skel)

# Build BPF object file
add_custom_command(OUTPUT ${BPF_O_FILE}
COMMAND ${BPFOBJECT_CLANG_EXE} -g -O2 -target bpf -D__TARGET_ARCH_${ARCH}
${CLANG_SYSTEM_INCLUDES} -I${GENERATED_VMLINUX_DIR}
-isystem ${LIBBPF_INCLUDE_DIRS} -c ${BPF_C_FILE} -o ${BPF_O_FILE}
COMMAND_EXPAND_LISTS
VERBATIM
DEPENDS ${BPF_C_FILE} ${BPF_H_FILES}
COMMENT "[clang] Building BPF object: ${name}")

# Build BPF skeleton header
add_custom_command(OUTPUT ${BPF_SKEL_FILE}
COMMAND bash -c "${BPFOBJECT_BPFTOOL_EXE} gen skeleton ${BPF_O_FILE} > ${BPF_SKEL_FILE}"
VERBATIM
DEPENDS ${BPF_O_FILE}
COMMENT "[skel] Building BPF skeleton: ${name}")

add_library(${OUTPUT_TARGET} INTERFACE)
target_sources(${OUTPUT_TARGET} INTERFACE ${BPF_SKEL_FILE})
target_include_directories(${OUTPUT_TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(${OUTPUT_TARGET} SYSTEM INTERFACE ${LIBBPF_INCLUDE_DIRS})
target_link_libraries(${OUTPUT_TARGET} INTERFACE ${LIBBPF_LIBRARIES} -lelf -lz)
endmacro()

32 changes: 32 additions & 0 deletions cmake/FindLibBpf.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause

find_path(LIBBPF_INCLUDE_DIRS
NAMES
bpf/bpf.h
bpf/btf.h
bpf/libbpf.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
/sw/include
ENV CPATH)

find_library(LIBBPF_LIBRARIES
NAMES
bpf
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
ENV LIBRARY_PATH
ENV LD_LIBRARY_PATH)

include (FindPackageHandleStandardArgs)

FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBpf "Please install the libbpf development package"
LIBBPF_LIBRARIES
LIBBPF_INCLUDE_DIRS)

mark_as_advanced(LIBBPF_INCLUDE_DIRS LIBBPF_LIBRARIES)
7 changes: 6 additions & 1 deletion collector/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
find_package(BpfObject REQUIRED)

add_subdirectory(sources)

file(GLOB COLLECTOR_LIB_SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/system-inspector/*.cpp
)

add_library(collector_lib ${DRIVER_HEADERS} ${COLLECTOR_LIB_SRC_FILES})
add_dependencies(collector_lib sinsp)
add_dependencies(collector_lib sinsp collector_scrapers)
target_link_libraries(collector_lib sinsp)
target_link_libraries(collector_lib stdc++fs) # This is needed for GCC-8 to link against the filesystem library
target_link_libraries(collector_lib cap-ng)
Expand All @@ -14,6 +18,7 @@ target_link_libraries(collector_lib civetweb::civetweb-cpp)
target_link_libraries(collector_lib yaml-cpp::yaml-cpp)

target_link_libraries(collector_lib rox-proto)
target_link_libraries(collector_lib collector_scrapers)

if(NOT DISABLE_PROFILING)
find_library(GPERFTOOLS_PROFILER profiler)
Expand Down
2 changes: 2 additions & 0 deletions collector/lib/CollectorService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ void CollectorService::RunForever() {
server.addHandler(collector_config_inspector->kBaseRoute, collector_config_inspector.get());
}

bpf_programs_.Load();

system_inspector_.Init(config_, conn_tracker);
system_inspector_.Start();

Expand Down
2 changes: 2 additions & 0 deletions collector/lib/CollectorService.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "CollectorConfig.h"
#include "Control.h"
#include "sources/bpf-scraper/BPFProgramIterator.h"
#include "system-inspector/Service.h"

namespace collector {
Expand All @@ -24,6 +25,7 @@ class CollectorService {
const std::atomic<int>& signum_;

system_inspector::Service system_inspector_;
sources::BPFProgramIterator bpf_programs_;
};

bool SetupKernelDriver(CollectorService& collector, const CollectorConfig& config);
Expand Down
2 changes: 2 additions & 0 deletions collector/lib/sources/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

add_subdirectory(bpf-scraper)
65 changes: 65 additions & 0 deletions collector/lib/sources/bpf-scraper/BPFProgramIterator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "BPFProgramIterator.h"

#include <unistd.h>

#include <bpf/bpf.h>
#include <bpf/libbpf.h>

#include "bpf/common.h"
#include "program_iter.skel.h"

namespace collector::sources {

static struct program_iter_bpf* skeleton_;

bool BPFProgramIterator::Load() {
skeleton_ = program_iter_bpf__open_and_load();
if (!skeleton_) {
return false;
}

if (program_iter_bpf__attach(skeleton_) != 0) {
return false;
}

return true;
}

void BPFProgramIterator::Unload() {
program_iter_bpf__destroy(skeleton_);
}

std::vector<sensor::SignalStreamMessage*> BPFProgramIterator::LoadedPrograms() {
int iter_fd = bpf_iter_create(bpf_link__fd(skeleton_->links.dump_bpf_prog));
if (iter_fd < 0) {
return {};
}

std::vector<sensor::SignalStreamMessage*> messages;
struct bpf_prog_result result;

while (true) {
int ret = read(iter_fd, &result, sizeof(struct bpf_prog_result));
if (ret < 0) {
if (errno == EAGAIN) {
continue;
}
break;
}

if (ret == 0) {
break;
}

auto message = formatter_.ToProtoMessage(&result);
if (message == nullptr) {
continue;
}

messages.push_back(message);
}

return messages;
}

} // namespace collector::sources
23 changes: 23 additions & 0 deletions collector/lib/sources/bpf-scraper/BPFProgramIterator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef _COLLECTOR_BPF_PROGRAM_ITERATOR_
#define _COLLECTOR_BPF_PROGRAM_ITERATOR_

#include <vector>

#include "internalapi/sensor/signal_iservice.pb.h"

#include "BPFSignalFormatter.h"

namespace collector::sources {
class BPFProgramIterator {
public:
bool Load();
void Unload();

std::vector<sensor::SignalStreamMessage*> LoadedPrograms();

private:
BPFSignalFormatter formatter_;
};
}; // namespace collector::sources

#endif
Loading

0 comments on commit 323c063

Please sign in to comment.