diff options
author | Yabin Cui <yabinc@google.com> | 2019-01-10 15:35:39 -0800 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2019-01-23 16:27:00 -0800 |
commit | 1a30a5878984bee10aee06e674a7f5cc0269d9ef (patch) | |
tree | 73488effe90c67f4902ad462f2bca4eec8d986a2 /simpleperf | |
parent | 918f046c3972527f5e8ac606bd11d1ab180f9524 (diff) | |
download | extras-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.cpp | 58 | ||||
-rw-r--r-- | simpleperf/cmd_stat.cpp | 42 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 202 | ||||
-rw-r--r-- | simpleperf/simpleperf_app_runner/Android.bp | 28 | ||||
-rw-r--r-- | simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp | 214 |
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"); +} |