Commit a3086a88 authored by Daniel Xu's avatar Daniel Xu

Add -c CMD option

This patch adds a command running option to bpftrace. The user can now
run something like:

    ./bpftrace -e '...' -c 'sleep 5'

which is a convenience wrapper around something like:

    sleep 5 & ./bpfrace -e '...' -p `pidof sleep`

`-c` is better because it:
* ensures a tighter tracing range around CMD (ie we trace less of the
system while it is not running CMD)
* makes bpftrace exit (which is convenient) when CMD terminates
    * previously, it was not possible to get a full trace of CMDs
    execution and have bpftrace exit upon CMD termination

Test Plan:
Trivial successful example:
```
$ sudo ./build/src/bpftrace -e 'tracepoint:syscalls:sys_enter_nanosleep
{ printf("%s nanoslept\n", comm); }' -c '/bin/sleep 1'
[sudo] password for dlxu:
chdir(/lib/modules/4.19.8-200.fc28.x86_64/build): No such file or
directory
Attaching 1 probe...
sleep nanoslept
splunkd nanoslept
webrtc_audio_mo nanoslept
gnome-terminal- nanoslept
webrtc_audio_mo nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept
gnome-terminal- nanoslept

$
```

Ambigous executable:
```
$ sudo ./build/src/bpftrace -e 'tracepoint:syscalls:sys_enter_nanosleep
{ printf("%s nanoslept\n", comm); }' -c 'sleep 1'
chdir(/lib/modules/4.19.8-200.fc28.x86_64/build): No such file or
directory
Attaching 1 probe...
execve: No such file or directory
Failed to spawn child=sleep 1
splunkd nanoslept

$
```

This closes #253
parent d33e6f03
......@@ -60,7 +60,12 @@ Execute PROGRAM.
.
.TP
\fB\-p PID\fR
Process ID for enabling USDT probes.
Enable USDT probes on PID. Will terminate bpftrace on PID termination. Note this is not a global PID filter on probes.
.
.TP
\fB\-c CMD\fR
Helper to run CMD. Equivalent to manually running CMD and then giving passing the PID to -p. This is useful to ensure
you've traced at least the duration CMD's execution. You must provide an absolute path for the executable.
.
.TP
\fB\-v\fR
......@@ -85,6 +90,10 @@ List probes containing "sleep".
Trace processes calling sleep.
.
.TP
\fBbpftrace \-c \'sleep 5\' \-e \'kprobe:do_nanosleep { printf("PID %d sleeping\en", pid); }\'\fR
run "sleep 5" in a new process and then trace processes calling sleep.
.
.TP
\fBbpftrace \-e \'tracepoint:raw_syscalls:sys_enter { @[comm]=count(); }\'\fR
Count syscalls by process name.
.
......
......@@ -10,7 +10,10 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include "bcc_syms.h"
#include "perf_reader.h"
......@@ -21,11 +24,25 @@
#include "triggers.h"
#include "resolve_cgroupid.h"
extern char** environ;
namespace bpftrace {
DebugLevel bt_debug = DebugLevel::kNone;
bool bt_verbose = false;
BPFtrace::~BPFtrace()
{
for (int pid : child_pids_)
{
// We don't care if waitpid returns any errors. We're just trying
// to make a best effort here. It's not like we could recover from
// an error.
int status;
waitpid(pid, &status, 0);
}
}
int BPFtrace::add_probe(ast::Probe &p)
{
for (auto attach_point : *p.attach_points)
......@@ -427,6 +444,21 @@ int BPFtrace::run(std::unique_ptr<BpfOrc> bpforc)
if (epollfd < 0)
return epollfd;
// Spawn a child process if we've been passed a command to run
if (cmd_.size())
{
auto args = split_string(cmd_, ' ');
int pid = spawn_child(args);
if (pid < 0)
{
std::cerr << "Failed to spawn child=" << cmd_ << std::endl;
return pid;
}
child_pids_.emplace_back(pid);
pid_ = pid;
}
BEGIN_trigger();
// NOTE (mmarchini): Apparently the kernel fires kprobe_events in the reverse
......@@ -1079,6 +1111,56 @@ int BPFtrace::print_lhist(const std::vector<uint64_t> &values, int min, int max,
return 0;
}
int BPFtrace::spawn_child(const std::vector<std::string>& args)
{
static const int maxargs = 256;
char* argv[maxargs];
// Convert vector of strings into raw array of C-strings for execve(2)
int idx = 0;
for (const auto& arg : args)
{
if (idx == maxargs - 1)
{
std::cerr << "Too many args passed into spawn_child (" << args.size()
<< " > " << maxargs - 1 << ")" << std::endl;
return -1;
}
argv[idx] = const_cast<char*>(arg.c_str());
++idx;
}
argv[idx] = nullptr; // must be null terminated
// Fork and exec
int ret = fork();
if (ret == 0)
{
// Receive SIGTERM if parent dies
//
// Useful if user doesn't kill the bpftrace process group
if (prctl(PR_SET_PDEATHSIG, SIGTERM))
perror("prctl(PR_SET_PDEATHSIG)");
if (execve(argv[0], argv, environ))
{
perror("execve");
return -1;
}
}
else if (ret > 0)
{
return ret;
}
else
{
perror("fork");
return -1;
}
return -1; // silence end of control compiler warning
}
std::string BPFtrace::hist_index_label(int power)
{
char suffix = '\0';
......@@ -1477,6 +1559,12 @@ bool BPFtrace::is_pid_alive(int pid)
throw std::runtime_error("failed to snprintf");
}
// Do a nonblocking wait on the pid just in case it's our child and it
// has exited. We don't really care about any errors, we're just trying
// to make a best effort.
int status;
waitpid(pid, &status, WNOHANG);
int fd = open(buf, 0, O_RDONLY);
if (fd < 0 && errno == ENOENT)
{
......
......@@ -50,7 +50,7 @@ class BPFtrace
{
public:
BPFtrace() : ncpus_(ebpf::get_possible_cpus().size()) { }
virtual ~BPFtrace() { }
virtual ~BPFtrace();
virtual int add_probe(ast::Probe &p);
int num_probes() const;
int run(std::unique_ptr<BpfOrc> bpforc);
......@@ -68,6 +68,7 @@ public:
std::string resolve_probe(uint64_t probe_id);
uint64_t resolve_cgroupid(const std::string &path);
std::vector<uint64_t> get_arg_values(std::vector<Field> args, uint8_t* arg_data);
std::string cmd_;
int pid_{0};
std::map<std::string, std::unique_ptr<IMap>> maps_;
......@@ -97,6 +98,7 @@ private:
std::map<int, void *> pid_sym_;
int ncpus_;
int online_cpus_;
std::vector<int> child_pids_;
std::unique_ptr<AttachedProbe> attach_probe(Probe &probe, const BpfOrc &bpforc);
int setup_perf_events();
......@@ -118,6 +120,7 @@ private:
static std::string lhist_index_label(int number);
static std::vector<std::string> split_string(std::string &str, char split_by);
std::vector<uint8_t> find_empty_key(IMap &map, size_t size) const;
static int spawn_child(const std::vector<std::string>& args);
static bool is_pid_alive(int pid);
};
......
......@@ -25,7 +25,8 @@ void usage()
std::cerr << " -e 'program' execute this program" << std::endl;
std::cerr << " -h show this help message" << std::endl;
std::cerr << " -l [search] list probes" << std::endl;
std::cerr << " -p PID PID for enabling USDT probes" << std::endl;
std::cerr << " -p PID enable USDT probes on PID" << std::endl;
std::cerr << " -c 'CMD' run CMD and enable USDT probes on resulting process" << std::endl;
std::cerr << " -v verbose messages" << std::endl << std::endl;
std::cerr << "EXAMPLES:" << std::endl;
std::cerr << "bpftrace -l '*sleep*'" << std::endl;
......@@ -56,12 +57,13 @@ int main(int argc, char *argv[])
{
int err;
Driver driver;
char *pid_str = NULL;
char *pid_str = nullptr;
char *cmd_str = nullptr;
bool listing = false;
std::string script, search;
int c;
while ((c = getopt(argc, argv, "de:hlp:v")) != -1)
while ((c = getopt(argc, argv, "de:hlp:vc:")) != -1)
{
switch (c)
{
......@@ -84,6 +86,9 @@ int main(int argc, char *argv[])
case 'l':
listing = true;
break;
case 'c':
cmd_str = optarg;
break;
default:
usage();
return 1;
......@@ -97,6 +102,13 @@ int main(int argc, char *argv[])
return 1;
}
if (cmd_str && pid_str)
{
std::cerr << "USAGE: Cannot use both -c and -p." << std::endl;
usage();
return 1;
}
// Listing probes
if (listing)
{
......@@ -152,6 +164,9 @@ int main(int argc, char *argv[])
if (pid_str)
bpftrace.pid_ = atoi(pid_str);
if (cmd_str)
bpftrace.cmd_ = cmd_str;
TracepointFormatParser::parse(driver.root_);
if (bt_debug != DebugLevel::kNone)
......
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