Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Crashpad Authors. All rights reserved. | 1 // Copyright 2015 The Crashpad Authors. All rights reserved. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 #include "util/win/process_info.h" | 15 #include "util/win/process_info.h" |
| 16 | 16 |
| 17 #include <winternl.h> | |
| 18 | |
| 17 #include "base/logging.h" | 19 #include "base/logging.h" |
| 20 #include "util/win/process_structs.h" | |
| 18 | 21 |
| 19 namespace crashpad { | 22 namespace crashpad { |
| 20 | 23 |
| 21 namespace { | 24 namespace { |
| 22 | 25 |
| 23 NTSTATUS NtQueryInformationProcess(HANDLE process_handle, | 26 NTSTATUS NtQueryInformationProcess(HANDLE process_handle, |
| 24 PROCESSINFOCLASS process_information_class, | 27 PROCESSINFOCLASS process_information_class, |
| 25 PVOID process_information, | 28 PVOID process_information, |
| 26 ULONG process_information_length, | 29 ULONG process_information_length, |
| 27 PULONG return_length) { | 30 PULONG return_length) { |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 43 if (!is_wow64_process) | 46 if (!is_wow64_process) |
| 44 return false; | 47 return false; |
| 45 BOOL is_wow64; | 48 BOOL is_wow64; |
| 46 if (!is_wow64_process(process_handle, &is_wow64)) { | 49 if (!is_wow64_process(process_handle, &is_wow64)) { |
| 47 PLOG(ERROR) << "IsWow64Process"; | 50 PLOG(ERROR) << "IsWow64Process"; |
| 48 return false; | 51 return false; |
| 49 } | 52 } |
| 50 return is_wow64; | 53 return is_wow64; |
| 51 } | 54 } |
| 52 | 55 |
| 56 template <class T> | |
| 53 bool ReadUnicodeString(HANDLE process, | 57 bool ReadUnicodeString(HANDLE process, |
| 54 const UNICODE_STRING& us, | 58 const UNICODE_STRING_T<T>& us, |
| 55 std::wstring* result) { | 59 std::wstring* result) { |
| 56 if (us.Length == 0) { | 60 if (us.Length == 0) { |
| 57 result->clear(); | 61 result->clear(); |
| 58 return true; | 62 return true; |
| 59 } | 63 } |
| 60 DCHECK_EQ(us.Length % sizeof(wchar_t), 0u); | 64 DCHECK_EQ(us.Length % sizeof(wchar_t), 0u); |
| 61 result->resize(us.Length / sizeof(wchar_t)); | 65 result->resize(us.Length / sizeof(wchar_t)); |
| 62 SIZE_T bytes_read; | 66 SIZE_T bytes_read; |
| 63 if (!ReadProcessMemory( | 67 if (!ReadProcessMemory(process, |
| 64 process, us.Buffer, &result->operator[](0), us.Length, &bytes_read)) { | 68 reinterpret_cast<const void*>(us.Buffer), |
| 69 &result->operator[](0), | |
| 70 us.Length, | |
| 71 &bytes_read)) { | |
| 65 PLOG(ERROR) << "ReadProcessMemory UNICODE_STRING"; | 72 PLOG(ERROR) << "ReadProcessMemory UNICODE_STRING"; |
| 66 return false; | 73 return false; |
| 67 } | 74 } |
| 68 if (bytes_read != us.Length) { | 75 if (bytes_read != us.Length) { |
| 69 LOG(ERROR) << "ReadProcessMemory UNICODE_STRING incorrect size"; | 76 LOG(ERROR) << "ReadProcessMemory UNICODE_STRING incorrect size"; |
| 70 return false; | 77 return false; |
| 71 } | 78 } |
| 72 return true; | 79 return true; |
| 73 } | 80 } |
| 74 | 81 |
| 75 template <class T> bool ReadStruct(HANDLE process, uintptr_t at, T* into) { | 82 template <class T> bool ReadStruct(HANDLE process, uintptr_t at, T* into) { |
| 76 SIZE_T bytes_read; | 83 SIZE_T bytes_read; |
| 77 if (!ReadProcessMemory(process, | 84 if (!ReadProcessMemory(process, |
| 78 reinterpret_cast<const void*>(at), | 85 reinterpret_cast<const void*>(at), |
| 79 into, | 86 into, |
| 80 sizeof(T), | 87 sizeof(T), |
| 81 &bytes_read)) { | 88 &bytes_read)) { |
| 82 // We don't have a name for the type we're reading, so include the signature | 89 // We don't have a name for the type we're reading, so include the signature |
| 83 // to get the type of T. | 90 // to get the type of T. |
| 84 PLOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__; | 91 PLOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__; |
| 85 return false; | 92 return false; |
| 86 } | 93 } |
| 87 if (bytes_read != sizeof(T)) { | 94 if (bytes_read != sizeof(T)) { |
| 88 LOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__ << " incorrect size"; | 95 LOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__ << " incorrect size"; |
| 89 return false; | 96 return false; |
| 90 } | 97 } |
| 91 return true; | 98 return true; |
| 92 } | 99 } |
| 93 | 100 |
| 94 // PEB_LDR_DATA in winternl.h doesn't document the trailing | |
| 95 // InInitializationOrderModuleList field. See `dt ntdll!PEB_LDR_DATA`. | |
| 96 struct FULL_PEB_LDR_DATA : public PEB_LDR_DATA { | |
| 97 LIST_ENTRY InInitializationOrderModuleList; | |
| 98 }; | |
| 99 | |
| 100 // LDR_DATA_TABLE_ENTRY doesn't include InInitializationOrderLinks, define a | |
| 101 // complete version here. See `dt ntdll!_LDR_DATA_TABLE_ENTRY`. | |
| 102 struct FULL_LDR_DATA_TABLE_ENTRY { | |
| 103 LIST_ENTRY InLoadOrderLinks; | |
| 104 LIST_ENTRY InMemoryOrderLinks; | |
| 105 LIST_ENTRY InInitializationOrderLinks; | |
| 106 PVOID DllBase; | |
| 107 PVOID EntryPoint; | |
| 108 ULONG SizeOfImage; | |
| 109 UNICODE_STRING FullDllName; | |
| 110 UNICODE_STRING BaseDllName; | |
| 111 ULONG Flags; | |
| 112 WORD LoadCount; | |
| 113 WORD TlsIndex; | |
| 114 LIST_ENTRY HashLinks; | |
| 115 ULONG TimeDateStamp; | |
| 116 _ACTIVATION_CONTEXT* EntryPointActivationContext; | |
| 117 }; | |
| 118 | |
| 119 } // namespace | 101 } // namespace |
| 120 | 102 |
| 103 template <class T> | |
| 104 bool ReadProcessData(HANDLE process, | |
| 105 uintptr_t peb_address, | |
|
Mark Mentovai
2015/03/09 16:11:46
peb_address is in the remote process’ address spac
scottmg
2015/03/09 21:16:36
Right, that's why. Done.
| |
| 106 ProcessInfo* process_info) { | |
| 107 // Try to read the process environment block. | |
| 108 PEB_T<T> peb; | |
| 109 if (!ReadStruct(process, peb_address, &peb)) | |
| 110 return false; | |
| 111 | |
| 112 RTL_USER_PROCESS_PARAMETERS_T<T> process_parameters; | |
| 113 if (!ReadStruct(process, peb.ProcessParameters, &process_parameters)) | |
| 114 return false; | |
| 115 | |
| 116 if (!ReadUnicodeString(process, | |
| 117 process_parameters.CommandLine, | |
| 118 &process_info->command_line_)) { | |
| 119 return false; | |
| 120 } | |
| 121 | |
| 122 PEB_LDR_DATA_T<T> peb_ldr_data; | |
| 123 if (!ReadStruct(process, peb.Ldr, &peb_ldr_data)) | |
| 124 return false; | |
| 125 | |
| 126 // Include the first module in the memory order list to get our own name as | |
| 127 // it's not included in initialization order below. | |
| 128 std::wstring self_module; | |
| 129 LDR_DATA_TABLE_ENTRY_T<T> self_ldr_data_table_entry; | |
| 130 if (!ReadStruct(process, | |
| 131 reinterpret_cast<uintptr_t>( | |
| 132 reinterpret_cast<const char*>( | |
| 133 peb_ldr_data.InMemoryOrderModuleList.Flink) - | |
| 134 offsetof(LDR_DATA_TABLE_ENTRY_T<T>, InMemoryOrderLinks)), | |
| 135 &self_ldr_data_table_entry)) { | |
| 136 return false; | |
| 137 } | |
| 138 if (!ReadUnicodeString( | |
| 139 process, self_ldr_data_table_entry.FullDllName, &self_module)) { | |
| 140 return false; | |
| 141 } | |
| 142 process_info->modules_.push_back(self_module); | |
| 143 | |
| 144 // Walk the PEB LDR structure (doubly-linked list) to get the list of loaded | |
| 145 // modules. We use this method rather than EnumProcessModules to get the | |
| 146 // modules in initialization order rather than memory order. | |
| 147 T last = peb_ldr_data.InInitializationOrderModuleList.Blink; | |
| 148 LDR_DATA_TABLE_ENTRY_T<T> ldr_data_table_entry; | |
| 149 for (T cur = peb_ldr_data.InInitializationOrderModuleList.Flink;; | |
| 150 cur = ldr_data_table_entry.InInitializationOrderLinks.Flink) { | |
| 151 // |cur| is the pointer to the LIST_ENTRY embedded in the | |
| 152 // LDR_DATA_TABLE_ENTRY, in the target process's address space. So we need | |
| 153 // to read from the target, and also offset back to the beginning of the | |
| 154 // structure. | |
| 155 if (!ReadStruct( | |
| 156 process, | |
| 157 reinterpret_cast<uintptr_t>(reinterpret_cast<const char*>(cur) - | |
| 158 offsetof(LDR_DATA_TABLE_ENTRY_T<T>, | |
| 159 InInitializationOrderLinks)), | |
| 160 &ldr_data_table_entry)) { | |
| 161 break; | |
| 162 } | |
| 163 // TODO(scottmg): Capture TimeDateStamp, Checksum, etc. too? | |
| 164 std::wstring module; | |
| 165 if (!ReadUnicodeString(process, ldr_data_table_entry.FullDllName, &module)) | |
| 166 break; | |
| 167 process_info->modules_.push_back(module); | |
| 168 if (cur == last) | |
| 169 break; | |
| 170 } | |
| 171 | |
| 172 return true; | |
| 173 } | |
| 174 | |
| 121 ProcessInfo::ProcessInfo() | 175 ProcessInfo::ProcessInfo() |
| 122 : process_basic_information_(), | 176 : process_id_(), |
| 177 inherited_from_process_id_(), | |
| 123 command_line_(), | 178 command_line_(), |
| 124 modules_(), | 179 modules_(), |
| 125 is_64_bit_(false), | 180 is_64_bit_(false), |
| 126 is_wow64_(false), | 181 is_wow64_(false), |
| 127 initialized_() { | 182 initialized_() { |
| 128 } | 183 } |
| 129 | 184 |
| 130 ProcessInfo::~ProcessInfo() { | 185 ProcessInfo::~ProcessInfo() { |
| 131 } | 186 } |
| 132 | 187 |
| 133 bool ProcessInfo::Initialize(HANDLE process) { | 188 bool ProcessInfo::Initialize(HANDLE process) { |
| 134 INITIALIZATION_STATE_SET_INITIALIZING(initialized_); | 189 INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| 135 | 190 |
| 136 is_wow64_ = IsProcessWow64(process); | 191 is_wow64_ = IsProcessWow64(process); |
| 137 | 192 |
| 138 if (is_wow64_) { | 193 if (is_wow64_) { |
| 139 // If it's WoW64, then it's 32-on-64. | 194 // If it's WoW64, then it's 32-on-64. |
| 140 is_64_bit_ = false; | 195 is_64_bit_ = false; |
| 141 } else { | 196 } else { |
| 142 // Otherwise, it's either 32 on 32, or 64 on 64. Use GetSystemInfo() to | 197 // Otherwise, it's either 32 on 32, or 64 on 64. Use GetSystemInfo() to |
| 143 // distinguish between these two cases. | 198 // distinguish between these two cases. |
| 144 SYSTEM_INFO system_info; | 199 SYSTEM_INFO system_info; |
| 145 GetSystemInfo(&system_info); | 200 GetSystemInfo(&system_info); |
| 146 is_64_bit_ = | 201 is_64_bit_ = |
| 147 system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; | 202 system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; |
| 148 } | 203 } |
| 149 | 204 |
| 150 #if ARCH_CPU_64_BITS | 205 #if ARCH_CPU_32_BITS |
| 151 if (!is_64_bit_) { | |
| 152 LOG(ERROR) << "Reading different bitness not yet supported"; | |
| 153 return false; | |
| 154 } | |
| 155 #else | |
| 156 if (is_64_bit_) { | 206 if (is_64_bit_) { |
| 157 LOG(ERROR) << "Reading x64 process from x86 process not supported"; | 207 LOG(ERROR) << "Reading x64 process from x86 process not supported"; |
| 158 return false; | 208 return false; |
| 159 } | 209 } |
| 160 #endif | 210 #endif |
| 161 | 211 |
| 162 ULONG bytes_returned; | 212 ULONG bytes_returned; |
| 213 // We assume this process is not running on Wow64. The | |
|
Mark Mentovai
2015/03/09 16:11:46
I don’t think that this assumption can really hold
scottmg
2015/03/09 21:16:36
OK. I would prefer not to support running as Wow64
| |
| 214 // PROCESS_BASIC_INFORMATION uses the OS size (that is, even Wow64 has a 64 | |
| 215 // bit one.) | |
| 216 #if ARCH_CPU_32_BITS | |
| 217 PROCESS_BASIC_INFORMATION_T<DWORD> process_basic_information; | |
| 218 #else | |
| 219 PROCESS_BASIC_INFORMATION_T<DWORD64> process_basic_information; | |
| 220 #endif | |
| 163 NTSTATUS status = | 221 NTSTATUS status = |
| 164 crashpad::NtQueryInformationProcess(process, | 222 crashpad::NtQueryInformationProcess(process, |
| 165 ProcessBasicInformation, | 223 ProcessBasicInformation, |
| 166 &process_basic_information_, | 224 &process_basic_information, |
| 167 sizeof(process_basic_information_), | 225 sizeof(process_basic_information), |
| 168 &bytes_returned); | 226 &bytes_returned); |
| 169 if (status < 0) { | 227 if (status < 0) { |
| 170 LOG(ERROR) << "NtQueryInformationProcess: status=" << status; | 228 LOG(ERROR) << "NtQueryInformationProcess: status=" << status; |
| 171 return false; | 229 return false; |
| 172 } | 230 } |
| 173 if (bytes_returned != sizeof(process_basic_information_)) { | 231 if (bytes_returned != sizeof(process_basic_information)) { |
| 174 LOG(ERROR) << "NtQueryInformationProcess incorrect size"; | 232 LOG(ERROR) << "NtQueryInformationProcess incorrect size"; |
| 175 return false; | 233 return false; |
| 176 } | 234 } |
| 235 process_id_ = process_basic_information.UniqueProcessId; | |
| 236 inherited_from_process_id_ = | |
| 237 process_basic_information.InheritedFromUniqueProcessId; | |
| 177 | 238 |
| 178 // Try to read the process environment block. | 239 // We now want to read the PEB to gather the rest of our information. The |
| 179 PEB peb; | 240 // PebBaseAddress as returned above is what we want for 64-on-64 and 32-on-32, |
| 180 if (!ReadStruct(process, | 241 // but for Wow64, we want to read the 32 bit PEB (a Wow64 process has both). |
| 181 reinterpret_cast<uintptr_t>( | 242 // The address of this is found by a second call to NtQueryInformationProcess. |
| 182 process_basic_information_.PebBaseAddress), | 243 uintptr_t peb_address = process_basic_information.PebBaseAddress; |
| 183 &peb)) { | 244 if (is_wow64_) { |
| 184 return false; | 245 ULONG_PTR wow64_peb_address; |
| 246 status = | |
| 247 crashpad::NtQueryInformationProcess(process, | |
| 248 ProcessWow64Information, | |
| 249 &wow64_peb_address, | |
| 250 sizeof(wow64_peb_address), | |
| 251 &bytes_returned); | |
| 252 if (status < 0) { | |
| 253 LOG(ERROR) << "NtQueryInformationProcess: status=" << status; | |
| 254 return false; | |
| 255 } | |
| 256 if (bytes_returned != sizeof(wow64_peb_address)) { | |
| 257 LOG(ERROR) << "NtQueryInformationProcess incorrect size"; | |
| 258 return false; | |
| 259 } | |
| 260 peb_address = wow64_peb_address; | |
| 185 } | 261 } |
| 186 | 262 |
| 187 RTL_USER_PROCESS_PARAMETERS process_parameters; | 263 // Read the PEB data using the correct word size. |
| 188 if (!ReadStruct(process, | 264 bool result = is_64_bit_ |
| 189 reinterpret_cast<uintptr_t>(peb.ProcessParameters), | 265 ? ReadProcessData<DWORD64>(process, peb_address, this) |
| 190 &process_parameters)) { | 266 : ReadProcessData<DWORD>(process, peb_address, this); |
| 267 if (!result) | |
| 191 return false; | 268 return false; |
| 192 } | |
| 193 | |
| 194 if (!ReadUnicodeString( | |
| 195 process, process_parameters.CommandLine, &command_line_)) { | |
| 196 return false; | |
| 197 } | |
| 198 | |
| 199 FULL_PEB_LDR_DATA peb_ldr_data; | |
| 200 if (!ReadStruct(process, reinterpret_cast<uintptr_t>(peb.Ldr), &peb_ldr_data)) | |
| 201 return false; | |
| 202 | |
| 203 // Include the first module in the memory order list to get our own name as | |
| 204 // it's not included in initialization order below. | |
| 205 std::wstring self_module; | |
| 206 FULL_LDR_DATA_TABLE_ENTRY self_ldr_data_table_entry; | |
| 207 if (!ReadStruct(process, | |
| 208 reinterpret_cast<uintptr_t>( | |
| 209 reinterpret_cast<const char*>( | |
| 210 peb_ldr_data.InMemoryOrderModuleList.Flink) - | |
| 211 offsetof(FULL_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)), | |
| 212 &self_ldr_data_table_entry)) { | |
| 213 return false; | |
| 214 } | |
| 215 if (!ReadUnicodeString( | |
| 216 process, self_ldr_data_table_entry.FullDllName, &self_module)) { | |
| 217 return false; | |
| 218 } | |
| 219 modules_.push_back(self_module); | |
| 220 | |
| 221 // Walk the PEB LDR structure (doubly-linked list) to get the list of loaded | |
| 222 // modules. We use this method rather than EnumProcessModules to get the | |
| 223 // modules in initialization order rather than memory order. | |
| 224 const LIST_ENTRY* last = peb_ldr_data.InInitializationOrderModuleList.Blink; | |
| 225 FULL_LDR_DATA_TABLE_ENTRY ldr_data_table_entry; | |
| 226 for (const LIST_ENTRY* cur = | |
| 227 peb_ldr_data.InInitializationOrderModuleList.Flink; | |
| 228 ; | |
| 229 cur = ldr_data_table_entry.InInitializationOrderLinks.Flink) { | |
| 230 // |cur| is the pointer to the LIST_ENTRY embedded in the | |
| 231 // FULL_LDR_DATA_TABLE_ENTRY, in the target process's address space. So we | |
| 232 // need to read from the target, and also offset back to the beginning of | |
| 233 // the structure. | |
| 234 if (!ReadStruct( | |
| 235 process, | |
| 236 reinterpret_cast<uintptr_t>(reinterpret_cast<const char*>(cur) - | |
| 237 offsetof(FULL_LDR_DATA_TABLE_ENTRY, | |
| 238 InInitializationOrderLinks)), | |
| 239 &ldr_data_table_entry)) { | |
| 240 break; | |
| 241 } | |
| 242 // TODO(scottmg): Capture TimeDateStamp, Checksum, etc. too? | |
| 243 std::wstring module; | |
| 244 if (!ReadUnicodeString(process, ldr_data_table_entry.FullDllName, &module)) | |
| 245 break; | |
| 246 modules_.push_back(module); | |
| 247 if (cur == last) | |
| 248 break; | |
| 249 } | |
| 250 | 269 |
| 251 INITIALIZATION_STATE_SET_VALID(initialized_); | 270 INITIALIZATION_STATE_SET_VALID(initialized_); |
| 252 return true; | 271 return true; |
| 253 } | 272 } |
| 254 | 273 |
| 255 bool ProcessInfo::Is64Bit() const { | 274 bool ProcessInfo::Is64Bit() const { |
| 256 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 275 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 257 return is_64_bit_; | 276 return is_64_bit_; |
| 258 } | 277 } |
| 259 | 278 |
| 260 bool ProcessInfo::IsWow64() const { | 279 bool ProcessInfo::IsWow64() const { |
| 261 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 280 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 262 return is_wow64_; | 281 return is_wow64_; |
| 263 } | 282 } |
| 264 | 283 |
| 265 pid_t ProcessInfo::ProcessID() const { | 284 pid_t ProcessInfo::ProcessID() const { |
| 266 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 285 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 267 return process_basic_information_.UniqueProcessId; | 286 return process_id_; |
| 268 } | 287 } |
| 269 | 288 |
| 270 pid_t ProcessInfo::ParentProcessID() const { | 289 pid_t ProcessInfo::ParentProcessID() const { |
| 271 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 290 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 272 return process_basic_information_.InheritedFromUniqueProcessId; | 291 return inherited_from_process_id_; |
| 273 } | 292 } |
| 274 | 293 |
| 275 bool ProcessInfo::CommandLine(std::wstring* command_line) const { | 294 bool ProcessInfo::CommandLine(std::wstring* command_line) const { |
| 276 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 295 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 277 *command_line = command_line_; | 296 *command_line = command_line_; |
| 278 return true; | 297 return true; |
| 279 } | 298 } |
| 280 | 299 |
| 281 bool ProcessInfo::Modules(std::vector<std::wstring>* modules) const { | 300 bool ProcessInfo::Modules(std::vector<std::wstring>* modules) const { |
| 282 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 301 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 283 *modules = modules_; | 302 *modules = modules_; |
| 284 return true; | 303 return true; |
| 285 } | 304 } |
| 286 | 305 |
| 287 } // namespace crashpad | 306 } // namespace crashpad |
| OLD | NEW |