// Copyright 2015 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 "snapshot/minidump/process_snapshot_minidump.h" #include #include "base/logging.h" #include "base/notreached.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "minidump/minidump_extensions.h" #include "snapshot/memory_map_region_snapshot.h" #include "snapshot/minidump/minidump_simple_string_dictionary_reader.h" #include "snapshot/minidump/minidump_string_reader.h" #include "util/file/file_io.h" namespace crashpad { namespace internal { class MemoryMapRegionSnapshotMinidump : public MemoryMapRegionSnapshot { public: MemoryMapRegionSnapshotMinidump(MINIDUMP_MEMORY_INFO info) : info_(info) {} ~MemoryMapRegionSnapshotMinidump() override = default; const MINIDUMP_MEMORY_INFO& AsMinidumpMemoryInfo() const override { return info_; } private: MINIDUMP_MEMORY_INFO info_; }; } // namespace internal ProcessSnapshotMinidump::ProcessSnapshotMinidump() : ProcessSnapshot(), header_(), stream_directory_(), stream_map_(), modules_(), threads_(), unloaded_modules_(), mem_regions_(), mem_regions_exposed_(), custom_streams_(), crashpad_info_(), system_snapshot_(), exception_snapshot_(), arch_(CPUArchitecture::kCPUArchitectureUnknown), annotations_simple_map_(), file_reader_(nullptr), process_id_(kInvalidProcessID), create_time_(0), user_time_(0), kernel_time_(0), initialized_() {} ProcessSnapshotMinidump::~ProcessSnapshotMinidump() {} bool ProcessSnapshotMinidump::Initialize(FileReaderInterface* file_reader) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); file_reader_ = file_reader; if (!file_reader_->SeekSet(0)) { return false; } if (!file_reader_->ReadExactly(&header_, sizeof(header_))) { return false; } if (header_.Signature != MINIDUMP_SIGNATURE) { LOG(ERROR) << "minidump signature mismatch"; return false; } if (header_.Version != MINIDUMP_VERSION) { LOG(ERROR) << "minidump version mismatch"; return false; } if (!file_reader->SeekSet(header_.StreamDirectoryRva)) { return false; } stream_directory_.resize(header_.NumberOfStreams); if (!stream_directory_.empty() && !file_reader_->ReadExactly( &stream_directory_[0], header_.NumberOfStreams * sizeof(stream_directory_[0]))) { return false; } for (const MINIDUMP_DIRECTORY& directory : stream_directory_) { const MinidumpStreamType stream_type = static_cast(directory.StreamType); if (stream_map_.find(stream_type) != stream_map_.end()) { LOG(ERROR) << "duplicate streams for type " << directory.StreamType; return false; } stream_map_[stream_type] = &directory.Location; } if (!InitializeCrashpadInfo() || !InitializeMiscInfo() || !InitializeModules() || !InitializeSystemSnapshot() || !InitializeMemoryInfo() || !InitializeExtraMemory() || !InitializeThreads() || !InitializeCustomMinidumpStreams() || !InitializeExceptionSnapshot()) { return false; } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } crashpad::ProcessID ProcessSnapshotMinidump::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_id_; } crashpad::ProcessID ProcessSnapshotMinidump::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // https://crashpad.chromium.org/bug/10 } void ProcessSnapshotMinidump::SnapshotTime(timeval* snapshot_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); snapshot_time->tv_sec = header_.TimeDateStamp; snapshot_time->tv_usec = 0; } void ProcessSnapshotMinidump::ProcessStartTime(timeval* start_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); start_time->tv_sec = create_time_; start_time->tv_usec = 0; } void ProcessSnapshotMinidump::ProcessCPUTimes(timeval* user_time, timeval* system_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); user_time->tv_sec = user_time_; user_time->tv_usec = 0; system_time->tv_sec = kernel_time_; system_time->tv_usec = 0; } void ProcessSnapshotMinidump::ReportID(UUID* report_id) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *report_id = crashpad_info_.report_id; } void ProcessSnapshotMinidump::ClientID(UUID* client_id) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); *client_id = crashpad_info_.client_id; } const std::map& ProcessSnapshotMinidump::AnnotationsSimpleMap() const { // TODO(mark): This method should not be const, although the interface // currently imposes this requirement. Making it non-const would allow // annotations_simple_map_ to be lazily constructed: InitializeCrashpadInfo() // could be called here, and from other locations that require it, rather than // calling it from Initialize(). // https://crashpad.chromium.org/bug/9 INITIALIZATION_STATE_DCHECK_VALID(initialized_); return annotations_simple_map_; } const SystemSnapshot* ProcessSnapshotMinidump::System() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return &system_snapshot_; } std::vector ProcessSnapshotMinidump::Threads() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector threads; for (const auto& thread : threads_) { threads.push_back(thread.get()); } return threads; } std::vector ProcessSnapshotMinidump::Modules() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector modules; for (const auto& module : modules_) { modules.push_back(module.get()); } return modules; } std::vector ProcessSnapshotMinidump::UnloadedModules() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // https://crashpad.chromium.org/bug/10 } const ExceptionSnapshot* ProcessSnapshotMinidump::Exception() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (exception_snapshot_.IsValid()) { return &exception_snapshot_; } // Allow caller to know whether the minidump contained an exception stream. return nullptr; } std::vector ProcessSnapshotMinidump::MemoryMap() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return mem_regions_exposed_; } std::vector ProcessSnapshotMinidump::Handles() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // https://crashpad.chromium.org/bug/10 } std::vector ProcessSnapshotMinidump::ExtraMemory() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector chunks; for (const auto& chunk : extra_memory_) { chunks.push_back(chunk.get()); } return chunks; } const ProcessMemory* ProcessSnapshotMinidump::Memory() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return nullptr; } std::vector ProcessSnapshotMinidump::CustomMinidumpStreams() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::vector result; result.reserve(custom_streams_.size()); for (const auto& custom_stream : custom_streams_) { result.push_back(custom_stream.get()); } return result; } bool ProcessSnapshotMinidump::InitializeCrashpadInfo() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeCrashpadInfo); if (stream_it == stream_map_.end()) { return true; } constexpr size_t crashpad_info_min_size = offsetof(decltype(crashpad_info_), reserved); size_t remaining_data_size = stream_it->second->DataSize; if (remaining_data_size < crashpad_info_min_size) { LOG(ERROR) << "crashpad_info size mismatch"; return false; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } if (!file_reader_->ReadExactly(&crashpad_info_, crashpad_info_min_size)) { return false; } remaining_data_size -= crashpad_info_min_size; // Read `reserved` if available. size_t crashpad_reserved_size = sizeof(crashpad_info_.reserved); if (remaining_data_size >= crashpad_reserved_size) { if (!file_reader_->ReadExactly(&crashpad_info_.reserved, crashpad_reserved_size)) { return false; } remaining_data_size -= crashpad_reserved_size; } else { crashpad_info_.reserved = 0; } // Read `address_mask` if available. size_t crashpad_address_mask_size = sizeof(crashpad_info_.address_mask); if (remaining_data_size >= crashpad_address_mask_size) { if (!file_reader_->ReadExactly(&crashpad_info_.address_mask, crashpad_address_mask_size)) { return false; } remaining_data_size -= crashpad_address_mask_size; } else { crashpad_info_.address_mask = 0; } if (crashpad_info_.version != MinidumpCrashpadInfo::kVersion) { LOG(ERROR) << "crashpad_info version mismatch"; return false; } return internal::ReadMinidumpSimpleStringDictionary( file_reader_, crashpad_info_.simple_annotations, &annotations_simple_map_); } bool ProcessSnapshotMinidump::InitializeMiscInfo() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeMiscInfo); if (stream_it == stream_map_.end()) { return true; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } const size_t size = stream_it->second->DataSize; if (size != sizeof(MINIDUMP_MISC_INFO_5) && size != sizeof(MINIDUMP_MISC_INFO_4) && size != sizeof(MINIDUMP_MISC_INFO_3) && size != sizeof(MINIDUMP_MISC_INFO_2) && size != sizeof(MINIDUMP_MISC_INFO)) { LOG(ERROR) << "misc_info size mismatch"; return false; } MINIDUMP_MISC_INFO_5 info; if (!file_reader_->ReadExactly(&info, size)) { return false; } switch (stream_it->second->DataSize) { case sizeof(MINIDUMP_MISC_INFO_5): case sizeof(MINIDUMP_MISC_INFO_4): #if defined(WCHAR_T_IS_16_BIT) full_version_ = base::WideToUTF8(info.BuildString); #else full_version_ = base::UTF16ToUTF8(info.BuildString); #endif full_version_ = full_version_.substr(0, full_version_.find(';')); [[fallthrough]]; case sizeof(MINIDUMP_MISC_INFO_3): case sizeof(MINIDUMP_MISC_INFO_2): case sizeof(MINIDUMP_MISC_INFO): // TODO(jperaza): Save the remaining misc info. // https://crashpad.chromium.org/bug/10 process_id_ = info.ProcessId; create_time_ = info.ProcessCreateTime; user_time_ = info.ProcessUserTime; kernel_time_ = info.ProcessKernelTime; } return true; } bool ProcessSnapshotMinidump::InitializeModules() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeModuleList); if (stream_it == stream_map_.end()) { return true; } std::map module_crashpad_info_links; if (!InitializeModulesCrashpadInfo(&module_crashpad_info_links)) { return false; } if (stream_it->second->DataSize < sizeof(MINIDUMP_MODULE_LIST)) { LOG(ERROR) << "module_list size mismatch"; return false; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } uint32_t module_count; if (!file_reader_->ReadExactly(&module_count, sizeof(module_count))) { return false; } if (sizeof(MINIDUMP_MODULE_LIST) + module_count * sizeof(MINIDUMP_MODULE) != stream_it->second->DataSize) { LOG(ERROR) << "module_list size mismatch"; return false; } for (uint32_t module_index = 0; module_index < module_count; ++module_index) { const RVA module_rva = stream_it->second->Rva + sizeof(module_count) + module_index * sizeof(MINIDUMP_MODULE); const auto& module_crashpad_info_it = module_crashpad_info_links.find(module_index); const MINIDUMP_LOCATION_DESCRIPTOR* module_crashpad_info_location = module_crashpad_info_it != module_crashpad_info_links.end() ? &module_crashpad_info_it->second : nullptr; auto module = std::make_unique(); if (!module->Initialize( file_reader_, module_rva, module_crashpad_info_location)) { return false; } modules_.push_back(std::move(module)); } return true; } bool ProcessSnapshotMinidump::InitializeModulesCrashpadInfo( std::map* module_crashpad_info_links) { module_crashpad_info_links->clear(); if (crashpad_info_.version != MinidumpCrashpadInfo::kVersion) { return false; } if (crashpad_info_.module_list.Rva == 0) { return true; } if (crashpad_info_.module_list.DataSize < sizeof(MinidumpModuleCrashpadInfoList)) { LOG(ERROR) << "module_crashpad_info_list size mismatch"; return false; } if (!file_reader_->SeekSet(crashpad_info_.module_list.Rva)) { return false; } uint32_t crashpad_module_count; if (!file_reader_->ReadExactly(&crashpad_module_count, sizeof(crashpad_module_count))) { return false; } if (crashpad_info_.module_list.DataSize != sizeof(MinidumpModuleCrashpadInfoList) + crashpad_module_count * sizeof(MinidumpModuleCrashpadInfoLink)) { LOG(ERROR) << "module_crashpad_info_list size mismatch"; return false; } std::unique_ptr minidump_links( new MinidumpModuleCrashpadInfoLink[crashpad_module_count]); if (!file_reader_->ReadExactly( &minidump_links[0], crashpad_module_count * sizeof(MinidumpModuleCrashpadInfoLink))) { return false; } for (uint32_t crashpad_module_index = 0; crashpad_module_index < crashpad_module_count; ++crashpad_module_index) { const MinidumpModuleCrashpadInfoLink& minidump_link = minidump_links[crashpad_module_index]; if (!module_crashpad_info_links ->insert(std::make_pair(minidump_link.minidump_module_list_index, minidump_link.location)) .second) { LOG(WARNING) << "duplicate module_crashpad_info_list minidump_module_list_index " << minidump_link.minidump_module_list_index; return false; } } return true; } bool ProcessSnapshotMinidump::InitializeMemoryInfo() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeMemoryInfoList); if (stream_it == stream_map_.end()) { return true; } if (stream_it->second->DataSize < sizeof(MINIDUMP_MEMORY_INFO_LIST)) { LOG(ERROR) << "memory_info_list size mismatch"; return false; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } MINIDUMP_MEMORY_INFO_LIST list; if (!file_reader_->ReadExactly(&list, sizeof(list))) { return false; } if (list.SizeOfHeader != sizeof(list)) { return false; } if (list.SizeOfEntry != sizeof(MINIDUMP_MEMORY_INFO)) { return false; } if (sizeof(MINIDUMP_MEMORY_INFO_LIST) + list.NumberOfEntries * list.SizeOfEntry != stream_it->second->DataSize) { LOG(ERROR) << "memory_info_list size mismatch"; return false; } for (uint32_t i = 0; i < list.NumberOfEntries; i++) { MINIDUMP_MEMORY_INFO info; if (!file_reader_->ReadExactly(&info, sizeof(info))) { return false; } mem_regions_.emplace_back( std::make_unique(info)); mem_regions_exposed_.emplace_back(mem_regions_.back().get()); } return true; } bool ProcessSnapshotMinidump::InitializeExtraMemory() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeMemoryList); if (stream_it == stream_map_.end()) { return true; } if (stream_it->second->DataSize < sizeof(MINIDUMP_MEMORY_LIST)) { LOG(ERROR) << "memory_list size mismatch"; return false; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } // MSVC won't let us stack-allocate a MINIDUMP_MEMORY_LIST because of its // trailing zero-element array. Luckily we're only interested in its other // field anyway: a uint32_t indicating the number of memory descriptors that // follow. static_assert( sizeof(MINIDUMP_MEMORY_LIST) == 4, "MINIDUMP_MEMORY_LIST's only actual field should be an uint32_t"); uint32_t num_ranges; if (!file_reader_->ReadExactly(&num_ranges, sizeof(num_ranges))) { return false; } // We have to manually keep track of the locations of the entries in the // contiguous list of MINIDUMP_MEMORY_DESCRIPTORs, because the Initialize() // function jumps around the file to find the contents of each snapshot. FileOffset location = file_reader_->SeekGet(); for (uint32_t i = 0; i < num_ranges; i++) { extra_memory_.emplace_back( std::make_unique()); if (!extra_memory_.back()->Initialize(file_reader_, static_cast(location))) { return false; } location += sizeof(MINIDUMP_MEMORY_DESCRIPTOR); } return true; } bool ProcessSnapshotMinidump::InitializeThreads() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeThreadList); if (stream_it == stream_map_.end()) { return true; } if (stream_it->second->DataSize < sizeof(MINIDUMP_THREAD_LIST)) { LOG(ERROR) << "thread_list size mismatch"; return false; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } uint32_t thread_count; if (!file_reader_->ReadExactly(&thread_count, sizeof(thread_count))) { return false; } if (sizeof(MINIDUMP_THREAD_LIST) + thread_count * sizeof(MINIDUMP_THREAD) != stream_it->second->DataSize) { LOG(ERROR) << "thread_list size mismatch"; return false; } if (!InitializeThreadNames()) { return false; } for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) { const RVA thread_rva = stream_it->second->Rva + sizeof(thread_count) + thread_index * sizeof(MINIDUMP_THREAD); auto thread = std::make_unique(); if (!thread->Initialize(file_reader_, thread_rva, arch_, thread_names_)) { return false; } threads_.push_back(std::move(thread)); } return true; } bool ProcessSnapshotMinidump::InitializeThreadNames() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeThreadNameList); if (stream_it == stream_map_.end()) { return true; } if (stream_it->second->DataSize < sizeof(MINIDUMP_THREAD_NAME_LIST)) { LOG(ERROR) << "thread_name_list size mismatch"; return false; } if (!file_reader_->SeekSet(stream_it->second->Rva)) { return false; } uint32_t thread_name_count; if (!file_reader_->ReadExactly(&thread_name_count, sizeof(thread_name_count))) { return false; } if (sizeof(MINIDUMP_THREAD_NAME_LIST) + thread_name_count * sizeof(MINIDUMP_THREAD_NAME) != stream_it->second->DataSize) { LOG(ERROR) << "thread_name_list size mismatch"; return false; } for (uint32_t thread_name_index = 0; thread_name_index < thread_name_count; ++thread_name_index) { const RVA thread_name_rva = stream_it->second->Rva + sizeof(thread_name_count) + thread_name_index * sizeof(MINIDUMP_THREAD_NAME); if (!file_reader_->SeekSet(thread_name_rva)) { return false; } MINIDUMP_THREAD_NAME minidump_thread_name; if (!file_reader_->ReadExactly(&minidump_thread_name, sizeof(minidump_thread_name))) { return false; } std::string name; if (!internal::ReadMinidumpUTF16String( file_reader_, minidump_thread_name.RvaOfThreadName, &name)) { return false; } // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36566 const uint32_t thread_id = minidump_thread_name.ThreadId; thread_names_.emplace(thread_id, std::move(name)); } return true; } bool ProcessSnapshotMinidump::InitializeSystemSnapshot() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeSystemInfo); if (stream_it == stream_map_.end()) { return true; } if (stream_it->second->DataSize < sizeof(MINIDUMP_SYSTEM_INFO)) { LOG(ERROR) << "system info size mismatch"; return false; } if (!system_snapshot_.Initialize( file_reader_, stream_it->second->Rva, full_version_)) { return false; } arch_ = system_snapshot_.GetCPUArchitecture(); return true; } bool ProcessSnapshotMinidump::InitializeCustomMinidumpStreams() { for (size_t i = 0; i < stream_directory_.size(); i++) { const auto& stream = stream_directory_[i]; // Filter out reserved minidump and crashpad streams. const uint32_t stream_type = stream.StreamType; if (stream_type <= MinidumpStreamType::kMinidumpStreamTypeLastReservedStream || (stream_type >= MinidumpStreamType::kMinidumpStreamTypeCrashpadInfo && stream_type <= MinidumpStreamType:: kMinidumpStreamTypeCrashpadLastReservedStream)) { continue; } std::vector data(stream.Location.DataSize); if (!file_reader_->SeekSet(stream.Location.Rva) || !file_reader_->ReadExactly(data.data(), data.size())) { LOG(ERROR) << "Failed to read stream with ID 0x" << std::hex << stream_type << std::dec << " at index " << i; return false; } custom_streams_.push_back( std::make_unique(stream_type, std::move(data))); } return true; } bool ProcessSnapshotMinidump::InitializeExceptionSnapshot() { const auto& stream_it = stream_map_.find(kMinidumpStreamTypeException); if (stream_it == stream_map_.end()) { return true; } if (stream_it->second->DataSize < sizeof(MINIDUMP_EXCEPTION_STREAM)) { LOG(ERROR) << "system info size mismatch"; return false; } if (!exception_snapshot_.Initialize( file_reader_, arch_, stream_it->second->Rva)) { return false; } return true; } } // namespace crashpad