diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp index f415b68c4..b10776786 100644 --- a/debuggerd/debuggerd_test.cpp +++ b/debuggerd/debuggerd_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include +#include #include #include @@ -54,6 +56,9 @@ #include #include +#include +#include + #include #include @@ -2227,3 +2232,209 @@ TEST_F(CrasherTest, verify_dex_pc_with_function_name) { // Now verify that the dex_pc frame includes a proper function name. ASSERT_MATCH(result, R"( \[anon:dex\] \(Main\.\\+2)"); } + +static std::string format_map_pointer(uintptr_t ptr) { +#if defined(__LP64__) + return android::base::StringPrintf("%08x'%08x", static_cast(ptr >> 32), + static_cast(ptr & 0xffffffff)); +#else + return android::base::StringPrintf("%08x", ptr); +#endif +} + +// Verify that map data is properly formatted. +TEST_F(CrasherTest, verify_map_format) { + // Create multiple maps to make sure that the map data is formatted properly. + void* none_map = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + ASSERT_NE(MAP_FAILED, none_map); + void* r_map = mmap(nullptr, getpagesize(), PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + ASSERT_NE(MAP_FAILED, r_map); + void* w_map = mmap(nullptr, getpagesize(), PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + ASSERT_NE(MAP_FAILED, w_map); + void* x_map = mmap(nullptr, getpagesize(), PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + ASSERT_NE(MAP_FAILED, x_map); + + TemporaryFile tf; + ASSERT_EQ(0x2000, lseek(tf.fd, 0x2000, SEEK_SET)); + char c = 'f'; + ASSERT_EQ(1, write(tf.fd, &c, 1)); + ASSERT_EQ(0x5000, lseek(tf.fd, 0x5000, SEEK_SET)); + ASSERT_EQ(1, write(tf.fd, &c, 1)); + ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)); + void* file_map = mmap(nullptr, 0x3001, PROT_READ, MAP_PRIVATE, tf.fd, 0x2000); + ASSERT_NE(MAP_FAILED, file_map); + + StartProcess([]() { abort(); }); + + ASSERT_EQ(0, munmap(none_map, getpagesize())); + ASSERT_EQ(0, munmap(r_map, getpagesize())); + ASSERT_EQ(0, munmap(w_map, getpagesize())); + ASSERT_EQ(0, munmap(x_map, getpagesize())); + ASSERT_EQ(0, munmap(file_map, 0x3001)); + + unique_fd output_fd; + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + int intercept_result; + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + + std::string match_str; + // Verify none. + match_str = android::base::StringPrintf( + " %s-%s --- 0 1000\\n", + format_map_pointer(reinterpret_cast(none_map)).c_str(), + format_map_pointer(reinterpret_cast(none_map) + getpagesize() - 1).c_str()); + ASSERT_MATCH(result, match_str); + + // Verify read-only. + match_str = android::base::StringPrintf( + " %s-%s r-- 0 1000\\n", + format_map_pointer(reinterpret_cast(r_map)).c_str(), + format_map_pointer(reinterpret_cast(r_map) + getpagesize() - 1).c_str()); + ASSERT_MATCH(result, match_str); + + // Verify write-only. + match_str = android::base::StringPrintf( + " %s-%s -w- 0 1000\\n", + format_map_pointer(reinterpret_cast(w_map)).c_str(), + format_map_pointer(reinterpret_cast(w_map) + getpagesize() - 1).c_str()); + ASSERT_MATCH(result, match_str); + + // Verify exec-only. + match_str = android::base::StringPrintf( + " %s-%s --x 0 1000\\n", + format_map_pointer(reinterpret_cast(x_map)).c_str(), + format_map_pointer(reinterpret_cast(x_map) + getpagesize() - 1).c_str()); + ASSERT_MATCH(result, match_str); + + // Verify file map with non-zero offset and a name. + match_str = android::base::StringPrintf( + " %s-%s r-- 2000 4000 %s\\n", + format_map_pointer(reinterpret_cast(file_map)).c_str(), + format_map_pointer(reinterpret_cast(file_map) + 0x3fff).c_str(), tf.path); + ASSERT_MATCH(result, match_str); +} + +// Verify that the tombstone map data is correct. +TEST_F(CrasherTest, verify_header) { + StartProcess([]() { abort(); }); + + unique_fd output_fd; + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + int intercept_result; + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + + std::string match_str = android::base::StringPrintf( + "Build fingerprint: '%s'\\nRevision: '%s'\\n", + android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(), + android::base::GetProperty("ro.revision", "unknown").c_str()); + match_str += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING); + ASSERT_MATCH(result, match_str); +} + +// Verify that the thread header is formatted properly. +TEST_F(CrasherTest, verify_thread_header) { + void* shared_map = + mmap(nullptr, sizeof(pid_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, shared_map); + memset(shared_map, 0, sizeof(pid_t)); + + StartProcess([&shared_map]() { + std::atomic_bool tid_written; + std::thread thread([&tid_written, &shared_map]() { + pid_t tid = gettid(); + memcpy(shared_map, &tid, sizeof(pid_t)); + tid_written = true; + volatile bool done = false; + while (!done) + ; + }); + thread.detach(); + while (!tid_written.load(std::memory_order_acquire)) + ; + abort(); + }); + + pid_t primary_pid = crasher_pid; + + unique_fd output_fd; + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + int intercept_result; + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + // Read the tid data out. + pid_t tid; + memcpy(&tid, shared_map, sizeof(pid_t)); + ASSERT_NE(0, tid); + + ASSERT_EQ(0, munmap(shared_map, sizeof(pid_t))); + + std::string result; + ConsumeFd(std::move(output_fd), &result); + + // Verify that there are two headers, one where the tid is "primary_pid" + // and the other where the tid is "tid". + std::string match_str = android::base::StringPrintf("pid: %d, tid: %d, name: .* >>> .* <<<\\n", + primary_pid, primary_pid); + ASSERT_MATCH(result, match_str); + + match_str = + android::base::StringPrintf("pid: %d, tid: %d, name: .* >>> .* <<<\\n", primary_pid, tid); + ASSERT_MATCH(result, match_str); +} + +// Verify that there is a BuildID present in the map section and set properly. +TEST_F(CrasherTest, verify_build_id) { + StartProcess([]() { abort(); }); + + unique_fd output_fd; + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + int intercept_result; + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + + // Find every /system or /apex lib and verify the BuildID is displayed + // properly. + bool found_valid_elf = false; + std::smatch match; + std::regex build_id_regex(R"( ((/system/|/apex/)\S+) \(BuildId: ([^\)]+)\))"); + for (std::string prev_file; std::regex_search(result, match, build_id_regex); + result = match.suffix()) { + if (prev_file == match[1]) { + // Already checked this file. + continue; + } + + prev_file = match[1]; + unwindstack::Elf elf(unwindstack::Memory::CreateFileMemory(prev_file, 0).release()); + if (!elf.Init() || !elf.valid()) { + // Skipping invalid elf files. + continue; + } + ASSERT_EQ(match[3], elf.GetPrintableBuildID()); + + found_valid_elf = true; + } + ASSERT_TRUE(found_valid_elf) << "Did not find any elf files with valid BuildIDs to check."; +} diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp index a14dcb0e7..1cbfb56b6 100644 --- a/debuggerd/libdebuggerd/test/tombstone_test.cpp +++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp @@ -32,9 +32,6 @@ #include "host_signal_fixup.h" #include "log_fake.h" -// Include tombstone.cpp to define log_tag before GWP-ASan includes log. -#include "tombstone.cpp" - #include "gwp_asan.cpp" using ::testing::MatchesRegex; @@ -82,283 +79,6 @@ class TombstoneTest : public ::testing::Test { std::string amfd_data_; }; -TEST_F(TombstoneTest, single_map) { -#if defined(__LP64__) - unwinder_mock_->MockAddMap(0x123456789abcd000UL, 0x123456789abdf000UL, 0, 0, "", 0); -#else - unwinder_mock_->MockAddMap(0x1234000, 0x1235000, 0, 0, "", 0); -#endif - - dump_all_maps(&log_, unwinder_mock_.get(), 0); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = \ -"\nmemory map (1 entry):\n" -#if defined(__LP64__) -" 12345678'9abcd000-12345678'9abdefff --- 0 12000\n"; -#else -" 01234000-01234fff --- 0 1000\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, single_map_elf_build_id) { - uint64_t build_id_offset; -#if defined(__LP64__) - build_id_offset = 0x123456789abcd000UL; - unwinder_mock_->MockAddMap(build_id_offset, 0x123456789abdf000UL, 0, PROT_READ, - "/system/lib/libfake.so", 0); -#else - build_id_offset = 0x1234000; - unwinder_mock_->MockAddMap(0x1234000, 0x1235000, 0, PROT_READ, "/system/lib/libfake.so", 0); -#endif - - unwinder_mock_->MockSetBuildID( - build_id_offset, - std::string{static_cast(0xab), static_cast(0xcd), static_cast(0xef), - static_cast(0x12), static_cast(0x34), static_cast(0x56), - static_cast(0x78), static_cast(0x90), static_cast(0xab), - static_cast(0xcd), static_cast(0xef), static_cast(0x12), - static_cast(0x34), static_cast(0x56), static_cast(0x78), - static_cast(0x90)}); - dump_all_maps(&log_, unwinder_mock_.get(), 0); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = \ -"\nmemory map (1 entry):\n" -#if defined(__LP64__) -" 12345678'9abcd000-12345678'9abdefff r-- 0 12000 /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n"; -#else -" 01234000-01234fff r-- 0 1000 /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, multiple_maps) { - unwinder_mock_->MockAddMap(0xa234000, 0xa235000, 0, 0, "", 0); - unwinder_mock_->MockAddMap(0xa334000, 0xa335000, 0xf000, PROT_READ, "", 0); - unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000); - unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000); - unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC, - "/system/lib/fake.so", 0); - - dump_all_maps(&log_, unwinder_mock_.get(), 0); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = - "\nmemory map (5 entries):\n" -#if defined(__LP64__) - " 00000000'0a234000-00000000'0a234fff --- 0 1000\n" - " 00000000'0a334000-00000000'0a334fff r-- f000 1000\n" - " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n" - " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#else - " 0a234000-0a234fff --- 0 1000\n" - " 0a334000-0a334fff r-- f000 1000\n" - " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n" - " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, multiple_maps_fault_address_before) { - unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000); - unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000); - unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC, - "/system/lib/fake.so", 0); - - dump_all_maps(&log_, unwinder_mock_.get(), 0x1000); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = - "\nmemory map (3 entries):\n" -#if defined(__LP64__) - "--->Fault address falls at 00000000'00001000 before any mapped regions\n" - " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n" - " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#else - "--->Fault address falls at 00001000 before any mapped regions\n" - " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n" - " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, multiple_maps_fault_address_between) { - unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000); - unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000); - unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC, - "/system/lib/fake.so", 0); - - dump_all_maps(&log_, unwinder_mock_.get(), 0xa533000); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = - "\nmemory map (3 entries): (fault address prefixed with --->)\n" -#if defined(__LP64__) - " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n" - "--->Fault address falls at 00000000'0a533000 between mapped regions\n" - " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#else - " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n" - "--->Fault address falls at 0a533000 between mapped regions\n" - " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, multiple_maps_fault_address_in_map) { - unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000); - unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000); - unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC, - "/system/lib/fake.so", 0); - - dump_all_maps(&log_, unwinder_mock_.get(), 0xa534040); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = - "\nmemory map (3 entries): (fault address prefixed with --->)\n" -#if defined(__LP64__) - " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n" - "--->00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#else - " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n" - "--->0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, multiple_maps_fault_address_after) { - unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000); - unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000); - unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC, - "/system/lib/fake.so", 0); - -#if defined(__LP64__) - uint64_t addr = 0x12345a534040UL; -#else - uint64_t addr = 0xf534040UL; -#endif - dump_all_maps(&log_, unwinder_mock_.get(), addr); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - const char* expected_dump = - "\nmemory map (3 entries): (fault address prefixed with --->)\n" -#if defined(__LP64__) - " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n" - " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n" - "--->Fault address falls at 00001234'5a534040 after any mapped regions\n"; -#else - " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n" - " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n" - " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n" - "--->Fault address falls at 0f534040 after any mapped regions\n"; -#endif - ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); - - // Verify that the log buf is empty, and no error messages. - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("", getFakeLogPrint().c_str()); -} - -TEST_F(TombstoneTest, dump_log_file_error) { - log_.should_retrieve_logcat = true; - dump_log_file(&log_, 123, "/fake/filename", 10); - - std::string tombstone_contents; - ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); - ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - ASSERT_STREQ("", tombstone_contents.c_str()); - - ASSERT_STREQ("", getFakeLogBuf().c_str()); - ASSERT_STREQ("6 DEBUG Unable to open /fake/filename: Permission denied\n\n", - getFakeLogPrint().c_str()); - - ASSERT_STREQ("", amfd_data_.c_str()); -} - -TEST_F(TombstoneTest, dump_header_info) { - dump_header_info(&log_); - - std::string expected = android::base::StringPrintf( - "Build fingerprint: '%s'\nRevision: '%s'\n", - android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(), - android::base::GetProperty("ro.revision", "unknown").c_str()); - expected += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING); - ASSERT_STREQ(expected.c_str(), amfd_data_.c_str()); -} - -TEST_F(TombstoneTest, dump_thread_info_uid) { - std::vector cmdline = {"some_process"}; - dump_thread_info( - &log_, - ThreadInfo{ - .uid = 1, .tid = 3, .thread_name = "some_thread", .pid = 2, .command_line = cmdline}); - std::string expected = "pid: 2, tid: 3, name: some_thread >>> some_process <<<\nuid: 1\n"; - ASSERT_STREQ(expected.c_str(), amfd_data_.c_str()); -} - class GwpAsanCrashDataTest : public GwpAsanCrashData { public: GwpAsanCrashDataTest( @@ -483,4 +203,3 @@ TEST_F(TombstoneTest, gwp_asan_cause_invalid_free_outside) { "Cause: \\[GWP-ASan\\]: Invalid \\(Wild\\) Free, 33 bytes right of a 32-byte " "allocation at 0x[a-fA-F0-9]+\n")); } - diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp index 20539b040..f21a20378 100644 --- a/debuggerd/libdebuggerd/tombstone.cpp +++ b/debuggerd/libdebuggerd/tombstone.cpp @@ -18,566 +18,38 @@ #include "libdebuggerd/tombstone.h" -#include #include -#include -#include #include #include #include #include -#include -#include -#include -#include -#include +#include +#include #include #include #include -#include -#include -#include -#include #include #include #include -#include #include -#include -#include #include -#include -#include -#include #include #include #include #include "libdebuggerd/backtrace.h" -#include "libdebuggerd/gwp_asan.h" #include "libdebuggerd/open_files_list.h" #include "libdebuggerd/utility.h" #include "util.h" -#if defined(USE_SCUDO) -#include "libdebuggerd/scudo.h" -#endif - -#include "gwp_asan/common.h" -#include "gwp_asan/crash_handler.h" - #include "tombstone.pb.h" -using android::base::GetBoolProperty; -using android::base::GetProperty; -using android::base::StringPrintf; using android::base::unique_fd; using namespace std::literals::string_literals; -#define STACK_WORDS 16 - -static void dump_header_info(log_t* log) { - auto fingerprint = GetProperty("ro.build.fingerprint", "unknown"); - auto revision = GetProperty("ro.revision", "unknown"); - - _LOG(log, logtype::HEADER, "Build fingerprint: '%s'\n", fingerprint.c_str()); - _LOG(log, logtype::HEADER, "Revision: '%s'\n", revision.c_str()); - _LOG(log, logtype::HEADER, "ABI: '%s'\n", ABI_STRING); -} - -static std::string get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp, - unwindstack::Maps* maps) { - static constexpr uint64_t kMaxDifferenceBytes = 256; - uint64_t difference; - if (sp >= fault_addr) { - difference = sp - fault_addr; - } else { - difference = fault_addr - sp; - } - if (difference <= kMaxDifferenceBytes) { - // The faulting address is close to the current sp, check if the sp - // indicates a stack overflow. - // On arm, the sp does not get updated when the instruction faults. - // In this case, the sp will still be in a valid map, which is the - // last case below. - // On aarch64, the sp does get updated when the instruction faults. - // In this case, the sp will be in either an invalid map if triggered - // on the main thread, or in a guard map if in another thread, which - // will be the first case or second case from below. - auto map_info = maps->Find(sp); - if (map_info == nullptr) { - return "stack pointer is in a non-existent map; likely due to stack overflow."; - } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) { - return "stack pointer is not in a rw map; likely due to stack overflow."; - } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) { - return "stack pointer is close to top of stack; likely stack overflow."; - } - } - return ""; -} - -static void dump_probable_cause(log_t* log, unwindstack::Unwinder* unwinder, - const ProcessInfo& process_info, const ThreadInfo& main_thread) { -#if defined(USE_SCUDO) - ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info); - if (scudo_crash_data.CrashIsMine()) { - scudo_crash_data.DumpCause(log, unwinder); - return; - } -#endif - - GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info, - main_thread); - if (gwp_asan_crash_data.CrashIsMine()) { - gwp_asan_crash_data.DumpCause(log); - return; - } - - unwindstack::Maps* maps = unwinder->GetMaps(); - unwindstack::Regs* regs = main_thread.registers.get(); - const siginfo_t* si = main_thread.siginfo; - std::string cause; - if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) { - if (si->si_addr < reinterpret_cast(4096)) { - cause = StringPrintf("null pointer dereference"); - } else if (si->si_addr == reinterpret_cast(0xffff0ffc)) { - cause = "call to kuser_helper_version"; - } else if (si->si_addr == reinterpret_cast(0xffff0fe0)) { - cause = "call to kuser_get_tls"; - } else if (si->si_addr == reinterpret_cast(0xffff0fc0)) { - cause = "call to kuser_cmpxchg"; - } else if (si->si_addr == reinterpret_cast(0xffff0fa0)) { - cause = "call to kuser_memory_barrier"; - } else if (si->si_addr == reinterpret_cast(0xffff0f60)) { - cause = "call to kuser_cmpxchg64"; - } else { - cause = get_stack_overflow_cause(reinterpret_cast(si->si_addr), regs->sp(), maps); - } - } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) { - uint64_t fault_addr = reinterpret_cast(si->si_addr); - auto map_info = maps->Find(fault_addr); - if (map_info != nullptr && map_info->flags() == PROT_EXEC) { - cause = "execute-only (no-read) memory access error; likely due to data in .text."; - } else { - cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps); - } - } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { - cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING, - si->si_syscall); - } - - if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str()); -} - -static void dump_signal_info(log_t* log, const ThreadInfo& thread_info, - const ProcessInfo& process_info, unwindstack::Memory* process_memory) { - char addr_desc[64]; // ", fault addr 0x1234" - if (process_info.has_fault_address) { - // SIGILL faults will never have tagged addresses, so okay to - // indiscriminately use the tagged address here. - size_t addr = process_info.maybe_tagged_fault_address; - if (thread_info.siginfo->si_signo == SIGILL) { - uint32_t instruction = {}; - process_memory->Read(addr, &instruction, sizeof(instruction)); - snprintf(addr_desc, sizeof(addr_desc), "0x%zx (*pc=%#08x)", addr, instruction); - } else { - snprintf(addr_desc, sizeof(addr_desc), "0x%zx", addr); - } - } else { - snprintf(addr_desc, sizeof(addr_desc), "--------"); - } - - char sender_desc[32] = {}; // " from pid 1234, uid 666" - if (signal_has_sender(thread_info.siginfo, thread_info.pid)) { - get_signal_sender(sender_desc, sizeof(sender_desc), thread_info.siginfo); - } - - _LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s%s), fault addr %s\n", - thread_info.siginfo->si_signo, get_signame(thread_info.siginfo), - thread_info.siginfo->si_code, get_sigcode(thread_info.siginfo), sender_desc, addr_desc); -} - -static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) { - // Don't try to collect logs from the threads that implement the logging system itself. - if (thread_info.uid == AID_LOGD) log->should_retrieve_logcat = false; - - const char* process_name = ""; - if (!thread_info.command_line.empty()) { - process_name = thread_info.command_line[0].c_str(); - } - - _LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s >>> %s <<<\n", thread_info.pid, - thread_info.tid, thread_info.thread_name.c_str(), process_name); - _LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid); - if (thread_info.tagged_addr_ctrl != -1) { - _LOG(log, logtype::HEADER, "tagged_addr_ctrl: %016lx%s\n", thread_info.tagged_addr_ctrl, - describe_tagged_addr_ctrl(thread_info.tagged_addr_ctrl).c_str()); - } -} - -static std::string get_addr_string(uint64_t addr) { - std::string addr_str; -#if defined(__LP64__) - addr_str = StringPrintf("%08x'%08x", static_cast(addr >> 32), - static_cast(addr & 0xffffffff)); -#else - addr_str = StringPrintf("%08x", static_cast(addr)); -#endif - return addr_str; -} - -static void dump_abort_message(log_t* log, unwindstack::Memory* process_memory, uint64_t address) { - if (address == 0) { - return; - } - - size_t length; - if (!process_memory->ReadFully(address, &length, sizeof(length))) { - _LOG(log, logtype::HEADER, "Failed to read abort message header: %s\n", strerror(errno)); - return; - } - - // The length field includes the length of the length field itself. - if (length < sizeof(size_t)) { - _LOG(log, logtype::HEADER, "Abort message header malformed: claimed length = %zd\n", length); - return; - } - - length -= sizeof(size_t); - - // The abort message should be null terminated already, but reserve a spot for NUL just in case. - std::vector msg(length + 1); - if (!process_memory->ReadFully(address + sizeof(length), &msg[0], length)) { - _LOG(log, logtype::HEADER, "Failed to read abort message: %s\n", strerror(errno)); - return; - } - - // Remove any trailing newlines. - size_t index = length; - while (index > 0 && (msg[index - 1] == '\0' || msg[index - 1] == '\n')) { - --index; - } - msg[index] = '\0'; - _LOG(log, logtype::HEADER, "Abort message: '%s'\n", &msg[0]); -} - -static void dump_all_maps(log_t* log, unwindstack::Unwinder* unwinder, uint64_t addr) { - bool print_fault_address_marker = addr; - - unwindstack::Maps* maps = unwinder->GetMaps(); - _LOG(log, logtype::MAPS, - "\n" - "memory map (%zu entr%s):", - maps->Total(), maps->Total() == 1 ? "y" : "ies"); - if (print_fault_address_marker) { - if (maps->Total() != 0 && addr < maps->Get(0)->start()) { - _LOG(log, logtype::MAPS, "\n--->Fault address falls at %s before any mapped regions\n", - get_addr_string(addr).c_str()); - print_fault_address_marker = false; - } else { - _LOG(log, logtype::MAPS, " (fault address prefixed with --->)\n"); - } - } else { - _LOG(log, logtype::MAPS, "\n"); - } - - std::shared_ptr& process_memory = unwinder->GetProcessMemory(); - - std::string line; - for (auto const& map_info : *maps) { - line = " "; - if (print_fault_address_marker) { - if (addr < map_info->start()) { - _LOG(log, logtype::MAPS, "--->Fault address falls at %s between mapped regions\n", - get_addr_string(addr).c_str()); - print_fault_address_marker = false; - } else if (addr >= map_info->start() && addr < map_info->end()) { - line = "--->"; - print_fault_address_marker = false; - } - } - line += get_addr_string(map_info->start()) + '-' + get_addr_string(map_info->end() - 1) + ' '; - if (map_info->flags() & PROT_READ) { - line += 'r'; - } else { - line += '-'; - } - if (map_info->flags() & PROT_WRITE) { - line += 'w'; - } else { - line += '-'; - } - if (map_info->flags() & PROT_EXEC) { - line += 'x'; - } else { - line += '-'; - } - line += StringPrintf(" %8" PRIx64 " %8" PRIx64, map_info->offset(), - map_info->end() - map_info->start()); - bool space_needed = true; - if (!map_info->name().empty()) { - space_needed = false; - line += " " + map_info->name(); - std::string build_id = map_info->GetPrintableBuildID(); - if (!build_id.empty()) { - line += " (BuildId: " + build_id + ")"; - } - } - uint64_t load_bias = map_info->GetLoadBias(process_memory); - if (load_bias != 0) { - if (space_needed) { - line += ' '; - } - line += StringPrintf(" (load bias 0x%" PRIx64 ")", load_bias); - } - _LOG(log, logtype::MAPS, "%s\n", line.c_str()); - } - if (print_fault_address_marker) { - _LOG(log, logtype::MAPS, "--->Fault address falls at %s after any mapped regions\n", - get_addr_string(addr).c_str()); - } -} - -static void print_register_row(log_t* log, - const std::vector>& registers) { - std::string output; - for (auto& [name, value] : registers) { - output += android::base::StringPrintf(" %-3s %0*" PRIx64, name.c_str(), - static_cast(2 * sizeof(void*)), - static_cast(value)); - } - - _LOG(log, logtype::REGISTERS, " %s\n", output.c_str()); -} - -void dump_registers(log_t* log, unwindstack::Regs* regs) { - // Split lr/sp/pc into their own special row. - static constexpr size_t column_count = 4; - std::vector> current_row; - std::vector> special_row; - -#if defined(__arm__) || defined(__aarch64__) - static constexpr const char* special_registers[] = {"ip", "lr", "sp", "pc", "pst"}; -#elif defined(__i386__) - static constexpr const char* special_registers[] = {"ebp", "esp", "eip"}; -#elif defined(__x86_64__) - static constexpr const char* special_registers[] = {"rbp", "rsp", "rip"}; -#else - static constexpr const char* special_registers[] = {}; -#endif - - regs->IterateRegisters([log, ¤t_row, &special_row](const char* name, uint64_t value) { - auto row = ¤t_row; - for (const char* special_name : special_registers) { - if (strcmp(special_name, name) == 0) { - row = &special_row; - break; - } - } - - row->emplace_back(name, value); - if (current_row.size() == column_count) { - print_register_row(log, current_row); - current_row.clear(); - } - }); - - if (!current_row.empty()) { - print_register_row(log, current_row); - } - - print_register_row(log, special_row); -} - -void dump_memory_and_code(log_t* log, unwindstack::Maps* maps, unwindstack::Memory* memory, - unwindstack::Regs* regs) { - regs->IterateRegisters([log, maps, memory](const char* reg_name, uint64_t reg_value) { - std::string label{"memory near "s + reg_name}; - if (maps) { - auto map_info = maps->Find(untag_address(reg_value)); - if (map_info != nullptr && !map_info->name().empty()) { - label += " (" + map_info->name() + ")"; - } - } - dump_memory(log, memory, reg_value, label); - }); -} - -static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const ThreadInfo& thread_info, - const ProcessInfo& process_info, bool primary_thread) { - log->current_tid = thread_info.tid; - if (!primary_thread) { - _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); - } - dump_thread_info(log, thread_info); - - if (thread_info.siginfo) { - dump_signal_info(log, thread_info, process_info, unwinder->GetProcessMemory().get()); - } - - if (primary_thread) { - // The main thread must have a valid siginfo. - CHECK(thread_info.siginfo != nullptr); - dump_probable_cause(log, unwinder, process_info, thread_info); - - dump_abort_message(log, unwinder->GetProcessMemory().get(), process_info.abort_msg_address); - } - - dump_registers(log, thread_info.registers.get()); - - // Unwind will mutate the registers, so make a copy first. - std::unique_ptr regs_copy(thread_info.registers->Clone()); - unwinder->SetRegs(regs_copy.get()); - unwinder->Unwind(); - if (unwinder->NumFrames() == 0) { - _LOG(log, logtype::THREAD, "Failed to unwind\n"); - if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) { - _LOG(log, logtype::THREAD, " Error code: %s\n", unwinder->LastErrorCodeString()); - _LOG(log, logtype::THREAD, " Error address: 0x%" PRIx64 "\n", unwinder->LastErrorAddress()); - } - } else { - _LOG(log, logtype::BACKTRACE, "\nbacktrace:\n"); - log_backtrace(log, unwinder, " "); - } - - if (primary_thread) { - GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info, - thread_info); - - if (gwp_asan_crash_data.HasDeallocationTrace()) { - gwp_asan_crash_data.DumpDeallocationTrace(log, unwinder); - } - - if (gwp_asan_crash_data.HasAllocationTrace()) { - gwp_asan_crash_data.DumpAllocationTrace(log, unwinder); - } - - unwindstack::Maps* maps = unwinder->GetMaps(); - dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(), - thread_info.registers.get()); - if (maps != nullptr) { - uint64_t addr = 0; - if (process_info.has_fault_address) { - addr = process_info.untagged_fault_address; - } - dump_all_maps(log, unwinder, addr); - } - } - - log->current_tid = log->crashed_tid; - return true; -} - -// Reads the contents of the specified log device, filters out the entries -// that don't match the specified pid, and writes them to the tombstone file. -// -// If "tail" is non-zero, log the last "tail" number of lines. -static void dump_log_file(log_t* log, pid_t pid, const char* filename, unsigned int tail) { - bool first = true; - logger_list* logger_list; - - if (!log->should_retrieve_logcat) { - return; - } - - logger_list = - android_logger_list_open(android_name_to_log_id(filename), ANDROID_LOG_NONBLOCK, tail, pid); - - if (!logger_list) { - ALOGE("Unable to open %s: %s\n", filename, strerror(errno)); - return; - } - - while (true) { - log_msg log_entry; - ssize_t actual = android_logger_list_read(logger_list, &log_entry); - - if (actual < 0) { - if (actual == -EINTR) { - // interrupted by signal, retry - continue; - } else if (actual == -EAGAIN) { - // non-blocking EOF; we're done - break; - } else { - ALOGE("Error while reading log: %s\n", strerror(-actual)); - break; - } - } else if (actual == 0) { - ALOGE("Got zero bytes while reading log: %s\n", strerror(errno)); - break; - } - - // NOTE: if you ALOGV something here, this will spin forever, - // because you will be writing as fast as you're reading. Any - // high-frequency debug diagnostics should just be written to - // the tombstone file. - - if (first) { - _LOG(log, logtype::LOGS, "--------- %slog %s\n", tail ? "tail end of " : "", filename); - first = false; - } - - // Msg format is: \0\0 - // - // We want to display it in the same format as "logcat -v threadtime" - // (although in this case the pid is redundant). - char timeBuf[32]; - time_t sec = static_cast(log_entry.entry.sec); - tm tm; - localtime_r(&sec, &tm); - strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", &tm); - - char* msg = log_entry.msg(); - if (msg == nullptr) { - continue; - } - unsigned char prio = msg[0]; - char* tag = msg + 1; - msg = tag + strlen(tag) + 1; - - // consume any trailing newlines - char* nl = msg + strlen(msg) - 1; - while (nl >= msg && *nl == '\n') { - *nl-- = '\0'; - } - - static const char* kPrioChars = "!.VDIWEFS"; - char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'); - - // Look for line breaks ('\n') and display each text line - // on a separate line, prefixed with the header, like logcat does. - do { - nl = strchr(msg, '\n'); - if (nl != nullptr) { - *nl = '\0'; - ++nl; - } - - _LOG(log, logtype::LOGS, "%s.%03d %5d %5d %c %-8s: %s\n", timeBuf, - log_entry.entry.nsec / 1000000, log_entry.entry.pid, log_entry.entry.tid, prioChar, tag, - msg); - } while ((msg = nl)); - } - - android_logger_list_free(logger_list); -} - -// Dumps the logs generated by the specified pid to the tombstone, from both -// "system" and "main" log devices. Ideally we'd interleave the output. -static void dump_logs(log_t* log, pid_t pid, unsigned int tail) { - if (pid == getpid()) { - // Cowardly refuse to dump logs while we're running in-process. - return; - } - - dump_log_file(log, pid, "system", tail); - dump_log_file(log, pid, "main", tail); -} - void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address, siginfo_t* siginfo, ucontext_t* ucontext) { pid_t uid = getuid(); @@ -646,45 +118,7 @@ void engrave_tombstone(unique_fd output_fd, unique_fd proto_fd, unwindstack::Unw log.tfd = output_fd.get(); log.amfd_data = amfd_data; - bool translate_proto = GetBoolProperty("debug.debuggerd.translate_proto_to_text", true); - if (translate_proto) { - tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) { - _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str()); - }); - } else { - bool want_logs = GetBoolProperty("ro.debuggable", false); - - _LOG(&log, logtype::HEADER, - "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); - dump_header_info(&log); - _LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str()); - - auto it = threads.find(target_thread); - if (it == threads.end()) { - async_safe_fatal("failed to find target thread"); - } - - dump_thread(&log, unwinder, it->second, process_info, true); - - if (want_logs) { - dump_logs(&log, it->second.pid, 50); - } - - for (auto& [tid, thread_info] : threads) { - if (tid == target_thread) { - continue; - } - - dump_thread(&log, unwinder, thread_info, process_info, false); - } - - if (open_files) { - _LOG(&log, logtype::OPEN_FILES, "\nopen files:\n"); - dump_open_files_list(&log, *open_files, " "); - } - - if (want_logs) { - dump_logs(&log, it->second.pid, 0); - } - } + tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) { + _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str()); + }); }