Skip to content

Commit

Permalink
Merge pull request #170 from kinvolk/alban/func_cgroupid
Browse files Browse the repository at this point in the history
Implement function cgroupid(path) -> cgroup id
  • Loading branch information
brendangregg committed Oct 16, 2018
2 parents 51500df + 5db00d7 commit 2a906eb
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Expand Up @@ -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})
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions docs/reference_guide.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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).
Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Expand Up @@ -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)
Expand Down
43 changes: 43 additions & 0 deletions src/act_helpers.h
@@ -0,0 +1,43 @@
#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)
7 changes: 7 additions & 0 deletions src/ast/codegen_llvm.cpp
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions src/ast/semantic_analyser.cpp
Expand Up @@ -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)) {
Expand Down
62 changes: 62 additions & 0 deletions src/bpftrace.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/bpftrace.h
Expand Up @@ -65,6 +65,7 @@ class BPFtrace
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_;

Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Expand Up @@ -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)
Expand Down
44 changes: 44 additions & 0 deletions tests/codegen.cpp
Expand Up @@ -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) }",
Expand Down

0 comments on commit 2a906eb

Please sign in to comment.