| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/trace_event/malloc_dump_provider.h" | 5 #include "base/trace_event/malloc_dump_provider.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include "base/allocator/allocator_extension.h" | 9 #include "base/allocator/allocator_extension.h" |
| 10 #include "base/allocator/allocator_shim.h" | 10 #include "base/allocator/allocator_shim.h" |
| 11 #include "base/allocator/features.h" | 11 #include "base/allocator/features.h" |
| 12 #include "base/debug/profiler.h" | 12 #include "base/debug/profiler.h" |
| 13 #include "base/trace_event/heap_profiler_allocation_context.h" | 13 #include "base/trace_event/heap_profiler_allocation_context.h" |
| 14 #include "base/trace_event/heap_profiler_allocation_context_tracker.h" | 14 #include "base/trace_event/heap_profiler_allocation_context_tracker.h" |
| 15 #include "base/trace_event/heap_profiler_allocation_register.h" | 15 #include "base/trace_event/heap_profiler_allocation_register.h" |
| 16 #include "base/trace_event/heap_profiler_heap_dump_writer.h" | 16 #include "base/trace_event/heap_profiler_heap_dump_writer.h" |
| 17 #include "base/trace_event/process_memory_dump.h" | 17 #include "base/trace_event/process_memory_dump.h" |
| 18 #include "base/trace_event/trace_event_argument.h" | 18 #include "base/trace_event/trace_event_argument.h" |
| 19 #include "build/build_config.h" | 19 #include "build/build_config.h" |
| 20 | 20 |
| 21 #if defined(OS_MACOSX) | 21 #if defined(OS_MACOSX) |
| 22 #include <malloc/malloc.h> | 22 #include <malloc/malloc.h> |
| 23 #else | 23 #else |
| 24 #include <malloc.h> | 24 #include <malloc.h> |
| 25 #endif | 25 #endif |
| 26 #if defined(OS_WIN) | 26 #if defined(OS_WIN) |
| 27 #include <windows.h> | 27 #include <windows.h> |
| 28 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa366703 |
| 29 #define HEAP_LFH 2 |
| 28 #endif | 30 #endif |
| 29 | 31 |
| 30 namespace base { | 32 namespace base { |
| 31 namespace trace_event { | 33 namespace trace_event { |
| 32 | 34 |
| 33 namespace { | 35 namespace { |
| 34 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM) | 36 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM) |
| 35 | 37 |
| 36 using allocator::AllocatorDispatch; | 38 using allocator::AllocatorDispatch; |
| 37 | 39 |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 96 #if defined(OS_WIN) | 98 #if defined(OS_WIN) |
| 97 // A structure containing some information about a given heap. | 99 // A structure containing some information about a given heap. |
| 98 struct WinHeapInfo { | 100 struct WinHeapInfo { |
| 99 HANDLE heap_id; | 101 HANDLE heap_id; |
| 100 size_t committed_size; | 102 size_t committed_size; |
| 101 size_t uncommitted_size; | 103 size_t uncommitted_size; |
| 102 size_t allocated_size; | 104 size_t allocated_size; |
| 103 size_t block_count; | 105 size_t block_count; |
| 104 }; | 106 }; |
| 105 | 107 |
| 106 bool GetHeapInformation(WinHeapInfo* heap_info, | 108 bool TryHeapLock(HANDLE heap) { |
| 107 const std::set<void*>& block_to_skip) { | 109 HANDLE main_heap = ::GetProcessHeap(); |
| 110 LOG(ERROR) << "Trying to lock the heap..."; |
| 111 |
| 112 // NOTE: crbug.com/665516 |
| 113 // We should never try to lock a heap created with HEAP_NO_SERIALIZE flag, |
| 114 // since behaviour of HeapLock is undefined and confirmed to be crashy. |
| 115 // Unfortunately, WinAPI lacks a function to tell the flags heap has been |
| 116 // created with, so we don't account any potentially unsafe heap. |
| 117 ULONG heap_info; |
| 118 if (!::HeapQueryInformation(heap, HeapCompatibilityInformation, |
| 119 &heap_info, sizeof(heap_info), nullptr)) { |
| 120 LOG(ERROR) << "Unable to get heap info."; |
| 121 return false; |
| 122 } |
| 123 // Low-fragmentation heaps are used by default since Windows Vista and |
| 124 // incompatible with HEAP_NO_SERIALIZE flag, hence there is an indicator. |
| 125 // However, main heap is always accountable even if not LFH because |
| 126 // Windows Runtime could spawn some utility threads implicitly. |
| 127 if (heap_info != HEAP_LFH && heap != main_heap) { |
| 128 LOG(ERROR) << "Non-LFH heap skipped."; |
| 129 return false; |
| 130 } |
| 131 |
| 108 // NOTE: crbug.com/464430 | 132 // NOTE: crbug.com/464430 |
| 109 // As a part of the Client/Server Runtine Subsystem (CSRSS) lockdown in the | 133 // As a part of the Client/Server Runtine Subsystem (CSRSS) lockdown in the |
| 110 // referenced bug, it will invalidate the heap used by CSRSS. The author has | 134 // referenced bug, it will invalidate the heap used by CSRSS. |
| 111 // not found a way to clean up an invalid heap handle, so it will be left in | 135 // HeapLock implicitly checks certain aspects of heap structure. |
| 112 // the process's heap list. Therefore we need to support when there is this | 136 // If this passes, we assume that this heap is valid and is not the heap |
| 113 // invalid heap handle in the heap list. | 137 // owned by CSRSS nor some another corrupted heap. |
| 114 // HeapLock implicitly checks certain aspects of the HEAP structure, such as | 138 if (!::HeapLock(heap)) { |
| 115 // the signature. If this passes, we assume that this heap is valid and is | 139 LOG(ERROR) << "Unable to lock the heap."; |
| 116 // not the one owned by CSRSS. | 140 CHECK(heap != main_heap) << "Main WinHeap is not accessible."; |
| 117 if (!::HeapLock(heap_info->heap_id)) { | 141 return false; |
| 142 } else { |
| 143 return true; |
| 144 } |
| 145 } |
| 146 |
| 147 void TestWinHeapOps() { |
| 148 HANDLE temp_heap; |
| 149 ULONG heap_info; |
| 150 LOG(ERROR) << "=== Testing WinHeap operations ==="; |
| 151 |
| 152 temp_heap = ::HeapCreate(0, 102400, 10240000); |
| 153 ::HeapDestroy(temp_heap); |
| 154 LOG(ERROR) << "Test 1: Heap created and destroyed (OK)"; |
| 155 if (!::HeapQueryInformation(temp_heap, HeapCompatibilityInformation, |
| 156 &heap_info, sizeof(heap_info), nullptr)) { |
| 157 LOG(ERROR) << "Test 1: HeapQueryInformation failed on destroyed heap (OK)"; |
| 158 } |
| 159 if (!::HeapLock(temp_heap)) { |
| 160 LOG(ERROR) << "Test 1: HeapLock failed on destroyed heap (OK)"; |
| 161 } |
| 162 |
| 163 // NOTE: Surprise! It crashes. |
| 164 std::unique_ptr<HANDLE[]> all_heaps(new HANDLE[100]); |
| 165 int num_of_heaps = ::GetProcessHeaps(100, all_heaps.get()); |
| 166 LOG(ERROR) << "Test 1: GetProcessHeaps succeeded (OK)"; |
| 167 for (int i = 0; i < num_of_heaps; i++) { |
| 168 if (temp_heap == all_heaps[i]) { |
| 169 LOG(ERROR) << "Test 1: Destroyed heap found among process heaps (OK)"; |
| 170 } |
| 171 } |
| 172 |
| 173 // NOTE: This test crashes too. |
| 174 // So, let's trust MSDN that LFH and HEAP_NO_SERIALIZE can't be enabled |
| 175 // together. |
| 176 // |
| 177 // temp_heap = ::HeapCreate(HEAP_NO_SERIALIZE, 102400, 10240000); |
| 178 // heap_info = HEAP_LFH; |
| 179 // if (!::HeapSetInformation(temp_heap, HeapCompatibilityInformation, |
| 180 // &heap_info, sizeof(heap_info))) { |
| 181 // LOG(ERROR) << "Test 2: Unable to set LFH mode for non-serialized heap (OK
)"; |
| 182 // } |
| 183 |
| 184 // NOTE: This test crashes as well. |
| 185 // |
| 186 // if (!::HeapQueryInformation((HANDLE) 0xDEADBEEF, HeapCompatibilityInformati
on, |
| 187 // &heap_info, sizeof(heap_info), nullptr)) { |
| 188 // LOG(ERROR) << "Test 3: HeapQueryInformation is okay with wrong handle (OK
)"; |
| 189 // } |
| 190 // if (!::HeapLock((HANDLE) 0xDEADBEEF)) { |
| 191 // LOG(ERROR) << "Test 3: HeapLock is okay with wrong handle (OK)"; |
| 192 // } |
| 193 |
| 194 LOG(ERROR) << "=== [END] Testing WinHeap operations [END] ==="; |
| 195 } |
| 196 |
| 197 bool GetWinHeapInformation(WinHeapInfo* heap_info, |
| 198 const std::set<void*>& block_to_skip) { |
| 199 // Some heaps aren't accountable, see TryHeapLock for details. |
| 200 if (!TryHeapLock(heap_info->heap_id)) { |
| 118 return false; | 201 return false; |
| 119 } | 202 } |
| 120 PROCESS_HEAP_ENTRY heap_entry; | 203 PROCESS_HEAP_ENTRY heap_entry; |
| 121 heap_entry.lpData = nullptr; | 204 heap_entry.lpData = nullptr; |
| 122 // Walk over all the entries in this heap. | 205 // Walk over all the entries in this heap. |
| 123 while (::HeapWalk(heap_info->heap_id, &heap_entry) != FALSE) { | 206 while (::HeapWalk(heap_info->heap_id, &heap_entry) != FALSE) { |
| 124 if (block_to_skip.count(heap_entry.lpData) == 1) | 207 if (block_to_skip.count(heap_entry.lpData) == 1) |
| 125 continue; | 208 continue; |
| 126 if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) { | 209 if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) { |
| 127 heap_info->allocated_size += heap_entry.cbData; | 210 heap_info->allocated_size += heap_entry.cbData; |
| 128 heap_info->block_count++; | 211 heap_info->block_count++; |
| 129 } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) { | 212 } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) { |
| 130 heap_info->committed_size += heap_entry.Region.dwCommittedSize; | 213 heap_info->committed_size += heap_entry.Region.dwCommittedSize; |
| 131 heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize; | 214 heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize; |
| 132 } | 215 } |
| 133 } | 216 } |
| 134 CHECK(::HeapUnlock(heap_info->heap_id) == TRUE); | 217 CHECK(::HeapUnlock(heap_info->heap_id) == TRUE); |
| 135 return true; | 218 return true; |
| 136 } | 219 } |
| 137 | 220 |
| 138 void WinHeapMemoryDumpImpl(WinHeapInfo* all_heap_info) { | 221 void WinHeapMemoryDumpImpl(WinHeapInfo* all_heap_info) { |
| 139 // This method might be flaky for 2 reasons: | 222 // This method might be flaky for 2 reasons: |
| 140 // - GetProcessHeaps is racy by design. It returns a snapshot of the | 223 // - GetProcessHeaps is racy by design. It returns a snapshot of the |
| 141 // available heaps, but there's no guarantee that that snapshot remains | 224 // available heaps, but there's no guarantee that that snapshot remains |
| 142 // valid. If a heap disappears between GetProcessHeaps() and HeapWalk() | 225 // valid. If a heap disappears between GetProcessHeaps() and HeapWalk() |
| 143 // then chaos should be assumed. This flakyness is acceptable for tracing. | 226 // then chaos should be assumed. This flakyness is acceptable for tracing. |
| 144 // - The MSDN page for HeapLock says: "If the HeapLock function is called on | |
| 145 // a heap created with the HEAP_NO_SERIALIZATION flag, the results are | |
| 146 // undefined." | |
| 147 // - Note that multiple heaps occur on Windows primarily because system and | 227 // - Note that multiple heaps occur on Windows primarily because system and |
| 148 // 3rd party DLLs will each create their own private heap. It's possible to | 228 // 3rd party DLLs will each create their own private heap. It's possible to |
| 149 // retrieve the heap the CRT allocates from and report specifically on that | 229 // retrieve the heap the CRT allocates from and report specifically on that |
| 150 // heap. It's interesting to report all heaps, as e.g. loading or invoking | 230 // heap. It's interesting to report all heaps, as e.g. loading or invoking |
| 151 // on a Windows API may consume memory from a private heap. | 231 // on a Windows API may consume memory from a private heap. |
| 152 #if defined(SYZYASAN) | 232 #if defined(SYZYASAN) |
| 153 if (base::debug::IsBinaryInstrumented()) | 233 if (base::debug::IsBinaryInstrumented()) |
| 154 return; | 234 return; |
| 155 #endif | 235 #endif |
| 156 | 236 |
| 237 TestWinHeapOps(); |
| 238 |
| 239 LOG(ERROR) << "=== Dumping WinHeaps ==="; |
| 240 |
| 157 // Retrieves the number of heaps in the current process. | 241 // Retrieves the number of heaps in the current process. |
| 158 DWORD number_of_heaps = ::GetProcessHeaps(0, NULL); | 242 DWORD number_of_heaps = ::GetProcessHeaps(0, NULL); |
| 159 | 243 |
| 160 // Try to retrieve a handle to all the heaps owned by this process. Returns | 244 // Try to retrieve a handle to all the heaps owned by this process. Returns |
| 161 // false if the number of heaps has changed. | 245 // false if the number of heaps has changed. |
| 162 // | 246 // |
| 163 // This is inherently racy as is, but it's not something that we observe a lot | 247 // This is inherently racy as is, but it's not something that we observe a lot |
| 164 // in Chrome, the heaps tend to be created at startup only. | 248 // in Chrome, the heaps tend to be created at startup only. |
| 165 std::unique_ptr<HANDLE[]> all_heaps(new HANDLE[number_of_heaps]); | 249 std::unique_ptr<HANDLE[]> all_heaps(new HANDLE[number_of_heaps]); |
| 166 if (::GetProcessHeaps(number_of_heaps, all_heaps.get()) != number_of_heaps) | 250 if (::GetProcessHeaps(number_of_heaps, all_heaps.get()) != number_of_heaps) |
| 167 return; | 251 return; |
| 168 | 252 |
| 169 // Skip the pointer to the heap array to avoid accounting the memory used by | 253 // Skip the pointer to the heap array to avoid accounting the memory used by |
| 170 // this dump provider. | 254 // this dump provider. |
| 171 std::set<void*> block_to_skip; | 255 std::set<void*> block_to_skip; |
| 172 block_to_skip.insert(all_heaps.get()); | 256 block_to_skip.insert(all_heaps.get()); |
| 173 | 257 |
| 258 HANDLE main_heap = ::GetProcessHeap(); |
| 259 size_t committed_size_main = 0; |
| 174 // Retrieves some metrics about each heap. | 260 // Retrieves some metrics about each heap. |
| 175 size_t heap_info_errors = 0; | |
| 176 for (size_t i = 0; i < number_of_heaps; ++i) { | 261 for (size_t i = 0; i < number_of_heaps; ++i) { |
| 177 WinHeapInfo heap_info = {0}; | 262 WinHeapInfo heap_info = {0}; |
| 178 heap_info.heap_id = all_heaps[i]; | 263 heap_info.heap_id = all_heaps[i]; |
| 179 if (GetHeapInformation(&heap_info, block_to_skip)) { | 264 if (GetWinHeapInformation(&heap_info, block_to_skip)) { |
| 180 all_heap_info->allocated_size += heap_info.allocated_size; | 265 all_heap_info->allocated_size += heap_info.allocated_size; |
| 181 all_heap_info->committed_size += heap_info.committed_size; | 266 all_heap_info->committed_size += heap_info.committed_size; |
| 182 all_heap_info->uncommitted_size += heap_info.uncommitted_size; | 267 all_heap_info->uncommitted_size += heap_info.uncommitted_size; |
| 183 all_heap_info->block_count += heap_info.block_count; | 268 all_heap_info->block_count += heap_info.block_count; |
| 184 } else { | 269 if (heap_info.heap_id == main_heap) { |
| 185 ++heap_info_errors; | 270 committed_size_main = heap_info.committed_size; |
| 186 // See notes in GetHeapInformation() but we only expect 1 heap to not be | 271 } |
| 187 // able to be read. | |
| 188 CHECK_EQ(1u, heap_info_errors); | |
| 189 } | 272 } |
| 190 } | 273 } |
| 274 |
| 275 LOG(ERROR) << "Allocated in secondary heaps: " |
| 276 << std::to_string(all_heap_info->committed_size - |
| 277 committed_size_main) |
| 278 << " bytes."; |
| 191 } | 279 } |
| 192 #endif // defined(OS_WIN) | 280 #endif // defined(OS_WIN) |
| 193 } // namespace | 281 } // namespace |
| 194 | 282 |
| 195 // static | 283 // static |
| 196 const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects"; | 284 const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects"; |
| 197 | 285 |
| 198 // static | 286 // static |
| 199 MallocDumpProvider* MallocDumpProvider::GetInstance() { | 287 MallocDumpProvider* MallocDumpProvider::GetInstance() { |
| 200 return Singleton<MallocDumpProvider, | 288 return Singleton<MallocDumpProvider, |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 374 tid_dumping_heap_ == PlatformThread::CurrentId()) | 462 tid_dumping_heap_ == PlatformThread::CurrentId()) |
| 375 return; | 463 return; |
| 376 AutoLock lock(allocation_register_lock_); | 464 AutoLock lock(allocation_register_lock_); |
| 377 if (!allocation_register_) | 465 if (!allocation_register_) |
| 378 return; | 466 return; |
| 379 allocation_register_->Remove(address); | 467 allocation_register_->Remove(address); |
| 380 } | 468 } |
| 381 | 469 |
| 382 } // namespace trace_event | 470 } // namespace trace_event |
| 383 } // namespace base | 471 } // namespace base |
| OLD | NEW |