| // Copyright 2014 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 "minidump/minidump_file_writer.h" |
| |
| #include <utility> |
| |
| #include "base/logging.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_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 "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<MINIDUMP_TYPE>(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<MinidumpSystemInfoWriter>(); |
| system_info->InitializeFromSnapshot(system_snapshot); |
| bool add_stream_result = AddStream(std::move(system_info)); |
| DCHECK(add_stream_result); |
| |
| auto misc_info = std::make_unique<MinidumpMiscInfoWriter>(); |
| misc_info->InitializeFromSnapshot(process_snapshot); |
| add_stream_result = AddStream(std::move(misc_info)); |
| DCHECK(add_stream_result); |
| |
| auto memory_list = std::make_unique<MinidumpMemoryListWriter>(); |
| auto thread_list = std::make_unique<MinidumpThreadListWriter>(); |
| 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); |
| |
| const ExceptionSnapshot* exception_snapshot = process_snapshot->Exception(); |
| if (exception_snapshot) { |
| auto exception = std::make_unique<MinidumpExceptionWriter>(); |
| exception->InitializeFromSnapshot(exception_snapshot, thread_id_map); |
| add_stream_result = AddStream(std::move(exception)); |
| DCHECK(add_stream_result); |
| } |
| |
| auto module_list = std::make_unique<MinidumpModuleListWriter>(); |
| 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<MinidumpUnloadedModuleListWriter>(); |
| 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<MinidumpCrashpadInfoWriter>(); |
| 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<const MemoryMapRegionSnapshot*> memory_map_snapshot = |
| process_snapshot->MemoryMap(); |
| if (!memory_map_snapshot.empty()) { |
| auto memory_info_list = std::make_unique<MinidumpMemoryInfoListWriter>(); |
| memory_info_list->InitializeFromSnapshot(memory_map_snapshot); |
| add_stream_result = AddStream(std::move(memory_info_list)); |
| DCHECK(add_stream_result); |
| } |
| |
| std::vector<HandleSnapshot> handles_snapshot = process_snapshot->Handles(); |
| if (!handles_snapshot.empty()) { |
| auto handle_data_writer = std::make_unique<MinidumpHandleDataWriter>(); |
| 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<MinidumpUserStreamWriter>(); |
| 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<internal::MinidumpStreamWriter> 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<MinidumpUserExtensionStreamDataSource> |
| user_extension_stream_data) { |
| DCHECK_EQ(state(), kStateMutable); |
| |
| auto user_stream = std::make_unique<MinidumpUserStreamWriter>(); |
| 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<internal::MinidumpWritable*> MinidumpFileWriter::Children() { |
| DCHECK_GE(state(), kStateFrozen); |
| DCHECK_EQ(streams_.size(), stream_types_.size()); |
| |
| std::vector<MinidumpWritable*> 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<WritableIoVec> 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 |