Commit 54909d3e authored by Daniel Xu's avatar Daniel Xu Committed by Matheus Marchini

Terminate bpftrace when traced PID terminates

If the user provides a specific PID to trace, it doesn't really make
sense to keep running if the tracee terminates. This patch makes
bpftrace exit cleanly if the tracee terminates.

I spent quite a bit of time thinking about the generic problem of
figuring out when an arbitrary pid terminates. After some experiments,
here is what I've learned:
* wait(2) and waitpid(2) can only wait on child processes (duh)
* epoll(2) does not support procfs (or other pseudo filesystems)
* inotify does not support procfs either b/c procfs changes are not made
through the filesystem (by another userspace entity)
* ptrace with PTRACE_SEIZE might work but might have extra overhead on
the tracee
* the netlink interface for process state changes seems a bit overkill
* the only sane solution (AFAICT) is to poll /proc/PID/ for changes

Thus, I've made some minor changes to the main event loop to support
polling procfs.

Test Plan:
Make sure non-pid-specific tracing still works:
```
$ sudo ./build/src/bpftrace -e 'uretprobe:/bin/bash:readline { printf("read a line\n"); }'
[sudo] password for dlxu:
Attaching 1 probe...
read a line
read a line
read a line
read a line
read a line
read a line
read a line
read a line
^C

```

Verify pid-specific tracing (ie usdt) exits on tracee termination:
```
// in window 1
$ ./python -q

// in window 2
$ sudo ~/dev/bpftrace/build/src/bpftrace -p $(pidof python) -e 'usdt:/home/dlxu/dev/cpython/python:function__entry { printf("%s %s\n", str(arg0), str(arg1)) }'
[sudo] password for dlxu:
Attaching 1 probe...
<stdin> <module>

// in window 1
>>> print('wow')
wow
>>>

// verify bpftrace has exited in window 2

```
parent e653297a
...@@ -448,7 +448,7 @@ int BPFtrace::run(std::unique_ptr<BpfOrc> bpforc) ...@@ -448,7 +448,7 @@ int BPFtrace::run(std::unique_ptr<BpfOrc> bpforc)
attached_probes_.clear(); attached_probes_.clear();
END_trigger(); END_trigger();
poll_perf_events(epollfd, 100); poll_perf_events(epollfd, true);
special_attached_probes_.clear(); special_attached_probes_.clear();
return 0; return 0;
...@@ -490,13 +490,17 @@ int BPFtrace::setup_perf_events() ...@@ -490,13 +490,17 @@ int BPFtrace::setup_perf_events()
return epollfd; return epollfd;
} }
void BPFtrace::poll_perf_events(int epollfd, int timeout) void BPFtrace::poll_perf_events(int epollfd, bool drain)
{ {
auto events = std::vector<struct epoll_event>(online_cpus_); auto events = std::vector<struct epoll_event>(online_cpus_);
while (true) while (true)
{ {
int ready = epoll_wait(epollfd, events.data(), online_cpus_, timeout); int ready = epoll_wait(epollfd, events.data(), online_cpus_, 100);
if (ready <= 0)
// Return if either
// * epoll_wait has encountered an error (eg signal delivery)
// * There's no events left and we've been instructed to drain
if (ready < 0 || (ready == 0 && drain))
{ {
return; return;
} }
...@@ -505,6 +509,17 @@ void BPFtrace::poll_perf_events(int epollfd, int timeout) ...@@ -505,6 +509,17 @@ void BPFtrace::poll_perf_events(int epollfd, int timeout)
{ {
perf_reader_event_read((perf_reader*)events[i].data.ptr); perf_reader_event_read((perf_reader*)events[i].data.ptr);
} }
// If we are tracing a specific pid and it has exited, we should exit
// as well b/c otherwise we'd be tracing nothing.
//
// Note that there technically is a race with a new process using the
// same pid, but we're polling at 100ms and it would be unlikely that
// the pids wrap around that fast.
if (pid_ > 0 && !is_pid_alive(pid_))
{
return;
}
} }
return; return;
} }
...@@ -1453,4 +1468,23 @@ void BPFtrace::sort_by_key(std::vector<SizedType> key_args, ...@@ -1453,4 +1468,23 @@ void BPFtrace::sort_by_key(std::vector<SizedType> key_args,
} }
} }
bool BPFtrace::is_pid_alive(int pid)
{
char buf[256];
int ret = snprintf(buf, sizeof(buf), "/proc/%d/status", pid);
if (ret < 0)
{
throw std::runtime_error("failed to snprintf");
}
int fd = open(buf, 0, O_RDONLY);
if (fd < 0 && errno == ENOENT)
{
return false;
}
close(fd);
return true;
}
} // namespace bpftrace } // namespace bpftrace
...@@ -68,7 +68,7 @@ public: ...@@ -68,7 +68,7 @@ public:
std::string resolve_probe(uint64_t probe_id); std::string resolve_probe(uint64_t probe_id);
uint64_t resolve_cgroupid(const std::string &path); uint64_t resolve_cgroupid(const std::string &path);
std::vector<uint64_t> get_arg_values(std::vector<Field> args, uint8_t* arg_data); std::vector<uint64_t> get_arg_values(std::vector<Field> args, uint8_t* arg_data);
int pid_; int pid_{0};
std::map<std::string, std::unique_ptr<IMap>> maps_; std::map<std::string, std::unique_ptr<IMap>> maps_;
std::map<std::string, Struct> structs_; std::map<std::string, Struct> structs_;
...@@ -100,7 +100,7 @@ private: ...@@ -100,7 +100,7 @@ private:
std::unique_ptr<AttachedProbe> attach_probe(Probe &probe, const BpfOrc &bpforc); std::unique_ptr<AttachedProbe> attach_probe(Probe &probe, const BpfOrc &bpforc);
int setup_perf_events(); int setup_perf_events();
void poll_perf_events(int epollfd, int timeout=-1); void poll_perf_events(int epollfd, bool drain=false);
int clear_map(IMap &map); int clear_map(IMap &map);
int zero_map(IMap &map); int zero_map(IMap &map);
int print_map(IMap &map, uint32_t top, uint32_t div); int print_map(IMap &map, uint32_t top, uint32_t div);
...@@ -118,6 +118,7 @@ private: ...@@ -118,6 +118,7 @@ private:
static std::string lhist_index_label(int number); static std::string lhist_index_label(int number);
static std::vector<std::string> split_string(std::string &str, char split_by); 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; std::vector<uint8_t> find_empty_key(IMap &map, size_t size) const;
static bool is_pid_alive(int pid);
}; };
} // namespace bpftrace } // namespace bpftrace
...@@ -149,7 +149,6 @@ int main(int argc, char *argv[]) ...@@ -149,7 +149,6 @@ int main(int argc, char *argv[])
// PID is currently only used for USDT probes that need enabling. Future work: // PID is currently only used for USDT probes that need enabling. Future work:
// - make PID a filter for all probe types: pass to perf_event_open(), etc. // - make PID a filter for all probe types: pass to perf_event_open(), etc.
// - provide PID in USDT probe specification as a way to override -p. // - provide PID in USDT probe specification as a way to override -p.
bpftrace.pid_ = 0;
if (pid_str) if (pid_str)
bpftrace.pid_ = atoi(pid_str); bpftrace.pid_ = atoi(pid_str);
......
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