Commit 2a906ebc authored by Brendan Gregg's avatar Brendan Gregg Committed by GitHub

Merge pull request #170 from kinvolk/alban/func_cgroupid

Implement function cgroupid(path) -> cgroup id
parents 51500dfb 5db00d70
......@@ -60,6 +60,11 @@ add_flex_bison_dependency(flex_lexer bison_parser)
add_library(parser ${BISON_bison_parser_OUTPUTS} ${FLEX_flex_lexer_OUTPUTS})
target_include_directories(parser PUBLIC src src/ast ${CMAKE_BINARY_DIR})
include(CheckSymbolExists)
set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
check_symbol_exists(name_to_handle_at "sys/types.h;sys/stat.h;fcntl.h" HAVE_NAME_TO_HANDLE_AT)
set(CMAKE_REQUIRED_DEFINITIONS)
find_package(LLVM REQUIRED)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
......
......@@ -139,7 +139,7 @@ bpftrace -e 'hardware:cache-misses:1000000 { @[comm, pid] = count(); }'
bpftrace -e 'profile:hz:99 /pid == 189/ { @[ustack] = count(); }'
# Files opened, for processes in the root cgroup-v2
bpftrace -e 'tracepoint:syscalls:sys_enter_open /cgroup == 0x100000001/ { printf("%s\n", str(args->filename)); }'
bpftrace -e 'tracepoint:syscalls:sys_enter_openat /cgroup == cgroupid("/sys/fs/cgroup/unified/mycg")/ { printf("%s\n", str(args->filename)); }'
```
## Tools
......
......@@ -54,6 +54,7 @@ This is a work in progress. If something is missing, check the bpftrace source t
- [10. `reg()`: Registers](#10-reg-registers)
- [11. `system()`: System](#11-system-system)
- [12. `exit()`: Exit](#12-exit-exit)
- [13. `cgroupid()`: Resolve cgroup ID](#13-cgroupid-resolve-cgroup-id)
- [Map Functions](#map-functions)
- [1. Builtins](#1-builtins-2)
- [2. `count()`: Count](#2-count-count)
......@@ -863,6 +864,7 @@ That would fire once for every 1000000 cache misses. This usually indicates the
- `name` - Full name of the probe
- `curtask` - Current task struct as a u64
- `rand` - Random number as a u32
- `cgroup` - Cgroup ID of the current process
Many of these are discussed in other sections (use search).
......@@ -1124,6 +1126,7 @@ Note that for this example to work, bash had to be recompiled with frame pointer
- `reg(char *name)` - Returns the value stored in the named register
- `system(char *fmt)` - Execute shell command
- `exit()` - Quit bpftrace
- `cgroupid(char *path)` - Resolve cgroup ID
Some of these are asynchronous: the kernel queues the event, but some time later (milliseconds) it is processed in user-space. The asynchronous actions are: <tt>printf()</tt>, <tt>time()</tt>, and <tt>join()</tt>. Both <tt>sym()</tt> and <tt>usym()</tt>, as well as the variables <tt>stack</tt> and </tt>ustack</tt>, record addresses synchronously, but then do symbol translation asynchronously.
......@@ -1317,6 +1320,29 @@ Attaching 2 probes...
@opens: 119
```
## 13. `cgroupid`: Resolve cgroup ID
Syntax: `cgroupid(char *path)`
This returns a cgroup ID of a specific cgroup, and can be combined with the `cgroup` builtin to filter the tasks that belong to the specific cgroup, for example:
```
# bpftrace -e 'tracepoint:syscalls:sys_enter_openat /cgroup == cgroupid("/sys/fs/cgroup/unified/mycg")/ { printf("%s\n", str(args->filename)); }':
Attaching 1 probe...
/etc/ld.so.cache
/lib64/libc.so.6
/usr/lib/locale/locale-archive
/etc/shadow
^C
```
And in other terminal:
```
# echo $$ > /sys/fs/cgroup/unified/mycg/cgroup.procs
# cat /etc/shadow
```
# Map Functions
Maps are special BPF data types that can be used to store counts, statistics, and histograms. They are also used for some variable types as discussed in the previous section, whenever `@` is used: [globals](#21-global), [per thread variables](#22-per-thread), and [associative arrays](#3--associative-arrays).
......
......@@ -13,6 +13,9 @@ add_executable(bpftrace
list.cpp
)
if(HAVE_NAME_TO_HANDLE_AT)
target_compile_definitions(bpftrace PRIVATE HAVE_NAME_TO_HANDLE_AT=1)
endif(HAVE_NAME_TO_HANDLE_AT)
target_link_libraries(bpftrace arch ast parser resources)
ExternalProject_Get_Property(bcc source_dir binary_dir)
......
#pragma once
// Annoying C type helpers.
//
// C headers sometimes contain struct ending with a flexible array
// member, which is not supported in C++. An example of such a type is
// file_handle from fcntl.h (man name_to_handle_at)
//
// Here are some helper macros helping to ensure if the C++
// counterpart has members of the same type and offset.
#include <type_traits>
namespace act_helpers
{
template <typename T, typename M> M get_member_type(M T:: *);
}
#define ACTH_SAME_SIZE(cxxtype, ctype, extra_type) \
(sizeof(cxxtype) == sizeof(ctype) + sizeof(extra_type))
#define ACTH_GET_TYPE_OF(mem) \
decltype(act_helpers::get_member_type(&mem))
#define ACTH_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem) \
(std::is_standard_layout<cxxtype>::value && \
std::is_standard_layout<ctype>::value && \
(offsetof(cxxtype, cxxmem) == offsetof(ctype, cmem)))
#define ACTH_SAME_TYPE(cxxtype, cxxmem, ctype, cmem) \
std::is_same<ACTH_GET_TYPE_OF(cxxtype :: cxxmem), ACTH_GET_TYPE_OF(ctype :: cmem)>::value
#define ACTH_ASSERT_SAME_SIZE(cxxtype, ctype, extra_type) \
static_assert(ACTH_SAME_SIZE(cxxtype, ctype, extra_type), \
"assumption that is broken: " #cxxtype " == " #ctype " + " # extra_type)
#define ACTH_ASSERT_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem) \
static_assert(ACTH_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem), \
"assumption that is broken: " #cxxtype "::" #cxxmem " is at the same offset as " #ctype "::" #cmem)
#define ACTH_ASSERT_SAME_TYPE(cxxtype, cxxmem, ctype, cmem) \
static_assert(ACTH_SAME_TYPE(cxxtype, cxxmem, ctype, cmem), \
"assumption that is broken: " #cxxtype "::" #cxxmem " has the same type as " #ctype "::" #cmem)
#define ACTH_ASSERT_SAME_MEMBER(cxxtype, cxxmem, ctype, cmem) \
ACTH_ASSERT_SAME_TYPE(cxxtype, cxxmem, ctype, cmem); \
ACTH_ASSERT_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem)
......@@ -341,6 +341,13 @@ void CodegenLLVM::visit(Call &call)
addr = bpftrace_.resolve_uname(name, current_attach_point_->target);
expr_ = b_.getInt64(addr);
}
else if (call.func == "cgroupid")
{
uint64_t cgroupid;
auto &path = static_cast<String&>(*call.vargs->at(0)).str;
cgroupid = bpftrace_.resolve_cgroupid(path);
expr_ = b_.getInt64(cgroupid);
}
else if (call.func == "join")
{
call.vargs->front()->accept(*this);
......
......@@ -254,6 +254,12 @@ void SemanticAnalyser::visit(Call &call)
}
call.type = SizedType(Type::integer, 8);
}
else if (call.func == "cgroupid") {
if (check_nargs(call, 1)) {
check_arg(call, Type::string, 0, true);
}
call.type = SizedType(Type::integer, 8);
}
else if (call.func == "printf" || call.func == "system") {
check_assignment(call, false, false);
if (check_varargs(call, 1, 7)) {
......
......@@ -7,9 +7,14 @@
#include <sys/epoll.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "bcc_syms.h"
#include "perf_reader.h"
#include "act_helpers.h"
#include "bpforc.h"
#include "bpftrace.h"
#include "attached_probe.h"
......@@ -1282,6 +1287,63 @@ uint64_t BPFtrace::resolve_kname(const std::string &name)
return addr;
}
#ifdef HAVE_NAME_TO_HANDLE_AT
namespace
{
// Not embedding file_handle directly in cgid_file_handle, because C++
// has problems with zero-sized array as struct members and
// file_handle's f_handle is a zero-sized array.
//
// Also, not embedding file_handle through the public inheritance,
// since checking for member offsets with offsetof within a
// non-standard-layout type is conditionally-supported (so compilers
// are not required to support it). And we want to do it to make sure
// we got the wrapper right.
//
// Hence open coding the file_handle members directly in
// cgid_file_handle and the static asserts following it.
struct cgid_file_handle
{
file_handle *as_file_handle_ptr()
{
return reinterpret_cast<file_handle*>(this);
}
unsigned int handle_bytes = sizeof(uint64_t);
int handle_type;
uint64_t cgid;
};
ACTH_ASSERT_SAME_SIZE(cgid_file_handle, file_handle, uint64_t);
ACTH_ASSERT_SAME_MEMBER(cgid_file_handle, handle_bytes, file_handle, handle_bytes);
ACTH_ASSERT_SAME_MEMBER(cgid_file_handle, handle_type, file_handle, handle_type);
ACTH_ASSERT_SAME_OFFSET(cgid_file_handle, cgid, file_handle, f_handle);
}
uint64_t BPFtrace::resolve_cgroupid(const std::string &path)
{
cgid_file_handle cfh;
int mount_id;
auto err = name_to_handle_at(AT_FDCWD, path.c_str(), cfh.as_file_handle_ptr(), &mount_id, 0);
if (err < 0) {
throw std::runtime_error("cgroup '" + path + "' not found");
}
return cfh.cgid;
}
#else
uint64_t BPFtrace::resolve_cgroupid(const std::string &path)
{
throw std::runtime_error("cgroupid is not supported on this system");
}
#endif
uint64_t BPFtrace::resolve_uname(const std::string &name, const std::string &path)
{
uint64_t addr = 0;
......
......@@ -65,6 +65,7 @@ public:
uint64_t resolve_kname(const std::string &name);
uint64_t resolve_uname(const std::string &name, const std::string &path);
std::string resolve_name(uint64_t name_id);
uint64_t resolve_cgroupid(const std::string &path);
std::vector<uint64_t> get_arg_values(std::vector<Field> args, uint8_t* arg_data);
int pid_;
......
......@@ -24,6 +24,9 @@ add_executable(bpftrace_test
${CMAKE_SOURCE_DIR}/src/types.cpp
)
if(HAVE_NAME_TO_HANDLE_AT)
target_compile_definitions(bpftrace_test PRIVATE HAVE_NAME_TO_HANDLE_AT=1)
endif(HAVE_NAME_TO_HANDLE_AT)
target_link_libraries(bpftrace_test arch ast parser resources)
ExternalProject_Get_Property(bcc source_dir binary_dir)
......
......@@ -1128,6 +1128,50 @@ TEST(codegen, call_uaddr)
// TODO: test uaddr()
}
TEST(codegen, call_cgroup)
{
test("tracepoint:syscalls:sys_enter_openat /cgroup == 0x100000001/ { @x = cgroup }",
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"tracepoint:syscalls:sys_enter_openat"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_openat_1" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%get_cgroup_id = tail call i64 inttoptr (i64 80 to i64 ()*)()
%1 = icmp eq i64 %get_cgroup_id, 4294967297
br i1 %1, label %pred_true, label %pred_false
pred_false: ; preds = %entry
ret i64 0
pred_true: ; preds = %entry
%get_cgroup_id1 = tail call i64 inttoptr (i64 80 to i64 ()*)()
%2 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 0, i64* %"@x_key", align 8
%3 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 %get_cgroup_id1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, call_hist)
{
test("kprobe:f { @x = hist(pid) }",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment