summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2020-10-16 21:50:07 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-10-16 21:50:07 +0000
commitcfc7e1bce2210d25cd70b602d37e4e1bd4f7fc87 (patch)
tree5e06b39a1e0bd35b0518f8d8b9e6a72709af9632
parent999544e25e0b0eb23a911bb954ff85dbd4a37469 (diff)
parent142acc899fc03502b7751704dab25558bcccc6a8 (diff)
downloadextras-cfc7e1bce2210d25cd70b602d37e4e1bd4f7fc87.tar.gz
Merge "simpleperf: add --kprobe option to record cmd."
-rw-r--r--simpleperf/Android.bp2
-rw-r--r--simpleperf/ProbeEvents.cpp150
-rw-r--r--simpleperf/ProbeEvents.h52
-rw-r--r--simpleperf/ProbeEvents_test.cpp51
-rw-r--r--simpleperf/RecordReadThread.cpp11
-rw-r--r--simpleperf/cmd_record.cpp36
-rw-r--r--simpleperf/cmd_record_impl.h1
-rw-r--r--simpleperf/cmd_record_test.cpp19
-rw-r--r--simpleperf/environment.cpp15
-rw-r--r--simpleperf/event_selection_set.cpp11
-rw-r--r--simpleperf/event_selection_set.h1
-rw-r--r--simpleperf/event_type.cpp2
-rw-r--r--simpleperf/test_util.h10
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();