| // Copyright 2015 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/profiler/native_stack_sampler.h" | 
 |  | 
 | #include <objbase.h> | 
 | #include <windows.h> | 
 | #include <stddef.h> | 
 | #include <winternl.h> | 
 |  | 
 | #include <cstdlib> | 
 | #include <map> | 
 | #include <memory> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/lazy_instance.h" | 
 | #include "base/logging.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/profiler/win32_stack_frame_unwinder.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/time/time.h" | 
 | #include "base/win/pe_image.h" | 
 | #include "base/win/scoped_handle.h" | 
 |  | 
 | namespace base { | 
 |  | 
 | // Stack recording functions -------------------------------------------------- | 
 |  | 
 | namespace { | 
 |  | 
 | // The thread environment block internal type. | 
 | struct TEB { | 
 |   NT_TIB Tib; | 
 |   // Rest of struct is ignored. | 
 | }; | 
 |  | 
 | // Returns the thread environment block pointer for |thread_handle|. | 
 | const TEB* GetThreadEnvironmentBlock(HANDLE thread_handle) { | 
 |   // Define the internal types we need to invoke NtQueryInformationThread. | 
 |   enum THREAD_INFORMATION_CLASS { ThreadBasicInformation }; | 
 |  | 
 |   struct CLIENT_ID { | 
 |     HANDLE UniqueProcess; | 
 |     HANDLE UniqueThread; | 
 |   }; | 
 |  | 
 |   struct THREAD_BASIC_INFORMATION { | 
 |     NTSTATUS ExitStatus; | 
 |     TEB* Teb; | 
 |     CLIENT_ID ClientId; | 
 |     KAFFINITY AffinityMask; | 
 |     LONG Priority; | 
 |     LONG BasePriority; | 
 |   }; | 
 |  | 
 |   using NtQueryInformationThreadFunction = | 
 |       NTSTATUS (WINAPI*)(HANDLE, THREAD_INFORMATION_CLASS, PVOID, ULONG, | 
 |                          PULONG); | 
 |  | 
 |   const NtQueryInformationThreadFunction nt_query_information_thread = | 
 |       reinterpret_cast<NtQueryInformationThreadFunction>( | 
 |           ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), | 
 |                            "NtQueryInformationThread")); | 
 |   if (!nt_query_information_thread) | 
 |     return nullptr; | 
 |  | 
 |   THREAD_BASIC_INFORMATION basic_info = {0}; | 
 |   NTSTATUS status = | 
 |       nt_query_information_thread(thread_handle, ThreadBasicInformation, | 
 |                                   &basic_info, sizeof(THREAD_BASIC_INFORMATION), | 
 |                                   nullptr); | 
 |   if (status != 0) | 
 |     return nullptr; | 
 |  | 
 |   return basic_info.Teb; | 
 | } | 
 |  | 
 | #if defined(_WIN64) | 
 | // If the value at |pointer| points to the original stack, rewrite it to point | 
 | // to the corresponding location in the copied stack. | 
 | void RewritePointerIfInOriginalStack(uintptr_t top, uintptr_t bottom, | 
 |                                      void* stack_copy, const void** pointer) { | 
 |   const uintptr_t value = reinterpret_cast<uintptr_t>(*pointer); | 
 |   if (value >= bottom && value < top) { | 
 |     *pointer = reinterpret_cast<const void*>( | 
 |         static_cast<unsigned char*>(stack_copy) + (value - bottom)); | 
 |   } | 
 | } | 
 | #endif | 
 |  | 
 | void CopyMemoryFromStack(void* to, const void* from, size_t length) | 
 |     NO_SANITIZE("address") { | 
 | #if defined(ADDRESS_SANITIZER) | 
 |   // The following loop is an inlined version of memcpy. The code must be | 
 |   // inlined to avoid instrumentation when using ASAN (memory sanitizer). The | 
 |   // stack profiler is generating false positive when walking the stack. | 
 |   for (size_t pos = 0; pos < length; ++pos) | 
 |     reinterpret_cast<char*>(to)[pos] = reinterpret_cast<const char*>(from)[pos]; | 
 | #else | 
 |   std::memcpy(to, from, length); | 
 | #endif | 
 | } | 
 |  | 
 | // Rewrites possible pointers to locations within the stack to point to the | 
 | // corresponding locations in the copy, and rewrites the non-volatile registers | 
 | // in |context| likewise. This is necessary to handle stack frames with dynamic | 
 | // stack allocation, where a pointer to the beginning of the dynamic allocation | 
 | // area is stored on the stack and/or in a non-volatile register. | 
 | // | 
 | // Eager rewriting of anything that looks like a pointer to the stack, as done | 
 | // in this function, does not adversely affect the stack unwinding. The only | 
 | // other values on the stack the unwinding depends on are return addresses, | 
 | // which should not point within the stack memory. The rewriting is guaranteed | 
 | // to catch all pointers because the stacks are guaranteed by the ABI to be | 
 | // sizeof(void*) aligned. | 
 | // | 
 | // Note: this function must not access memory in the original stack as it may | 
 | // have been changed or deallocated by this point. This is why |top| and | 
 | // |bottom| are passed as uintptr_t. | 
 | void RewritePointersToStackMemory(uintptr_t top, uintptr_t bottom, | 
 |                                   CONTEXT* context, void* stack_copy) { | 
 | #if defined(_WIN64) | 
 |   DWORD64 CONTEXT::* const nonvolatile_registers[] = { | 
 |     &CONTEXT::R12, | 
 |     &CONTEXT::R13, | 
 |     &CONTEXT::R14, | 
 |     &CONTEXT::R15, | 
 |     &CONTEXT::Rdi, | 
 |     &CONTEXT::Rsi, | 
 |     &CONTEXT::Rbx, | 
 |     &CONTEXT::Rbp, | 
 |     &CONTEXT::Rsp | 
 |   }; | 
 |  | 
 |   // Rewrite pointers in the context. | 
 |   for (size_t i = 0; i < arraysize(nonvolatile_registers); ++i) { | 
 |     DWORD64* const reg = &(context->*nonvolatile_registers[i]); | 
 |     RewritePointerIfInOriginalStack(top, bottom, stack_copy, | 
 |                                     reinterpret_cast<const void**>(reg)); | 
 |   } | 
 |  | 
 |   // Rewrite pointers on the stack. | 
 |   const void** start = reinterpret_cast<const void**>(stack_copy); | 
 |   const void** end = reinterpret_cast<const void**>( | 
 |       reinterpret_cast<char*>(stack_copy) + (top - bottom)); | 
 |   for (const void** loc = start; loc < end; ++loc) | 
 |     RewritePointerIfInOriginalStack(top, bottom, stack_copy, loc); | 
 | #endif | 
 | } | 
 |  | 
 | // Movable type representing a recorded stack frame. | 
 | struct RecordedFrame { | 
 |   RecordedFrame() {} | 
 |  | 
 |   RecordedFrame(RecordedFrame&& other) | 
 |       : instruction_pointer(other.instruction_pointer), | 
 |         module(std::move(other.module)) { | 
 |   } | 
 |  | 
 |   RecordedFrame& operator=(RecordedFrame&& other) { | 
 |     instruction_pointer = other.instruction_pointer; | 
 |     module = std::move(other.module); | 
 |     return *this; | 
 |   } | 
 |  | 
 |   const void* instruction_pointer; | 
 |   ScopedModuleHandle module; | 
 |  | 
 |  private: | 
 |   DISALLOW_COPY_AND_ASSIGN(RecordedFrame); | 
 | }; | 
 |  | 
 | // Walks the stack represented by |context| from the current frame downwards, | 
 | // recording the instruction pointer and associated module for each frame in | 
 | // |stack|. | 
 | void RecordStack(CONTEXT* context, std::vector<RecordedFrame>* stack) { | 
 | #ifdef _WIN64 | 
 |   DCHECK(stack->empty()); | 
 |  | 
 |   // Reserve enough memory for most stacks, to avoid repeated | 
 |   // allocations. Approximately 99.9% of recorded stacks are 128 frames or | 
 |   // fewer. | 
 |   stack->reserve(128); | 
 |  | 
 |   Win32StackFrameUnwinder frame_unwinder; | 
 |   while (context->Rip) { | 
 |     const void* instruction_pointer = | 
 |         reinterpret_cast<const void*>(context->Rip); | 
 |     ScopedModuleHandle module; | 
 |     if (!frame_unwinder.TryUnwind(context, &module)) | 
 |       return; | 
 |     RecordedFrame frame; | 
 |     frame.instruction_pointer = instruction_pointer; | 
 |     frame.module = std::move(module); | 
 |     stack->push_back(std::move(frame)); | 
 |   } | 
 | #endif | 
 | } | 
 |  | 
 | // Gets the unique build ID for a module. Windows build IDs are created by a | 
 | // concatenation of a GUID and AGE fields found in the headers of a module. The | 
 | // GUID is stored in the first 16 bytes and the AGE is stored in the last 4 | 
 | // bytes. Returns the empty string if the function fails to get the build ID. | 
 | // | 
 | // Example: | 
 | // dumpbin chrome.exe /headers | find "Format:" | 
 | //   ... Format: RSDS, {16B2A428-1DED-442E-9A36-FCE8CBD29726}, 10, ... | 
 | // | 
 | // The resulting buildID string of this instance of chrome.exe is | 
 | // "16B2A4281DED442E9A36FCE8CBD2972610". | 
 | // | 
 | // Note that the AGE field is encoded in decimal, not hex. | 
 | std::string GetBuildIDForModule(HMODULE module_handle) { | 
 |   GUID guid; | 
 |   DWORD age; | 
 |   win::PEImage(module_handle).GetDebugId(&guid, &age, /* pdb_file= */ nullptr); | 
 |   const int kGUIDSize = 39; | 
 |   std::wstring build_id; | 
 |   int result = | 
 |       ::StringFromGUID2(guid, WriteInto(&build_id, kGUIDSize), kGUIDSize); | 
 |   if (result != kGUIDSize) | 
 |     return std::string(); | 
 |   RemoveChars(build_id, L"{}-", &build_id); | 
 |   build_id += StringPrintf(L"%d", age); | 
 |   return WideToUTF8(build_id); | 
 | } | 
 |  | 
 | // ScopedDisablePriorityBoost ------------------------------------------------- | 
 |  | 
 | // Disables priority boost on a thread for the lifetime of the object. | 
 | class ScopedDisablePriorityBoost { | 
 |  public: | 
 |   ScopedDisablePriorityBoost(HANDLE thread_handle); | 
 |   ~ScopedDisablePriorityBoost(); | 
 |  | 
 |  private: | 
 |   HANDLE thread_handle_; | 
 |   BOOL got_previous_boost_state_; | 
 |   BOOL boost_state_was_disabled_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(ScopedDisablePriorityBoost); | 
 | }; | 
 |  | 
 | ScopedDisablePriorityBoost::ScopedDisablePriorityBoost(HANDLE thread_handle) | 
 |     : thread_handle_(thread_handle), | 
 |       got_previous_boost_state_(false), | 
 |       boost_state_was_disabled_(false) { | 
 |   got_previous_boost_state_ = | 
 |       ::GetThreadPriorityBoost(thread_handle_, &boost_state_was_disabled_); | 
 |   if (got_previous_boost_state_) { | 
 |     // Confusingly, TRUE disables priority boost. | 
 |     ::SetThreadPriorityBoost(thread_handle_, TRUE); | 
 |   } | 
 | } | 
 |  | 
 | ScopedDisablePriorityBoost::~ScopedDisablePriorityBoost() { | 
 |   if (got_previous_boost_state_) | 
 |     ::SetThreadPriorityBoost(thread_handle_, boost_state_was_disabled_); | 
 | } | 
 |  | 
 | // ScopedSuspendThread -------------------------------------------------------- | 
 |  | 
 | // Suspends a thread for the lifetime of the object. | 
 | class ScopedSuspendThread { | 
 |  public: | 
 |   ScopedSuspendThread(HANDLE thread_handle); | 
 |   ~ScopedSuspendThread(); | 
 |  | 
 |   bool was_successful() const { return was_successful_; } | 
 |  | 
 |  private: | 
 |   HANDLE thread_handle_; | 
 |   bool was_successful_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread); | 
 | }; | 
 |  | 
 | ScopedSuspendThread::ScopedSuspendThread(HANDLE thread_handle) | 
 |     : thread_handle_(thread_handle), | 
 |       was_successful_(::SuspendThread(thread_handle) != | 
 |                       static_cast<DWORD>(-1)) {} | 
 |  | 
 | ScopedSuspendThread::~ScopedSuspendThread() { | 
 |   if (!was_successful_) | 
 |     return; | 
 |  | 
 |   // Disable the priority boost that the thread would otherwise receive on | 
 |   // resume. We do this to avoid artificially altering the dynamics of the | 
 |   // executing application any more than we already are by suspending and | 
 |   // resuming the thread. | 
 |   // | 
 |   // Note that this can racily disable a priority boost that otherwise would | 
 |   // have been given to the thread, if the thread is waiting on other wait | 
 |   // conditions at the time of SuspendThread and those conditions are satisfied | 
 |   // before priority boost is reenabled. The measured length of this window is | 
 |   // ~100us, so this should occur fairly rarely. | 
 |   ScopedDisablePriorityBoost disable_priority_boost(thread_handle_); | 
 |   bool resume_thread_succeeded = | 
 |       ::ResumeThread(thread_handle_) != static_cast<DWORD>(-1); | 
 |   CHECK(resume_thread_succeeded) << "ResumeThread failed: " << GetLastError(); | 
 | } | 
 |  | 
 | // Tests whether |stack_pointer| points to a location in the guard page. | 
 | // | 
 | // IMPORTANT NOTE: This function is invoked while the target thread is | 
 | // suspended so it must not do any allocation from the default heap, including | 
 | // indirectly via use of DCHECK/CHECK or other logging statements. Otherwise | 
 | // this code can deadlock on heap locks in the default heap acquired by the | 
 | // target thread before it was suspended. | 
 | bool PointsToGuardPage(uintptr_t stack_pointer) { | 
 |   MEMORY_BASIC_INFORMATION memory_info; | 
 |   SIZE_T result = ::VirtualQuery(reinterpret_cast<LPCVOID>(stack_pointer), | 
 |                                  &memory_info, | 
 |                                  sizeof(memory_info)); | 
 |   return result != 0 && (memory_info.Protect & PAGE_GUARD); | 
 | } | 
 |  | 
 | // Suspends the thread with |thread_handle|, copies its stack and resumes the | 
 | // thread, then records the stack frames and associated modules into |stack|. | 
 | // | 
 | // IMPORTANT NOTE: No allocations from the default heap may occur in the | 
 | // ScopedSuspendThread scope, including indirectly via use of DCHECK/CHECK or | 
 | // other logging statements. Otherwise this code can deadlock on heap locks in | 
 | // the default heap acquired by the target thread before it was suspended. | 
 | void SuspendThreadAndRecordStack( | 
 |     HANDLE thread_handle, | 
 |     const void* base_address, | 
 |     void* stack_copy_buffer, | 
 |     size_t stack_copy_buffer_size, | 
 |     std::vector<RecordedFrame>* stack, | 
 |     NativeStackSampler::AnnotateCallback annotator, | 
 |     StackSamplingProfiler::Sample* sample, | 
 |     NativeStackSamplerTestDelegate* test_delegate) { | 
 |   DCHECK(stack->empty()); | 
 |  | 
 |   CONTEXT thread_context = {0}; | 
 |   thread_context.ContextFlags = CONTEXT_FULL; | 
 |   // The stack bounds are saved to uintptr_ts for use outside | 
 |   // ScopedSuspendThread, as the thread's memory is not safe to dereference | 
 |   // beyond that point. | 
 |   const uintptr_t top = reinterpret_cast<uintptr_t>(base_address); | 
 |   uintptr_t bottom = 0u; | 
 |  | 
 |   { | 
 |     ScopedSuspendThread suspend_thread(thread_handle); | 
 |  | 
 |     if (!suspend_thread.was_successful()) | 
 |       return; | 
 |  | 
 |     if (!::GetThreadContext(thread_handle, &thread_context)) | 
 |       return; | 
 | #if defined(_WIN64) | 
 |     bottom = thread_context.Rsp; | 
 | #else | 
 |     bottom = thread_context.Esp; | 
 | #endif | 
 |  | 
 |     if ((top - bottom) > stack_copy_buffer_size) | 
 |       return; | 
 |  | 
 |     // Dereferencing a pointer in the guard page in a thread that doesn't own | 
 |     // the stack results in a STATUS_GUARD_PAGE_VIOLATION exception and a crash. | 
 |     // This occurs very rarely, but reliably over the population. | 
 |     if (PointsToGuardPage(bottom)) | 
 |       return; | 
 |  | 
 |     (*annotator)(sample); | 
 |  | 
 |     CopyMemoryFromStack(stack_copy_buffer, | 
 |                         reinterpret_cast<const void*>(bottom), top - bottom); | 
 |   } | 
 |  | 
 |   if (test_delegate) | 
 |     test_delegate->OnPreStackWalk(); | 
 |  | 
 |   RewritePointersToStackMemory(top, bottom, &thread_context, stack_copy_buffer); | 
 |  | 
 |   RecordStack(&thread_context, stack); | 
 | } | 
 |  | 
 | // NativeStackSamplerWin ------------------------------------------------------ | 
 |  | 
 | class NativeStackSamplerWin : public NativeStackSampler { | 
 |  public: | 
 |   NativeStackSamplerWin(win::ScopedHandle thread_handle, | 
 |                         AnnotateCallback annotator, | 
 |                         NativeStackSamplerTestDelegate* test_delegate); | 
 |   ~NativeStackSamplerWin() override; | 
 |  | 
 |   // StackSamplingProfiler::NativeStackSampler: | 
 |   void ProfileRecordingStarting( | 
 |       std::vector<StackSamplingProfiler::Module>* modules) override; | 
 |   void RecordStackSample(StackBuffer* stack_buffer, | 
 |                          StackSamplingProfiler::Sample* sample) override; | 
 |   void ProfileRecordingStopped(StackBuffer* stack_buffer) override; | 
 |  | 
 |  private: | 
 |   // Attempts to query the module filename, base address, and id for | 
 |   // |module_handle|, and store them in |module|. Returns true if it succeeded. | 
 |   static bool GetModuleForHandle(HMODULE module_handle, | 
 |                                  StackSamplingProfiler::Module* module); | 
 |  | 
 |   // Gets the index for the Module corresponding to |module_handle| in | 
 |   // |modules|, adding it if it's not already present. Returns | 
 |   // StackSamplingProfiler::Frame::kUnknownModuleIndex if no Module can be | 
 |   // determined for |module|. | 
 |   size_t GetModuleIndex(HMODULE module_handle, | 
 |                         std::vector<StackSamplingProfiler::Module>* modules); | 
 |  | 
 |   // Copies the information represented by |stack| into |sample| and |modules|. | 
 |   void CopyToSample(const std::vector<RecordedFrame>& stack, | 
 |                     StackSamplingProfiler::Sample* sample, | 
 |                     std::vector<StackSamplingProfiler::Module>* modules); | 
 |  | 
 |   win::ScopedHandle thread_handle_; | 
 |  | 
 |   const AnnotateCallback annotator_; | 
 |  | 
 |   NativeStackSamplerTestDelegate* const test_delegate_; | 
 |  | 
 |   // The stack base address corresponding to |thread_handle_|. | 
 |   const void* const thread_stack_base_address_; | 
 |  | 
 |   // Weak. Points to the modules associated with the profile being recorded | 
 |   // between ProfileRecordingStarting() and ProfileRecordingStopped(). | 
 |   std::vector<StackSamplingProfiler::Module>* current_modules_; | 
 |  | 
 |   // Maps a module handle to the corresponding Module's index within | 
 |   // current_modules_. | 
 |   std::map<HMODULE, size_t> profile_module_index_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerWin); | 
 | }; | 
 |  | 
 | NativeStackSamplerWin::NativeStackSamplerWin( | 
 |     win::ScopedHandle thread_handle, | 
 |     AnnotateCallback annotator, | 
 |     NativeStackSamplerTestDelegate* test_delegate) | 
 |     : thread_handle_(thread_handle.Take()), | 
 |       annotator_(annotator), | 
 |       test_delegate_(test_delegate), | 
 |       thread_stack_base_address_( | 
 |           GetThreadEnvironmentBlock(thread_handle_.Get())->Tib.StackBase) { | 
 |   DCHECK(annotator_); | 
 | } | 
 |  | 
 | NativeStackSamplerWin::~NativeStackSamplerWin() { | 
 | } | 
 |  | 
 | void NativeStackSamplerWin::ProfileRecordingStarting( | 
 |     std::vector<StackSamplingProfiler::Module>* modules) { | 
 |   current_modules_ = modules; | 
 |   profile_module_index_.clear(); | 
 | } | 
 |  | 
 | void NativeStackSamplerWin::RecordStackSample( | 
 |     StackBuffer* stack_buffer, | 
 |     StackSamplingProfiler::Sample* sample) { | 
 |   DCHECK(stack_buffer); | 
 |   DCHECK(current_modules_); | 
 |  | 
 |   std::vector<RecordedFrame> stack; | 
 |   SuspendThreadAndRecordStack(thread_handle_.Get(), thread_stack_base_address_, | 
 |                               stack_buffer->buffer(), stack_buffer->size(), | 
 |                               &stack, annotator_, sample, test_delegate_); | 
 |   CopyToSample(stack, sample, current_modules_); | 
 | } | 
 |  | 
 | void NativeStackSamplerWin::ProfileRecordingStopped(StackBuffer* stack_buffer) { | 
 |   current_modules_ = nullptr; | 
 | } | 
 |  | 
 | // static | 
 | bool NativeStackSamplerWin::GetModuleForHandle( | 
 |     HMODULE module_handle, | 
 |     StackSamplingProfiler::Module* module) { | 
 |   wchar_t module_name[MAX_PATH]; | 
 |   DWORD result_length = | 
 |       GetModuleFileName(module_handle, module_name, arraysize(module_name)); | 
 |   if (result_length == 0) | 
 |     return false; | 
 |  | 
 |   module->filename = base::FilePath(module_name); | 
 |  | 
 |   module->base_address = reinterpret_cast<uintptr_t>(module_handle); | 
 |  | 
 |   module->id = GetBuildIDForModule(module_handle); | 
 |   if (module->id.empty()) | 
 |     return false; | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | size_t NativeStackSamplerWin::GetModuleIndex( | 
 |     HMODULE module_handle, | 
 |     std::vector<StackSamplingProfiler::Module>* modules) { | 
 |   if (!module_handle) | 
 |     return StackSamplingProfiler::Frame::kUnknownModuleIndex; | 
 |  | 
 |   auto loc = profile_module_index_.find(module_handle); | 
 |   if (loc == profile_module_index_.end()) { | 
 |     StackSamplingProfiler::Module module; | 
 |     if (!GetModuleForHandle(module_handle, &module)) | 
 |       return StackSamplingProfiler::Frame::kUnknownModuleIndex; | 
 |     modules->push_back(module); | 
 |     loc = profile_module_index_.insert(std::make_pair( | 
 |         module_handle, modules->size() - 1)).first; | 
 |   } | 
 |  | 
 |   return loc->second; | 
 | } | 
 |  | 
 | void NativeStackSamplerWin::CopyToSample( | 
 |     const std::vector<RecordedFrame>& stack, | 
 |     StackSamplingProfiler::Sample* sample, | 
 |     std::vector<StackSamplingProfiler::Module>* modules) { | 
 |   sample->frames.clear(); | 
 |   sample->frames.reserve(stack.size()); | 
 |  | 
 |   for (const RecordedFrame& frame : stack) { | 
 |     sample->frames.push_back(StackSamplingProfiler::Frame( | 
 |         reinterpret_cast<uintptr_t>(frame.instruction_pointer), | 
 |         GetModuleIndex(frame.module.Get(), modules))); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | std::unique_ptr<NativeStackSampler> NativeStackSampler::Create( | 
 |     PlatformThreadId thread_id, | 
 |     AnnotateCallback annotator, | 
 |     NativeStackSamplerTestDelegate* test_delegate) { | 
 | #if _WIN64 | 
 |   // Get the thread's handle. | 
 |   HANDLE thread_handle = ::OpenThread( | 
 |       THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, | 
 |       FALSE, | 
 |       thread_id); | 
 |  | 
 |   if (thread_handle) { | 
 |     return std::unique_ptr<NativeStackSampler>(new NativeStackSamplerWin( | 
 |         win::ScopedHandle(thread_handle), annotator, test_delegate)); | 
 |   } | 
 | #endif | 
 |   return std::unique_ptr<NativeStackSampler>(); | 
 | } | 
 |  | 
 | size_t NativeStackSampler::GetStackBufferSize() { | 
 |   // The default Win32 reserved stack size is 1 MB and Chrome Windows threads | 
 |   // currently always use the default, but this allows for expansion if it | 
 |   // occurs. The size beyond the actual stack size consists of unallocated | 
 |   // virtual memory pages so carries little cost (just a bit of wasted address | 
 |   // space). | 
 |   return 2 << 20;  // 2 MiB | 
 | } | 
 |  | 
 | }  // namespace base |