| /* |
| * Copyright (c) 2016 GitHub, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <linux/version.h> |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "catch.hpp" |
| #include "usdt.h" |
| #include "api/BPF.h" |
| |
| /* required to insert USDT probes on this very executable -- |
| * we're gonna be testing them live! */ |
| #include "folly/tracing/StaticTracepoint.h" |
| |
| static int a_probed_function() { |
| int an_int = 23 + getpid(); |
| void *a_pointer = malloc(4); |
| FOLLY_SDT(libbcc_test, sample_probe_1, an_int, a_pointer); |
| free(a_pointer); |
| return an_int; |
| } |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) |
| FOLLY_SDT_DEFINE_SEMAPHORE(libbcc_test, sample_probe_2) |
| static int a_probed_function_with_sem() { |
| int an_int = 23 + getpid(); |
| void *a_pointer = malloc(4); |
| FOLLY_SDT_WITH_SEMAPHORE(libbcc_test, sample_probe_2, an_int, a_pointer); |
| free(a_pointer); |
| return an_int; |
| } |
| #endif // linux version >= 4.20 |
| |
| extern "C" int lib_probed_function(); |
| |
| int call_shared_lib_func() { |
| return lib_probed_function(); |
| } |
| |
| TEST_CASE("test finding a probe in our own process", "[usdt]") { |
| USDT::Context ctx(getpid()); |
| REQUIRE(ctx.num_probes() >= 1); |
| |
| SECTION("our test probe") { |
| auto probe = ctx.get("sample_probe_1"); |
| REQUIRE(probe); |
| |
| if(probe->in_shared_object(probe->bin_path())) |
| return; |
| REQUIRE(probe->name() == "sample_probe_1"); |
| REQUIRE(probe->provider() == "libbcc_test"); |
| REQUIRE(probe->bin_path().find("/test_libbcc") != std::string::npos); |
| |
| REQUIRE(probe->num_locations() == 1); |
| REQUIRE(probe->num_arguments() == 2); |
| REQUIRE(probe->need_enable() == false); |
| |
| REQUIRE(a_probed_function() != 0); |
| } |
| } |
| |
| TEST_CASE("test probe's attributes with C++ API", "[usdt]") { |
| const ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event"); |
| |
| REQUIRE(u.binary_path() == "/proc/self/exe"); |
| REQUIRE(u.pid() == -1); |
| REQUIRE(u.provider() == "libbcc_test"); |
| REQUIRE(u.name() == "sample_probe_1"); |
| REQUIRE(u.probe_func() == "on_event"); |
| } |
| |
| TEST_CASE("test fine a probe in our own binary with C++ API", "[usdt]") { |
| ebpf::BPF bpf; |
| ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event"); |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.ok()); |
| |
| res = bpf.attach_usdt(u); |
| REQUIRE(res.ok()); |
| |
| res = bpf.detach_usdt(u); |
| REQUIRE(res.ok()); |
| } |
| |
| TEST_CASE("test fine probes in our own binary with C++ API", "[usdt]") { |
| ebpf::BPF bpf; |
| ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_1", "on_event"); |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.ok()); |
| |
| res = bpf.attach_usdt_all(); |
| REQUIRE(res.ok()); |
| |
| res = bpf.detach_usdt_all(); |
| REQUIRE(res.ok()); |
| } |
| |
| TEST_CASE("test fine a probe in our Process with C++ API", "[usdt]") { |
| ebpf::BPF bpf; |
| ebpf::USDT u(::getpid(), "libbcc_test", "sample_probe_1", "on_event"); |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.ok()); |
| |
| res = bpf.attach_usdt(u); |
| REQUIRE(res.ok()); |
| |
| res = bpf.detach_usdt(u); |
| REQUIRE(res.ok()); |
| } |
| |
| TEST_CASE("test find a probe in our process' shared libs with c++ API", "[usdt]") { |
| ebpf::BPF bpf; |
| ebpf::USDT u(::getpid(), "libbcc_test", "sample_lib_probe_1", "on_event"); |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.msg() == ""); |
| REQUIRE(res.ok()); |
| } |
| |
| TEST_CASE("test usdt partial init w/ fail init_usdt", "[usdt]") { |
| ebpf::BPF bpf; |
| ebpf::USDT u(::getpid(), "libbcc_test", "sample_lib_probe_nonexistent", "on_event"); |
| ebpf::USDT p(::getpid(), "libbcc_test", "sample_lib_probe_1", "on_event"); |
| |
| // We should be able to fail initialization and subsequently do bpf.init w/o USDT |
| // successfully |
| auto res = bpf.init_usdt(u); |
| REQUIRE(res.msg() != ""); |
| REQUIRE(!res.ok()); |
| |
| // Shouldn't be necessary to re-init bpf object either after failure to init w/ |
| // bad USDT |
| res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.msg() != ""); |
| REQUIRE(!res.ok()); |
| |
| res = bpf.init_usdt(p); |
| REQUIRE(res.msg() == ""); |
| REQUIRE(res.ok()); |
| |
| res = bpf.init("int on_event() { return 0; }", {}, {}); |
| REQUIRE(res.msg() == ""); |
| REQUIRE(res.ok()); |
| } |
| |
| class ChildProcess { |
| pid_t pid_; |
| |
| public: |
| ChildProcess(const char *name, char *const argv[]) { |
| pid_ = fork(); |
| if (pid_ == 0) { |
| execvp(name, argv); |
| exit(0); |
| } |
| if (spawned()) { |
| usleep(250000); |
| if (kill(pid_, 0) < 0) |
| pid_ = -1; |
| } |
| } |
| |
| ~ChildProcess() { |
| if (spawned()) { |
| int status; |
| kill(pid_, SIGKILL); |
| if (waitpid(pid_, &status, 0) != pid_) |
| abort(); |
| } |
| } |
| |
| bool spawned() const { return pid_ > 0; } |
| pid_t pid() const { return pid_; } |
| }; |
| |
| extern int cmd_scanf(const char *cmd, const char *fmt, ...); |
| |
| static int probe_num_locations(const char *bin_path, const char *func_name) { |
| int num_locations; |
| char cmd[512]; |
| const char *cmdfmt = "readelf -n %s | grep -c \"Name: %s$\""; |
| |
| sprintf(cmd, cmdfmt, bin_path, func_name); |
| if (cmd_scanf(cmd, "%d", &num_locations) != 0) { |
| return -1; |
| } |
| |
| return num_locations; |
| } |
| |
| static int probe_num_arguments(const char *bin_path, const char *func_name) { |
| int num_arguments; |
| char cmd[512]; |
| const char *cmdfmt = "readelf -n %s | grep -m 1 -A 2 \" %s$\" | " \ |
| "tail -1 | cut -d \" \" -f 6- | wc -w"; |
| |
| sprintf(cmd, cmdfmt, bin_path, func_name); |
| if (cmd_scanf(cmd, "%d", &num_arguments) != 0) { |
| return -1; |
| } |
| |
| return num_arguments; |
| } |
| |
| // Unsharing pid namespace requires forking |
| // this uses pgrep to find the child process, by searching for a process |
| // that has the unshare as its parent |
| static int unshared_child_pid(const int ppid) { |
| int child_pid; |
| char cmd[512]; |
| const char *cmdfmt = "pgrep -P %d"; |
| |
| sprintf(cmd, cmdfmt, ppid); |
| if (cmd_scanf(cmd, "%d", &child_pid) != 0) { |
| return -1; |
| } |
| return child_pid; |
| } |
| |
| // FIXME This seems like a legitimate bug with probing ruby where the |
| // ruby symbols are in libruby.so? |
| TEST_CASE("test listing all USDT probes in Ruby/MRI", "[usdt][!mayfail]") { |
| size_t mri_probe_count = 0; |
| |
| SECTION("without a running Ruby process") { |
| USDT::Context ctx("ruby"); |
| |
| if (!ctx.loaded()) |
| return; |
| |
| REQUIRE(ctx.num_probes() > 10); |
| mri_probe_count = ctx.num_probes(); |
| |
| SECTION("GC static probe") { |
| auto name = "gc__mark__begin"; |
| auto probe = ctx.get(name); |
| REQUIRE(probe); |
| |
| REQUIRE(probe->in_shared_object(probe->bin_path()) == true); |
| REQUIRE(probe->name() == name); |
| REQUIRE(probe->provider() == "ruby"); |
| |
| auto bin_path = probe->bin_path(); |
| bool bin_path_match = |
| (bin_path.find("/ruby") != std::string::npos) || |
| (bin_path.find("/libruby") != std::string::npos); |
| REQUIRE(bin_path_match); |
| |
| int exp_locations, exp_arguments; |
| exp_locations = probe_num_locations(bin_path.c_str(), name); |
| exp_arguments = probe_num_arguments(bin_path.c_str(), name); |
| REQUIRE(probe->num_locations() == exp_locations); |
| REQUIRE(probe->num_arguments() == exp_arguments); |
| REQUIRE(probe->need_enable() == true); |
| } |
| |
| SECTION("object creation probe") { |
| auto name = "object__create"; |
| auto probe = ctx.get(name); |
| REQUIRE(probe); |
| |
| REQUIRE(probe->in_shared_object(probe->bin_path()) == true); |
| REQUIRE(probe->name() == name); |
| REQUIRE(probe->provider() == "ruby"); |
| |
| auto bin_path = probe->bin_path(); |
| bool bin_path_match = |
| (bin_path.find("/ruby") != std::string::npos) || |
| (bin_path.find("/libruby") != std::string::npos); |
| REQUIRE(bin_path_match); |
| |
| int exp_locations, exp_arguments; |
| exp_locations = probe_num_locations(bin_path.c_str(), name); |
| exp_arguments = probe_num_arguments(bin_path.c_str(), name); |
| REQUIRE(probe->num_locations() == exp_locations); |
| REQUIRE(probe->num_arguments() == exp_arguments); |
| REQUIRE(probe->need_enable() == true); |
| } |
| |
| SECTION("array creation probe") { |
| auto name = "array__create"; |
| auto probe = ctx.get(name); |
| REQUIRE(probe); |
| REQUIRE(probe->name() == name); |
| |
| auto bin_path = probe->bin_path().c_str(); |
| int exp_locations, exp_arguments; |
| exp_locations = probe_num_locations(bin_path, name); |
| exp_arguments = probe_num_arguments(bin_path, name); |
| REQUIRE(probe->num_locations() == exp_locations); |
| REQUIRE(probe->num_arguments() == exp_arguments); |
| REQUIRE(probe->need_enable() == true); |
| } |
| } |
| |
| SECTION("with a running Ruby process") { |
| static char _ruby[] = "ruby"; |
| char *const argv[2] = {_ruby, NULL}; |
| |
| ChildProcess ruby(argv[0], argv); |
| if (!ruby.spawned()) |
| return; |
| |
| USDT::Context ctx(ruby.pid()); |
| REQUIRE(ctx.num_probes() >= mri_probe_count); |
| |
| SECTION("get probe in running process") { |
| auto name = "gc__mark__begin"; |
| auto probe = ctx.get(name); |
| REQUIRE(probe); |
| |
| REQUIRE(probe->in_shared_object(probe->bin_path()) == true); |
| REQUIRE(probe->name() == name); |
| REQUIRE(probe->provider() == "ruby"); |
| |
| auto bin_path = probe->bin_path(); |
| bool bin_path_match = |
| (bin_path.find("/ruby") != std::string::npos) || |
| (bin_path.find("/libruby") != std::string::npos); |
| REQUIRE(bin_path_match); |
| |
| int exp_locations, exp_arguments; |
| exp_locations = probe_num_locations(bin_path.c_str(), name); |
| exp_arguments = probe_num_arguments(bin_path.c_str(), name); |
| REQUIRE(probe->num_locations() == exp_locations); |
| REQUIRE(probe->num_arguments() == exp_arguments); |
| REQUIRE(probe->need_enable() == true); |
| } |
| } |
| } |
| |
| // These tests are expected to fail if there is no Ruby with dtrace probes |
| TEST_CASE("test probing running Ruby process in namespaces", |
| "[usdt][!mayfail]") { |
| SECTION("in separate mount namespace") { |
| static char _unshare[] = "unshare"; |
| const char *const argv[4] = {_unshare, "--mount", "ruby", NULL}; |
| |
| ChildProcess unshare(argv[0], (char **const)argv); |
| if (!unshare.spawned()) |
| return; |
| int ruby_pid = unshare.pid(); |
| |
| ebpf::BPF bpf; |
| ebpf::USDT u(ruby_pid, "ruby", "gc__mark__begin", "on_event"); |
| u.set_probe_matching_kludge(1); // Also required for overlayfs... |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.msg() == ""); |
| REQUIRE(res.ok()); |
| |
| res = bpf.attach_usdt(u, ruby_pid); |
| REQUIRE(res.ok()); |
| |
| res = bpf.detach_usdt(u, ruby_pid); |
| REQUIRE(res.ok()); |
| } |
| |
| SECTION("in separate mount namespace and separate PID namespace") { |
| static char _unshare[] = "unshare"; |
| const char *const argv[8] = {_unshare, "--fork", |
| "--mount", "--pid", "--mount-proc", |
| "ruby", NULL}; |
| |
| ChildProcess unshare(argv[0], (char **const)argv); |
| if (!unshare.spawned()) |
| return; |
| int ruby_pid = unshared_child_pid(unshare.pid()); |
| |
| ebpf::BPF bpf; |
| ebpf::USDT u(ruby_pid, "ruby", "gc__mark__begin", "on_event"); |
| u.set_probe_matching_kludge(1); // Also required for overlayfs... |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.msg() == ""); |
| REQUIRE(res.ok()); |
| |
| res = bpf.attach_usdt(u, ruby_pid); |
| REQUIRE(res.ok()); |
| |
| res = bpf.detach_usdt(u, ruby_pid); |
| REQUIRE(res.ok()); |
| |
| struct bcc_symbol sym; |
| std::string pid_root= "/proc/" + std::to_string(ruby_pid) + "/root/"; |
| std::string module = pid_root + "usr/local/bin/ruby"; |
| REQUIRE(bcc_resolve_symname(module.c_str(), "rb_gc_mark", 0x0, ruby_pid, nullptr, &sym) == 0); |
| REQUIRE(std::string(sym.module).find(pid_root, 1) == std::string::npos); |
| kill(ruby_pid, SIGKILL); |
| bcc_procutils_free(sym.module); |
| } |
| } |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) |
| TEST_CASE("Test uprobe refcnt semaphore activation", "[usdt]") { |
| ebpf::BPF bpf; |
| |
| REQUIRE(!FOLLY_SDT_IS_ENABLED(libbcc_test, sample_probe_2)); |
| |
| ebpf::USDT u("/proc/self/exe", "libbcc_test", "sample_probe_2", "on_event"); |
| |
| auto res = bpf.init("int on_event() { return 0; }", {}, {u}); |
| REQUIRE(res.ok()); |
| |
| res = bpf.attach_usdt(u); |
| REQUIRE(res.ok()); |
| |
| REQUIRE(FOLLY_SDT_IS_ENABLED(libbcc_test, sample_probe_2)); |
| |
| res = bpf.detach_usdt(u); |
| REQUIRE(res.ok()); |
| |
| REQUIRE(a_probed_function_with_sem() != 0); |
| } |
| #endif // linux version >= 4.20 |