Commit b57abde0 authored by Brendan Gregg's avatar Brendan Gregg

add name builtin

parent 282748b0
......@@ -49,6 +49,7 @@ class Builtin : public Expression {
public:
explicit Builtin(std::string ident) : ident(ident) { }
std::string ident;
int name_id;
void accept(Visitor &v) override;
};
......@@ -196,6 +197,7 @@ public:
void accept(Visitor &v) override;
std::string name() const;
bool need_expansion = false; // must build a BPF program per wildcard match
};
using ProbeList = std::vector<Probe *>;
......
......@@ -97,6 +97,14 @@ void CodegenLLVM::visit(Builtin &builtin)
expr_ = b_.CreateLoad(dst);
b_.CreateLifetimeEnd(dst);
}
else if (builtin.ident == "name")
{
static int name_id = 0;
bpftrace_.name_ids_.push_back(probefull_);
builtin.name_id = name_id;
name_id++;
expr_ = b_.getInt64(builtin.name_id);
}
else
{
abort();
......@@ -659,21 +667,70 @@ void CodegenLLVM::visit(Probe &probe)
b_.getInt64Ty(),
{b_.getInt8PtrTy()}, // struct pt_regs *ctx
false);
Function *func = Function::Create(func_type, Function::ExternalLinkage, probe.name(), module_.get());
func->setSection("s_" + probe.name());
BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", func);
b_.SetInsertPoint(entry);
ctx_ = func->arg_begin();
/*
* Most of the time, we can take a probe like kprobe:do_f* and build a
* single BPF program for that, called "s_kprobe:do_f*", and attach it to
* each wildcard match. An exception is the "name" builtin, where we need
* to build different BPF programs for each wildcard match that cantains an
* ID for the match. Those programs will be called "s_kprobe:do_fcntl" etc.
*/
if (probe.need_expansion == false) {
// build a single BPF program pre-wildcards
Function *func = Function::Create(func_type, Function::ExternalLinkage, probe.name(), module_.get());
func->setSection("s_" + probe.name());
BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", func);
b_.SetInsertPoint(entry);
if (probe.pred) {
probe.pred->accept(*this);
}
for (Statement *stmt : *probe.stmts) {
stmt->accept(*this);
}
ctx_ = func->arg_begin();
b_.CreateRet(ConstantInt::get(module_->getContext(), APInt(64, 0)));
if (probe.pred) {
probe.pred->accept(*this);
}
for (Statement *stmt : *probe.stmts) {
stmt->accept(*this);
}
b_.CreateRet(ConstantInt::get(module_->getContext(), APInt(64, 0)));
} else {
// build a separate BPF programs for each wildcard match
for (auto &attach_point : *probe.attach_points) {
std::string file_name;
switch (probetype(attach_point->provider))
{
case ProbeType::kprobe:
case ProbeType::kretprobe:
file_name = "/sys/kernel/debug/tracing/available_filter_functions";
break;
case ProbeType::tracepoint:
file_name = "/sys/kernel/debug/tracing/available_events";
break;
default:
std::cerr << "Wildcard matches aren't available on probe type '"
<< attach_point->provider << "'" << std::endl;
return;
}
auto matches = bpftrace_.find_wildcard_matches(attach_point->target, attach_point->func, file_name);
for (auto &match : matches) {
probefull_ = attach_point->name(match);
Function *func = Function::Create(func_type, Function::ExternalLinkage, attach_point->name(match), module_.get());
func->setSection("s_" + attach_point->name(match));
BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", func);
b_.SetInsertPoint(entry);
// check: do the following 8 lines need to be in the wildcard loop?
ctx_ = func->arg_begin();
if (probe.pred) {
probe.pred->accept(*this);
}
for (Statement *stmt : *probe.stmts) {
stmt->accept(*this);
}
b_.CreateRet(ConstantInt::get(module_->getContext(), APInt(64, 0)));
}
}
}
}
void CodegenLLVM::visit(Include &include)
......
......@@ -65,6 +65,7 @@ private:
Value *expr_ = nullptr;
Value *ctx_;
BPFtrace &bpftrace_;
std::string probefull_;
std::map<std::string, Value *> variables_;
};
......
......@@ -68,6 +68,10 @@ void SemanticAnalyser::visit(Builtin &builtin)
err_ << arch::name() << " doesn't support " << builtin.ident << std::endl;
builtin.type = SizedType(Type::integer, 8);
}
else if (builtin.ident == "name") {
builtin.type = SizedType(Type::name, 8);
probe_->need_expansion = true;
}
else {
builtin.type = SizedType(Type::none, 0);
err_ << "Unknown builtin variable: '" << builtin.ident << "'" << std::endl;
......
......@@ -55,6 +55,8 @@ AttachedProbe::AttachedProbe(Probe &probe, std::tuple<uint8_t *, uintptr_t> func
: probe_(probe), func_(func)
{
load_prog();
if (bt_verbose)
std::cerr << "Attaching " << probe_.name << std::endl;
switch (probe_.type)
{
case ProbeType::kprobe:
......@@ -277,8 +279,16 @@ void AttachedProbe::attach_kprobe()
int perf_event_fd = bpf_attach_kprobe(progfd_, attachtype(probe_.type),
eventname().c_str(), probe_.attach_point.c_str(), 0);
if (perf_event_fd < 0)
throw std::runtime_error("Error attaching probe: '" + probe_.name + "'");
if (perf_event_fd < 0) {
if (probe_.orig_name != probe_.name) {
// a wildcard expansion couldn't probe something, just print a warning
// as this is normal for some kernel functions (eg, do_debug())
std::cerr << "Warning: could not attach probe " << probe_.name << ", skipping." << std::endl;
} else {
// an explicit match failed, so fail as the user must have wanted it
throw std::runtime_error("Error attaching probe: '" + probe_.name + "'");
}
}
perf_event_fds_.push_back(perf_event_fd);
}
......
......@@ -30,7 +30,7 @@ int BPFtrace::add_probe(ast::Probe &p)
probe.path = "/proc/self/exe";
probe.attach_point = "BEGIN_trigger";
probe.type = probetype(attach_point->provider);
probe.prog_name = p.name();
probe.orig_name = p.name();
probe.name = p.name();
special_probes_.push_back(probe);
continue;
......@@ -41,7 +41,7 @@ int BPFtrace::add_probe(ast::Probe &p)
probe.path = "/proc/self/exe";
probe.attach_point = "END_trigger";
probe.type = probetype(attach_point->provider);
probe.prog_name = p.name();
probe.orig_name = p.name();
probe.name = p.name();
special_probes_.push_back(probe);
continue;
......@@ -83,7 +83,7 @@ int BPFtrace::add_probe(ast::Probe &p)
probe.path = attach_point->target;
probe.attach_point = func;
probe.type = probetype(attach_point->provider);
probe.prog_name = p.name();
probe.orig_name = p.name();
probe.name = attach_point->name(func);
probe.freq = attach_point->freq;
probes_.push_back(probe);
......@@ -117,7 +117,9 @@ std::set<std::string> BPFtrace::find_wildcard_matches(const std::string &prefix,
if (std::regex_search(line, match, func_regex))
{
assert(match.size() == 2);
matches.insert(match[1]);
// skip the ".part.N" kprobe variants, as they can't be traced:
if (std::strstr(match.str(1).c_str(), ".part.") == NULL)
matches.insert(match[1]);
}
}
return matches;
......@@ -201,6 +203,7 @@ void perf_event_printer(void *cb_cookie, void *data, int size)
auto args = std::get<1>(bpftrace->printf_args_[printf_id]);
std::vector<uint64_t> arg_values;
std::vector<std::unique_ptr<char>> resolved_symbols;
char *name;
for (auto arg : args)
{
switch (arg.type)
......@@ -221,6 +224,10 @@ void perf_event_printer(void *cb_cookie, void *data, int size)
bpftrace->resolve_usym(*(uint64_t*)arg_data).c_str()));
arg_values.push_back((uint64_t)resolved_symbols.back().get());
break;
case Type::name:
name = strdup(bpftrace->resolve_name(*(uint64_t*)arg_data).c_str());
arg_values.push_back((uint64_t)name);
break;
default:
abort();
}
......@@ -265,10 +272,19 @@ void perf_event_lost(void *cb_cookie, uint64_t lost)
std::unique_ptr<AttachedProbe> BPFtrace::attach_probe(Probe &probe, const BpfOrc &bpforc)
{
auto func = bpforc.sections_.find("s_" + probe.prog_name);
// use the single-probe program if it exists (as is the case with wildcards
// and the name builtin, which must be expanded into separate programs per
// probe), else try to find a the program based on the original probe name
// that includes wildcards.
auto func = bpforc.sections_.find("s_" + probe.name);
if (func == bpforc.sections_.end())
func = bpforc.sections_.find("s_" + probe.orig_name);
if (func == bpforc.sections_.end())
{
std::cerr << "Code not generated for probe: " << probe.name << std::endl;
if (probe.name != probe.orig_name)
std::cerr << "Code not generated for probe: " << probe.name << " from: " << probe.orig_name << std::endl;
else
std::cerr << "Code not generated for probe: " << probe.name << std::endl;
return nullptr;
}
try
......@@ -309,6 +325,9 @@ int BPFtrace::run(std::unique_ptr<BpfOrc> bpforc)
attached_probes_.push_back(std::move(attached_probe));
}
if (bt_verbose)
std::cerr << "Running..." << std::endl;
poll_perf_events(epollfd);
attached_probes_.clear();
......@@ -623,6 +642,8 @@ int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div)
std::cout << min_value(value, ncpus_) / div << std::endl;
else if (map.type_.type == Type::max)
std::cout << max_value(value, ncpus_) / div << std::endl;
else if (map.type_.type == Type::name)
std::cout << resolve_name(*(uint64_t*)value.data()) << std::endl;
else
std::cout << *(int64_t*)value.data() / div << std::endl;
}
......@@ -1044,6 +1065,12 @@ std::string BPFtrace::resolve_usym(uintptr_t addr) const
return symbol.str();
}
std::string BPFtrace::resolve_name(uint64_t name_id)
{
assert(name_id < name_ids_.size());
return name_ids_[name_id];
}
void BPFtrace::sort_by_key(std::vector<SizedType> key_args,
std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>> &values_by_key)
{
......
......@@ -37,6 +37,7 @@ public:
std::string get_stack(uint32_t stackid, bool ustack, int indent=0);
std::string resolve_sym(uintptr_t addr, bool show_offset=false);
std::string resolve_usym(uintptr_t addr) const;
std::string resolve_name(uint64_t name_id);
int pid_;
std::map<std::string, std::unique_ptr<IMap>> maps_;
......@@ -46,14 +47,15 @@ public:
std::unique_ptr<IMap> stackid_map_;
std::unique_ptr<IMap> join_map_;
std::unique_ptr<IMap> perf_event_map_;
std::vector<std::string> name_ids_;
int join_argnum_;
int join_argsize_;
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);
virtual std::set<std::string> find_wildcard_matches(const std::string &prefix, const std::string &attach_point, const std::string &file_name);
protected:
virtual std::set<std::string> find_wildcard_matches(const std::string &prefix, const std::string &attach_point, const std::string &file_name);
std::vector<Probe> probes_;
std::vector<Probe> special_probes_;
......
......@@ -38,7 +38,7 @@ header <(\\.|[_\-\./a-zA-Z0-9])*>
{vspace}+ { loc.lines(yyleng); loc.step(); }
"//".*$ // Comments
pid|tid|uid|gid|nsecs|cpu|comm|stack|ustack|arg[0-9]|retval|func {
pid|tid|uid|gid|nsecs|cpu|comm|stack|ustack|arg[0-9]|retval|func|name {
return Parser::make_BUILTIN(yytext, loc); }
{ident} { return Parser::make_IDENT(yytext, loc); }
{path} { return Parser::make_PATH(yytext, loc); }
......
......@@ -73,6 +73,8 @@ std::string MapKey::argument_value(BPFtrace &bpftrace,
return bpftrace.resolve_sym(*(uint64_t*)data);
case Type::usym:
return bpftrace.resolve_usym(*(uint64_t*)data);
case Type::name:
return bpftrace.name_ids_[*(uint64_t*)data];
case Type::string:
return std::string((char*)data);
}
......
......@@ -32,7 +32,7 @@ std::string verify_format_string(const std::string &fmt, std::vector<SizedType>
for (int i=0; i<num_args; i++, token_iter++)
{
Type arg_type = args.at(i).type;
if (arg_type == Type::sym || arg_type == Type::usym)
if (arg_type == Type::sym || arg_type == Type::usym || arg_type == Type::name)
arg_type = Type::string; // Symbols should be printed as strings
int offset = 1;
......
......@@ -41,6 +41,7 @@ std::string typestr(Type t)
case Type::sym: return "sym"; break;
case Type::usym: return "usym"; break;
case Type::cast: return "cast"; break;
case Type::name: return "name"; break;
default: abort();
}
}
......
......@@ -30,6 +30,7 @@ enum class Type
usym,
cast,
join,
name,
};
std::ostream &operator<<(std::ostream &os, Type type);
......@@ -71,10 +72,11 @@ class Probe
{
public:
ProbeType type;
std::string path;
std::string attach_point;
std::string prog_name;
std::string name;
std::string path; // file path if used
std::string attach_point; // probe name (last component)
std::string orig_name; // original full probe name,
// before wildcard expansion
std::string name; // full probe name
uint64_t loc;
int freq;
};
......
This diff is collapsed.
......@@ -667,6 +667,118 @@ attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
TEST(codegen, builtin_name)
{
test("tracepoint:syscalls:sys_enter_nanosleep { @x = name }",
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_nanosleep"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_nanosleep" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 0, 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 %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
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, builtin_func_wild)
{
test("tracepoint:syscalls:sys_enter_nanoslee* { @x = func }",
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_nanoslee*"(i8*) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_nanoslee*" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%func = alloca i64, align 8
%1 = bitcast i64* %func to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%2 = getelementptr i8, i8* %0, i64 128
%probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %func, i64 8, i8* %2)
%3 = load i64, i64* %func, align 8
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
%4 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4)
store i64 0, i64* %"@x_key", align 8
%5 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
store i64 %3, i64* %"@x_val", align 8
%pseudo = 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 %4)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5)
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, builtin_name_wild)
{
test("tracepoint:syscalls:sys_enter_nanoslee* { @x = name }",
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_nanosleep"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_nanosleep" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, 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 %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
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_reg) // Identical to builtin_func apart from variable names
{
test("kprobe:f { @x = reg(\"ip\") }",
......
......@@ -35,6 +35,7 @@ TEST(Parser, builtin_variables)
test("kprobe:f { arg0 }", "Program\n kprobe:f\n builtin: arg0\n");
test("kprobe:f { retval }", "Program\n kprobe:f\n builtin: retval\n");
test("kprobe:f { func }", "Program\n kprobe:f\n builtin: func\n");
test("kprobe:f { name }", "Program\n kprobe:f\n builtin: name\n");
}
TEST(Parser, map_assign)
......
......@@ -73,6 +73,7 @@ TEST(semantic_analyser, builtin_variables)
test("kprobe:f { arg0 }", 0);
test("kprobe:f { retval }", 0);
test("kprobe:f { func }", 0);
test("kprobe:f { name }", 0);
// test("kprobe:f { fake }", 1);
}
......@@ -260,6 +261,24 @@ TEST(semantic_analyser, call_reg)
test("kprobe:f { reg(123); }", 1);
}
TEST(semantic_analyser, call_func)
{
test("kprobe:f { @[func] = count(); }", 0);
test("kprobe:f { printf(\"%s\", func); }", 0);
test("kprobe:f { func(\"blah\"); }", 1);
test("kprobe:f { func(); }", 1);
test("kprobe:f { func(123); }", 1);
}
TEST(semantic_analyser, call_name)
{
test("kprobe:f { @[name] = count(); }", 0);
test("kprobe:f { printf(\"%s\", name); }", 0);
test("kprobe:f { name(\"blah\"); }", 1);
test("kprobe:f { name(); }", 1);
test("kprobe:f { name(123); }", 1);
}
TEST(semantic_analyser, map_reassignment)
{
test("kprobe:f { @x = 1; @x = 2; }", 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