// Copyright 2014 The Crashpad Authors // // 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 "minidump/minidump_file_writer.h" #include #include "base/check_op.h" #include "base/logging.h" #include "build/build_config.h" #include "minidump/minidump_crashpad_info_writer.h" #include "minidump/minidump_exception_writer.h" #include "minidump/minidump_handle_writer.h" #include "minidump/minidump_memory_info_writer.h" #include "minidump/minidump_memory_writer.h" #include "minidump/minidump_misc_info_writer.h" #include "minidump/minidump_module_writer.h" #include "minidump/minidump_system_info_writer.h" #include "minidump/minidump_thread_id_map.h" #include "minidump/minidump_thread_name_list_writer.h" #include "minidump/minidump_thread_writer.h" #include "minidump/minidump_unloaded_module_writer.h" #include "minidump/minidump_user_extension_stream_data_source.h" #include "minidump/minidump_user_stream_writer.h" #include "minidump/minidump_writer_util.h" #include "snapshot/exception_snapshot.h" #include "snapshot/module_snapshot.h" #include "snapshot/process_snapshot.h" #include "snapshot/thread_snapshot.h" #include "util/file/file_writer.h" #include "util/numeric/safe_assignment.h" namespace crashpad { MinidumpFileWriter::MinidumpFileWriter() : MinidumpWritable(), header_(), streams_(), stream_types_() { // Don’t set the signature field right away. Leave it set to 0, so that a // partially-written minidump file isn’t confused for a complete and valid // one. The header will be rewritten in WriteToFile(). header_.Signature = 0; header_.Version = MINIDUMP_VERSION; header_.CheckSum = 0; header_.Flags = MiniDumpNormal; } MinidumpFileWriter::~MinidumpFileWriter() { } void MinidumpFileWriter::InitializeFromSnapshot( const ProcessSnapshot* process_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(header_.Signature, 0u); DCHECK_EQ(header_.TimeDateStamp, 0u); DCHECK_EQ(static_cast(header_.Flags), MiniDumpNormal); DCHECK(streams_.empty()); // This time is truncated to an integer number of seconds, not rounded, for // compatibility with the truncation of process_snapshot->ProcessStartTime() // done by MinidumpMiscInfoWriter::InitializeFromSnapshot(). Handling both // timestamps in the same way allows the highest-fidelity computation of // process uptime as the difference between the two values. timeval snapshot_time; process_snapshot->SnapshotTime(&snapshot_time); SetTimestamp(snapshot_time.tv_sec); const SystemSnapshot* system_snapshot = process_snapshot->System(); auto system_info = std::make_unique(); system_info->InitializeFromSnapshot(system_snapshot); bool add_stream_result = AddStream(std::move(system_info)); DCHECK(add_stream_result); auto misc_info = std::make_unique(); misc_info->InitializeFromSnapshot(process_snapshot); if (misc_info->HasXStateData()) header_.Flags = header_.Flags | MiniDumpWithAvxXStateContext; add_stream_result = AddStream(std::move(misc_info)); DCHECK(add_stream_result); auto memory_list = std::make_unique(); auto thread_list = std::make_unique(); thread_list->SetMemoryListWriter(memory_list.get()); MinidumpThreadIDMap thread_id_map; thread_list->InitializeFromSnapshot(process_snapshot->Threads(), &thread_id_map); add_stream_result = AddStream(std::move(thread_list)); DCHECK(add_stream_result); bool has_thread_name = false; for (const ThreadSnapshot* thread_snapshot : process_snapshot->Threads()) { if (!thread_snapshot->ThreadName().empty()) { has_thread_name = true; break; } } if (has_thread_name) { auto thread_name_list = std::make_unique(); thread_name_list->InitializeFromSnapshot(process_snapshot->Threads(), thread_id_map); add_stream_result = AddStream(std::move(thread_name_list)); DCHECK(add_stream_result); } const ExceptionSnapshot* exception_snapshot = process_snapshot->Exception(); if (exception_snapshot) { auto exception = std::make_unique(); #if BUILDFLAG(IS_IOS) // It's expected that iOS intermediate dumps can be written with missing // information, but it's better to try and report as much as possible // rather than drop the incomplete minidump. constexpr bool allow_missing_thread_id_from_map = true; #else constexpr bool allow_missing_thread_id_from_map = false; #endif exception->InitializeFromSnapshot( exception_snapshot, thread_id_map, allow_missing_thread_id_from_map); add_stream_result = AddStream(std::move(exception)); DCHECK(add_stream_result); } auto module_list = std::make_unique(); module_list->InitializeFromSnapshot(process_snapshot->Modules()); add_stream_result = AddStream(std::move(module_list)); DCHECK(add_stream_result); auto unloaded_modules = process_snapshot->UnloadedModules(); if (!unloaded_modules.empty()) { auto unloaded_module_list = std::make_unique(); unloaded_module_list->InitializeFromSnapshot(unloaded_modules); add_stream_result = AddStream(std::move(unloaded_module_list)); DCHECK(add_stream_result); } auto crashpad_info = std::make_unique(); crashpad_info->InitializeFromSnapshot(process_snapshot); // Since the MinidumpCrashpadInfo stream is an extension, it’s safe to not add // it to the minidump file if it wouldn’t carry any useful information. if (crashpad_info->IsUseful()) { add_stream_result = AddStream(std::move(crashpad_info)); DCHECK(add_stream_result); } std::vector memory_map_snapshot = process_snapshot->MemoryMap(); if (!memory_map_snapshot.empty()) { auto memory_info_list = std::make_unique(); memory_info_list->InitializeFromSnapshot(memory_map_snapshot); add_stream_result = AddStream(std::move(memory_info_list)); DCHECK(add_stream_result); } std::vector handles_snapshot = process_snapshot->Handles(); if (!handles_snapshot.empty()) { auto handle_data_writer = std::make_unique(); handle_data_writer->InitializeFromSnapshot(handles_snapshot); add_stream_result = AddStream(std::move(handle_data_writer)); DCHECK(add_stream_result); } memory_list->AddFromSnapshot(process_snapshot->ExtraMemory()); if (exception_snapshot) { memory_list->AddFromSnapshot(exception_snapshot->ExtraMemory()); } // These user streams must be added last. Otherwise, a user stream with the // same type as a well-known stream could preempt the well-known stream. As it // stands now, earlier-discovered user streams can still preempt // later-discovered ones. The well-known memory list stream is added after // these user streams, but only with a check here to avoid adding a user // stream that would preempt the memory list stream. for (const auto& module : process_snapshot->Modules()) { for (const UserMinidumpStream* stream : module->CustomMinidumpStreams()) { if (stream->stream_type() == kMinidumpStreamTypeMemoryList) { LOG(WARNING) << "discarding duplicate stream of type " << stream->stream_type(); continue; } auto user_stream = std::make_unique(); user_stream->InitializeFromSnapshot(stream); AddStream(std::move(user_stream)); } } // The memory list stream should be added last. This keeps the “extra memory” // at the end so that if the minidump file is truncated, other, more critical // data is more likely to be preserved. Note that non-“extra” memory regions // will not have to ride at the end of the file. Thread stack memory, for // example, exists as a children of threads, and appears alongside them in the // file, despite also being mentioned by the memory list stream. add_stream_result = AddStream(std::move(memory_list)); DCHECK(add_stream_result); } void MinidumpFileWriter::SetTimestamp(time_t timestamp) { DCHECK_EQ(state(), kStateMutable); internal::MinidumpWriterUtil::AssignTimeT(&header_.TimeDateStamp, timestamp); } bool MinidumpFileWriter::AddStream( std::unique_ptr stream) { DCHECK_EQ(state(), kStateMutable); MinidumpStreamType stream_type = stream->StreamType(); auto rv = stream_types_.insert(stream_type); if (!rv.second) { LOG(WARNING) << "discarding duplicate stream of type " << stream_type; return false; } streams_.push_back(std::move(stream)); DCHECK_EQ(streams_.size(), stream_types_.size()); return true; } bool MinidumpFileWriter::AddUserExtensionStream( std::unique_ptr user_extension_stream_data) { DCHECK_EQ(state(), kStateMutable); auto user_stream = std::make_unique(); user_stream->InitializeFromUserExtensionStream( std::move(user_extension_stream_data)); return AddStream(std::move(user_stream)); } bool MinidumpFileWriter::WriteEverything(FileWriterInterface* file_writer) { return WriteMinidump(file_writer, true); } bool MinidumpFileWriter::WriteMinidump(FileWriterInterface* file_writer, bool allow_seek) { DCHECK_EQ(state(), kStateMutable); FileOffset start_offset = -1; if (allow_seek) { start_offset = file_writer->Seek(0, SEEK_CUR); if (start_offset < 0) { return false; } } else { header_.Signature = MINIDUMP_SIGNATURE; } if (!MinidumpWritable::WriteEverything(file_writer)) { return false; } if (!allow_seek) return true; FileOffset end_offset = file_writer->Seek(0, SEEK_CUR); if (end_offset < 0) { return false; } // Now that the entire minidump file has been completely written, go back to // the beginning and rewrite the header with the correct signature to identify // it as a valid minidump file. header_.Signature = MINIDUMP_SIGNATURE; if (file_writer->Seek(start_offset, SEEK_SET) < 0) { return false; } if (!file_writer->Write(&header_, sizeof(header_))) { return false; } // Seek back to the end of the file, in case some non-minidump content will be // written to the file after the minidump content. return file_writer->Seek(end_offset, SEEK_SET) >= 0; } bool MinidumpFileWriter::Freeze() { DCHECK_EQ(state(), kStateMutable); if (!MinidumpWritable::Freeze()) { return false; } size_t stream_count = streams_.size(); CHECK_EQ(stream_count, stream_types_.size()); if (!AssignIfInRange(&header_.NumberOfStreams, stream_count)) { LOG(ERROR) << "stream_count " << stream_count << " out of range"; return false; } return true; } size_t MinidumpFileWriter::SizeOfObject() { DCHECK_GE(state(), kStateFrozen); DCHECK_EQ(streams_.size(), stream_types_.size()); return sizeof(header_) + streams_.size() * sizeof(MINIDUMP_DIRECTORY); } std::vector MinidumpFileWriter::Children() { DCHECK_GE(state(), kStateFrozen); DCHECK_EQ(streams_.size(), stream_types_.size()); std::vector children; for (const auto& stream : streams_) { children.push_back(stream.get()); } return children; } bool MinidumpFileWriter::WillWriteAtOffsetImpl(FileOffset offset) { DCHECK_EQ(state(), kStateFrozen); DCHECK_EQ(offset, 0); DCHECK_EQ(streams_.size(), stream_types_.size()); auto directory_offset = streams_.empty() ? 0 : offset + sizeof(header_); if (!AssignIfInRange(&header_.StreamDirectoryRva, directory_offset)) { LOG(ERROR) << "offset " << directory_offset << " out of range"; return false; } return MinidumpWritable::WillWriteAtOffsetImpl(offset); } bool MinidumpFileWriter::WriteObject(FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); DCHECK_EQ(streams_.size(), stream_types_.size()); WritableIoVec iov; iov.iov_base = &header_; iov.iov_len = sizeof(header_); std::vector iovecs(1, iov); for (const auto& stream : streams_) { iov.iov_base = stream->DirectoryListEntry(); iov.iov_len = sizeof(MINIDUMP_DIRECTORY); iovecs.push_back(iov); } return file_writer->WriteIoVec(&iovecs); } } // namespace crashpad