platform profiler signal: add traced_perf codepath
This patch adds a case for the profiling signal handler (previously just
for native heapprofd profiling) when si_value == 1, corresponding to
traced_perf being the requesting party.
The handler opens /proc/self/{maps,mem}, connects to (init-created)
/dev/socket/traced_perf, and then sends the fds over the socket.
Everything happens synchronously within the signal handler. Socket is
made non-blocking, and we do not retry.
Bug: 144281346
Change-Id: Iea904694caeefe317ed8818e5b150e8819af91c2
diff --git a/libc/bionic/android_profiling_dynamic.cpp b/libc/bionic/android_profiling_dynamic.cpp
index dd87ed8..183a614 100644
--- a/libc/bionic/android_profiling_dynamic.cpp
+++ b/libc/bionic/android_profiling_dynamic.cpp
@@ -30,11 +30,19 @@
#error This file should not be compiled for static targets.
#endif
+#include <fcntl.h>
#include <signal.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
#include <async_safe/log.h>
#include <platform/bionic/malloc.h>
#include <platform/bionic/reserved_signals.h>
+#include <private/ErrnoRestorer.h>
+#include <private/ScopedFd.h>
#include "malloc_heapprofd.h"
@@ -42,7 +50,9 @@
// platform's profilers. The accompanying signal value discriminates between
// specific requestors:
// 0: heapprofd heap profiler.
+// 1: traced_perf perf profiler.
static constexpr int kHeapprofdSignalValue = 0;
+static constexpr int kTracedPerfSignalValue = 1;
static void HandleProfilingSignal(int, siginfo_t*, void*);
@@ -54,12 +64,18 @@
sigaction(BIONIC_SIGNAL_PROFILER, &action, nullptr);
}
+static void HandleTracedPerfSignal();
+
static void HandleProfilingSignal(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) {
- if (info->si_code != SI_QUEUE)
+ // Avoid clobbering errno.
+ ErrnoRestorer errno_restorer;
+
+ if (info->si_code != SI_QUEUE) {
return;
+ }
int signal_value = info->si_value.sival_int;
- async_safe_format_log(ANDROID_LOG_WARN, "libc", "%s: received profiling signal with si_value: %d",
+ async_safe_format_log(ANDROID_LOG_INFO, "libc", "%s: received profiling signal with si_value: %d",
getprogname(), signal_value);
// Proceed only if the process is considered profileable.
@@ -72,8 +88,65 @@
if (signal_value == kHeapprofdSignalValue) {
HandleHeapprofdSignal();
+ } else if (signal_value == kTracedPerfSignalValue) {
+ HandleTracedPerfSignal();
} else {
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "unrecognized profiling signal si_value: %d",
signal_value);
}
}
+
+// Open /proc/self/{maps,mem}, connect to traced_perf, send the fds over the
+// socket. Everything happens synchronously within the signal handler. Socket
+// is made non-blocking, and we do not retry.
+static void HandleTracedPerfSignal() {
+ ScopedFd sock_fd{ socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0 /*protocol*/) };
+ if (sock_fd.get() == -1) {
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to create socket: %s", strerror(errno));
+ return;
+ }
+
+ sockaddr_un saddr{ AF_UNIX, "/dev/socket/traced_perf" };
+ size_t addrlen = sizeof(sockaddr_un);
+ if (connect(sock_fd.get(), reinterpret_cast<const struct sockaddr*>(&saddr), addrlen) == -1) {
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to connect to traced_perf socket: %s",
+ strerror(errno));
+ return;
+ }
+
+ ScopedFd maps_fd{ open("/proc/self/maps", O_RDONLY | O_CLOEXEC) };
+ if (maps_fd.get() == -1) {
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open /proc/self/maps: %s",
+ strerror(errno));
+ return;
+ }
+ ScopedFd mem_fd{ open("/proc/self/mem", O_RDONLY | O_CLOEXEC) };
+ if (mem_fd.get() == -1) {
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open /proc/self/mem: %s",
+ strerror(errno));
+ return;
+ }
+
+ // Send 1 byte with auxiliary data carrying two fds.
+ int send_fds[2] = { maps_fd.get(), mem_fd.get() };
+ int num_fds = 2;
+ char iobuf[1] = {};
+ msghdr msg_hdr = {};
+ iovec iov = { reinterpret_cast<void*>(iobuf), sizeof(iobuf) };
+ msg_hdr.msg_iov = &iov;
+ msg_hdr.msg_iovlen = 1;
+ alignas(cmsghdr) char control_buf[256] = {};
+ const auto raw_ctl_data_sz = num_fds * sizeof(int);
+ const size_t control_buf_len = static_cast<size_t>(CMSG_SPACE(raw_ctl_data_sz));
+ msg_hdr.msg_control = control_buf;
+ msg_hdr.msg_controllen = control_buf_len; // used by CMSG_FIRSTHDR
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg_hdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = static_cast<size_t>(CMSG_LEN(raw_ctl_data_sz));
+ memcpy(CMSG_DATA(cmsg), send_fds, num_fds * sizeof(int));
+
+ if (sendmsg(sock_fd.get(), &msg_hdr, 0) == -1) {
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to sendmsg: %s", strerror(errno));
+ }
+}