diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb73233e397..fd5c6bef0c91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to - [#3060](https://github.com/bpftrace/bpftrace/pull/3060) - Disable func builtin for kretprobes and uretprobes when `get_func_ip` feature is not available - [#2645](https://github.com/bpftrace/bpftrace/pull/2645) +- Move probe expansion into codegen + - [#3155](https://github.com/bpftrace/bpftrace/pull/3155) #### Deprecated #### Removed #### Fixed diff --git a/src/ast/ast.h b/src/ast/ast.h index 2c726b1acfa1..ccdb86e7bea0 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -627,7 +627,15 @@ class AttachPoint : public Node { uint64_t len = 0; // for watchpoint probes, the width of watched addr std::string mode; // for watchpoint probes, the watch mode bool async = false; // for watchpoint probes, if it's an async watchpoint - bool need_expansion = false; + + // There are 2 kinds of attach point expansion: + // - full expansion - separate LLVM function is generated for each match + // - multi expansion - one LLVM function and BPF program is generated for all + // matches, the list of expanded functions is attached to + // the BPF program using the k(u)probe.multi mechanism + bool need_full_expansion = false; + bool need_multi_expansion = false; + uint64_t address = 0; uint64_t func_offset = 0; bool ignore_invalid = false; @@ -639,6 +647,11 @@ class AttachPoint : public Node { int index() const; void set_index(int index); + bool need_expansion() + { + return need_full_expansion || need_multi_expansion; + } + private: AttachPoint(const AttachPoint &other) = default; diff --git a/src/ast/attachpoint_parser.cpp b/src/ast/attachpoint_parser.cpp index 005365208a6b..323a3f1b82cc 100644 --- a/src/ast/attachpoint_parser.cpp +++ b/src/ast/attachpoint_parser.cpp @@ -340,9 +340,18 @@ AttachPointParser::State AttachPointParser::kprobe_parser(bool allow_offset) ap_->func = parts_[func_idx]; } - if (ap_->func.find('*') != std::string::npos || - ap_->target.find('*') != std::string::npos) - ap_->need_expansion = true; + // kprobe_multi does not support the "module:function" syntax so in case of + // a wildcarded module, always use full expansion + if (has_wildcard(ap_->target)) + ap_->need_full_expansion = true; + + if (has_wildcard(ap_->func)) { + if (bpftrace_.feature_->has_kprobe_multi()) { + ap_->need_multi_expansion = true; + } else { + ap_->need_full_expansion = true; + } + } return OK; } @@ -440,9 +449,17 @@ AttachPointParser::State AttachPointParser::uprobe_parser(bool allow_offset, ap_->func = func; } - if (ap_->target.find('*') != std::string::npos || - ap_->func.find('*') != std::string::npos) - ap_->need_expansion = true; + // As the C++ language supports function overload, a given function name + // (without parameters) could have multiple matches even when no + // wildcards are used. + if (has_wildcard(ap_->func) || has_wildcard(ap_->target) || + ap_->lang == "cpp") { + if (bpftrace_.feature_->has_uprobe_multi()) { + ap_->need_multi_expansion = true; + } else { + ap_->need_full_expansion = true; + } + } return OK; } @@ -477,10 +494,10 @@ AttachPointParser::State AttachPointParser::usdt_parser() ap_->func = parts_[3]; } - if (ap_->target.find('*') != std::string::npos || - ap_->ns.find('*') != std::string::npos || ap_->ns.empty() || - ap_->func.find('*') != std::string::npos || bpftrace_.pid()) - ap_->need_expansion = true; + // Always fully expand USDT probes as they may access args + if (has_wildcard(ap_->target) || has_wildcard(ap_->ns) || ap_->ns.empty() || + has_wildcard(ap_->func) || bpftrace_.pid()) + ap_->need_full_expansion = true; return OK; } @@ -505,7 +522,7 @@ AttachPointParser::State AttachPointParser::tracepoint_parser() if (ap_->target.find('*') != std::string::npos || ap_->func.find('*') != std::string::npos) - ap_->need_expansion = true; + ap_->need_full_expansion = true; return OK; } @@ -627,7 +644,7 @@ AttachPointParser::State AttachPointParser::watchpoint_parser(bool async) ap_->func = func_arg_parts[0]; if (ap_->func.find('*') != std::string::npos) - ap_->need_expansion = true; + ap_->need_full_expansion = true; if (func_arg_parts[1].size() <= 3 || func_arg_parts[1].find("arg") != 0) { errs_ << "Invalid function argument" << std::endl; @@ -696,7 +713,7 @@ AttachPointParser::State AttachPointParser::kfunc_parser() if (ap_->func.find('*') != std::string::npos || ap_->target.find('*') != std::string::npos) - ap_->need_expansion = true; + ap_->need_full_expansion = true; return OK; } @@ -714,7 +731,7 @@ AttachPointParser::State AttachPointParser::iter_parser() if (parts_[1].find('*') != std::string::npos) { if (listing_) { - ap_->need_expansion = true; + ap_->need_full_expansion = true; } else { if (ap_->ignore_invalid) return SKIP; @@ -744,7 +761,7 @@ AttachPointParser::State AttachPointParser::raw_tracepoint_parser() ap_->func = parts_[1]; if (has_wildcard(ap_->func)) - ap_->need_expansion = true; + ap_->need_full_expansion = true; return OK; } diff --git a/src/ast/passes/codegen_llvm.cpp b/src/ast/passes/codegen_llvm.cpp index 0025cffdd571..acb38bc071a0 100644 --- a/src/ast/passes/codegen_llvm.cpp +++ b/src/ast/passes/codegen_llvm.cpp @@ -2550,6 +2550,51 @@ void CodegenLLVM::generateProbe(Probe &probe, func_type, name, current_attach_point_->address, index); } +void CodegenLLVM::add_probe(AttachPoint &ap, + Probe &probe, + const std::string &name, + FunctionType *func_type) +{ + current_attach_point_ = ≈ + probefull_ = ap.name(); + if (probetype(ap.provider) == ProbeType::usdt) { + auto usdt = USDTHelper::find(bpftrace_.pid(), ap.target, ap.ns, ap.func); + if (!usdt.has_value()) { + // Unfortunately, it is not easy to mock USDTHelper in tests as it's fully + // static. Since we only need to override usdt.num_locations, pass the + // value via env variable for testing purposes. + auto test_usdt = std::getenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS"); + if (test_usdt) + ap.usdt.num_locations = std::stoi(test_usdt); + else + LOG(FATAL) << "Failed to find usdt probe: " << probefull_; + } else + ap.usdt = *usdt; + + // A "unique" USDT probe can be present in a binary in multiple + // locations. One case where this happens is if a function + // containing a USDT probe is inlined into a caller. So we must + // generate a new program for each instance. We _must_ regenerate + // because argument locations may differ between instance locations + // (eg arg0. may not be found in the same offset from the same + // register in each location) + auto reset_ids = create_reset_ids(); + current_usdt_location_index_ = 0; + for (int i = 0; i < ap.usdt.num_locations; ++i) { + reset_ids(); + + std::string full_func_id = name + "_loc" + std::to_string(i); + generateProbe(probe, full_func_id, probefull_, func_type, i); + bpftrace_.add_probe(ap, probe, i); + current_usdt_location_index_++; + } + } else { + generateProbe(probe, name, probefull_, func_type); + bpftrace_.add_probe(ap, probe); + } + current_attach_point_ = nullptr; +} + void CodegenLLVM::visit(Subprog &subprog) { std::vector arg_types; @@ -2642,54 +2687,19 @@ void CodegenLLVM::createRet(Value *value) void CodegenLLVM::visit(Probe &probe) { FunctionType *func_type = FunctionType::get(b_.getInt64Ty(), - { b_.GET_PTR_TY() }, // struct - // pt_regs - // *ctx + { b_.GET_PTR_TY() }, // ctx false); - // Probe has at least one attach point (required by the parser) - auto &attach_point = (*probe.attach_points)[0]; - - // All usdt probes need expansion to be able to read arguments - if (probetype(attach_point->provider) == ProbeType::usdt) - probe.need_expansion = true; - - bool generated = false; - current_attach_point_ = attach_point; - inside_subprog_ = false; - - /* - * 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 "probe" builtin, where we need - * to build different BPF programs for each wildcard match that contains 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 - probefull_ = probe.name(); - if (probe.index() == 0) - probe.set_index(getNextIndexForProbe()); - generateProbe(probe, probefull_, probefull_, func_type); - generated = true; - } else { - /* - * Build a separate BPF program for each wildcard match. - * We begin by saving state that gets changed by the codegen pass, so we - * can restore it for the next pass (printf_id_, time_id_). - */ - auto reset_ids = create_reset_ids(); - - for (auto attach_point : *probe.attach_points) { - current_attach_point_ = attach_point; - - std::set matches; - if (attach_point->provider == "BEGIN" || - attach_point->provider == "END") { - matches.insert(attach_point->provider); - } else { - matches = bpftrace_.probe_matcher_->get_matches_for_ap(*attach_point); - } + // We begin by saving state that gets changed by the codegen pass, so we + // can restore it for the next pass (printf_id_, time_id_). + auto reset_ids = create_reset_ids(); + for (auto *attach_point : *probe.attach_points) { + reset_ids(); + current_attach_point_ = attach_point; + if (probe.need_expansion || attach_point->need_full_expansion) { + // Do expansion - generate a separate LLVM function for each match + auto matches = bpftrace_.probe_matcher_->get_matches_for_ap( + *attach_point); probe_count_ += matches.size(); uint64_t max_bpf_progs = bpftrace_.config_.get( @@ -2704,54 +2714,24 @@ void CodegenLLVM::visit(Probe &probe) "environment variable."; } - tracepoint_struct_ = ""; - for (const auto &m : matches) { + for (auto &match : matches) { reset_ids(); - std::string match = m; - generated = true; - if (attach_point->index() == 0) attach_point->set_index(getNextIndexForProbe()); - AttachPoint match_ap = attach_point->create_expansion_copy(match); - probefull_ = match_ap.name(); - current_attach_point_ = &match_ap; - - if (probetype(attach_point->provider) == ProbeType::usdt) { - // Set the probe identifier so that we can read arguments later - auto usdt = USDTHelper::find( - bpftrace_.pid(), match_ap.target, match_ap.ns, match_ap.func); - if (!usdt.has_value()) - LOG(BUG) << "Failed to find usdt probe: " << probefull_; - match_ap.usdt = *usdt; - - // A "unique" USDT probe can be present in a binary in multiple - // locations. One case where this happens is if a function containing - // a USDT probe is inlined into a caller. So we must generate a new - // program for each instance. We _must_ regenerate because argument - // locations may differ between instance locations (eg arg0. may not - // be found in the same offset from the same register in each - // location) - current_usdt_location_index_ = 0; - for (int i = 0; i < match_ap.usdt.num_locations; ++i) { - reset_ids(); - - std::string full_func_id = match + "_loc" + std::to_string(i); - generateProbe(probe, full_func_id, probefull_, func_type, i); - current_usdt_location_index_++; - } - } else { - generateProbe(probe, match, probefull_, func_type); - } + auto match_ap = attach_point->create_expansion_copy(match); + add_probe(match_ap, probe, match, func_type); } + if (matches.empty()) { + generateProbe(probe, "dummy", "dummy", func_type, std::nullopt, true); + } + } else { + if (probe.index() == 0) + probe.set_index(getNextIndexForProbe()); + add_probe(*attach_point, probe, attach_point->name(), func_type); } - - if (!generated) - generateProbe(probe, "dummy", "dummy", func_type, std::nullopt, true); } - if (generated) - bpftrace_.add_probe(probe); current_attach_point_ = nullptr; } diff --git a/src/ast/passes/codegen_llvm.h b/src/ast/passes/codegen_llvm.h index ba15c3c60d24..553c478f711a 100644 --- a/src/ast/passes/codegen_llvm.h +++ b/src/ast/passes/codegen_llvm.h @@ -152,6 +152,12 @@ class CodegenLLVM : public Visitor { std::optional usdt_location_index = std::nullopt, bool dummy = false); + // Generate a probe and register it to the BPFtrace class. + void add_probe(AttachPoint &ap, + Probe &probe, + const std::string &name, + FunctionType *func_type); + [[nodiscard]] ScopedExprDeleter accept(Node *node); [[nodiscard]] std::tuple getMapKey(Map &map); AllocaInst *getMultiMapKey(Map &map, const std::vector &extra_keys); diff --git a/src/ast/passes/field_analyser.cpp b/src/ast/passes/field_analyser.cpp index 3e5364da77a1..4c209d079547 100644 --- a/src/ast/passes/field_analyser.cpp +++ b/src/ast/passes/field_analyser.cpp @@ -165,7 +165,7 @@ void FieldAnalyser::resolve_args(Probe &probe) probe_type != ProbeType::uprobe) continue; - if (ap->need_expansion) { + if (ap->need_expansion()) { std::set matches; // Find all the matches for the wildcard.. diff --git a/src/ast/passes/semantic_analyser.cpp b/src/ast/passes/semantic_analyser.cpp index 60d8b743e60b..54fa8f7e96d1 100644 --- a/src/ast/passes/semantic_analyser.cpp +++ b/src/ast/passes/semantic_analyser.cpp @@ -424,7 +424,7 @@ void SemanticAnalyser::visit(Builtin &builtin) ProbeType type = probetype(attach_point->provider); if (type == ProbeType::tracepoint) { - probe->need_expansion = true; + attach_point->need_full_expansion = true; builtin_args_tracepoint(attach_point, builtin); } } diff --git a/src/bpftrace.cpp b/src/bpftrace.cpp index ca43bb4d76de..d118a1451400 100644 --- a/src/bpftrace.cpp +++ b/src/bpftrace.cpp @@ -78,7 +78,9 @@ Probe BPFtrace::generateWatchpointSetupProbe(const ast::AttachPoint &ap, return setup_probe; } -Probe BPFtrace::generate_probe(const ast::AttachPoint &ap, const ast::Probe &p) +Probe BPFtrace::generate_probe(const ast::AttachPoint &ap, + const ast::Probe &p, + int usdt_location_idx) { Probe probe; probe.path = ap.target; @@ -93,6 +95,7 @@ Probe BPFtrace::generate_probe(const ast::AttachPoint &ap, const ast::Probe &p) probe.address = ap.address; probe.func_offset = ap.func_offset; probe.loc = 0; + probe.usdt_location_idx = usdt_location_idx; probe.index = ap.index() ?: p.index(); probe.len = ap.len; probe.mode = ap.mode; @@ -101,211 +104,93 @@ Probe BPFtrace::generate_probe(const ast::AttachPoint &ap, const ast::Probe &p) return probe; } -int BPFtrace::add_probe(ast::Probe &p) -{ - for (auto attach_point : *p.attach_points) { - if (attach_point->provider == "BEGIN" || attach_point->provider == "END") { - auto probe = generate_probe(*attach_point, p); - probe.path = "/proc/self/exe"; - probe.attach_point = attach_point->provider + "_trigger"; - probe.name = p.name(); - resources.special_probes.push_back(probe); - continue; - } - - std::vector attach_funcs; - // An underspecified usdt probe is a probe that has no wildcards and - // either an empty namespace or a specified PID. - // We try to find a unique match for such a probe. - bool underspecified_usdt_probe = probetype(attach_point->provider) == - ProbeType::usdt && - !has_wildcard(attach_point->target) && - !has_wildcard(attach_point->ns) && - !has_wildcard(attach_point->func) && - (attach_point->ns.empty() || pid() > 0); - if (attach_point->need_expansion && - (has_wildcard(attach_point->func) || - has_wildcard(attach_point->target) || has_wildcard(attach_point->ns) || - underspecified_usdt_probe)) { - std::set matches; - try { - matches = probe_matcher_->get_matches_for_ap(*attach_point); - } catch (const WildcardException &e) { - LOG(ERROR) << e.what(); - return 1; - } - - if (underspecified_usdt_probe && matches.size() > 1) { - LOG(ERROR) << "namespace for " << attach_point->name() - << " not specified, matched " << matches.size() - << " probes. Please specify a unique namespace or use '*' " - "to attach " - << "to all matched probes"; - return 1; - } - - attach_funcs.insert(attach_funcs.end(), matches.begin(), matches.end()); - - if (feature_->has_kprobe_multi() && has_wildcard(attach_point->func) && - !p.need_expansion && attach_funcs.size() && - (probetype(attach_point->provider) == ProbeType::kprobe || - probetype(attach_point->provider) == ProbeType::kretprobe) && - attach_point->target.empty()) { - auto probe = generate_probe(*attach_point, p); - probe.funcs = attach_funcs; - resources.probes.push_back(probe); - continue; - } - - if ((probetype(attach_point->provider) == ProbeType::uprobe || - probetype(attach_point->provider) == ProbeType::uretprobe) && - feature_->has_uprobe_multi() && has_wildcard(attach_point->func) && - !p.need_expansion && attach_funcs.size()) { - if (!has_wildcard(attach_point->target)) { - auto probe = generate_probe(*attach_point, p); - probe.funcs = attach_funcs; - resources.probes.push_back(probe); - continue; - } else { - // If we have a wildcard in the target path, we need to generate one - // probe per expanded target. - std::unordered_map target_map; - for (const auto &func : attach_funcs) { - ast::AttachPoint ap = attach_point->create_expansion_copy(func); - // Use the original (possibly wildcarded) function name - ap.func = attach_point->func; - auto found = target_map.find(ap.target); - if (found != target_map.end()) { - found->second.funcs.push_back(func); - } else { - auto probe = generate_probe(ap, p); - probe.funcs.push_back(func); - target_map.insert({ { ap.target, probe } }); - } - } - for (auto &pair : target_map) { - resources.probes.push_back(std::move(pair.second)); - } - continue; - } - } - } else if ((probetype(attach_point->provider) == ProbeType::uprobe || - probetype(attach_point->provider) == ProbeType::uretprobe || - probetype(attach_point->provider) == ProbeType::watchpoint || - probetype(attach_point->provider) == - ProbeType::asyncwatchpoint) && - !attach_point->func.empty()) { - std::set matches; - - struct symbol sym = {}; - int err = resolve_uname(attach_point->func, &sym, attach_point->target); - - if (attach_point->lang == "cpp") { - // As the C++ language supports function overload, a given function name - // (without parameters) could have multiple matches even when no - // wildcards are used. - matches = probe_matcher_->get_matches_for_ap(*attach_point); +int BPFtrace::add_probe(const ast::AttachPoint &ap, + const ast::Probe &p, + int usdt_location_idx) +{ + auto type = probetype(ap.provider); + auto probe = generate_probe(ap, p, usdt_location_idx); + + // Add the new probe(s) to resources + if (ap.provider == "BEGIN" || ap.provider == "END") { + // special probes + probe.path = "/proc/self/exe"; + probe.attach_point = ap.provider + "_trigger"; + resources.special_probes.push_back(std::move(probe)); + } else if ((type == ProbeType::watchpoint || + type == ProbeType::asyncwatchpoint) && + ap.func.size()) { + // (async)watchpoint - generate also the setup probe + resources.probes.emplace_back(generateWatchpointSetupProbe(ap, p)); + resources.watchpoint_probes.emplace_back(std::move(probe)); + } else if (ap.need_multi_expansion) { + // k(u)probe_multi - do expansion and set probe.funcs + auto matches = probe_matcher_->get_matches_for_ap(ap); + if (!has_wildcard(ap.target)) { + if (!matches.empty()) { + std::vector funcs(matches.begin(), matches.end()); + probe.funcs = funcs; + resources.probes.push_back(std::move(probe)); } - - if (err >= 0 && sym.address != 0) - matches.insert(attach_point->target + ":" + attach_point->func); - - attach_funcs.insert(attach_funcs.end(), matches.begin(), matches.end()); } else { - if (probetype(attach_point->provider) == ProbeType::usdt && - !attach_point->ns.empty()) - attach_funcs.push_back(attach_point->target + ":" + attach_point->ns + - ":" + attach_point->func); - else if (probetype(attach_point->provider) == ProbeType::tracepoint || - probetype(attach_point->provider) == ProbeType::uprobe || - probetype(attach_point->provider) == ProbeType::uretprobe || - probetype(attach_point->provider) == ProbeType::kfunc || - probetype(attach_point->provider) == ProbeType::kretfunc) - attach_funcs.push_back(attach_point->target + ":" + attach_point->func); - else if ((probetype(attach_point->provider) == ProbeType::kprobe || - probetype(attach_point->provider) == ProbeType::kretprobe) && - !attach_point->target.empty()) { - attach_funcs.push_back(attach_point->target + ":" + attach_point->func); - } else { - attach_funcs.push_back(attach_point->func); - } - } - - // You may notice that the below loop is somewhat duplicated in - // codegen_llvm.cpp. The reason is because codegen tries to avoid - // generating duplicate programs if it can be avoided. For example, a - // program `kprobe:do_* { print("hi") }` can be generated once and reused - // for multiple attachpoints. Thus, we need this loop here to attach the - // single program to multiple attach points. - // - // There may be a way to refactor and unify the codepaths in a clean manner - // but so far it has eluded your author. - for (const auto &f : attach_funcs) { - ast::AttachPoint match_ap = attach_point->create_expansion_copy(f); - - if (probetype(attach_point->provider) == ProbeType::usdt) { - // Necessary for correct number of locations if wildcard expands to - // multiple probes. - std::optional usdt; - if ((p.need_expansion || match_ap.need_expansion) && - (usdt = USDTHelper::find( - this->pid(), match_ap.target, match_ap.ns, match_ap.func))) { - match_ap.usdt = *usdt; + // If we have a wildcard in the target path, we need to generate one + // probe per expanded target. + assert(type == ProbeType::uprobe || type == ProbeType::uretprobe); + std::unordered_map target_map; + for (const auto &func : matches) { + ast::AttachPoint match_ap = ap.create_expansion_copy(func); + // Use the original (possibly wildcarded) function name + match_ap.func = ap.func; + auto found = target_map.find(match_ap.target); + if (found != target_map.end()) { + found->second.funcs.push_back(func); + } else { + auto probe = generate_probe(match_ap, p); + probe.funcs.push_back(func); + target_map.insert({ { match_ap.target, probe } }); } - } else if (probetype(attach_point->provider) == ProbeType::iter) { - has_iter_ = true; } - - auto probe = generate_probe(match_ap, p); - - if (probetype(attach_point->provider) == ProbeType::usdt) { - // We must attach to all locations of a USDT marker if duplicates exist - // in a target binary. See comment in codegen_llvm.cpp probe generation - // code for more details. - for (int i = 0; i < match_ap.usdt.num_locations; ++i) { - Probe probe_copy = probe; - probe_copy.usdt_location_idx = i; - probe_copy.index = attach_point->index() > 0 ? attach_point->index() - : p.index(); - - resources.probes.emplace_back(std::move(probe_copy)); - } - } else if ((probetype(attach_point->provider) == ProbeType::watchpoint || - probetype(attach_point->provider) == - ProbeType::asyncwatchpoint) && - attach_point->func.size()) { - resources.probes.emplace_back( - generateWatchpointSetupProbe(match_ap, p)); - - resources.watchpoint_probes.emplace_back(std::move(probe)); - } else { - resources.probes.push_back(probe); + for (auto &pair : target_map) { + resources.probes.push_back(std::move(pair.second)); } } - if (resources.probes_using_usym.find(&p) != - resources.probes_using_usym.end() && - bcc_elf_is_exe(attach_point->target.c_str())) { - auto user_symbol_cache_type = config_.get( - ConfigKeyUserSymbolCacheType::default_); - // preload symbol table for executable to make it available even if the - // binary is not present at symbol resolution time - // note: this only makes sense with ASLR disabled, since with ASLR offsets - // might be different - if (user_symbol_cache_type == UserSymbolCacheType::per_program && - symbol_table_cache_.find(attach_point->target) == - symbol_table_cache_.end()) - symbol_table_cache_[attach_point->target] = get_symbol_table_for_elf( - attach_point->target); - - if (user_symbol_cache_type == UserSymbolCacheType::per_pid) - // preload symbol tables from running processes - // this allows symbol resolution for processes that are running at probe - // attach time, but not at symbol resolution time, even with ASLR - // enabled, since BCC symcache records the offsets - for (int pid : get_pids_for_program(attach_point->target)) - pid_sym_[pid] = bcc_symcache_new(pid, &get_symbol_opts()); + std::vector funcs; + for (auto &match : matches) { + std::string func = match; + if (func.find(":") != std::string::npos) + erase_prefix(func); + funcs.push_back(func); } + probe.funcs = funcs; + } else { + resources.probes.emplace_back(std::move(probe)); + } + + if (type == ProbeType::iter) + has_iter_ = true; + + // Preload symbol tables if necessary + if (resources.probes_using_usym.find(&p) != + resources.probes_using_usym.end() && + bcc_elf_is_exe(ap.target.c_str())) { + auto user_symbol_cache_type = config_.get( + ConfigKeyUserSymbolCacheType::default_); + // preload symbol table for executable to make it available even if the + // binary is not present at symbol resolution time + // note: this only makes sense with ASLR disabled, since with ASLR offsets + // might be different + if (user_symbol_cache_type == UserSymbolCacheType::per_program && + symbol_table_cache_.find(ap.target) == symbol_table_cache_.end()) + symbol_table_cache_[ap.target] = get_symbol_table_for_elf(ap.target); + + if (user_symbol_cache_type == UserSymbolCacheType::per_pid) + // preload symbol tables from running processes + // this allows symbol resolution for processes that are running at probe + // attach time, but not at symbol resolution time, even with ASLR + // enabled, since BCC symcache records the offsets + for (int pid : get_pids_for_program(ap.target)) + pid_sym_[pid] = bcc_symcache_new(pid, &get_symbol_opts()); } return 0; diff --git a/src/bpftrace.h b/src/bpftrace.h index b17b534e273a..2efa734916fb 100644 --- a/src/bpftrace.h +++ b/src/bpftrace.h @@ -103,7 +103,9 @@ class BPFtrace { { } virtual ~BPFtrace(); - virtual int add_probe(ast::Probe &p); + virtual int add_probe(const ast::AttachPoint &ap, + const ast::Probe &p, + int usdt_location_idx = 0); Probe generateWatchpointSetupProbe(const ast::AttachPoint &ap, const ast::Probe &probe); int num_probes() const; @@ -264,7 +266,9 @@ class BPFtrace { static uint64_t read_address_from_output(std::string output); std::optional> find_empty_key(const BpfMap &map) const; struct bcc_symbol_option &get_symbol_opts(); - Probe generate_probe(const ast::AttachPoint &ap, const ast::Probe &p); + Probe generate_probe(const ast::AttachPoint &ap, + const ast::Probe &p, + int usdt_location_idx = 0); bool has_iter_ = false; int epollfd_ = -1; struct ring_buffer *ringbuf_ = nullptr; diff --git a/src/driver.cpp b/src/driver.cpp index 17a2086cf7ac..dca3f1c6a5ab 100644 --- a/src/driver.cpp +++ b/src/driver.cpp @@ -88,7 +88,7 @@ std::set Driver::list_modules() const ((probe_type == ProbeType::kprobe || probe_type == ProbeType::kretprobe) && !ap->target.empty())) { - if (ap->need_expansion) { + if (ap->need_expansion()) { for (auto &match : bpftrace_.probe_matcher_->get_matches_for_ap(*ap)) { std::string func = match; diff --git a/src/required_resources.h b/src/required_resources.h index 27f8d3075669..c03f994409c6 100644 --- a/src/required_resources.h +++ b/src/required_resources.h @@ -127,7 +127,7 @@ class RequiredResources { std::vector watchpoint_probes; // List of probes using userspace symbol resolution - std::unordered_set probes_using_usym; + std::unordered_set probes_using_usym; private: friend class cereal::access; diff --git a/tests/bpftrace.cpp b/tests/bpftrace.cpp index 55d2c1203fed..e3d562a74721 100644 --- a/tests/bpftrace.cpp +++ b/tests/bpftrace.cpp @@ -1,8 +1,13 @@ #include #include "bpftrace.h" +#include "clang_parser.h" #include "driver.h" #include "mocks.h" +#include "passes/codegen_llvm.h" +#include "passes/field_analyser.h" +#include "passes/resource_analyser.h" +#include "passes/semantic_analyser.h" #include "tracefs.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -29,39 +34,23 @@ static const std::string kprobe_name(const std::string &attach_point, return "kprobe:" + attach_point + str; } -static auto make_probe(std::vector elems) +static auto parse_probe(const std::string &str, BPFtrace &bpftrace) { - auto apl = new ast::AttachPointList(elems); - return new ast::Probe(apl, nullptr, nullptr); -} + Driver driver(bpftrace); + ASSERT_EQ(driver.parse_str(str), 0); -static auto make_usdt_probe(const std::string &target, - const std::string &ns, - const std::string &func, - bool need_expansion = false, - int locations = 0) -{ - auto a = new ast::AttachPoint(""); - a->provider = "usdt"; - a->target = target; - a->ns = ns; - a->func = func; - a->need_expansion = need_expansion; - a->usdt.num_locations = locations; - return make_probe({ a }); -} + ast::FieldAnalyser fields(driver.root.get(), bpftrace); + ASSERT_EQ(fields.analyse(), 0); -static auto parse_probe(const std::string &str) -{ - StrictMock b; - Driver d(b); + ClangParser clang; + clang.parse(driver.root.get(), bpftrace); - if (d.parse_str(str)) { - throw std::runtime_error("Parser failed"); - } - auto probe = d.root->probes->front(); - d.root->probes->clear(); - return probe; + ast::SemanticAnalyser semantics(driver.root.get(), bpftrace); + ASSERT_EQ(semantics.analyse(), 0); + + std::stringstream out; + ast::CodegenLLVM codegen(driver.root.get(), bpftrace); + codegen.generate_ir(); } void check_kprobe(Probe &p, @@ -213,10 +202,9 @@ void check_special_probe(Probe &p, TEST(bpftrace, add_begin_probe) { - ast::Probe *probe = parse_probe("BEGIN{}"); - StrictMock bpftrace; - ASSERT_EQ(0, bpftrace.add_probe(*probe)); + parse_probe("BEGIN{}", bpftrace); + ASSERT_EQ(0U, bpftrace.get_probes().size()); ASSERT_EQ(1U, bpftrace.get_special_probes().size()); @@ -227,10 +215,9 @@ TEST(bpftrace, add_begin_probe) TEST(bpftrace, add_end_probe) { - ast::Probe *probe = parse_probe("END{}"); - StrictMock bpftrace; - ASSERT_EQ(0, bpftrace.add_probe(*probe)); + parse_probe("END{}", bpftrace); + ASSERT_EQ(0U, bpftrace.get_probes().size()); ASSERT_EQ(1U, bpftrace.get_special_probes().size()); @@ -241,9 +228,8 @@ TEST(bpftrace, add_end_probe) TEST(bpftrace, add_probes_single) { - ast::Probe *probe = parse_probe("kprobe:sys_read {}"); StrictMock bpftrace; - ASSERT_EQ(0, bpftrace.add_probe(*probe)); + parse_probe("kprobe:sys_read {}", bpftrace); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -252,9 +238,8 @@ TEST(bpftrace, add_probes_single) TEST(bpftrace, add_probes_multiple) { - ast::Probe *probe = parse_probe("kprobe:sys_read,kprobe:sys_write{}"); StrictMock bpftrace; - ASSERT_EQ(0, bpftrace.add_probe(*probe)); + parse_probe("kprobe:sys_read,kprobe:sys_write{}", bpftrace); ASSERT_EQ(2U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -265,17 +250,14 @@ TEST(bpftrace, add_probes_multiple) TEST(bpftrace, add_probes_wildcard) { - ast::Probe *probe = parse_probe( - "kprobe:sys_read,kprobe:my_*,kprobe:sys_write{}"); - auto bpftrace = get_strict_mock_bpftrace(); bpftrace->feature_ = std::make_unique(false); - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_traceable_funcs(false)) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("kprobe:sys_read,kprobe:my_*,kprobe:sys_write{}", *bpftrace); + ASSERT_EQ(4U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -288,17 +270,14 @@ TEST(bpftrace, add_probes_wildcard) TEST(bpftrace, add_probes_wildcard_kprobe_multi) { - ast::Probe *probe = parse_probe( - "kprobe:sys_read,kprobe:my_*,kprobe:sys_write{}"); - auto bpftrace = get_strict_mock_bpftrace(); bpftrace->feature_ = std::make_unique(true); - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_traceable_funcs(false)) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("kprobe:sys_read,kprobe:my_*,kprobe:sys_write{}", *bpftrace); + ASSERT_EQ(3U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -313,15 +292,34 @@ TEST(bpftrace, add_probes_wildcard_kprobe_multi) TEST(bpftrace, add_probes_wildcard_no_matches) { - ast::Probe *probe = parse_probe( - "kprobe:sys_read,kprobe:not_here_*,kprobe:sys_write{}"); + auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(true); + EXPECT_CALL(*bpftrace->mock_probe_matcher, + get_symbols_from_traceable_funcs(false)) + .Times(1); + + parse_probe("kprobe:sys_read,kprobe:not_here_*,kprobe:sys_write{}", + *bpftrace); + ASSERT_EQ(2U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); + + std::string probe_orig_name = + "kprobe:sys_read,kprobe:not_here_*,kprobe:sys_write"; + check_kprobe(bpftrace->get_probes().at(0), "sys_read", probe_orig_name); + check_kprobe(bpftrace->get_probes().at(1), "sys_write", probe_orig_name); +} + +TEST(bpftrace, add_probes_wildcard_no_matches_kprobe_multi) +{ auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_traceable_funcs(false)) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("kprobe:sys_read,kprobe:not_here_*,kprobe:sys_write{}", + *bpftrace); + ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -333,10 +331,9 @@ TEST(bpftrace, add_probes_wildcard_no_matches) TEST(bpftrace, add_probes_kernel_module) { - ast::Probe *probe = parse_probe("kprobe:func_in_mod{}"); - auto bpftrace = get_strict_mock_bpftrace(); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("kprobe:func_in_mod{}", *bpftrace); + ASSERT_EQ(1U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -346,10 +343,9 @@ TEST(bpftrace, add_probes_kernel_module) TEST(bpftrace, add_probes_specify_kernel_module) { - ast::Probe *probe = parse_probe("kprobe:kernel_mod:func_in_mod{}"); - auto bpftrace = get_strict_mock_bpftrace(); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("kprobe:kernel_mod:func_in_mod{}", *bpftrace); + ASSERT_EQ(1U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -364,9 +360,8 @@ TEST(bpftrace, add_probes_specify_kernel_module) TEST(bpftrace, add_probes_offset) { auto offset = 10; - ast::Probe *probe = parse_probe("kprobe:sys_read+10{}"); StrictMock bpftrace; - ASSERT_EQ(0, bpftrace.add_probe(*probe)); + parse_probe("kprobe:sys_read+10{}", bpftrace); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -378,9 +373,8 @@ TEST(bpftrace, add_probes_offset) TEST(bpftrace, add_probes_uprobe) { StrictMock bpftrace; - ast::Probe *probe = parse_probe("uprobe:/bin/sh:foo {}"); + parse_probe("uprobe:/bin/sh:foo {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), @@ -392,16 +386,14 @@ TEST(bpftrace, add_probes_uprobe) TEST(bpftrace, add_probes_uprobe_wildcard) { - ast::Probe *probe = parse_probe("uprobe:/bin/sh:*open {}"); - auto bpftrace = get_strict_mock_bpftrace(); bpftrace->feature_ = std::make_unique(false); - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("uprobe:/bin/sh:*open {}", *bpftrace); + ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -420,17 +412,15 @@ TEST(bpftrace, add_probes_uprobe_wildcard) TEST(bpftrace, add_probes_uprobe_wildcard_uprobe_multi) { - ast::Probe *probe = parse_probe("uprobe:/bin/sh:*open {}"); - auto bpftrace = get_strict_mock_bpftrace(); bpftrace->feature_ = std::make_unique(true); - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(1); + parse_probe("uprobe:/bin/sh:*open {}", *bpftrace); + std::string probe_orig_name = "uprobe:/bin/sh:*open"; - ASSERT_EQ(0, bpftrace->add_probe(*probe)); ASSERT_EQ(1U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -443,44 +433,18 @@ TEST(bpftrace, add_probes_uprobe_wildcard_uprobe_multi) TEST(bpftrace, add_probes_uprobe_wildcard_file) { - ast::Probe *probe = parse_probe("uprobe:/bin/*sh:first_open {}"); auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(false); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/*sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); - ASSERT_EQ(2U, bpftrace->get_probes().size()); - ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - - std::string probe_orig_name = "uprobe:/bin/*sh:first_open"; - check_uprobe(bpftrace->get_probes().at(0), - "/bin/bash", - "first_open", - probe_orig_name, - "uprobe:/bin/bash:first_open"); - check_uprobe(bpftrace->get_probes().at(1), - "/bin/sh", - "first_open", - probe_orig_name, - "uprobe:/bin/sh:first_open"); -} - -TEST(bpftrace, add_probes_uprobe_wildcard_for_target) -{ - ast::Probe *probe = parse_probe("uprobe:*:*open {}"); - - auto bpftrace = get_strict_mock_bpftrace(); - bpftrace->feature_ = std::make_unique(false); - - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "*")) - .Times(1); + parse_probe("uprobe:/bin/*sh:*open {}", *bpftrace); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); - ASSERT_EQ(4U, bpftrace->get_probes().size()); + ASSERT_EQ(3U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - std::string probe_orig_name = "uprobe:*:*open"; + std::string probe_orig_name = "uprobe:/bin/*sh:*open"; check_uprobe(bpftrace->get_probes().at(0), "/bin/bash", "first_open", @@ -496,39 +460,28 @@ TEST(bpftrace, add_probes_uprobe_wildcard_for_target) "second_open", probe_orig_name, "uprobe:/bin/sh:second_open"); - check_uprobe(bpftrace->get_probes().at(3), - "/proc/1234/exe", - "third_open", - probe_orig_name, - "uprobe:/proc/1234/exe:third_open"); } -TEST(bpftrace, add_probes_uprobe_wildcard_for_target_uprobe_multi) +TEST(bpftrace, add_probes_uprobe_wildcard_file_uprobe_multi) { - ast::Probe *probe = parse_probe("uprobe:*:*open {}"); - auto bpftrace = get_strict_mock_bpftrace(); bpftrace->feature_ = std::make_unique(true); - - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "*")) + EXPECT_CALL(*bpftrace->mock_probe_matcher, + get_func_symbols_from_file(0, "/bin/*sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); - ASSERT_EQ(3U, bpftrace->get_probes().size()); + parse_probe("uprobe:/bin/*sh:*open {}", *bpftrace); + + ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - std::string probe_orig_name = "uprobe:*:*open"; + std::string probe_orig_name = "uprobe:/bin/*sh:*open"; check_uprobe_multi(bpftrace->get_probes().at(0), - "/proc/1234/exe", - { "/proc/1234/exe:third_open" }, - probe_orig_name, - "uprobe:/proc/1234/exe:*open"); - check_uprobe_multi(bpftrace->get_probes().at(1), "/bin/sh", { "/bin/sh:first_open", "/bin/sh:second_open" }, probe_orig_name, "uprobe:/bin/sh:*open"); - check_uprobe_multi(bpftrace->get_probes().at(2), + check_uprobe_multi(bpftrace->get_probes().at(1), "/bin/bash", { "/bin/bash:first_open" }, probe_orig_name, @@ -537,43 +490,37 @@ TEST(bpftrace, add_probes_uprobe_wildcard_for_target_uprobe_multi) TEST(bpftrace, add_probes_uprobe_wildcard_no_matches) { - ast::Probe *probe = parse_probe("uprobe:/bin/sh:foo* {}"); auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(false); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("uprobe:/bin/sh:foo* {}", *bpftrace); + ASSERT_EQ(0U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } -TEST(bpftrace, add_probes_uprobe_string_literal) +TEST(bpftrace, add_probes_uprobe_wildcard_no_matches_multi) { - auto a = new ast::AttachPoint(""); - a->provider = "uprobe"; - a->target = "/bin/sh"; - a->func = "foo*"; + auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(true); + EXPECT_CALL(*bpftrace->mock_probe_matcher, + get_func_symbols_from_file(0, "/bin/sh")) + .Times(1); - auto probe = make_probe({ a }); - StrictMock bpftrace; + parse_probe("uprobe:/bin/sh:foo* {}", *bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); - ASSERT_EQ(1U, bpftrace.get_probes().size()); - ASSERT_EQ(0U, bpftrace.get_special_probes().size()); - check_uprobe(bpftrace.get_probes().at(0), - "/bin/sh", - "foo*", - "uprobe:/bin/sh:foo*", - "uprobe:/bin/sh:foo*"); + ASSERT_EQ(0U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } TEST(bpftrace, add_probes_uprobe_address) { - ast::Probe *probe = parse_probe("uprobe:/bin/sh:1024 {}"); StrictMock bpftrace; + parse_probe("uprobe:/bin/sh:1024 {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), @@ -586,10 +533,9 @@ TEST(bpftrace, add_probes_uprobe_address) TEST(bpftrace, add_probes_uprobe_string_offset) { - ast::Probe *probe = parse_probe("uprobe:/bin/sh:foo+10{}"); StrictMock bpftrace; + parse_probe("uprobe:/bin/sh:foo+10{}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), @@ -604,15 +550,15 @@ TEST(bpftrace, add_probes_uprobe_string_offset) TEST(bpftrace, add_probes_uprobe_cpp_symbol) { for (const std::string provider : { "uprobe", "uretprobe" }) { - std::string prog = provider + ":/bin/sh:cpp:cpp_mangled{}"; - ast::Probe *probe = parse_probe(prog); - auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(false); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + std::string prog = provider + ":/bin/sh:cpp:cpp_mangled{}"; + parse_probe(prog, *bpftrace); + ASSERT_EQ(3U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); check_uprobe(bpftrace->get_probes().at(0), @@ -635,14 +581,14 @@ TEST(bpftrace, add_probes_uprobe_cpp_symbol) TEST(bpftrace, add_probes_uprobe_cpp_symbol_full) { - auto probe = parse_probe("uprobe:/bin/sh:cpp:\"cpp_mangled(int)\"{}"); - auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(false); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("uprobe:/bin/sh:cpp:\"cpp_mangled(int)\"{}", *bpftrace); + ASSERT_EQ(1U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); check_uprobe(bpftrace->get_probes().at(0), @@ -654,16 +600,14 @@ TEST(bpftrace, add_probes_uprobe_cpp_symbol_full) TEST(bpftrace, add_probes_uprobe_cpp_symbol_wildcard) { - auto probe = parse_probe("uprobe:/bin/sh:cpp:cpp_man*{}"); - auto bpftrace = get_strict_mock_bpftrace(); bpftrace->feature_ = std::make_unique(false); - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("uprobe:/bin/sh:cpp:cpp_man*{}", *bpftrace); + ASSERT_EQ(4U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -691,15 +635,14 @@ TEST(bpftrace, add_probes_uprobe_cpp_symbol_wildcard) TEST(bpftrace, add_probes_uprobe_no_demangling) { - // Without the :cpp prefix, only look for non-mangled "cpp_mangled" symbol - auto probe = parse_probe("uprobe:/bin/sh:cpp_mangled {}"); - auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_func_symbols_from_file(0, "/bin/sh")) .Times(0); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + // Without the :cpp prefix, only look for non-mangled "cpp_mangled" symbol + parse_probe("uprobe:/bin/sh:cpp_mangled {}", *bpftrace); + ASSERT_EQ(1U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); check_uprobe(bpftrace->get_probes().at(0), @@ -711,12 +654,10 @@ TEST(bpftrace, add_probes_uprobe_no_demangling) TEST(bpftrace, add_probes_usdt) { - auto probe = parse_probe("usdt:/bin/sh:prov1:mytp{}"); - probe->attach_points->front()->usdt.num_locations = 1; - StrictMock bpftrace; + setenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS", "1", true); + parse_probe("usdt:/bin/sh:prov1:mytp {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_usdt(bpftrace.get_probes().at(0), @@ -724,17 +665,20 @@ TEST(bpftrace, add_probes_usdt) "prov1", "mytp", "usdt:/bin/sh:prov1:mytp"); + + unsetenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS"); } TEST(bpftrace, add_probes_usdt_wildcard) { - auto probe = make_usdt_probe("/bin/*sh", "prov*", "tp*", true, 1); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_usdt(0, "/bin/*sh")) .Times(1); + setenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS", "1", true); + + parse_probe("usdt:/bin/*sh:prov*:tp* {}", *bpftrace); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); ASSERT_EQ(4U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -746,45 +690,20 @@ TEST(bpftrace, add_probes_usdt_wildcard) check_usdt( bpftrace->get_probes().at(2), "/bin/sh", "prov1", "tp2", orig_name); check_usdt(bpftrace->get_probes().at(3), "/bin/sh", "prov2", "tp", orig_name); -} -TEST(bpftrace, add_probes_usdt_wildcard_for_target) -{ - auto probe = make_usdt_probe("*", "prov*", "tp*", true, 1); - - auto bpftrace = get_strict_mock_bpftrace(); - EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_usdt(0, "*")) - .Times(1); - - ASSERT_EQ(0, bpftrace->add_probe(*probe)); - ASSERT_EQ(5U, bpftrace->get_probes().size()); - ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - - const std::string orig_name = "usdt:*:prov*:tp*"; - check_usdt( - bpftrace->get_probes().at(0), "/bin/bash", "prov1", "tp3", orig_name); - check_usdt( - bpftrace->get_probes().at(1), "/bin/sh", "prov1", "tp1", orig_name); - check_usdt( - bpftrace->get_probes().at(2), "/bin/sh", "prov1", "tp2", orig_name); - check_usdt(bpftrace->get_probes().at(3), "/bin/sh", "prov2", "tp", orig_name); - check_usdt(bpftrace->get_probes().at(4), - "/proc/1234/exe", - "prov2", - "tp4", - orig_name); + unsetenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS"); } TEST(bpftrace, add_probes_usdt_empty_namespace) { - auto probe = make_usdt_probe("/bin/sh", "", "tp1", true, 1); - auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_usdt(0, "/bin/sh")) .Times(1); + setenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS", "1", true); + + parse_probe("usdt:/bin/sh:tp1 {}", *bpftrace); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); ASSERT_EQ(1U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); check_usdt(bpftrace->get_probes().at(0), @@ -792,51 +711,56 @@ TEST(bpftrace, add_probes_usdt_empty_namespace) "prov1", "tp1", "usdt:/bin/sh:tp1"); + + unsetenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS"); } TEST(bpftrace, add_probes_usdt_empty_namespace_conflict) { - auto probe = make_usdt_probe("/bin/sh", "", "tp", true, 1); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_usdt(0, "/bin/sh")) .Times(1); + setenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS", "1", true); + + parse_probe("usdt:/bin/sh:tp {}", *bpftrace); - ASSERT_EQ(1, bpftrace->add_probe(*probe)); + unsetenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS"); } TEST(bpftrace, add_probes_usdt_duplicate_markers) { - auto probe = make_usdt_probe("/bin/sh", "prov1", "mytp", false, 3); + auto bpftrace = get_strict_mock_bpftrace(); + setenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS", "3", true); - StrictMock bpftrace; + parse_probe("usdt:/bin/sh:prov1:mytp {}", *bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); - ASSERT_EQ(3U, bpftrace.get_probes().size()); - ASSERT_EQ(0U, bpftrace.get_special_probes().size()); - check_usdt(bpftrace.get_probes().at(0), + ASSERT_EQ(3U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); + check_usdt(bpftrace->get_probes().at(0), "/bin/sh", "prov1", "mytp", "usdt:/bin/sh:prov1:mytp"); - check_usdt(bpftrace.get_probes().at(1), + check_usdt(bpftrace->get_probes().at(1), "/bin/sh", "prov1", "mytp", "usdt:/bin/sh:prov1:mytp"); - check_usdt(bpftrace.get_probes().at(2), + check_usdt(bpftrace->get_probes().at(2), "/bin/sh", "prov1", "mytp", "usdt:/bin/sh:prov1:mytp"); + + unsetenv("BPFTRACE_TEST_USDT_NUM_LOCATIONS"); } TEST(bpftrace, add_probes_tracepoint) { - auto probe = parse_probe(("tracepoint:sched:sched_switch {}")); StrictMock bpftrace; + parse_probe("tracepoint:sched:sched_switch {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -847,14 +771,13 @@ TEST(bpftrace, add_probes_tracepoint) TEST(bpftrace, add_probes_tracepoint_wildcard) { - auto probe = parse_probe(("tracepoint:sched:sched_* {}")); auto bpftrace = get_strict_mock_bpftrace(); - std::set matches = { "sched_one", "sched_two" }; EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_file(tracefs::available_events())) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("tracepoint:sched:sched_* {}", *bpftrace); + ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -867,13 +790,13 @@ TEST(bpftrace, add_probes_tracepoint_wildcard) TEST(bpftrace, add_probes_tracepoint_category_wildcard) { - auto probe = parse_probe(("tracepoint:sched*:sched_* {}")); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_file(tracefs::available_events())) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("tracepoint:sched*:sched_* {}", *bpftrace); + ASSERT_EQ(3U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); @@ -890,41 +813,22 @@ TEST(bpftrace, add_probes_tracepoint_category_wildcard) TEST(bpftrace, add_probes_tracepoint_wildcard_no_matches) { - auto probe = parse_probe("tracepoint:type:typo_* {}"); - /* - ast::AttachPoint a(""); - a.provider = "tracepoint"; - a.target = "typo"; - a.func = "typo_*"; - a.need_expansion = true; - ast::AttachPointList attach_points = { &a }; - ast::Probe probe(&attach_points, nullptr, nullptr); -*/ auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_file(tracefs::available_events())) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("tracepoint:type:typo_* {}", *bpftrace); + ASSERT_EQ(0U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } TEST(bpftrace, add_probes_profile) { - /* - ast::AttachPoint a(""); - a.provider = "profile"; - a.target = "ms"; - a.freq = 997; - ast::AttachPointList attach_points = { &a }; - ast::Probe probe(&attach_points, nullptr, nullptr); - */ - auto probe = parse_probe("profile:ms:997 {}"); - StrictMock bpftrace; + parse_probe("profile:ms:997 {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -934,17 +838,9 @@ TEST(bpftrace, add_probes_profile) TEST(bpftrace, add_probes_interval) { - // ast::AttachPoint a(""); - // a.provider = "interval"; - // a.target = "s"; - // a.freq = 1; - // ast::AttachPointList attach_points = { &a }; - // ast::Probe probe(&attach_points, nullptr, nullptr); - auto probe = parse_probe("i:s:1 {}"); - StrictMock bpftrace; + parse_probe("i:s:1 {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -954,17 +850,9 @@ TEST(bpftrace, add_probes_interval) TEST(bpftrace, add_probes_software) { - // ast::AttachPoint a(""); - // a.provider = "software"; - // a.target = "faults"; - // a.freq = 1000; - // ast::AttachPointList attach_points = { &a }; - // ast::Probe probe(&attach_points, nullptr, nullptr); - auto probe = parse_probe("software:faults:1000 {}"); - StrictMock bpftrace; + parse_probe("software:faults:1000 {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -974,17 +862,9 @@ TEST(bpftrace, add_probes_software) TEST(bpftrace, add_probes_hardware) { - // ast::AttachPoint a(""); - // a.provider = "hardware"; - // a.target = "cache-references"; - // a.freq = 1000000; - // ast::AttachPointList attach_points = { &a }; - // ast::Probe probe(&attach_points, nullptr, nullptr); - auto probe = parse_probe("hardware:cache-references:1000000 {}"); - StrictMock bpftrace; + parse_probe("hardware:cache-references:1000000 {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -995,18 +875,6 @@ TEST(bpftrace, add_probes_hardware) probe_orig_name); } -TEST(bpftrace, invalid_provider) -{ - auto a = new ast::AttachPoint(""); - a->provider = "lookatme"; - a->func = "invalid"; - auto probe = make_probe({ a }); - - StrictMock bpftrace; - - ASSERT_EQ(0, bpftrace.add_probe(*probe)); -} - TEST(bpftrace, empty_attachpoints) { StrictMock bpftrace; @@ -1225,30 +1093,27 @@ void check_probe(Probe &p, ProbeType type, const std::string &name) TEST_F(bpftrace_btf, add_probes_kfunc) { - ast::Probe *probe = parse_probe("kfunc:func_1,kretfunc:func_1 {}"); - - StrictMock bpftrace; + auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(true); + parse_probe("kfunc:func_1,kretfunc:func_1 {}", *bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); - ASSERT_EQ(2U, bpftrace.get_probes().size()); - ASSERT_EQ(0U, bpftrace.get_special_probes().size()); + ASSERT_EQ(2U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - check_probe(bpftrace.get_probes().at(0), + check_probe(bpftrace->get_probes().at(0), ProbeType::kfunc, "kfunc:mock_vmlinux:func_1"); - check_probe(bpftrace.get_probes().at(1), + check_probe(bpftrace->get_probes().at(1), ProbeType::kretfunc, "kretfunc:mock_vmlinux:func_1"); } TEST_F(bpftrace_btf, add_probes_kprobe) { - ast::Probe *probe = parse_probe( - "kprobe:mock_vmlinux:func_1,kretprobe:mock_vmlinux:func_1 {}"); - StrictMock bpftrace; + parse_probe("kprobe:mock_vmlinux:func_1,kretprobe:mock_vmlinux:func_1 {}", + bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(2U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -1262,49 +1127,45 @@ TEST_F(bpftrace_btf, add_probes_kprobe) TEST_F(bpftrace_btf, add_probes_iter_task) { - ast::Probe *probe = parse_probe("iter:task {}"); - - StrictMock bpftrace; + auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(true); + parse_probe("iter:task {}", *bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); - ASSERT_EQ(1U, bpftrace.get_probes().size()); - ASSERT_EQ(0U, bpftrace.get_special_probes().size()); + ASSERT_EQ(1U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - check_probe(bpftrace.get_probes().at(0), ProbeType::iter, "iter:task"); + check_probe(bpftrace->get_probes().at(0), ProbeType::iter, "iter:task"); } TEST_F(bpftrace_btf, add_probes_iter_task_file) { - ast::Probe *probe = parse_probe("iter:task_file {}"); - - StrictMock bpftrace; + auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(true); + parse_probe("iter:task_file {}", *bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); - ASSERT_EQ(1U, bpftrace.get_probes().size()); - ASSERT_EQ(0U, bpftrace.get_special_probes().size()); + ASSERT_EQ(1U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - check_probe(bpftrace.get_probes().at(0), ProbeType::iter, "iter:task_file"); + check_probe(bpftrace->get_probes().at(0), ProbeType::iter, "iter:task_file"); } TEST_F(bpftrace_btf, add_probes_iter_task_vma) { - ast::Probe *probe = parse_probe("iter:task_vma {}"); - - StrictMock bpftrace; + auto bpftrace = get_strict_mock_bpftrace(); + bpftrace->feature_ = std::make_unique(true); + parse_probe("iter:task_vma {}", *bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); - ASSERT_EQ(1U, bpftrace.get_probes().size()); - ASSERT_EQ(0U, bpftrace.get_special_probes().size()); + ASSERT_EQ(1U, bpftrace->get_probes().size()); + ASSERT_EQ(0U, bpftrace->get_special_probes().size()); - check_probe(bpftrace.get_probes().at(0), ProbeType::iter, "iter:task_vma"); + check_probe(bpftrace->get_probes().at(0), ProbeType::iter, "iter:task_vma"); } TEST(bpftrace, add_probes_rawtracepoint) { - auto probe = parse_probe(("rawtracepoint:sched_switch {}")); StrictMock bpftrace; + parse_probe("rawtracepoint:sched_switch {}", bpftrace); - ASSERT_EQ(0, bpftrace.add_probe(*probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); @@ -1316,26 +1177,26 @@ TEST(bpftrace, add_probes_rawtracepoint) TEST(bpftrace, add_probes_rawtracepoint_wildcard) { - auto probe = parse_probe(("rawtracepoint:sched_* {}")); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_file(tracefs::available_events())) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe(("rawtracepoint:sched_* {}"), *bpftrace); + ASSERT_EQ(3U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } TEST(bpftrace, add_probes_rawtracepoint_wildcard_no_matches) { - auto probe = parse_probe("rawtracepoint:typo_* {}"); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace->mock_probe_matcher, get_symbols_from_file(tracefs::available_events())) .Times(1); - ASSERT_EQ(0, bpftrace->add_probe(*probe)); + parse_probe("rawtracepoint:typo_* {}", *bpftrace); + ASSERT_EQ(0U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } diff --git a/tests/codegen/general.cpp b/tests/codegen/general.cpp index 02a410ddf96d..8b113d7f750f 100644 --- a/tests/codegen/general.cpp +++ b/tests/codegen/general.cpp @@ -15,7 +15,8 @@ class MockBPFtrace : public BPFtrace { #ifdef __clang__ #pragma GCC diagnostic ignored "-Winconsistent-missing-override" #endif - MOCK_METHOD1(add_probe, int(ast::Probe &p)); + MOCK_METHOD3(add_probe, + int(const ast::AttachPoint &, const ast::Probe &, int)); #pragma GCC diagnostic pop int resolve_uname(const std::string &name, @@ -105,7 +106,7 @@ TEST(codegen, printf_offsets) TEST(codegen, probe_count) { MockBPFtrace bpftrace; - EXPECT_CALL(bpftrace, add_probe(_)).Times(2); + EXPECT_CALL(bpftrace, add_probe(_, _, _)).Times(2); Driver driver(bpftrace); diff --git a/tests/mocks.cpp b/tests/mocks.cpp index 0835a81ce395..e3cb178e4ccd 100644 --- a/tests/mocks.cpp +++ b/tests/mocks.cpp @@ -45,7 +45,6 @@ void setup_mock_probe_matcher(MockProbeMatcher &matcher) "/bin/sh:_Z11cpp_mangledv\n" "/bin/sh:_Z18cpp_mangled_suffixv\n"; std::string bash_usyms = "/bin/bash:first_open\n"; - std::string proc_usyms = "/proc/1234/exe:third_open\n"; ON_CALL(matcher, get_func_symbols_from_file(_, "/bin/sh")) .WillByDefault([sh_usyms](int, const std::string &) { return std::unique_ptr(new std::istringstream(sh_usyms)); @@ -56,12 +55,6 @@ void setup_mock_probe_matcher(MockProbeMatcher &matcher) return std::unique_ptr( new std::istringstream(sh_usyms + bash_usyms)); }); - ON_CALL(matcher, get_func_symbols_from_file(_, "*")) - .WillByDefault( - [sh_usyms, bash_usyms, proc_usyms](int, const std::string &) { - return std::unique_ptr( - new std::istringstream(sh_usyms + bash_usyms + proc_usyms)); - }); std::string sh_usdts = "/bin/sh:prov1:tp1\n" "/bin/sh:prov1:tp2\n" @@ -69,7 +62,6 @@ void setup_mock_probe_matcher(MockProbeMatcher &matcher) "/bin/sh:prov2:notatp\n" "/bin/sh:nahprov:tp\n"; std::string bash_usdts = "/bin/bash:prov1:tp3\n"; - std::string proc_usdts = "/proc/1234/exe:prov2:tp4\n"; ON_CALL(matcher, get_symbols_from_usdt(_, "/bin/sh")) .WillByDefault([sh_usdts](int, const std::string &) { return std::unique_ptr(new std::istringstream(sh_usdts)); @@ -79,12 +71,6 @@ void setup_mock_probe_matcher(MockProbeMatcher &matcher) return std::unique_ptr( new std::istringstream(sh_usdts + bash_usdts)); }); - ON_CALL(matcher, get_symbols_from_usdt(_, "*")) - .WillByDefault( - [sh_usdts, bash_usdts, proc_usdts](int, const std::string &) { - return std::unique_ptr( - new std::istringstream(sh_usdts + bash_usdts + proc_usdts)); - }); } void setup_mock_bpftrace(MockBPFtrace &bpftrace)