summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2019-01-10 15:35:39 -0800
committerYabin Cui <yabinc@google.com>2019-01-23 16:27:00 -0800
commit1a30a5878984bee10aee06e674a7f5cc0269d9ef (patch)
tree73488effe90c67f4902ad462f2bca4eec8d986a2 /simpleperf
parent918f046c3972527f5e8ac606bd11d1ab180f9524 (diff)
downloadextras-1a30a5878984bee10aee06e674a7f5cc0269d9ef.tar.gz
simpleperf: Use simpleperf_app_runner to profile profileable apps.
Simpleperf uses run-as to run in apps' context to profile debuggable apps. In Android Q, we want to profile <profileable shell="true"> apps. To support that, do below changes: 1. Add simpleperf_app_runner, which is similar to run-as, but is limited to only run simpleperf commands in profileable apps. 2. Add code using simpleperf_app_runner inside simpleperf, so it doesn't change current interface of using simpleperf. Bug: 118835348 Test: run simpleperf manually. Test: run simpleperf_unit_test. Change-Id: I85a8e3c80fe0e3ccdee97de38be968cbccd1d263
Diffstat (limited to 'simpleperf')
-rw-r--r--simpleperf/cmd_record.cpp58
-rw-r--r--simpleperf/cmd_stat.cpp42
-rw-r--r--simpleperf/environment.cpp202
-rw-r--r--simpleperf/simpleperf_app_runner/Android.bp28
-rw-r--r--simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp214
5 files changed, 472 insertions, 72 deletions
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index 82b734c0..0de8ed32 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -32,6 +32,7 @@
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#if defined(__ANDROID__)
#include <android-base/properties.h>
#endif
@@ -218,6 +219,8 @@ class RecordCommand : public Command {
// Below options are only used internally and shouldn't be visible to the public.
"--in-app We are already running in the app's context.\n"
"--tracepoint-events file_name Read tracepoint events from [file_name] instead of tracefs.\n"
+"--out-fd <fd> Write perf.data to a file descriptor.\n"
+"--stop-signal-fd <fd> Stop recording when fd is readable.\n"
#endif
// clang-format on
),
@@ -310,7 +313,9 @@ class RecordCommand : public Command {
ThreadTree thread_tree_;
std::string record_filename_;
+ android::base::unique_fd out_fd_;
std::unique_ptr<RecordFileWriter> record_file_writer_;
+ android::base::unique_fd stop_signal_fd_;
uint64_t sample_record_count_;
uint64_t lost_record_count_;
@@ -477,14 +482,21 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
return false;
}
IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
- if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM},
- [loop]() { return loop->ExitLoop(); })) {
+ auto exit_loop_callback = [loop]() {
+ return loop->ExitLoop();
+ };
+ if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM}, exit_loop_callback)) {
return false;
}
// Only add an event for SIGHUP if we didn't inherit SIG_IGN (e.g. from nohup).
if (!SignalIsIgnored(SIGHUP)) {
- if (!loop->AddSignalEvent(SIGHUP, [loop]() { return loop->ExitLoop(); })) {
+ if (!loop->AddSignalEvent(SIGHUP, exit_loop_callback)) {
+ return false;
+ }
+ }
+ if (stop_signal_fd_ != -1) {
+ if (!loop->AddReadEvent(stop_signal_fd_, exit_loop_callback)) {
return false;
}
}
@@ -545,6 +557,31 @@ bool RecordCommand::DoRecording(Workload* workload) {
return true;
}
+static bool WriteRecordDataToOutFd(const std::string& in_filename, android::base::unique_fd out_fd) {
+ android::base::unique_fd in_fd(FileHelper::OpenReadOnly(in_filename));
+ if (in_fd == -1) {
+ PLOG(ERROR) << "Failed to open " << in_filename;
+ return false;
+ }
+ char buf[8192];
+ while (true) {
+ ssize_t n = TEMP_FAILURE_RETRY(read(in_fd, buf, sizeof(buf)));
+ if (n < 0) {
+ PLOG(ERROR) << "Failed to read " << in_filename;
+ return false;
+ }
+ if (n == 0) {
+ break;
+ }
+ if (!android::base::WriteFully(out_fd, buf, n)) {
+ PLOG(ERROR) << "Failed to write to out_fd";
+ return false;
+ }
+ }
+ unlink(in_filename.c_str());
+ return true;
+}
+
bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
// 1. Post unwind dwarf callchain.
if (unwind_dwarf_callchain_ && post_unwind_) {
@@ -565,6 +602,9 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) {
if (!record_file_writer_->Close()) {
return false;
}
+ if (out_fd_ != -1 && !WriteRecordDataToOutFd(record_filename_, std::move(out_fd_))) {
+ return false;
+ }
time_stat_.post_process_time = GetSystemClock();
// 4. Show brief record result.
@@ -783,6 +823,12 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
return false;
}
record_filename_ = args[i];
+ } else if (args[i] == "--out-fd") {
+ int fd;
+ if (!GetUintOption(args, &i, &fd)) {
+ return false;
+ }
+ out_fd_.reset(fd);
} else if (args[i] == "-p") {
if (!NextArgumentOrError(args, &i)) {
return false;
@@ -810,6 +856,12 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
if (!GetUintOption(args, &i, &start_profiling_fd_)) {
return false;
}
+ } else if (args[i] == "--stop-signal-fd") {
+ int fd;
+ if (!GetUintOption(args, &i, &fd)) {
+ return false;
+ }
+ stop_signal_fd_.reset(fd);
} else if (args[i] == "--symfs") {
if (!NextArgumentOrError(args, &i)) {
return false;
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index 7deb1174..c9a153c1 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -29,6 +29,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include "command.h"
#include "environment.h"
@@ -338,6 +339,8 @@ class StatCommand : public Command {
// Below options are only used internally and shouldn't be visible to the public.
"--in-app We are already running in the app's context.\n"
"--tracepoint-events file_name Read tracepoint events from [file_name] instead of tracefs.\n"
+"--out-fd <fd> Write output to a file descriptor.\n"
+"--stop-signal-fd <fd> Stop stating when fd is readable.\n"
#endif
// clang-format on
),
@@ -375,9 +378,11 @@ class StatCommand : public Command {
std::vector<int> cpus_;
EventSelectionSet event_selection_set_;
std::string output_filename_;
+ android::base::unique_fd out_fd_;
bool csv_;
std::string app_package_name_;
bool in_app_context_;
+ android::base::unique_fd stop_signal_fd_;
};
bool StatCommand::Run(const std::vector<std::string>& args) {
@@ -438,15 +443,20 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
return false;
}
std::unique_ptr<FILE, decltype(&fclose)> fp_holder(nullptr, fclose);
- FILE* fp = stdout;
if (!output_filename_.empty()) {
- fp_holder.reset(fopen(output_filename_.c_str(), "w"));
+ fp_holder.reset(fopen(output_filename_.c_str(), "we"));
if (fp_holder == nullptr) {
PLOG(ERROR) << "failed to open " << output_filename_;
return false;
}
- fp = fp_holder.get();
+ } else if (out_fd_ != -1) {
+ fp_holder.reset(fdopen(out_fd_.release(), "we"));
+ if (fp_holder == nullptr) {
+ PLOG(ERROR) << "failed to write output.";
+ return false;
+ }
}
+ FILE* fp = fp_holder ? fp_holder.get() : stdout;
// 4. Add signal/periodic Events.
IOEventLoop* loop = event_selection_set_.GetIOEventLoop();
@@ -465,13 +475,19 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
if (need_to_check_targets && !event_selection_set_.StopWhenNoMoreTargets()) {
return false;
}
- if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM, SIGHUP},
- [&]() { return loop->ExitLoop(); })) {
+ auto exit_loop_callback = [loop]() {
+ return loop->ExitLoop();
+ };
+ if (!loop->AddSignalEvents({SIGCHLD, SIGINT, SIGTERM, SIGHUP}, exit_loop_callback)) {
return false;
}
+ if (stop_signal_fd_ != -1) {
+ if (!loop->AddReadEvent(stop_signal_fd_, exit_loop_callback)) {
+ return false;
+ }
+ }
if (duration_in_sec_ != 0) {
- if (!loop->AddPeriodicEvent(SecondToTimeval(duration_in_sec_),
- [&]() { return loop->ExitLoop(); })) {
+ if (!loop->AddPeriodicEvent(SecondToTimeval(duration_in_sec_), exit_loop_callback)) {
return false;
}
}
@@ -569,6 +585,12 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
return false;
}
output_filename_ = args[i];
+ } else if (args[i] == "--out-fd") {
+ int fd;
+ if (!GetUintOption(args, &i, &fd)) {
+ return false;
+ }
+ out_fd_.reset(fd);
} else if (args[i] == "-p") {
if (!NextArgumentOrError(args, &i)) {
return false;
@@ -578,6 +600,12 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
return false;
}
event_selection_set_.AddMonitoredProcesses(pids);
+ } else if (args[i] == "--stop-signal-fd") {
+ int fd;
+ if (!GetUintOption(args, &i, &fd)) {
+ return false;
+ }
+ stop_signal_fd_.reset(fd);
} else if (args[i] == "-t") {
if (!NextArgumentOrError(args, &i)) {
return false;
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 507859ec..8d18b526 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -622,119 +622,197 @@ std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
}
}
-class ScopedFile {
- public:
- ScopedFile(const std::string& filepath, const std::string& app_package_name = "")
- : filepath_(filepath), app_package_name_(app_package_name) {}
+namespace {
- ~ScopedFile() {
- if (app_package_name_.empty()) {
- unlink(filepath_.c_str());
- } else {
- Workload::RunCmd({"run-as", app_package_name_, "rm", "-rf", filepath_});
+class InAppRunner {
+ public:
+ InAppRunner(const std::string& package_name) : package_name_(package_name) {}
+ virtual ~InAppRunner() {
+ if (!tracepoint_file_.empty()) {
+ unlink(tracepoint_file_.c_str());
}
}
+ virtual bool Prepare() = 0;
+ bool RunCmdInApp(const std::string& cmd, const std::vector<std::string>& args,
+ size_t workload_args_size, const std::string& output_filepath,
+ bool need_tracepoint_events);
+ protected:
+ virtual std::vector<std::string> GetPrefixArgs(const std::string& cmd) = 0;
- private:
- std::string filepath_;
- std::string app_package_name_;
+ const std::string package_name_;
+ std::string tracepoint_file_;
};
-bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
- const std::vector<std::string>& args, size_t workload_args_size,
- const std::string& output_filepath, bool need_tracepoint_events) {
- // 1. Test if the package exists.
- if (!Workload::RunCmd({"run-as", app_package_name, "echo", ">/dev/null"}, false)) {
- LOG(ERROR) << "Package " << app_package_name << " doesn't exist or isn't debuggable.";
- return false;
- }
-
- // 2. Copy simpleperf binary to the package. Create tracepoint_file if needed.
- std::string simpleperf_path;
- if (!android::base::Readlink("/proc/self/exe", &simpleperf_path)) {
- PLOG(ERROR) << "ReadLink failed";
- return false;
- }
- if (!Workload::RunCmd({"run-as", app_package_name, "cp", simpleperf_path, "simpleperf"})) {
- return false;
- }
- ScopedFile scoped_simpleperf("simpleperf", app_package_name);
- std::unique_ptr<ScopedFile> scoped_tracepoint_file;
- const std::string tracepoint_file = "/data/local/tmp/tracepoint_events";
+bool InAppRunner::RunCmdInApp(const std::string& cmd, const std::vector<std::string>& cmd_args,
+ size_t workload_args_size, const std::string& output_filepath,
+ bool need_tracepoint_events) {
+ // 1. Build cmd args running in app's context.
+ std::vector<std::string> args = GetPrefixArgs(cmd);
+ args.insert(args.end(), {"--in-app", "--log", GetLogSeverityName()});
if (need_tracepoint_events) {
// Since we can't read tracepoint events from tracefs in app's context, we need to prepare
// them in tracepoint_file in shell's context, and pass the path of tracepoint_file to the
// child process using --tracepoint-events option.
+ const std::string tracepoint_file = "/data/local/tmp/tracepoint_events";
if (!android::base::WriteStringToFile(GetTracepointEvents(), tracepoint_file)) {
PLOG(ERROR) << "Failed to store tracepoint events";
return false;
}
- scoped_tracepoint_file.reset(new ScopedFile(tracepoint_file));
+ tracepoint_file_ = tracepoint_file;
+ args.insert(args.end(), {"--tracepoint-events", tracepoint_file_});
}
- // 3. Prepare to start child process to profile.
- std::string output_basename = output_filepath.empty() ? "" :
- android::base::Basename(output_filepath);
- std::vector<std::string> new_args =
- {"run-as", app_package_name, "./simpleperf", cmd, "--in-app", "--log", GetLogSeverityName()};
- if (need_tracepoint_events) {
- new_args.push_back("--tracepoint-events");
- new_args.push_back(tracepoint_file);
+ android::base::unique_fd out_fd;
+ if (!output_filepath.empty()) {
+ // A process running in app's context can't open a file outside it's data directory to write.
+ // So pass it a file descriptor to write.
+ out_fd = FileHelper::OpenWriteOnly(output_filepath);
+ if (out_fd == -1) {
+ PLOG(ERROR) << "Failed to open " << output_filepath;
+ return false;
+ }
+ args.insert(args.end(), {"--out-fd", std::to_string(int(out_fd))});
}
- for (size_t i = 0; i < args.size(); ++i) {
- if (i >= args.size() - workload_args_size || args[i] != "-o") {
- new_args.push_back(args[i]);
- } else {
- new_args.push_back(args[i++]);
- new_args.push_back(output_basename);
+
+ // We can't send signal to a process running in app's context. So use a pipe file to send stop
+ // signal.
+ android::base::unique_fd stop_signal_rfd;
+ android::base::unique_fd stop_signal_wfd;
+ if (!android::base::Pipe(&stop_signal_rfd, &stop_signal_wfd, 0)) {
+ PLOG(ERROR) << "pipe";
+ return false;
+ }
+ args.insert(args.end(), {"--stop-signal-fd", std::to_string(int(stop_signal_rfd))});
+
+ for (size_t i = 0; i < cmd_args.size(); ++i) {
+ if (i < cmd_args.size() - workload_args_size) {
+ // Omit "-o output_file". It is replaced by "--out-fd fd".
+ if (cmd_args[i] == "-o" || cmd_args[i] == "--app") {
+ i++;
+ continue;
+ }
}
+ args.push_back(cmd_args[i]);
+ }
+ char* argv[args.size() + 1];
+ for (size_t i = 0; i < args.size(); ++i) {
+ argv[i] = &args[i][0];
}
- std::unique_ptr<Workload> workload = Workload::CreateWorkload(new_args);
+ argv[args.size()] = nullptr;
+
+ // 2. Run child process in app's context.
+ auto ChildProcFn = [&]() {
+ stop_signal_wfd.reset();
+ execvp(argv[0], argv);
+ exit(1);
+ };
+ std::unique_ptr<Workload> workload = Workload::CreateWorkload(ChildProcFn);
if (!workload) {
return false;
}
+ stop_signal_rfd.reset();
+ // Wait on signals.
IOEventLoop loop;
- bool need_to_kill_child = false;
+ bool need_to_stop_child = false;
std::vector<int> stop_signals = {SIGINT, SIGTERM};
if (!SignalIsIgnored(SIGHUP)) {
stop_signals.push_back(SIGHUP);
}
if (!loop.AddSignalEvents(stop_signals,
- [&]() { need_to_kill_child = true; return loop.ExitLoop(); })) {
+ [&]() { need_to_stop_child = true; return loop.ExitLoop(); })) {
return false;
}
if (!loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); })) {
return false;
}
- // 4. Create child process to run run-as, and wait for the child process.
if (!workload->Start()) {
return false;
}
if (!loop.RunLoop()) {
return false;
}
- if (need_to_kill_child) {
- // The child process can exit before we kill it, so don't report kill errors.
- Workload::RunCmd({"run-as", app_package_name, "pkill", "simpleperf"}, false);
+ if (need_to_stop_child) {
+ stop_signal_wfd.reset();
}
int exit_code;
if (!workload->WaitChildProcess(&exit_code) || exit_code != 0) {
return false;
}
+ return true;
+}
- // 5. If there is any output file, copy it from the app's directory.
- if (!output_filepath.empty()) {
- if (!Workload::RunCmd({"run-as", app_package_name, "cat", output_basename,
- ">" + output_filepath})) {
- return false;
+class RunAs : public InAppRunner {
+ public:
+ RunAs(const std::string& package_name) : InAppRunner(package_name) {}
+ virtual ~RunAs() {
+ if (simpleperf_copied_in_app_) {
+ Workload::RunCmd({"run-as", package_name_, "rm", "-rf", "simpleperf"});
}
- if (!Workload::RunCmd({"run-as", app_package_name, "rm", output_basename})) {
+ }
+ bool Prepare() override;
+
+ protected:
+ std::vector<std::string> GetPrefixArgs(const std::string& cmd) {
+ return {"run-as", package_name_,
+ simpleperf_copied_in_app_ ? "./simpleperf" : simpleperf_path_, cmd,
+ "--app", package_name_};
+ }
+
+ bool simpleperf_copied_in_app_ = false;
+ std::string simpleperf_path_;
+};
+
+bool RunAs::Prepare() {
+ // Test if run-as can access the package.
+ if (!Workload::RunCmd({"run-as", package_name_, "echo", ">/dev/null", "2>/dev/null"}, false)) {
+ return false;
+ }
+ // run-as can't run /data/local/tmp/simpleperf directly. So copy simpleperf binary if needed.
+ if (!android::base::Readlink("/proc/self/exe", &simpleperf_path_)) {
+ PLOG(ERROR) << "ReadLink failed";
+ return false;
+ }
+ if (android::base::StartsWith(simpleperf_path_, "/system")) {
+ return true;
+ }
+ if (!Workload::RunCmd({"run-as", package_name_, "cp", simpleperf_path_, "simpleperf"})) {
+ return false;
+ }
+ simpleperf_copied_in_app_ = true;
+ return true;
+}
+
+class SimpleperfAppRunner : public InAppRunner {
+ public:
+ SimpleperfAppRunner(const std::string& package_name) : InAppRunner(package_name) {}
+ bool Prepare() override {
+ return GetAndroidVersion() >= kAndroidVersionP + 1;
+ }
+
+ protected:
+ std::vector<std::string> GetPrefixArgs(const std::string& cmd) {
+ return {"simpleperf_app_runner", package_name_, cmd};
+ }
+};
+
+} // namespace
+
+bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
+ const std::vector<std::string>& args, size_t workload_args_size,
+ const std::string& output_filepath, bool need_tracepoint_events) {
+ std::unique_ptr<InAppRunner> in_app_runner(new RunAs(app_package_name));
+ if (!in_app_runner->Prepare()) {
+ in_app_runner.reset(new SimpleperfAppRunner(app_package_name));
+ if (!in_app_runner->Prepare()) {
+ LOG(ERROR) << "Package " << app_package_name
+ << " doesn't exist or isn't debuggable/profileable.";
return false;
}
}
- return true;
+ return in_app_runner->RunCmdInApp(cmd, args, workload_args_size, output_filepath,
+ need_tracepoint_events);
}
static std::string default_package_name;
diff --git a/simpleperf/simpleperf_app_runner/Android.bp b/simpleperf/simpleperf_app_runner/Android.bp
new file mode 100644
index 00000000..389421b6
--- /dev/null
+++ b/simpleperf/simpleperf_app_runner/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.
+//
+
+cc_binary {
+ name: "simpleperf_app_runner",
+ srcs: [
+ "simpleperf_app_runner.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libselinux",
+ "libpackagelistparser",
+ "libminijail",
+ ],
+}
diff --git a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
new file mode 100644
index 00000000..a5b1656e
--- /dev/null
+++ b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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 <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <packagelistparser/packagelistparser.h>
+#include <private/android_filesystem_config.h>
+#include <scoped_minijail.h>
+#include <selinux/android.h>
+
+// simpleperf_app_runner is used to run simpleperf to profile apps with <profileable shell="true">
+// on user devices. It works as below:
+// simpleperf cmds in shell -> simpleperf_app_runner -> /system/bin/simpleperf in app's context
+//
+// 1. User types simpleperf cmds in adb shell. If that is to profile an app, simpleperf calls
+// /system/bin/simpleperf_app_runner with profiling arguments.
+// 2. simpleperf_app_runner checks if the app is profileable_from_shell. Then it switches the
+// process to the app's user id / group id, switches secontext to the app's domain, and
+// executes /system/bin/simpleperf with profiling arguments.
+// 3. /system/bin/simpleperf records profiling data and writes profiling data to a file descriptor
+// opened by simpleperf cmds in shell.
+
+struct PackageListCallbackArg {
+ const char* name;
+ pkg_info* info;
+};
+
+static bool PackageListParseCallback(pkg_info* info, void* userdata) {
+ PackageListCallbackArg* arg = static_cast<PackageListCallbackArg*>(userdata);
+ if (strcmp(arg->name, info->name) == 0) {
+ arg->info = info;
+ return false;
+ }
+ packagelist_free(info);
+ return true;
+}
+
+pkg_info* ReadPackageInfo(const char* pkgname) {
+ // Switch to package_info gid to read package info.
+ gid_t old_egid = getegid();
+ if (setegid(AID_PACKAGE_INFO) == -1) {
+ error(1, errno, "setegid failed");
+ }
+ PackageListCallbackArg arg;
+ arg.name = pkgname;
+ arg.info = nullptr;
+ if (!packagelist_parse(PackageListParseCallback, &arg)) {
+ error(1, errno, "packagelist_parse failed");
+ }
+ if (setegid(old_egid) == -1) {
+ error(1, errno, "setegid failed");
+ }
+ return arg.info;
+}
+
+std::vector<gid_t> GetSupplementaryGids(uid_t userAppId) {
+ std::vector<gid_t> gids;
+ int size = getgroups(0, &gids[0]);
+ if (size < 0) {
+ error(1, errno, "getgroups failed");
+ }
+ gids.resize(size);
+ size = getgroups(size, &gids[0]);
+ if (size != static_cast<int>(gids.size())) {
+ error(1, errno, "getgroups failed");
+ }
+ // Profile guide compiled oat files (like /data/app/xxx/oat/arm64/base.odex) are not readable
+ // worldwide (DEXOPT_PUBLIC flag isn't set). To support reading them (needed by simpleperf for
+ // profiling), add shared app gid to supplementary groups.
+ gid_t shared_app_gid = userAppId % AID_USER_OFFSET - AID_APP_START + AID_SHARED_GID_START;
+ gids.push_back(shared_app_gid);
+ return gids;
+}
+
+static void CheckSimpleperfArguments(const char* cmdname, char** args) {
+ if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0) {
+ error(1, 0, "only stat/record commands are allowed");
+ }
+ std::set<std::string> zero_arg_options = {
+ "-b", "--csv", "--exit-with-parent", "-g", "--in-app", "--interval-only-values",
+ "--no-callchain-joiner", "--no-dump-kernel-symbols", "--no-dump-symbols", "--no-inherit",
+ "--post-unwind=no", "--post-unwind=yes", "--trace-offcpu", "--verbose",
+ };
+ std::set<std::string> one_arg_options = {
+ "-c", "--call-graph", "--callchain-joiner-min-matching-nodes", "--clockid", "--cpu",
+ "--cpu-percent", "--duration", "-e", "-f", "--group", "--interval", "-j", "--log", "-m",
+ "-p", "--size-limit", "-t",
+ };
+ // options with a file descriptor
+ std::set<std::string> fd_options = {
+ "--start_profiling_fd", "--stop-signal-fd", "--out-fd",
+ };
+ // options with path from /data/local/tmp/
+ std::set<std::string> path_options = {
+ "--symfs", "--tracepoint-events",
+ };
+ one_arg_options.insert(fd_options.begin(), fd_options.end());
+ one_arg_options.insert(path_options.begin(), path_options.end());
+ for (int i = 0; args[i] != nullptr; ++i) {
+ if (zero_arg_options.count(args[i])) {
+ continue;
+ } else if (one_arg_options.count(args[i])) {
+ if (args[i + 1] == nullptr) {
+ error(1, 0, "invalid arg: %s", args[i]);
+ }
+ if (fd_options.count(args[i])) {
+ // Check if the file descriptor is valid.
+ int fd;
+ if (!android::base::ParseInt(args[i + 1], &fd) || fd < 3 || fcntl(fd, F_GETFD) == -1) {
+ error(1, 0, "invalid fd for arg: %s", args[i]);
+ }
+ } else if (path_options.count(args[i])) {
+ std::string path;
+ if (!android::base::Realpath(args[i + 1], &path) ||
+ !android::base::StartsWith(path, "/data/local/tmp/")) {
+ error(1, 0, "invalid path for arg: %s", args[i]);
+ }
+ }
+ ++i;
+ } else {
+ error(1, 0, "arg isn't allowed: %s", args[i]);
+ }
+ }
+}
+
+int main(int argc, char* argv[]) {
+ if (argc < 2) {
+ error(1, 0, "usage: simpleperf_app_runner package_name simpleperf_cmd simpleperf_cmd_args...");
+ }
+ if (argc < 3) {
+ error(1, 0, "no simpleperf command name");
+ }
+ char* pkgname = argv[1];
+ char* simpleperf_cmdname = argv[2];
+ int simpleperf_arg_start = 3;
+ CheckSimpleperfArguments(simpleperf_cmdname, argv + simpleperf_arg_start);
+
+ if (getuid() != AID_SHELL && getuid() != AID_ROOT) {
+ error(1, 0, "program can only run from shell or root");
+ }
+
+ pkg_info* info = ReadPackageInfo(pkgname);
+ if (info == nullptr) {
+ error(1, 0, "failed to find package %s", pkgname);
+ }
+ if (info->uid < AID_APP_START || info->uid > AID_APP_END) {
+ error(1, 0, "package isn't an application: %s", pkgname);
+ }
+ if (!info->profileable_from_shell) {
+ error(1, 0, "package isn't profileable from shell: %s", pkgname);
+ }
+
+ // Switch to the app's user id and group id.
+ uid_t uid = info->uid;
+ gid_t gid = info->uid;
+ std::vector<gid_t> supplementary_gids = GetSupplementaryGids(info->uid);
+ ScopedMinijail j(minijail_new());
+ minijail_change_uid(j.get(), uid);
+ minijail_change_gid(j.get(), gid);
+ minijail_set_supplementary_gids(j.get(), supplementary_gids.size(), &supplementary_gids[0]);
+ minijail_enter(j.get());
+
+ // Switch to the app's selinux context.
+ if (selinux_android_setcontext(uid, 0, info->seinfo, pkgname) < 0) {
+ error(1, errno, "couldn't set SELinux security context");
+ }
+
+ // Switch to the app's data directory.
+ if (TEMP_FAILURE_RETRY(chdir(info->data_dir)) == -1) {
+ error(1, errno, "couldn't chdir to package's data directory");
+ }
+
+ // Run /system/bin/simpleperf.
+ std::string simpleperf_in_system_img = "/system/bin/simpleperf";
+ int new_argc = 4 + argc - simpleperf_arg_start;
+ char* new_argv[new_argc + 1];
+
+ new_argv[0] = &simpleperf_in_system_img[0];
+ new_argv[1] = simpleperf_cmdname;
+ std::string app_option = "--app";
+ new_argv[2] = &app_option[0];
+ new_argv[3] = pkgname;
+ for (int i = 4, j = simpleperf_arg_start; j < argc;) {
+ new_argv[i++] = argv[j++];
+ }
+ new_argv[new_argc] = nullptr;
+ execvp(new_argv[0], new_argv);
+ error(1, errno, "exec failed");
+}