Commit a667d17b authored by Brendan Gregg's avatar Brendan Gregg

add join() function

parent 26a20580
...@@ -235,5 +235,6 @@ Functions: ...@@ -235,5 +235,6 @@ Functions:
- `sym(void *p)` - Resolve kernel address - `sym(void *p)` - Resolve kernel address
- `usym(void *p)` - Resolve user space address (incomplete) - `usym(void *p)` - Resolve user space address (incomplete)
- `reg(char *name)` - Returns the value stored in the named register - `reg(char *name)` - Returns the value stored in the named register
- `join(char *arr[])` - Prints the string array
- `time(char *fmt)` - Print the current time - `time(char *fmt)` - Print the current time
- `exit()` - Quit bpftrace - `exit()` - Quit bpftrace
...@@ -244,6 +244,40 @@ void CodegenLLVM::visit(Call &call) ...@@ -244,6 +244,40 @@ void CodegenLLVM::visit(Call &call)
b_.CreateProbeReadStr(buf, call.type.size, expr_); b_.CreateProbeReadStr(buf, call.type.size, expr_);
expr_ = buf; expr_ = buf;
} }
else if (call.func == "join")
{
call.vargs->front()->accept(*this);
AllocaInst *first = b_.CreateAllocaBPF(SizedType(Type::integer, 8), call.func + "_first");
AllocaInst *second = b_.CreateAllocaBPF(b_.getInt64Ty(), call.func+"_second");
Value *perfdata = b_.CreateGetJoinMap(ctx_);
Function *parent = b_.GetInsertBlock()->getParent();
BasicBlock *zero = BasicBlock::Create(module_->getContext(), "joinzero", parent);
BasicBlock *notzero = BasicBlock::Create(module_->getContext(), "joinnotzero", parent);
b_.CreateCondBr(b_.CreateICmpNE(perfdata, ConstantExpr::getCast(Instruction::IntToPtr, b_.getInt64(0), b_.getInt8PtrTy()), "joinzerocond"), notzero, zero);
// arg0
b_.SetInsertPoint(notzero);
b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::join)), perfdata);
AllocaInst *arr = b_.CreateAllocaBPF(b_.getInt64Ty(), call.func+"_r0");
b_.CreateProbeRead(arr, 8, expr_);
b_.CreateProbeReadStr(b_.CreateAdd(perfdata, b_.getInt64(8)), bpftrace_.join_argsize_, b_.CreateLoad(arr));
for (int i = 1; i < bpftrace_.join_argnum_; i++) {
// argi
b_.CreateStore(b_.CreateAdd(expr_, b_.getInt64(8 * i)), first);
b_.CreateProbeRead(second, 8, b_.CreateLoad(first));
b_.CreateProbeReadStr(b_.CreateAdd(perfdata, b_.getInt64(8 + i * bpftrace_.join_argsize_)), bpftrace_.join_argsize_, b_.CreateLoad(second));
}
// emit
b_.CreatePerfEventOutput(ctx_, perfdata, 8 + bpftrace_.join_argnum_ * bpftrace_.join_argsize_);
b_.CreateBr(zero);
// done
b_.SetInsertPoint(zero);
expr_ = nullptr;
}
else if (call.func == "sym" || call.func == "usym") else if (call.func == "sym" || call.func == "usym")
{ {
// We want expr_ to just pass through from the child node - don't set it here // We want expr_ to just pass through from the child node - don't set it here
......
...@@ -90,6 +90,26 @@ CallInst *IRBuilderBPF::CreateBpfPseudoCall(Map &map) ...@@ -90,6 +90,26 @@ CallInst *IRBuilderBPF::CreateBpfPseudoCall(Map &map)
return CreateBpfPseudoCall(mapfd); return CreateBpfPseudoCall(mapfd);
} }
CallInst *IRBuilderBPF::CreateGetJoinMap(Value *ctx)
{
Value *map_ptr = CreateBpfPseudoCall(bpftrace_.join_map_->mapfd_);
AllocaInst *key = CreateAllocaBPF(getInt32Ty(), "key");
Value *keyv = getInt32(0);
CreateStore(keyv, key);
FunctionType *lookup_func_type = FunctionType::get(
getInt8PtrTy(),
{getInt8PtrTy(), getInt8PtrTy()},
false);
PointerType *lookup_func_ptr_type = PointerType::get(lookup_func_type, 0);
Constant *lookup_func = ConstantExpr::getCast(
Instruction::IntToPtr,
getInt64(BPF_FUNC_map_lookup_elem),
lookup_func_ptr_type);
CallInst *call = CreateCall(lookup_func, {map_ptr, key}, "join_elem");
return call;
}
Value *IRBuilderBPF::CreateMapLookupElem(Map &map, AllocaInst *key) Value *IRBuilderBPF::CreateMapLookupElem(Map &map, AllocaInst *key)
{ {
Value *map_ptr = CreateBpfPseudoCall(map); Value *map_ptr = CreateBpfPseudoCall(map);
...@@ -194,7 +214,22 @@ void IRBuilderBPF::CreateProbeRead(AllocaInst *dst, size_t size, Value *src) ...@@ -194,7 +214,22 @@ void IRBuilderBPF::CreateProbeRead(AllocaInst *dst, size_t size, Value *src)
CallInst *call = CreateCall(proberead_func, {dst, getInt64(size), src}, "probe_read"); CallInst *call = CreateCall(proberead_func, {dst, getInt64(size), src}, "probe_read");
} }
void IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src) CallInst *IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src)
{
// int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr)
FunctionType *probereadstr_func_type = FunctionType::get(
getInt64Ty(),
{getInt8PtrTy(), getInt64Ty(), getInt8PtrTy()},
false);
PointerType *probereadstr_func_ptr_type = PointerType::get(probereadstr_func_type, 0);
Constant *probereadstr_func = ConstantExpr::getCast(
Instruction::IntToPtr,
getInt64(BPF_FUNC_probe_read_str),
probereadstr_func_ptr_type);
return CreateCall(probereadstr_func, {dst, getInt64(size), src}, "probe_read_str");
}
CallInst *IRBuilderBPF::CreateProbeReadStr(Value *dst, size_t size, Value *src)
{ {
// int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr) // int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr)
FunctionType *probereadstr_func_type = FunctionType::get( FunctionType *probereadstr_func_type = FunctionType::get(
...@@ -206,7 +241,7 @@ void IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src) ...@@ -206,7 +241,7 @@ void IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src)
Instruction::IntToPtr, Instruction::IntToPtr,
getInt64(BPF_FUNC_probe_read_str), getInt64(BPF_FUNC_probe_read_str),
probereadstr_func_ptr_type); probereadstr_func_ptr_type);
CallInst *call = CreateCall(probereadstr_func, {dst, getInt64(size), src}, "probe_read_str"); return CreateCall(probereadstr_func, {dst, getInt64(size), src}, "map_read_str");
} }
CallInst *IRBuilderBPF::CreateGetNs() CallInst *IRBuilderBPF::CreateGetNs()
......
...@@ -28,12 +28,14 @@ public: ...@@ -28,12 +28,14 @@ public:
void CreateMapUpdateElem(Map &map, AllocaInst *key, Value *val); void CreateMapUpdateElem(Map &map, AllocaInst *key, Value *val);
void CreateMapDeleteElem(Map &map, AllocaInst *key); void CreateMapDeleteElem(Map &map, AllocaInst *key);
void CreateProbeRead(AllocaInst *dst, size_t size, Value *src); void CreateProbeRead(AllocaInst *dst, size_t size, Value *src);
void CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src); CallInst *CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src);
CallInst *CreateProbeReadStr(Value *dst, size_t size, Value *src);
CallInst *CreateGetNs(); CallInst *CreateGetNs();
CallInst *CreateGetPidTgid(); CallInst *CreateGetPidTgid();
CallInst *CreateGetUidGid(); CallInst *CreateGetUidGid();
CallInst *CreateGetCpuId(); CallInst *CreateGetCpuId();
CallInst *CreateGetStackId(Value *ctx, bool ustack); CallInst *CreateGetStackId(Value *ctx, bool ustack);
CallInst *CreateGetJoinMap(Value *ctx);
void CreateGetCurrentComm(AllocaInst *buf, size_t size); void CreateGetCurrentComm(AllocaInst *buf, size_t size);
void CreatePerfEventOutput(Value *ctx, Value *data, size_t size); void CreatePerfEventOutput(Value *ctx, Value *data, size_t size);
......
...@@ -146,6 +146,13 @@ void SemanticAnalyser::visit(Call &call) ...@@ -146,6 +146,13 @@ void SemanticAnalyser::visit(Call &call)
else if (call.func == "usym") else if (call.func == "usym")
call.type = SizedType(Type::usym, 8); call.type = SizedType(Type::usym, 8);
} }
else if (call.func == "join") {
check_assignment(call, false, false);
check_nargs(call, 1);
check_arg(call, Type::integer, 0);
call.type = SizedType(Type::none, 0);
needs_join_map_ = true;
}
else if (call.func == "reg") { else if (call.func == "reg") {
if (check_nargs(call, 1)) { if (check_nargs(call, 1)) {
if (check_arg(call, Type::string, 0, true)) { if (check_arg(call, Type::string, 0, true)) {
...@@ -675,12 +682,28 @@ int SemanticAnalyser::create_maps(bool debug) ...@@ -675,12 +682,28 @@ int SemanticAnalyser::create_maps(bool debug)
{ {
if (needs_stackid_map_) if (needs_stackid_map_)
bpftrace_.stackid_map_ = std::make_unique<bpftrace::FakeMap>(BPF_MAP_TYPE_STACK_TRACE); bpftrace_.stackid_map_ = std::make_unique<bpftrace::FakeMap>(BPF_MAP_TYPE_STACK_TRACE);
if (needs_join_map_)
{
// join uses map storage as we'd like to process data larger than can fit on the BPF stack.
std::string map_ident = "join";
SizedType type = SizedType(Type::join, 8 + bpftrace_.join_argnum_ * bpftrace_.join_argsize_);
MapKey key;
bpftrace_.join_map_ = std::make_unique<bpftrace::FakeMap>(map_ident, type, key);
}
bpftrace_.perf_event_map_ = std::make_unique<bpftrace::FakeMap>(BPF_MAP_TYPE_PERF_EVENT_ARRAY); bpftrace_.perf_event_map_ = std::make_unique<bpftrace::FakeMap>(BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} }
else else
{ {
if (needs_stackid_map_) if (needs_stackid_map_)
bpftrace_.stackid_map_ = std::make_unique<bpftrace::Map>(BPF_MAP_TYPE_STACK_TRACE); bpftrace_.stackid_map_ = std::make_unique<bpftrace::Map>(BPF_MAP_TYPE_STACK_TRACE);
if (needs_join_map_)
{
// join uses map storage as we'd like to process data larger than can fit on the BPF stack.
std::string map_ident = "join";
SizedType type = SizedType(Type::join, 8 + bpftrace_.join_argnum_ * bpftrace_.join_argsize_);
MapKey key;
bpftrace_.join_map_ = std::make_unique<bpftrace::Map>(map_ident, type, key);
}
bpftrace_.perf_event_map_ = std::make_unique<bpftrace::Map>(BPF_MAP_TYPE_PERF_EVENT_ARRAY); bpftrace_.perf_event_map_ = std::make_unique<bpftrace::Map>(BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} }
......
...@@ -61,6 +61,7 @@ private: ...@@ -61,6 +61,7 @@ private:
std::map<std::string, SizedType> map_val_; std::map<std::string, SizedType> map_val_;
std::map<std::string, MapKey> map_key_; std::map<std::string, MapKey> map_key_;
bool needs_stackid_map_ = false; bool needs_stackid_map_ = false;
bool needs_join_map_ = false;
bool has_begin_probe_ = false; bool has_begin_probe_ = false;
bool has_end_probe_ = false; bool has_end_probe_ = false;
}; };
......
...@@ -181,6 +181,20 @@ void perf_event_printer(void *cb_cookie, void *data, int size) ...@@ -181,6 +181,20 @@ void perf_event_printer(void *cb_cookie, void *data, int size)
printf("%s", timestr); printf("%s", timestr);
return; return;
} }
else if (printf_id == asyncactionint(AsyncAction::join))
{
const char *joinstr = " ";
for (int i = 0; i < bpftrace->join_argnum_; i++) {
auto *arg = arg_data + i * bpftrace->join_argsize_;
if (arg[0] == 0)
break;
if (i)
printf("%s", joinstr);
printf("%s", arg);
}
printf("\n");
return;
}
// printf // printf
auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]).c_str(); auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]).c_str();
...@@ -318,7 +332,7 @@ int BPFtrace::setup_perf_events() ...@@ -318,7 +332,7 @@ int BPFtrace::setup_perf_events()
online_cpus_ = cpus.size(); online_cpus_ = cpus.size();
for (int cpu : cpus) for (int cpu : cpus)
{ {
int page_cnt = 8; int page_cnt = 64;
void *reader = bpf_open_perf_buffer(&perf_event_printer, &perf_event_lost, this, -1, cpu, page_cnt); void *reader = bpf_open_perf_buffer(&perf_event_printer, &perf_event_lost, this, -1, cpu, page_cnt);
if (reader == nullptr) if (reader == nullptr)
{ {
......
...@@ -44,7 +44,10 @@ public: ...@@ -44,7 +44,10 @@ public:
std::vector<std::tuple<std::string, std::vector<SizedType>>> printf_args_; std::vector<std::tuple<std::string, std::vector<SizedType>>> printf_args_;
std::vector<std::string> time_args_; std::vector<std::string> time_args_;
std::unique_ptr<IMap> stackid_map_; std::unique_ptr<IMap> stackid_map_;
std::unique_ptr<IMap> join_map_;
std::unique_ptr<IMap> perf_event_map_; std::unique_ptr<IMap> perf_event_map_;
int join_argnum_;
int join_argsize_;
static void sort_by_key(std::vector<SizedType> key_args, static void sort_by_key(std::vector<SizedType> key_args,
std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>> &values_by_key); std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>> &values_by_key);
......
...@@ -113,6 +113,10 @@ int main(int argc, char *argv[]) ...@@ -113,6 +113,10 @@ int main(int argc, char *argv[])
BPFtrace bpftrace; BPFtrace bpftrace;
// defaults
bpftrace.join_argnum_ = 16;
bpftrace.join_argsize_ = 1024;
// PID is currently only used for USDT probes that need enabling. Future work: // PID is currently only used for USDT probes that need enabling. Future work:
// - make PID a filter for all probe types: pass to perf_event_open(), etc. // - make PID a filter for all probe types: pass to perf_event_open(), etc.
// - provide PID in USDT probe specification as a way to override -p. // - provide PID in USDT probe specification as a way to override -p.
......
...@@ -22,6 +22,7 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key) ...@@ -22,6 +22,7 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key)
if (key_size == 0) if (key_size == 0)
key_size = 8; key_size = 8;
int max_entries = 128;
enum bpf_map_type map_type; enum bpf_map_type map_type;
if ((type.type == Type::quantize || type.type == Type::count || if ((type.type == Type::quantize || type.type == Type::count ||
type.type == Type::sum || type.type == Type::min || type.type == Type::max || type.type == Type::avg || type.type == Type::stats) && type.type == Type::sum || type.type == Type::min || type.type == Type::max || type.type == Type::avg || type.type == Type::stats) &&
...@@ -29,11 +30,16 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key) ...@@ -29,11 +30,16 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key)
{ {
map_type = BPF_MAP_TYPE_PERCPU_HASH; map_type = BPF_MAP_TYPE_PERCPU_HASH;
} }
else if (type.type == Type::join)
{
map_type = BPF_MAP_TYPE_PERCPU_ARRAY;
max_entries = 1;
key_size = 4;
}
else else
map_type = BPF_MAP_TYPE_HASH; map_type = BPF_MAP_TYPE_HASH;
int value_size = type.size; int value_size = type.size;
int max_entries = 128;
int flags = 0; int flags = 0;
mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags); mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
if (mapfd_ < 0) if (mapfd_ < 0)
......
...@@ -28,6 +28,7 @@ enum class Type ...@@ -28,6 +28,7 @@ enum class Type
sym, sym,
usym, usym,
cast, cast,
join,
}; };
std::ostream &operator<<(std::ostream &os, Type type); std::ostream &operator<<(std::ostream &os, Type type);
...@@ -85,6 +86,7 @@ enum class AsyncAction ...@@ -85,6 +86,7 @@ enum class AsyncAction
clear, clear,
zero, zero,
time, time,
join,
}; };
uint64_t asyncactionint(AsyncAction a); uint64_t asyncactionint(AsyncAction a);
......
...@@ -1476,6 +1476,8 @@ attributes #1 = { argmemonly nounwind } ...@@ -1476,6 +1476,8 @@ attributes #1 = { argmemonly nounwind }
)EXPECTED"); )EXPECTED");
} }
// TODO: add a join() test. It gets stuck in codegen.compile().
TEST(codegen, int_propagation) TEST(codegen, int_propagation)
{ {
test("kprobe:f { @x = 1234; @y = @x }", test("kprobe:f { @x = 1234; @y = @x }",
......
...@@ -93,6 +93,7 @@ TEST(semantic_analyser, builtin_functions) ...@@ -93,6 +93,7 @@ TEST(semantic_analyser, builtin_functions)
test("kprobe:f { exit() }", 0); test("kprobe:f { exit() }", 0);
test("kprobe:f { str(0xffff) }", 0); test("kprobe:f { str(0xffff) }", 0);
test("kprobe:f { printf(\"hello\\n\") }", 0); test("kprobe:f { printf(\"hello\\n\") }", 0);
test("kprobe:f { join(0) }", 0);
test("kprobe:f { sym(0xffff) }", 0); test("kprobe:f { sym(0xffff) }", 0);
test("kprobe:f { usym(0xffff) }", 0); test("kprobe:f { usym(0xffff) }", 0);
test("kprobe:f { reg(\"ip\") }", 0); test("kprobe:f { reg(\"ip\") }", 0);
...@@ -399,6 +400,16 @@ TEST(semantic_analyser, printf_format_multi) ...@@ -399,6 +400,16 @@ TEST(semantic_analyser, printf_format_multi)
test("kprobe:f { printf(\"%d %s %d\", 1, 2, \"mystr\") }", 10); test("kprobe:f { printf(\"%d %s %d\", 1, 2, \"mystr\") }", 10);
} }
TEST(semantic_analyser, join)
{
test("kprobe:f { join(arg0) }", 0);
test("kprobe:f { printf(\"%s\", join(arg0)) }", 10);
test("kprobe:f { join() }", 1);
test("kprobe:f { $fmt = \"mystring\"; join($fmt) }", 10);
test("kprobe:f { @x = join(arg0) }", 1);
test("kprobe:f { $x = join(arg0) }", 1);
}
TEST(semantic_analyser, kprobe) TEST(semantic_analyser, kprobe)
{ {
test("kprobe:f { 1 }", 0); test("kprobe:f { 1 }", 0);
......
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