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)
attached_probes_.clear();
END_trigger();
poll_perf_events(epollfd, 100);
poll_perf_events(epollfd, true);
special_attached_probes_.clear();
return 0;
......@@ -490,13 +490,17 @@ int BPFtrace::setup_perf_events()
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_);
while (true)
{
int ready = epoll_wait(epollfd, events.data(), online_cpus_, timeout);
if (ready <= 0)
int ready = epoll_wait(epollfd, events.data(), online_cpus_, 100);
// 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;
}
......@@ -505,6 +509,17 @@ void BPFtrace::poll_perf_events(int epollfd, int timeout)
{
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;
}
......@@ -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
......@@ -68,7 +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);
int pid_;
int pid_{0};
std::map<std::string, std::unique_ptr<IMap>> maps_;
std::map<std::string, Struct> structs_;
......@@ -100,7 +100,7 @@ private:
std::unique_ptr<AttachedProbe> attach_probe(Probe &probe, const BpfOrc &bpforc);
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 zero_map(IMap &map);
int print_map(IMap &map, uint32_t top, uint32_t div);
......@@ -118,6 +118,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 bool is_pid_alive(int pid);
};
} // namespace bpftrace
......@@ -149,7 +149,6 @@ int main(int argc, char *argv[])
// 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.
// - provide PID in USDT probe specification as a way to override -p.
bpftrace.pid_ = 0;
if (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