| // 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 <sys/utsname.h> |
| |
| #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<internal::IOSIntermediateDumpWriter>(); |
| 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<ConstThreadState>(&cpu_context), |
| MACHINE_THREAD_STATE_COUNT); |
| } |
| |
| const auto& path() const { return path_; } |
| auto writer() const { return writer_.get(); } |
| |
| private: |
| std::unique_ptr<internal::IOSIntermediateDumpWriter> 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<std::string, std::string> all_annotations_simple_map; |
| std::vector<AnnotationSnapshot> all_annotations; |
| for (const auto* module : process_snapshot.Modules()) { |
| std::map<std::string, std::string> module_annotations_simple_map = |
| module->AnnotationsSimpleMap(); |
| all_annotations_simple_map.insert(module_annotations_simple_map.begin(), |
| module_annotations_simple_map.end()); |
| |
| std::vector<AnnotationSnapshot> 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<uint16_t>(Annotation::Type::kString)); |
| std::string value(reinterpret_cast<const char*>(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<thread_info_t>(&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 |