| // Copyright 2015 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 "snapshot/win/process_snapshot_win.h" |
| |
| #include <algorithm> |
| |
| #include "base/logging.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "snapshot/win/memory_snapshot_win.h" |
| #include "snapshot/win/module_snapshot_win.h" |
| #include "util/win/registration_protocol_win.h" |
| #include "util/win/time.h" |
| |
| namespace crashpad { |
| |
| ProcessSnapshotWin::ProcessSnapshotWin() |
| : ProcessSnapshot(), |
| system_(), |
| threads_(), |
| modules_(), |
| exception_(), |
| memory_map_(), |
| process_reader_(), |
| report_id_(), |
| client_id_(), |
| annotations_simple_map_(), |
| snapshot_time_(), |
| initialized_() { |
| } |
| |
| ProcessSnapshotWin::~ProcessSnapshotWin() { |
| } |
| |
| bool ProcessSnapshotWin::Initialize( |
| HANDLE process, |
| ProcessSuspensionState suspension_state, |
| WinVMAddress debug_critical_section_address) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| |
| GetTimeOfDay(&snapshot_time_); |
| |
| if (!process_reader_.Initialize(process, suspension_state)) |
| return false; |
| |
| system_.Initialize(&process_reader_); |
| |
| if (process_reader_.Is64Bit()) { |
| InitializePebData<process_types::internal::Traits64>( |
| debug_critical_section_address); |
| } else { |
| InitializePebData<process_types::internal::Traits32>( |
| debug_critical_section_address); |
| } |
| |
| InitializeThreads(); |
| InitializeModules(); |
| |
| for (const MEMORY_BASIC_INFORMATION64& mbi : |
| process_reader_.GetProcessInfo().MemoryInfo()) { |
| memory_map_.push_back(new internal::MemoryMapRegionSnapshotWin(mbi)); |
| } |
| |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| bool ProcessSnapshotWin::InitializeException( |
| WinVMAddress exception_information_address) { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| DCHECK(!exception_); |
| |
| ExceptionInformation exception_information; |
| if (!process_reader_.ReadMemory(exception_information_address, |
| sizeof(exception_information), |
| &exception_information)) { |
| LOG(WARNING) << "ReadMemory ExceptionInformation failed"; |
| return false; |
| } |
| |
| exception_.reset(new internal::ExceptionSnapshotWin()); |
| if (!exception_->Initialize(&process_reader_, |
| exception_information.thread_id, |
| exception_information.exception_pointers)) { |
| exception_.reset(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ProcessSnapshotWin::GetCrashpadOptions( |
| CrashpadInfoClientOptions* options) { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| |
| CrashpadInfoClientOptions local_options; |
| |
| for (internal::ModuleSnapshotWin* module : modules_) { |
| CrashpadInfoClientOptions module_options; |
| module->GetCrashpadOptions(&module_options); |
| |
| if (local_options.crashpad_handler_behavior == TriState::kUnset) { |
| local_options.crashpad_handler_behavior = |
| module_options.crashpad_handler_behavior; |
| } |
| if (local_options.system_crash_reporter_forwarding == TriState::kUnset) { |
| local_options.system_crash_reporter_forwarding = |
| module_options.system_crash_reporter_forwarding; |
| } |
| |
| // If non-default values have been found for all options, the loop can end |
| // early. |
| if (local_options.crashpad_handler_behavior != TriState::kUnset && |
| local_options.system_crash_reporter_forwarding != TriState::kUnset) { |
| break; |
| } |
| } |
| |
| *options = local_options; |
| } |
| |
| pid_t ProcessSnapshotWin::ProcessID() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return process_reader_.GetProcessInfo().ProcessID(); |
| } |
| |
| pid_t ProcessSnapshotWin::ParentProcessID() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return process_reader_.GetProcessInfo().ParentProcessID(); |
| } |
| |
| void ProcessSnapshotWin::SnapshotTime(timeval* snapshot_time) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| *snapshot_time = snapshot_time_; |
| } |
| |
| void ProcessSnapshotWin::ProcessStartTime(timeval* start_time) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| process_reader_.StartTime(start_time); |
| } |
| |
| void ProcessSnapshotWin::ProcessCPUTimes(timeval* user_time, |
| timeval* system_time) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| process_reader_.CPUTimes(user_time, system_time); |
| } |
| |
| void ProcessSnapshotWin::ReportID(UUID* report_id) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| *report_id = report_id_; |
| } |
| |
| void ProcessSnapshotWin::ClientID(UUID* client_id) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| *client_id = client_id_; |
| } |
| |
| const std::map<std::string, std::string>& |
| ProcessSnapshotWin::AnnotationsSimpleMap() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return annotations_simple_map_; |
| } |
| |
| const SystemSnapshot* ProcessSnapshotWin::System() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| return &system_; |
| } |
| |
| std::vector<const ThreadSnapshot*> ProcessSnapshotWin::Threads() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const ThreadSnapshot*> threads; |
| for (internal::ThreadSnapshotWin* thread : threads_) { |
| threads.push_back(thread); |
| } |
| return threads; |
| } |
| |
| std::vector<const ModuleSnapshot*> ProcessSnapshotWin::Modules() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const ModuleSnapshot*> modules; |
| for (internal::ModuleSnapshotWin* module : modules_) { |
| modules.push_back(module); |
| } |
| return modules; |
| } |
| |
| const ExceptionSnapshot* ProcessSnapshotWin::Exception() const { |
| return exception_.get(); |
| } |
| |
| std::vector<const MemoryMapRegionSnapshot*> ProcessSnapshotWin::MemoryMap() |
| const { |
| std::vector<const MemoryMapRegionSnapshot*> memory_map; |
| for (const auto& item : memory_map_) |
| memory_map.push_back(item); |
| return memory_map; |
| } |
| |
| std::vector<HandleSnapshot> ProcessSnapshotWin::Handles() const { |
| std::vector<HandleSnapshot> result; |
| for (const auto& handle : process_reader_.GetProcessInfo().Handles()) { |
| HandleSnapshot snapshot; |
| // This is probably not strictly correct, but these are not localized so we |
| // expect them all to be in ASCII range anyway. This will need to be more |
| // carefully done if the object name is added. |
| snapshot.type_name = base::UTF16ToUTF8(handle.type_name); |
| snapshot.handle = handle.handle; |
| snapshot.attributes = handle.attributes; |
| snapshot.granted_access = handle.granted_access; |
| snapshot.pointer_count = handle.pointer_count; |
| snapshot.handle_count = handle.handle_count; |
| result.push_back(snapshot); |
| } |
| return result; |
| } |
| |
| std::vector<const MemorySnapshot*> ProcessSnapshotWin::ExtraMemory() const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| std::vector<const MemorySnapshot*> extra_memory; |
| for (const auto& em : extra_memory_) |
| extra_memory.push_back(em); |
| return extra_memory; |
| } |
| |
| void ProcessSnapshotWin::InitializeThreads() { |
| const std::vector<ProcessReaderWin::Thread>& process_reader_threads = |
| process_reader_.Threads(); |
| for (const ProcessReaderWin::Thread& process_reader_thread : |
| process_reader_threads) { |
| auto thread = make_scoped_ptr(new internal::ThreadSnapshotWin()); |
| if (thread->Initialize(&process_reader_, process_reader_thread)) { |
| threads_.push_back(thread.release()); |
| } |
| } |
| } |
| |
| void ProcessSnapshotWin::InitializeModules() { |
| const std::vector<ProcessInfo::Module>& process_reader_modules = |
| process_reader_.Modules(); |
| for (const ProcessInfo::Module& process_reader_module : |
| process_reader_modules) { |
| auto module = make_scoped_ptr(new internal::ModuleSnapshotWin()); |
| if (module->Initialize(&process_reader_, process_reader_module)) { |
| modules_.push_back(module.release()); |
| } |
| } |
| } |
| |
| template <class Traits> |
| void ProcessSnapshotWin::InitializePebData( |
| WinVMAddress debug_critical_section_address) { |
| WinVMAddress peb_address; |
| WinVMSize peb_size; |
| process_reader_.GetProcessInfo().Peb(&peb_address, &peb_size); |
| AddMemorySnapshot(peb_address, peb_size, &extra_memory_); |
| |
| process_types::PEB<Traits> peb_data; |
| if (!process_reader_.ReadMemory(peb_address, peb_size, &peb_data)) { |
| LOG(ERROR) << "ReadMemory PEB"; |
| return; |
| } |
| |
| process_types::PEB_LDR_DATA<Traits> peb_ldr_data; |
| AddMemorySnapshot(peb_data.Ldr, sizeof(peb_ldr_data), &extra_memory_); |
| if (!process_reader_.ReadMemory( |
| peb_data.Ldr, sizeof(peb_ldr_data), &peb_ldr_data)) { |
| LOG(ERROR) << "ReadMemory PEB_LDR_DATA"; |
| } else { |
| // Walk the LDR structure to retrieve its pointed-to data. |
| AddMemorySnapshotForLdrLIST_ENTRY( |
| peb_ldr_data.InLoadOrderModuleList, |
| offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, InLoadOrderLinks), |
| &extra_memory_); |
| AddMemorySnapshotForLdrLIST_ENTRY( |
| peb_ldr_data.InMemoryOrderModuleList, |
| offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, |
| InMemoryOrderLinks), |
| &extra_memory_); |
| AddMemorySnapshotForLdrLIST_ENTRY( |
| peb_ldr_data.InInitializationOrderModuleList, |
| offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, |
| InInitializationOrderLinks), |
| &extra_memory_); |
| } |
| |
| process_types::RTL_USER_PROCESS_PARAMETERS<Traits> process_parameters; |
| if (!process_reader_.ReadMemory(peb_data.ProcessParameters, |
| sizeof(process_parameters), |
| &process_parameters)) { |
| LOG(ERROR) << "ReadMemory RTL_USER_PROCESS_PARAMETERS"; |
| return; |
| } |
| AddMemorySnapshot( |
| peb_data.ProcessParameters, sizeof(process_parameters), &extra_memory_); |
| |
| AddMemorySnapshotForUNICODE_STRING( |
| process_parameters.CurrentDirectory.DosPath, &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.DllPath, |
| &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.ImagePathName, |
| &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.CommandLine, |
| &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.WindowTitle, |
| &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.DesktopInfo, |
| &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.ShellInfo, |
| &extra_memory_); |
| AddMemorySnapshotForUNICODE_STRING(process_parameters.RuntimeData, |
| &extra_memory_); |
| AddMemorySnapshot( |
| process_parameters.Environment, |
| DetermineSizeOfEnvironmentBlock(process_parameters.Environment), |
| &extra_memory_); |
| |
| // Walk the loader lock which is directly referenced by the PEB. It may or may |
| // not have a .DebugInfo list, but doesn't on more recent OSs (it does on |
| // Vista). If it does, then we may walk the lock list more than once, but |
| // AddMemorySnapshot() will take care of deduplicating the added regions. |
| ReadLocks<Traits>(peb_data.LoaderLock, &extra_memory_); |
| |
| // Traverse the locks with valid .DebugInfo if a starting point was supplied. |
| if (debug_critical_section_address) |
| ReadLocks<Traits>(debug_critical_section_address, &extra_memory_); |
| } |
| |
| void ProcessSnapshotWin::AddMemorySnapshot( |
| WinVMAddress address, |
| WinVMSize size, |
| PointerVector<internal::MemorySnapshotWin>* into) { |
| if (size == 0) |
| return; |
| |
| if (!process_reader_.GetProcessInfo().LoggingRangeIsFullyReadable( |
| CheckedRange<WinVMAddress, WinVMSize>(address, size))) { |
| return; |
| } |
| |
| // If we have already added this exact range, don't add it again. This is |
| // useful for the LDR module lists which are a set of doubly-linked lists, all |
| // pointing to the same module name strings. |
| // TODO(scottmg): A more general version of this, handling overlapping, |
| // contained, etc. https://crashpad.chromium.org/bug/61. |
| for (const auto& memory_snapshot : *into) { |
| if (memory_snapshot->Address() == address && |
| memory_snapshot->Size() == size) { |
| return; |
| } |
| } |
| |
| internal::MemorySnapshotWin* memory_snapshot = |
| new internal::MemorySnapshotWin(); |
| memory_snapshot->Initialize(&process_reader_, address, size); |
| into->push_back(memory_snapshot); |
| } |
| |
| template <class Traits> |
| void ProcessSnapshotWin::AddMemorySnapshotForUNICODE_STRING( |
| const process_types::UNICODE_STRING<Traits>& us, |
| PointerVector<internal::MemorySnapshotWin>* into) { |
| AddMemorySnapshot(us.Buffer, us.Length, into); |
| } |
| |
| template <class Traits> |
| void ProcessSnapshotWin::AddMemorySnapshotForLdrLIST_ENTRY( |
| const process_types::LIST_ENTRY<Traits>& le, size_t offset_of_member, |
| PointerVector<internal::MemorySnapshotWin>* into) { |
| // Walk the doubly-linked list of entries, adding the list memory itself, as |
| // well as pointed-to strings. |
| typename Traits::Pointer last = le.Blink; |
| process_types::LDR_DATA_TABLE_ENTRY<Traits> entry; |
| typename Traits::Pointer cur = le.Flink; |
| for (;;) { |
| // |cur| is the pointer to LIST_ENTRY embedded in the LDR_DATA_TABLE_ENTRY. |
| // So we need to offset back to the beginning of the structure. |
| if (!process_reader_.ReadMemory( |
| cur - offset_of_member, sizeof(entry), &entry)) { |
| return; |
| } |
| AddMemorySnapshot(cur - offset_of_member, sizeof(entry), into); |
| AddMemorySnapshotForUNICODE_STRING(entry.FullDllName, into); |
| AddMemorySnapshotForUNICODE_STRING(entry.BaseDllName, into); |
| |
| process_types::LIST_ENTRY<Traits>* links = |
| reinterpret_cast<process_types::LIST_ENTRY<Traits>*>( |
| reinterpret_cast<unsigned char*>(&entry) + offset_of_member); |
| cur = links->Flink; |
| if (cur == last) |
| break; |
| } |
| } |
| |
| WinVMSize ProcessSnapshotWin::DetermineSizeOfEnvironmentBlock( |
| WinVMAddress start_of_environment_block) { |
| // http://blogs.msdn.com/b/oldnewthing/archive/2010/02/03/9957320.aspx On |
| // newer OSs there's no stated limit, but in practice grabbing 32k characters |
| // should be more than enough. |
| std::wstring env_block; |
| env_block.resize(32768); |
| WinVMSize bytes_read = process_reader_.ReadAvailableMemory( |
| start_of_environment_block, |
| env_block.size() * sizeof(env_block[0]), |
| &env_block[0]); |
| env_block.resize( |
| static_cast<unsigned int>(bytes_read / sizeof(env_block[0]))); |
| const wchar_t terminator[] = { 0, 0 }; |
| size_t at = env_block.find(std::wstring(terminator, arraysize(terminator))); |
| if (at != std::wstring::npos) |
| env_block.resize(at + arraysize(terminator)); |
| |
| return env_block.size() * sizeof(env_block[0]); |
| } |
| |
| template <class Traits> |
| void ProcessSnapshotWin::ReadLocks( |
| WinVMAddress start, |
| PointerVector<internal::MemorySnapshotWin>* into) { |
| // We're walking the RTL_CRITICAL_SECTION_DEBUG ProcessLocksList, but starting |
| // from an actual RTL_CRITICAL_SECTION, so start by getting to the first |
| // RTL_CRITICAL_SECTION_DEBUG. |
| |
| process_types::RTL_CRITICAL_SECTION<Traits> critical_section; |
| if (!process_reader_.ReadMemory( |
| start, sizeof(critical_section), &critical_section)) { |
| LOG(ERROR) << "failed to read RTL_CRITICAL_SECTION"; |
| return; |
| } |
| |
| const decltype(critical_section.DebugInfo) kInvalid = |
| static_cast<decltype(critical_section.DebugInfo)>(-1); |
| if (critical_section.DebugInfo == kInvalid) |
| return; |
| |
| const WinVMAddress start_address_backward = critical_section.DebugInfo; |
| WinVMAddress current_address = start_address_backward; |
| WinVMAddress last_good_address; |
| |
| // Typically, this seems to be a circular list, but it's not clear that it |
| // always is, so follow Blink fields back to the head (or where we started) |
| // before following Flink to capture memory. |
| do { |
| last_good_address = current_address; |
| // Read the RTL_CRITICAL_SECTION_DEBUG structure to get ProcessLocksList. |
| process_types::RTL_CRITICAL_SECTION_DEBUG<Traits> critical_section_debug; |
| if (!process_reader_.ReadMemory(current_address, |
| sizeof(critical_section_debug), |
| &critical_section_debug)) { |
| LOG(ERROR) << "failed to read RTL_CRITICAL_SECTION_DEBUG"; |
| return; |
| } |
| |
| if (critical_section_debug.ProcessLocksList.Blink == 0) { |
| // At the head of the list. |
| break; |
| } |
| |
| // Move to the previous RTL_CRITICAL_SECTION_DEBUG by walking |
| // ProcessLocksList.Blink. |
| current_address = |
| critical_section_debug.ProcessLocksList.Blink - |
| offsetof(process_types::RTL_CRITICAL_SECTION_DEBUG<Traits>, |
| ProcessLocksList); |
| } while (current_address != start_address_backward && |
| current_address != kInvalid); |
| |
| if (current_address == kInvalid) { |
| // Unexpectedly encountered a bad record, so step back one. |
| current_address = last_good_address; |
| } |
| |
| const WinVMAddress start_address_forward = current_address; |
| |
| // current_address is now the head of the list, walk Flink to add the whole |
| // list. |
| do { |
| // Read the RTL_CRITICAL_SECTION_DEBUG structure to get ProcessLocksList. |
| process_types::RTL_CRITICAL_SECTION_DEBUG<Traits> critical_section_debug; |
| if (!process_reader_.ReadMemory(current_address, |
| sizeof(critical_section_debug), |
| &critical_section_debug)) { |
| LOG(ERROR) << "failed to read RTL_CRITICAL_SECTION_DEBUG"; |
| return; |
| } |
| |
| // Add both RTL_CRITICAL_SECTION_DEBUG and RTL_CRITICAL_SECTION to the extra |
| // memory to be saved. |
| AddMemorySnapshot(current_address, |
| sizeof(process_types::RTL_CRITICAL_SECTION_DEBUG<Traits>), |
| into); |
| AddMemorySnapshot(critical_section_debug.CriticalSection, |
| sizeof(process_types::RTL_CRITICAL_SECTION<Traits>), |
| into); |
| |
| if (critical_section_debug.ProcessLocksList.Flink == 0) |
| break; |
| |
| // Move to the next RTL_CRITICAL_SECTION_DEBUG by walking |
| // ProcessLocksList.Flink. |
| current_address = |
| critical_section_debug.ProcessLocksList.Flink - |
| offsetof(process_types::RTL_CRITICAL_SECTION_DEBUG<Traits>, |
| ProcessLocksList); |
| } while (current_address != start_address_forward && |
| current_address != kInvalid); |
| } |
| |
| } // namespace crashpad |