Commit b67f5598 authored by Alastair Robertson's avatar Alastair Robertson

Allow wildcard tracepoints and multiple attach points from different providers on a single probe

tracepoint:random:*_read { ... }
kprobe:sys_read, uprobe:/bin/bash:readline { ... }
parent 1c3a29d7
......@@ -113,7 +113,7 @@ Tracepoints are guaranteed to be stable between kernel versions, unlike kprobes.
### Multiple attachment points
More than one function/tracepoint can be specified for a single probe:
`kprobe:sys_read,sys_write { ... }`
`kprobe:sys_read,kprobe:sys_write { ... }`
### Wildcards
Some probe types allow wildcards to be used when attaching a probe:
......
......@@ -52,6 +52,10 @@ void Predicate::accept(Visitor &v) {
v.visit(*this);
}
void AttachPoint::accept(Visitor &v) {
v.visit(*this);
}
void Probe::accept(Visitor &v) {
v.visit(*this);
}
......@@ -93,27 +97,30 @@ std::string opstr(Unop &unop)
}
}
std::string AttachPoint::name(const std::string &attach_point) const
{
std::string n = provider;
if (target != "")
n += ":" + target;
if (attach_point != "")
n += ":" + attach_point;
return n;
}
std::string Probe::name() const
{
std::string n = type;
if (path != "")
n += ":" + path;
n += ":";
for (std::string attach_point : *attach_points)
std::string n = "";
for (auto attach_point : *attach_points)
{
n += attach_point + ",";
n += attach_point->provider;
if (attach_point->target != "")
n += ":" + attach_point->target;
if (attach_point->func != "")
n += ":" + attach_point->func;
n += ",";
}
return n.substr(0, n.size()-1);
}
std::string Probe::name(const std::string &attach_point) const
{
std::string n = type;
if (path != "")
n += ":" + path;
n += ":" + attach_point;
return n;
}
} // namespace ast
} // namespace bpftrace
......@@ -136,30 +136,38 @@ public:
void accept(Visitor &v) override;
};
using AttachPointList = std::vector<std::string>;
class AttachPoint : public Node {
public:
explicit AttachPoint(const std::string &provider)
: provider(provider) { }
AttachPoint(const std::string &provider,
const std::string &func)
: provider(provider), func(func) { }
AttachPoint(const std::string &provider,
const std::string &target,
const std::string &func)
: provider(provider), target(target), func(func) { }
std::string provider;
std::string target;
std::string func;
void accept(Visitor &v) override;
std::string name(const std::string &attach_point) const;
};
using AttachPointList = std::vector<AttachPoint *>;
class Probe : public Node {
public:
// BEGIN/END
Probe(const std::string &type, Predicate *pred, StatementList *stmts)
: type(type), attach_points(new AttachPointList), pred(pred), stmts(stmts) { }
// kprobe
Probe(const std::string &type, AttachPointList *attach_points,
Predicate *pred, StatementList *stmts)
: type(type), attach_points(attach_points), pred(pred), stmts(stmts) { }
// uprobe, tracepoint
Probe(const std::string &type, const std::string &path,
AttachPointList *attach_points, Predicate *pred, StatementList *stmts)
: type(type), path(path), attach_points(attach_points), pred(pred), stmts(stmts) { }
std::string type;
std::string path;
Probe(AttachPointList *attach_points, Predicate *pred, StatementList *stmts)
: attach_points(attach_points), pred(pred), stmts(stmts) { }
AttachPointList *attach_points;
Predicate *pred;
StatementList *stmts;
void accept(Visitor &v) override;
std::string name() const;
std::string name(const std::string &attach_point) const;
};
using ProbeList = std::vector<Probe *>;
......@@ -186,6 +194,7 @@ public:
virtual void visit(AssignMapStatement &assignment) = 0;
virtual void visit(AssignVarStatement &assignment) = 0;
virtual void visit(Predicate &pred) = 0;
virtual void visit(AttachPoint &ap) = 0;
virtual void visit(Probe &probe) = 0;
virtual void visit(Program &program) = 0;
};
......
#include <assert.h>
#include <fstream>
#include <iomanip>
#include <iostream>
......@@ -16,73 +17,82 @@ namespace bpftrace {
int BPFtrace::add_probe(ast::Probe &p)
{
if (p.type == "BEGIN")
{
Probe probe;
probe.path = "/proc/self/exe";
probe.attach_point = "BEGIN_trigger";
probe.type = probetype(p.type);
probe.prog_name = p.name();
probe.name = p.name();
special_probes_.push_back(probe);
return 0;
}
else if (p.type == "END")
for (auto attach_point : *p.attach_points)
{
Probe probe;
probe.path = "/proc/self/exe";
probe.attach_point = "END_trigger";
probe.type = probetype(p.type);
probe.prog_name = p.name();
probe.name = p.name();
special_probes_.push_back(probe);
return 0;
}
if (attach_point->provider == "BEGIN")
{
Probe probe;
probe.path = "/proc/self/exe";
probe.attach_point = "BEGIN_trigger";
probe.type = probetype(attach_point->provider);
probe.prog_name = p.name();
probe.name = p.name();
special_probes_.push_back(probe);
continue;
}
else if (attach_point->provider == "END")
{
Probe probe;
probe.path = "/proc/self/exe";
probe.attach_point = "END_trigger";
probe.type = probetype(attach_point->provider);
probe.prog_name = p.name();
probe.name = p.name();
special_probes_.push_back(probe);
continue;
}
ast::AttachPointList expanded_attach_points;
for (std::string attach_point : *p.attach_points)
{
if (attach_point.find("*") != std::string::npos)
std::vector<std::string> attach_funcs;
if (attach_point->func.find("*") != std::string::npos)
{
std::string file_name;
switch (probetype(p.type))
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 '"
<< p.type << "'" << std::endl;
<< attach_point->provider << "'" << std::endl;
return 1;
}
auto matches = find_wildcard_matches(attach_point, file_name);
expanded_attach_points.insert(expanded_attach_points.end(),
matches.begin(), matches.end());
continue;
auto matches = find_wildcard_matches(attach_point->target,
attach_point->func,
file_name);
attach_funcs.insert(attach_funcs.end(), matches.begin(), matches.end());
}
else
{
attach_funcs.push_back(attach_point->func);
}
expanded_attach_points.push_back(attach_point);
for (auto func : attach_funcs)
{
Probe probe;
probe.path = attach_point->target;
probe.attach_point = func;
probe.type = probetype(attach_point->provider);
probe.prog_name = p.name();
probe.name = attach_point->name(func);
probes_.push_back(probe);
}
}
for (std::string attach_point : expanded_attach_points)
{
Probe probe;
probe.path = p.path;
probe.attach_point = attach_point;
probe.type = probetype(p.type);
probe.prog_name = p.name();
probe.name = p.name(attach_point);
probes_.push_back(probe);
}
return 0;
}
std::set<std::string> BPFtrace::find_wildcard_matches(std::string attach_point, std::string file_name)
std::set<std::string> BPFtrace::find_wildcard_matches(const std::string &prefix, const std::string &func, const std::string &file_name)
{
// Turn glob into a regex
attach_point = "^" + std::regex_replace(attach_point, std::regex("\\*"), "[^\\s]*");
std::regex attach_point_regex(attach_point);
auto regex_str = "(" + std::regex_replace(func, std::regex("\\*"), "[^\\s]*") + ")";
if (prefix != "")
regex_str = prefix + ":" + regex_str;
regex_str = "^" + regex_str;
std::regex func_regex(regex_str);
std::smatch match;
std::ifstream file(file_name);
......@@ -90,9 +100,10 @@ std::set<std::string> BPFtrace::find_wildcard_matches(std::string attach_point,
std::set<std::string> matches;
while (std::getline(file, line))
{
if (std::regex_search(line, match, attach_point_regex))
if (std::regex_search(line, match, func_regex))
{
matches.insert(match[0]);
assert(match.size() == 2);
matches.insert(match[1]);
}
}
return matches;
......
......@@ -38,7 +38,7 @@ public:
std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>> &values_by_key);
protected:
virtual std::set<std::string> find_wildcard_matches(std::string attach_point, std::string file_name);
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_;
......
......@@ -370,6 +370,11 @@ void CodegenLLVM::visit(Predicate &pred)
b_.SetInsertPoint(pred_true_block);
}
void CodegenLLVM::visit(AttachPoint &)
{
// Empty
}
void CodegenLLVM::visit(Probe &probe)
{
FunctionType *func_type = FunctionType::get(
......
......@@ -39,6 +39,7 @@ public:
void visit(AssignMapStatement &assignment) override;
void visit(AssignVarStatement &assignment) override;
void visit(Predicate &pred) override;
void visit(AttachPoint &ap) override;
void visit(Probe &probe) override;
void visit(Program &program) override;
AllocaInst *getMapKey(Map &map);
......
......@@ -85,7 +85,8 @@ void yyerror(bpftrace::Driver &driver, const char *s);
%type <ast::Variable *> var
%type <ast::ExpressionList *> vargs
%type <ast::AttachPointList *> attach_points
%type <std::string> attach_point
%type <ast::AttachPoint *> attach_point
%type <std::string> wildcard
%right ASSIGN
%left LOR
......@@ -110,21 +111,23 @@ probes : probes probe { $$ = $1; $1->push_back($2); }
| probe { $$ = new ast::ProbeList; $$->push_back($1); }
;
probe : IDENT pred block { $$ = new ast::Probe($1, $2, $3); }
| IDENT ":" attach_points pred block { $$ = new ast::Probe($1, $3, $4, $5); }
| IDENT PATH attach_points pred block { $$ = new ast::Probe($1, $2.substr(1, $2.size()-2), $3, $4, $5); }
probe : attach_points pred block { $$ = new ast::Probe($1, $2, $3); }
;
attach_points : attach_points "," attach_point { $$ = $1; $1->push_back($3); }
| attach_point { $$ = new ast::AttachPointList; $$->push_back($1); }
;
attach_point : attach_point IDENT { $$ = $1 + $2; }
| attach_point MUL { $$ = $1 + "*"; }
| IDENT { $$ = $1; }
| MUL { $$ = "*"; }
attach_point : IDENT { $$ = new ast::AttachPoint($1); }
| IDENT ":" wildcard { $$ = new ast::AttachPoint($1, $3); }
| IDENT PATH wildcard { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3); }
;
wildcard : wildcard IDENT { $$ = $1 + $2; }
| wildcard MUL { $$ = $1 + "*"; }
| { $$ = ""; }
;
pred : DIV expr ENDPRED { $$ = new ast::Predicate($2); }
| { $$ = nullptr; }
;
......
......@@ -123,10 +123,19 @@ void Printer::visit(Predicate &pred)
--depth_;
}
void Printer::visit(AttachPoint &ap)
{
std::string indent(depth_, ' ');
out_ << indent << ap.name(ap.func) << std::endl;
}
void Printer::visit(Probe &probe)
{
std::string indent(depth_, ' ');
out_ << indent << probe.name() << std::endl;
for (AttachPoint *ap : *probe.attach_points) {
ap->accept(*this);
}
++depth_;
if (probe.pred) {
......
......@@ -22,6 +22,7 @@ public:
void visit(AssignMapStatement &assignment) override;
void visit(AssignVarStatement &assignment) override;
void visit(Predicate &pred) override;
void visit(AttachPoint &ap) override;
void visit(Probe &probe) override;
void visit(Program &program) override;
......
......@@ -48,16 +48,19 @@ void SemanticAnalyser::visit(Builtin &builtin)
builtin.type = SizedType(Type::string, STRING_SIZE);
}
else if (builtin.ident == "func") {
ProbeType type = probetype(probe_->type);
if (type == ProbeType::kprobe ||
type == ProbeType::kretprobe ||
type == ProbeType::tracepoint)
builtin.type = SizedType(Type::sym, 8);
else if (type == ProbeType::uprobe || type == ProbeType::uretprobe)
builtin.type = SizedType(Type::usym, 8);
else
err_ << "The func builtin can not be used with '" << probe_->type
<< "' probes" << std::endl;
for (auto &attach_point : *probe_->attach_points)
{
ProbeType type = probetype(attach_point->provider);
if (type == ProbeType::kprobe ||
type == ProbeType::kretprobe ||
type == ProbeType::tracepoint)
builtin.type = SizedType(Type::sym, 8);
else if (type == ProbeType::uprobe || type == ProbeType::uretprobe)
builtin.type = SizedType(Type::usym, 8);
else
err_ << "The func builtin can not be used with '" << attach_point->provider
<< "' probes" << std::endl;
}
}
else if (!builtin.ident.compare(0, 3, "arg") && builtin.ident.size() == 4 &&
builtin.ident.at(3) >= '0' && builtin.ident.at(3) <= '9') {
......@@ -318,42 +321,34 @@ void SemanticAnalyser::visit(Predicate &pred)
}
}
void SemanticAnalyser::visit(Probe &probe)
void SemanticAnalyser::visit(AttachPoint &ap)
{
// Clear out map of variable names - variables should be probe-local
variable_val_.clear();
probe_ = &probe;
if (probe.type == "kprobe" || probe.type == "kretprobe") {
if (probe.attach_points->size() == 0)
err_ << "kprobes must have an attachment point" << std::endl;
if (probe.path != "")
err_ << "kprobes should not have a path" << std::endl;
}
else if (probe.type == "uprobe" || probe.type == "uretprobe") {
if (probe.attach_points->size() == 0)
err_ << "uprobes must have an attachment point" << std::endl;
if (probe.path == "")
err_ << "uprobes must have a path" << std::endl;
}
else if (probe.type == "tracepoint") {
if (probe.attach_points->size() == 0)
err_ << "tracepoint probe must have an event" << std::endl;
if (probe.path == "")
err_ << "tracepoint probe must have a category" << std::endl;
}
else if (probe.type == "BEGIN" || probe.type == "END") {
if (probe.attach_points->size() != 0)
err_ << "BEGIN/END probes should not have an attachment point" << std::endl;
if (probe.path != "")
err_ << "BEGIN/END probes should not have a path" << std::endl;
if (ap.provider == "kprobe" || ap.provider == "kretprobe") {
if (ap.target != "")
err_ << "kprobes should not have a target" << std::endl;
if (ap.func == "")
err_ << "kprobes should be attached to a function" << std::endl;
}
else if (ap.provider == "uprobe" || ap.provider == "uretprobe") {
if (ap.target == "")
err_ << "uprobes should have a target" << std::endl;
if (ap.func == "")
err_ << "uprobes should be attached to a function" << std::endl;
}
else if (ap.provider == "tracepoint") {
if (ap.target == "" || ap.func == "")
err_ << "tracepoint probe must have a target" << std::endl;
}
else if (ap.provider == "BEGIN" || ap.provider == "END") {
if (ap.target != "" || ap.func != "")
err_ << "BEGIN/END probes should not have a target" << std::endl;
if (is_final_pass()) {
if (probe.type == "BEGIN") {
if (ap.provider == "BEGIN") {
if (has_begin_probe_)
err_ << "More than one BEGIN probe defined" << std::endl;
has_begin_probe_ = true;
}
if (probe.type == "END") {
if (ap.provider == "END") {
if (has_end_probe_)
err_ << "More than one END probe defined" << std::endl;
has_end_probe_ = true;
......@@ -361,9 +356,19 @@ void SemanticAnalyser::visit(Probe &probe)
}
}
else {
err_ << "Invalid probe type: '" << probe.type << "'" << std::endl;
err_ << "Invalid provider: '" << ap.provider << "'" << std::endl;
}
}
void SemanticAnalyser::visit(Probe &probe)
{
// Clear out map of variable names - variables should be probe-local
variable_val_.clear();
probe_ = &probe;
for (AttachPoint *ap : *probe.attach_points) {
ap->accept(*this);
}
if (probe.pred) {
probe.pred->accept(*this);
}
......
......@@ -29,6 +29,7 @@ public:
void visit(AssignMapStatement &assignment) override;
void visit(AssignVarStatement &assignment) override;
void visit(Predicate &pred) override;
void visit(AttachPoint &ap) override;
void visit(Probe &probe) override;
void visit(Program &program) override;
......
......@@ -5,48 +5,60 @@ namespace bpftrace {
namespace test {
namespace ast {
using bpftrace::ast::AttachPoint;
using bpftrace::ast::AttachPointList;
using bpftrace::ast::Probe;
TEST(ast, probe_name_special)
{
Probe begin("BEGIN", nullptr, nullptr);
AttachPoint ap1("BEGIN");
AttachPointList attach_points1 = { &ap1 };
Probe begin(&attach_points1, nullptr, nullptr);
EXPECT_EQ(begin.name(), "BEGIN");
Probe end("END", nullptr, nullptr);
AttachPoint ap2("END");
AttachPointList attach_points2 = { &ap2 };
Probe end(&attach_points2, nullptr, nullptr);
EXPECT_EQ(end.name(), "END");
}
TEST(ast, probe_name_kprobe)
{
AttachPointList attach_points1 = { "sys_read" };
Probe kprobe1("kprobe", &attach_points1, nullptr, nullptr);
AttachPoint ap1("kprobe", "sys_read");
AttachPointList attach_points1 = { &ap1 };
Probe kprobe1(&attach_points1, nullptr, nullptr);
EXPECT_EQ(kprobe1.name(), "kprobe:sys_read");
AttachPointList attach_points2 = { "sys_read", "sys_write" };
Probe kprobe2("kprobe", &attach_points2, nullptr, nullptr);
EXPECT_EQ(kprobe2.name(), "kprobe:sys_read,sys_write");
AttachPoint ap2("kprobe", "sys_write");
AttachPointList attach_points2 = { &ap1, &ap2 };
Probe kprobe2(&attach_points2, nullptr, nullptr);
EXPECT_EQ(kprobe2.name(), "kprobe:sys_read,kprobe:sys_write");
}
TEST(ast, probe_name_uprobe)
{
AttachPointList attach_points1 = { "readline" };
Probe uprobe1("uprobe", "/bin/sh", &attach_points1, nullptr, nullptr);
AttachPoint ap1("uprobe", "/bin/sh", "readline");
AttachPointList attach_points1 = { &ap1 };
Probe uprobe1(&attach_points1, nullptr, nullptr);
EXPECT_EQ(uprobe1.name(), "uprobe:/bin/sh:readline");
AttachPointList attach_points2 = { "readline", "somefunc" };
Probe uprobe2("uprobe", "/bin/sh", &attach_points2, nullptr, nullptr);
EXPECT_EQ(uprobe2.name(), "uprobe:/bin/sh:readline,somefunc");
AttachPoint ap2("uprobe", "/bin/sh", "somefunc");
AttachPointList attach_points2 = { &ap1, &ap2 };
Probe uprobe2(&attach_points2, nullptr, nullptr);
EXPECT_EQ(uprobe2.name(), "uprobe:/bin/sh:readline,uprobe:/bin/sh:somefunc");
}
TEST(ast, probe_name_singular)
TEST(ast, attach_point_name)
{
AttachPointList attach_points = { "sys_read", "sys_thisone", "readline" };
Probe kprobe("kprobe", &attach_points, nullptr, nullptr);
EXPECT_EQ(kprobe.name("sys_thisone"), "kprobe:sys_thisone");
AttachPoint ap1("kprobe", "sys_read");
AttachPoint ap2("kprobe", "sys_thisone");
AttachPoint ap3("uprobe", "/bin/sh", "readline");
AttachPointList attach_points = { &ap1, &ap2, &ap3 };
Probe kprobe(&attach_points, nullptr, nullptr);
EXPECT_EQ(ap2.name("sys_thisone"), "kprobe:sys_thisone");
Probe uprobe("uprobe", "/bin/sh", &attach_points, nullptr, nullptr);
EXPECT_EQ(uprobe.name("readline"), "uprobe:/bin/sh:readline");
Probe uprobe(&attach_points, nullptr, nullptr);
EXPECT_EQ(ap3.name("readline"), "uprobe:/bin/sh:readline");
}
} // namespace ast
......
This diff is collapsed.
......@@ -222,24 +222,11 @@ TEST(Parser, begin_probe)
TEST(Parser, multiple_attach_points_kprobe)
{
test("kprobe:sys_open,sys_close { 1 }",
"Program\n"
" kprobe:sys_open,sys_close\n"
" int: 1\n");
}
TEST(Parser, multiple_attach_points_uprobe)
{
test("uprobe:/bin/sh:foo,bar { 1 }",
"Program\n"
" uprobe:/bin/sh:foo,bar\n"
" int: 1\n");
}
TEST(Parser, multiple_attach_points_tracepoint)
{
test("tracepoint:syscalls:sys_enter_* { 1 }",
test("BEGIN,kprobe:sys_open,uprobe:/bin/sh:foo,tracepoint:syscalls:sys_enter_* { 1 }",
"Program\n"
" BEGIN\n"
" kprobe:sys_open\n"
" uprobe:/bin/sh:foo\n"
" tracepoint:syscalls:sys_enter_*\n"
" int: 1\n");
}
......
......@@ -23,7 +23,7 @@ void test(BPFtrace &bpftrace, Driver &driver, const std::string &input, int expe
ast::SemanticAnalyser semantics(driver.root_, bpftrace, out);
std::stringstream msg;
msg << "\nInput:\n" << input << "\n\nOutput:\n";
EXPECT_EQ(semantics.analyse(), expected_result) << msg.str() + out.str();
EXPECT_EQ(expected_result, semantics.analyse()) << msg.str() + out.str();
}
void test(BPFtrace &bpftrace, const std::string &input, int expected_result=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