diff options
author | Yabin Cui <yabinc@google.com> | 2020-10-16 21:50:07 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-10-16 21:50:07 +0000 |
commit | cfc7e1bce2210d25cd70b602d37e4e1bd4f7fc87 (patch) | |
tree | 5e06b39a1e0bd35b0518f8d8b9e6a72709af9632 | |
parent | 999544e25e0b0eb23a911bb954ff85dbd4a37469 (diff) | |
parent | 142acc899fc03502b7751704dab25558bcccc6a8 (diff) | |
download | extras-cfc7e1bce2210d25cd70b602d37e4e1bd4f7fc87.tar.gz |
Merge "simpleperf: add --kprobe option to record cmd."
-rw-r--r-- | simpleperf/Android.bp | 2 | ||||
-rw-r--r-- | simpleperf/ProbeEvents.cpp | 150 | ||||
-rw-r--r-- | simpleperf/ProbeEvents.h | 52 | ||||
-rw-r--r-- | simpleperf/ProbeEvents_test.cpp | 51 | ||||
-rw-r--r-- | simpleperf/RecordReadThread.cpp | 11 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 36 | ||||
-rw-r--r-- | simpleperf/cmd_record_impl.h | 1 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 19 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 15 | ||||
-rw-r--r-- | simpleperf/event_selection_set.cpp | 11 | ||||
-rw-r--r-- | simpleperf/event_selection_set.h | 1 | ||||
-rw-r--r-- | simpleperf/event_type.cpp | 2 | ||||
-rw-r--r-- | simpleperf/test_util.h | 10 |
13 files changed, 339 insertions, 22 deletions
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp index 948294e9..c16369b8 100644 --- a/simpleperf/Android.bp +++ b/simpleperf/Android.bp @@ -227,6 +227,7 @@ cc_defaults { "IOEventLoop.cpp", "JITDebugReader.cpp", "OfflineUnwinder.cpp", + "ProbeEvents.cpp", "read_dex_file.cpp", "record_file_writer.cpp", "RecordReadThread.cpp", @@ -503,6 +504,7 @@ cc_defaults { "environment_test.cpp", "IOEventLoop_test.cpp", "OfflineUnwinder_test.cpp", + "ProbeEvents_test.cpp", "read_dex_file_test.cpp", "record_file_test.cpp", "RecordReadThread_test.cpp", diff --git a/simpleperf/ProbeEvents.cpp b/simpleperf/ProbeEvents.cpp new file mode 100644 index 00000000..5659b7e4 --- /dev/null +++ b/simpleperf/ProbeEvents.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 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 "ProbeEvents.h" + +#include <inttypes.h> + +#include <memory> +#include <regex> +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> + +#include "environment.h" +#include "utils.h" + +using android::base::ParseInt; +using android::base::ParseUint; +using android::base::Split; +using android::base::StringPrintf; +using android::base::unique_fd; +using android::base::WriteStringToFd; +using namespace simpleperf; + +namespace simpleperf { + +bool ProbeEvents::ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent* event) { + // kprobe_cmd is in formats described in <kernel>/Documentation/trace/kprobetrace.rst: + // p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] + // r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+offs] [FETCHARGS] + std::vector<std::string> args = Split(kprobe_cmd, " "); + if (args.size() < 2) { + return false; + } + + // Parse given name. + event->group_name = "kprobes"; + std::regex name_reg(R"(:([a-zA-Z_][\w_]*/)?([a-zA-Z_][\w_]*))"); + std::smatch matches; + if (std::regex_search(args[0], matches, name_reg)) { + if (matches[1].length() > 0) { + event->group_name = matches[1].str(); + event->group_name.pop_back(); + } + event->event_name = matches[2].str(); + return true; + } + + // Generate name from MEMADDR. + char probe_type = args[0][0]; + uint64_t kaddr; + if (ParseUint(args[1], &kaddr)) { + event->event_name = StringPrintf("%c_0x%" PRIx64, probe_type, kaddr); + return true; + } + + // Generate name from [MOD:]SYM[+offs]. + std::string symbol; + int64_t offset; + size_t split_pos = args[1].find_first_of("+-"); + if (split_pos == std::string::npos) { + symbol = args[1]; + offset = 0; + } else { + symbol = args[1].substr(0, split_pos); + if (!ParseInt(args[1].substr(split_pos), &offset) || offset < 0) { + return false; + } + } + std::string s = StringPrintf("%c_%s_%" PRId64, probe_type, symbol.c_str(), offset); + event->event_name = std::regex_replace(s, std::regex(R"(\.|:)"), "_"); + return true; +} + +bool ProbeEvents::IsKprobeSupported() { + if (!kprobe_control_path_.has_value()) { + kprobe_control_path_ = ""; + if (const char* tracefs_dir = GetTraceFsDir(); tracefs_dir != nullptr) { + std::string path = std::string(tracefs_dir) + "/kprobe_events"; + if (IsRegularFile(path)) { + kprobe_control_path_ = std::move(path); + } + } + } + return !kprobe_control_path_.value().empty(); +} + +bool ProbeEvents::AddKprobe(const std::string& kprobe_cmd) { + ProbeEvent event; + if (!ParseKprobeEventName(kprobe_cmd, &event)) { + LOG(ERROR) << "invalid kprobe cmd: " << kprobe_cmd; + return false; + } + if (!WriteKprobeCmd(kprobe_cmd)) { + return false; + } + kprobe_events_.emplace_back(std::move(event)); + return true; +} + +void ProbeEvents::Clear() { + for (const auto& kprobe_event : kprobe_events_) { + if (!WriteKprobeCmd("-:" + kprobe_event.group_name + "/" + kprobe_event.event_name)) { + LOG(WARNING) << "failed to delete kprobe event " << kprobe_event.group_name << ":" + << kprobe_event.event_name; + } + } + kprobe_events_.clear(); +} + +bool ProbeEvents::WriteKprobeCmd(const std::string& kprobe_cmd) { + if (!IsKprobeSupported()) { + LOG(ERROR) << "kprobe events isn't supported by the kernel."; + return false; + } + const std::string& path = kprobe_control_path_.value(); + unique_fd fd(open(path.c_str(), O_APPEND | O_WRONLY | O_CLOEXEC)); + if (!fd.ok()) { + PLOG(ERROR) << "failed to open " << path; + return false; + } + if (!WriteStringToFd(kprobe_cmd, fd)) { + PLOG(ERROR) << "failed to write '" << kprobe_cmd << "' to " << path; + return false; + } + fd.reset(); + std::string data; + android::base::ReadFileToString(path, &data); + return true; +} + +} // namespace simpleperf diff --git a/simpleperf/ProbeEvents.h b/simpleperf/ProbeEvents.h new file mode 100644 index 00000000..1f47acd9 --- /dev/null +++ b/simpleperf/ProbeEvents.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include <optional> +#include <string> +#include <vector> + +namespace simpleperf { + +struct ProbeEvent { + std::string group_name; + std::string event_name; +}; + +// Add kprobe events in /sys/kernel/debug/tracing/kprobe_events, and +// delete them in ProbeEvents::clear(). +class ProbeEvents { + public: + ~ProbeEvents() { Clear(); } + + static bool ParseKprobeEventName(const std::string& kprobe_cmd, ProbeEvent* event); + bool IsKprobeSupported(); + + // Accept kprobe cmd as in <linux_kernel>/Documentation/trace/kprobetrace.rst. + bool AddKprobe(const std::string& kprobe_cmd); + + bool IsEmpty() const { return kprobe_events_.empty(); } + void Clear(); + + private: + bool WriteKprobeCmd(const std::string& kprobe_cmd); + + std::vector<ProbeEvent> kprobe_events_; + std::optional<std::string> kprobe_control_path_; +}; + +} // namespace simpleperf diff --git a/simpleperf/ProbeEvents_test.cpp b/simpleperf/ProbeEvents_test.cpp new file mode 100644 index 00000000..be135386 --- /dev/null +++ b/simpleperf/ProbeEvents_test.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 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 "ProbeEvents.h" + +#include <gtest/gtest.h> + +#include "get_test_data.h" +#include "test_util.h" + +using namespace simpleperf; + +TEST(probe_events, ParseKprobeEventName) { + ProbeEvent event; + ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p:myprobe do_sys_open", &event)); + ASSERT_EQ(event.group_name, "kprobes"); + ASSERT_EQ(event.event_name, "myprobe"); + + ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p:mygroup/myprobe do_sys_open", &event)); + ASSERT_EQ(event.group_name, "mygroup"); + ASSERT_EQ(event.event_name, "myprobe"); + + ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p do_sys_open", &event)); + ASSERT_EQ(event.group_name, "kprobes"); + ASSERT_EQ(event.event_name, "p_do_sys_open_0"); + + ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("r do_sys_open+138", &event)); + ASSERT_EQ(event.group_name, "kprobes"); + ASSERT_EQ(event.event_name, "r_do_sys_open_138"); + + ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("r module:do_sys_open+138", &event)); + ASSERT_EQ(event.group_name, "kprobes"); + ASSERT_EQ(event.event_name, "r_module_do_sys_open_138"); + + ASSERT_TRUE(ProbeEvents::ParseKprobeEventName("p 0x12345678", &event)); + ASSERT_EQ(event.group_name, "kprobes"); + ASSERT_EQ(event.event_name, "p_0x12345678"); +} diff --git a/simpleperf/RecordReadThread.cpp b/simpleperf/RecordReadThread.cpp index bfc36e40..18bc84ba 100644 --- a/simpleperf/RecordReadThread.cpp +++ b/simpleperf/RecordReadThread.cpp @@ -272,10 +272,13 @@ bool RecordReadThread::SyncKernelBuffer() { } bool RecordReadThread::StopReadThread() { - bool result = SendCmdToReadThread(CMD_STOP_THREAD, nullptr); - if (result) { - read_thread_->join(); - read_thread_ = nullptr; + bool result = true; + if (read_thread_ != nullptr) { + result = SendCmdToReadThread(CMD_STOP_THREAD, nullptr); + if (result) { + read_thread_->join(); + read_thread_ = nullptr; + } } return result; } diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index 20312c1e..c02bdbb9 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -31,6 +31,7 @@ #include <android-base/logging.h> #include <android-base/file.h> #include <android-base/parseint.h> +#include <android-base/scopeguard.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> @@ -48,6 +49,7 @@ #include "IOEventLoop.h" #include "JITDebugReader.h" #include "OfflineUnwinder.h" +#include "ProbeEvents.h" #include "read_apk.h" #include "read_elf.h" #include "read_symbol_map.h" @@ -141,6 +143,7 @@ class RecordCommand : public Command { " 1) an event name listed in `simpleperf list`;\n" " 2) a raw PMU event in rN format. N is a hex number.\n" " For example, r1b selects event number 0x1b.\n" +" 3) a kprobe event added by --kprobe option.\n" " Modifiers can be added to define how the event should be\n" " monitored. Possible modifiers are:\n" " u - monitor user space events only\n" @@ -151,6 +154,11 @@ class RecordCommand : public Command { " same time.\n" "--trace-offcpu Generate samples when threads are scheduled off cpu.\n" " Similar to \"-c 1 -e sched:sched_switch\".\n" +"--kprobe kprobe_event1,kprobe_event2,...\n" +" Add kprobe events during recording. The kprobe_event format is in\n" +" Documentation/trace/kprobetrace.rst in the kernel. Examples:\n" +" 'p:myprobe do_sys_open $arg2:string' - add event kprobes:myprobe\n" +" 'r:myretprobe do_sys_open $retval:s64' - add event kprobes:myretprobe\n" "\n" "Select monitoring options:\n" "-f freq Set event sample frequency. It means recording at most [freq]\n" @@ -305,7 +313,8 @@ class RecordCommand : public Command { private: bool ParseOptions(const std::vector<std::string>& args, - std::vector<std::string>* non_option_args); + std::vector<std::string>* non_option_args, + ProbeEvents* probe_events); bool AdjustPerfEventLimit(); bool PrepareRecording(Workload* workload); bool DoRecording(Workload* workload); @@ -397,13 +406,22 @@ class RecordCommand : public Command { bool RecordCommand::Run(const std::vector<std::string>& args) { time_stat_.prepare_recording_time = GetSystemClock(); ScopedCurrentArch scoped_arch(GetMachineArch()); + if (!CheckPerfEventLimit()) { return false; } AllowMoreOpenedFiles(); std::vector<std::string> workload_args; - if (!ParseOptions(args, &workload_args)) { + ProbeEvents probe_events; + auto clear_probe_events_guard = android::base::make_scope_guard([this, &probe_events] { + if (!probe_events.IsEmpty()) { + // probe events can be deleted only when no perf event file is using them. + event_selection_set_.CloseEventFiles(); + probe_events.Clear(); + } + }); + if (!ParseOptions(args, &workload_args, &probe_events)) { return false; } if (!AdjustPerfEventLimit()) { @@ -727,7 +745,8 @@ bool RecordCommand::PostProcessRecording(const std::vector<std::string>& args) { } bool RecordCommand::ParseOptions(const std::vector<std::string>& args, - std::vector<std::string>* non_option_args) { + std::vector<std::string>* non_option_args, + ProbeEvents* probe_events) { OptionValueMap options; std::vector<std::pair<OptionName, OptionValue>> ordered_options; @@ -817,6 +836,17 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args, } } + if (auto values = options.PullValues("--kprobe"); values) { + for (const auto& value : values.value()) { + std::vector<std::string> cmds = android::base::Split(*value.str_value, ","); + for (const auto& cmd : cmds) { + if (!probe_events->AddKprobe(cmd)) { + return false; + } + } + } + } + if (auto value = options.PullValue("-m"); value) { if (!IsPowerOfTwo(value->uint_value) || value->uint_value > std::numeric_limits<size_t>::max()) { diff --git a/simpleperf/cmd_record_impl.h b/simpleperf/cmd_record_impl.h index 623f6e34..2993cafa 100644 --- a/simpleperf/cmd_record_impl.h +++ b/simpleperf/cmd_record_impl.h @@ -50,6 +50,7 @@ inline const OptionFormatMap& GetRecordCmdOptionFormats() { {"--group", {OptionValueType::STRING, OptionType::ORDERED, AppRunnerType::ALLOWED}}, {"--in-app", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}}, {"-j", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::ALLOWED}}, + {"--kprobe", {OptionValueType::STRING, OptionType::MULTIPLE, AppRunnerType::NOT_ALLOWED}}, {"-m", {OptionValueType::UINT, OptionType::SINGLE, AppRunnerType::ALLOWED}}, {"--no-callchain-joiner", {OptionValueType::NONE, OptionType::SINGLE, AppRunnerType::ALLOWED}}, diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index 510d5be2..75a33ea5 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -40,6 +40,7 @@ #include "ETMRecorder.h" #include "event_selection_set.h" #include "get_test_data.h" +#include "ProbeEvents.h" #include "record.h" #include "record_file.h" #include "test_util.h" @@ -290,8 +291,7 @@ static bool InCloudAndroid() { bool HasTracepointEvents() { static int has_tracepoint_events = -1; if (has_tracepoint_events == -1) { - // Cloud Android doesn't support tracepoint events. - has_tracepoint_events = InCloudAndroid() ? 0 : 1; + has_tracepoint_events = (GetTraceFsDir() != nullptr) ? 1 : 0; } return has_tracepoint_events == 1; } @@ -507,10 +507,7 @@ TEST(record_cmd, no_dump_symbols) { } TEST(record_cmd, dump_kernel_symbols) { - if (!IsRoot()) { - GTEST_LOG_(INFO) << "Test requires root privilege"; - return; - } + TEST_REQUIRE_ROOT(); TemporaryFile tmpfile; ASSERT_TRUE(RecordCmd()->Run({"-a", "-o", tmpfile.path, "-e", GetDefaultEvent(), "sleep", "1"})); bool has_kernel_symbols = false; @@ -1131,3 +1128,13 @@ TEST(record_cmd, ParseAddrFilterOption) { ASSERT_EQ(option_to_str("filter 0x12345678-0x1234567a"), "filter 0x12345678/0x2"); ASSERT_EQ(option_to_str("start 0x12345678,stop 0x1234567a"), "start 0x12345678,stop 0x1234567a"); } + +TEST(record_cmd, kprobe_option) { + TEST_REQUIRE_ROOT(); + ProbeEvents probe_events; + if (!probe_events.IsKprobeSupported()) { + GTEST_LOG_(INFO) << "Skip this test as kprobe isn't supported by the kernel."; + return; + } + ASSERT_TRUE(RunRecordCmd({"-e", "kprobes:myprobe", "--kprobe", "p:myprobe do_sys_open"})); +} diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index 9be657c2..2f15c1dd 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -949,15 +949,16 @@ std::string GetCompleteProcessName(pid_t pid) { } const char* GetTraceFsDir() { - static const char* tracefs_dirs[] = { - "/sys/kernel/debug/tracing", "/sys/kernel/tracing" - }; - for (const char* path : tracefs_dirs) { - if (IsDir(path)) { - return path; + static const char* tracefs_dir = nullptr; + if (tracefs_dir == nullptr) { + for (const char* path : {"/sys/kernel/debug/tracing", "/sys/kernel/tracing"}) { + if (IsDir(path)) { + tracefs_dir = path; + break; + } } } - return nullptr; + return tracefs_dir; } bool GetKernelVersion(int* major, int* minor) { diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp index 790db43d..bdca76ef 100644 --- a/simpleperf/event_selection_set.cpp +++ b/simpleperf/event_selection_set.cpp @@ -788,6 +788,17 @@ bool EventSelectionSet::FinishReadMmapEventData() { return true; } +void EventSelectionSet::CloseEventFiles() { + if (record_read_thread_) { + record_read_thread_->StopReadThread(); + } + for (auto& group : groups_) { + for (auto& event : group) { + event.event_fds.clear(); + } + } +} + bool EventSelectionSet::StopWhenNoMoreTargets(double check_interval_in_sec) { return loop_->AddPeriodicEvent(SecondToTimeval(check_interval_in_sec), [&]() { return CheckMonitoredTargets(); }); diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h index ad24d1a9..717c51d5 100644 --- a/simpleperf/event_selection_set.h +++ b/simpleperf/event_selection_set.h @@ -169,6 +169,7 @@ class EventSelectionSet { bool PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback); bool SyncKernelBuffer(); bool FinishReadMmapEventData(); + void CloseEventFiles(); const simpleperf::RecordStat& GetRecordStat() { return record_read_thread_->GetStat(); diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp index 7a8048a3..97473aaf 100644 --- a/simpleperf/event_type.cpp +++ b/simpleperf/event_type.cpp @@ -78,7 +78,7 @@ class EventTypeFinder { } virtual const EventType* FindType(const std::string& name) { - const auto types = GetTypes(); + const auto& types = GetTypes(); auto it = types.find(EventType(name, 0, 0, "", "")); if (it != types.end()) { return &*it; diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h index 5780b6c0..4d174056 100644 --- a/simpleperf/test_util.h +++ b/simpleperf/test_util.h @@ -45,10 +45,18 @@ bool IsRoot(); } \ } while (0) +#define TEST_REQUIRE_ROOT() \ + do { \ + if (!IsRoot()) { \ + GTEST_LOG_(INFO) << "Skip this test as it needs root privileges."; \ + return; \ + } \ + } while (0) + #if defined(__ANDROID__) #define TEST_REQUIRE_HOST_ROOT() #else -#define TEST_REQUIRE_HOST_ROOT() if (!IsRoot()) return +#define TEST_REQUIRE_HOST_ROOT() TEST_REQUIRE_ROOT() #endif bool IsInNativeAbi(); |