[MTE] Print cause and alloc/dealloc traces to logcat.
This information clearly meets the bar for being dumped to logcat. If we
omit the info, we may confuse the user into thinking that it's not
available at all, especially if it's their first time seeing an MTE
report.
This also adds some functionality to the integration testing library to
pull logcat messages and scan them to make sure the contents are in both
places.
Bug: 187881237
Test: atest debuggerd_test # on QEMU w/ MTE.
Change-Id: Icc17ea45bda7628331cc4812eaad3bc5c949b7a7
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 4634283..24804d0 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -51,6 +51,7 @@
#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libminijail.h>
@@ -65,6 +66,7 @@
using android::base::SendFileDescriptors;
using android::base::unique_fd;
+using ::testing::HasSubstr;
#if defined(__LP64__)
#define ARCH_SUFFIX "64"
@@ -307,6 +309,19 @@
*output = std::move(result);
}
+class LogcatCollector {
+ public:
+ LogcatCollector() { system("logcat -c"); }
+
+ void Collect(std::string* output) {
+ FILE* cmd_stdout = popen("logcat -d '*:S DEBUG'", "r");
+ ASSERT_NE(cmd_stdout, nullptr);
+ unique_fd tmp_fd(TEMP_FAILURE_RETRY(dup(fileno(cmd_stdout))));
+ ConsumeFd(std::move(tmp_fd), output);
+ pclose(cmd_stdout);
+ }
+};
+
TEST_F(CrasherTest, smoke) {
int intercept_result;
unique_fd output_fd;
@@ -441,6 +456,7 @@
}
GwpAsanTestParameters params = GetParam();
+ LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
@@ -460,17 +476,18 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
- ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
- if (params.free_before_access) {
- ASSERT_MATCH(result, R"(deallocated by thread .*
- #00 pc)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
+ ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
+ if (params.free_before_access) {
+ ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
+ }
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
}
- ASSERT_MATCH(result, R"(allocated by thread .*
- #00 pc)");
}
struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
@@ -488,6 +505,8 @@
return;
}
+ LogcatCollector logcat_collector;
+
int intercept_result;
unique_fd output_fd;
StartProcess([&]() {
@@ -504,16 +523,17 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
- std::to_string(GetParam()) + R"(-byte allocation)");
- ASSERT_MATCH(result, R"(deallocated by thread .*
- #00 pc)");
- ASSERT_MATCH(result, R"(allocated by thread .*
- #00 pc)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
+ std::to_string(GetParam()) + R"(-byte allocation)");
+ ASSERT_MATCH(result, R"(deallocated by thread .*?\n.*#00 pc)");
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)");
+ }
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -557,6 +577,7 @@
GTEST_SKIP() << "Requires MTE";
}
+ LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
StartProcess([&]() {
@@ -572,14 +593,16 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
- std::to_string(GetParam()) + R"(-byte allocation)");
- ASSERT_MATCH(result, R"(allocated by thread .*
- #00 pc)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
+ std::to_string(GetParam()) + R"(-byte allocation)");
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)");
+ }
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -612,7 +635,7 @@
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
- ASSERT_MATCH(result, R"(allocated by thread .*
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
@@ -625,6 +648,8 @@
GTEST_SKIP() << "Requires MTE";
}
+ LogcatCollector logcat_collector;
+
int intercept_result;
unique_fd output_fd;
StartProcess([]() {
@@ -657,17 +682,23 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(
- result,
- R"(Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.)");
-
- // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
- // overflows), so we can't match explicitly for an underflow message.
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, "
+ "listing them in decreasing order of probability."));
+ // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
+ // overflows), so we can't match explicitly for an underflow message.
+ ASSERT_MATCH(result,
+ R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
+ // Ensure there's at least two allocation traces (one for each cause).
+ ASSERT_MATCH(
+ result,
+ R"((^|\s)allocated by thread .*?\n.*#00 pc(.|\n)*?(^|\s)allocated by thread .*?\n.*#00 pc)");
+ }
#else
GTEST_SKIP() << "Requires aarch64";
#endif