// Copyright 2021 The Crashpad Authors. All rights reserved. // // 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 "client/ios_handler/in_process_intermediate_dump_handler.h" #include #include "base/cxx17_backports.h" #include "base/files/file_path.h" #include "build/build_config.h" #include "client/annotation.h" #include "client/annotation_list.h" #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" #include "gtest/gtest.h" #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" #include "util/file/filesystem.h" #include "util/misc/capture_context.h" namespace crashpad { namespace test { namespace { using internal::InProcessIntermediateDumpHandler; class InProcessIntermediateDumpHandlerTest : public testing::Test { protected: // testing::Test: void SetUp() override { path_ = temp_dir_.path().Append("dump_file"); writer_ = std::make_unique(); EXPECT_TRUE(writer_->Open(path_)); ASSERT_TRUE(IsRegularFile(path_)); } void TearDown() override { writer_.reset(); EXPECT_FALSE(IsRegularFile(path_)); } void WriteReport() { internal::IOSIntermediateDumpWriter::ScopedRootMap rootMap(writer_.get()); InProcessIntermediateDumpHandler::WriteHeader(writer_.get()); InProcessIntermediateDumpHandler::WriteProcessInfo(writer_.get()); InProcessIntermediateDumpHandler::WriteSystemInfo(writer_.get(), system_data_); InProcessIntermediateDumpHandler::WriteThreadInfo(writer_.get(), 0, 0); InProcessIntermediateDumpHandler::WriteModuleInfo(writer_.get()); } void WriteMachException() { crashpad::NativeCPUContext cpu_context; crashpad::CaptureContext(&cpu_context); const mach_exception_data_type_t code[2] = {}; static constexpr int kSimulatedException = -1; InProcessIntermediateDumpHandler::WriteExceptionFromMachException( writer_.get(), MACH_EXCEPTION_CODES, mach_thread_self(), kSimulatedException, code, base::size(code), MACHINE_THREAD_STATE, reinterpret_cast(&cpu_context), MACHINE_THREAD_STATE_COUNT); } const auto& path() const { return path_; } auto writer() const { return writer_.get(); } private: std::unique_ptr writer_; internal::IOSSystemDataCollector system_data_; ScopedTempDir temp_dir_; base::FilePath path_; }; TEST_F(InProcessIntermediateDumpHandlerTest, TestSystem) { WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {})); // Snpahot const SystemSnapshot* system = process_snapshot.System(); ASSERT_NE(system, nullptr); #if defined(ARCH_CPU_X86_64) EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureX86_64); EXPECT_STREQ(system->CPUVendor().c_str(), "GenuineIntel"); #elif defined(ARCH_CPU_ARM64) EXPECT_EQ(system->GetCPUArchitecture(), kCPUArchitectureARM64); utsname uts; ASSERT_EQ(uname(&uts), 0); EXPECT_STREQ(system->MachineDescription().c_str(), uts.machine); #else #error Port to your CPU architecture #endif EXPECT_EQ(system->GetOperatingSystem(), SystemSnapshot::kOperatingSystemIOS); } TEST_F(InProcessIntermediateDumpHandlerTest, TestAnnotations) { // This is “leaked” to crashpad_info. crashpad::SimpleStringDictionary* simple_annotations = new crashpad::SimpleStringDictionary(); simple_annotations->SetKeyValue("#TEST# pad", "break"); simple_annotations->SetKeyValue("#TEST# key", "value"); simple_annotations->SetKeyValue("#TEST# pad", "crash"); simple_annotations->SetKeyValue("#TEST# x", "y"); simple_annotations->SetKeyValue("#TEST# longer", "shorter"); simple_annotations->SetKeyValue("#TEST# empty_value", ""); crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); crashpad_info->set_simple_annotations(simple_annotations); crashpad::AnnotationList::Register(); // This is “leaked” to crashpad_info. static crashpad::StringAnnotation<32> test_annotation_one{"#TEST# one"}; static crashpad::StringAnnotation<32> test_annotation_two{"#TEST# two"}; static crashpad::StringAnnotation<32> test_annotation_three{ "#TEST# same-name"}; static crashpad::StringAnnotation<32> test_annotation_four{ "#TEST# same-name"}; test_annotation_one.Set("moocow"); test_annotation_two.Set("this will be cleared"); test_annotation_three.Set("same-name 3"); test_annotation_four.Set("same-name 4"); test_annotation_two.Clear(); WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {{"after_dump", "post"}})); auto process_map = process_snapshot.AnnotationsSimpleMap(); EXPECT_EQ(process_map.size(), 1u); EXPECT_EQ(process_map["after_dump"], "post"); std::map all_annotations_simple_map; std::vector all_annotations; for (const auto* module : process_snapshot.Modules()) { std::map module_annotations_simple_map = module->AnnotationsSimpleMap(); all_annotations_simple_map.insert(module_annotations_simple_map.begin(), module_annotations_simple_map.end()); std::vector annotations = module->AnnotationObjects(); all_annotations.insert( all_annotations.end(), annotations.begin(), annotations.end()); } EXPECT_EQ(all_annotations_simple_map.size(), 5u); EXPECT_EQ(all_annotations_simple_map["#TEST# pad"], "crash"); EXPECT_EQ(all_annotations_simple_map["#TEST# key"], "value"); EXPECT_EQ(all_annotations_simple_map["#TEST# x"], "y"); EXPECT_EQ(all_annotations_simple_map["#TEST# longer"], "shorter"); EXPECT_EQ(all_annotations_simple_map["#TEST# empty_value"], ""); bool saw_same_name_3 = false, saw_same_name_4 = false; for (const auto& annotation : all_annotations) { EXPECT_EQ(annotation.type, static_cast(Annotation::Type::kString)); std::string value(reinterpret_cast(annotation.value.data()), annotation.value.size()); if (annotation.name == "#TEST# one") { EXPECT_EQ(value, "moocow"); } else if (annotation.name == "#TEST# same-name") { if (value == "same-name 3") { EXPECT_FALSE(saw_same_name_3); saw_same_name_3 = true; } else if (value == "same-name 4") { EXPECT_FALSE(saw_same_name_4); saw_same_name_4 = true; } else { ADD_FAILURE() << "unexpected annotation value " << value; } } else { ADD_FAILURE() << "unexpected annotation " << annotation.name; } } } TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) { WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {})); const auto& threads = process_snapshot.Threads(); ASSERT_GT(threads.size(), 0u); thread_identifier_info identifier_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; ASSERT_EQ(thread_info(mach_thread_self(), THREAD_IDENTIFIER_INFO, reinterpret_cast(&identifier_info), &count), 0); EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id); } TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) { WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {})); EXPECT_EQ(process_snapshot.ProcessID(), getpid()); } TEST_F(InProcessIntermediateDumpHandlerTest, TestMachException) { WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {})); } TEST_F(InProcessIntermediateDumpHandlerTest, TestSignalException) { WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {})); } TEST_F(InProcessIntermediateDumpHandlerTest, TestNSException) { WriteReport(); internal::ProcessSnapshotIOSIntermediateDump process_snapshot; ASSERT_TRUE(process_snapshot.Initialize(path(), {})); } } // namespace } // namespace test } // namespace crashpad