1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/win/sampling_profiler.h" 6 7 #include <winternl.h> // for NTSTATUS. 8 9 #include "base/lazy_instance.h" 10 11 // Copied from wdm.h in the WDK as we don't want to take 12 // a dependency on the WDK. 13 typedef enum _KPROFILE_SOURCE { 14 ProfileTime, 15 ProfileAlignmentFixup, 16 ProfileTotalIssues, 17 ProfilePipelineDry, 18 ProfileLoadInstructions, 19 ProfilePipelineFrozen, 20 ProfileBranchInstructions, 21 ProfileTotalNonissues, 22 ProfileDcacheMisses, 23 ProfileIcacheMisses, 24 ProfileCacheMisses, 25 ProfileBranchMispredictions, 26 ProfileStoreInstructions, 27 ProfileFpInstructions, 28 ProfileIntegerInstructions, 29 Profile2Issue, 30 Profile3Issue, 31 Profile4Issue, 32 ProfileSpecialInstructions, 33 ProfileTotalCycles, 34 ProfileIcacheIssues, 35 ProfileDcacheAccesses, 36 ProfileMemoryBarrierCycles, 37 ProfileLoadLinkedIssues, 38 ProfileMaximum 39 } KPROFILE_SOURCE; 40 41 42 namespace { 43 44 // Signatures for the native functions we need to access the sampling profiler. 45 typedef NTSTATUS (NTAPI *ZwSetIntervalProfileFunc)(ULONG, KPROFILE_SOURCE); 46 typedef NTSTATUS (NTAPI *ZwQueryIntervalProfileFunc)(KPROFILE_SOURCE, PULONG); 47 48 typedef NTSTATUS (NTAPI *ZwCreateProfileFunc)(PHANDLE profile, 49 HANDLE process, 50 PVOID code_start, 51 ULONG code_size, 52 ULONG eip_bucket_shift, 53 PULONG buckets, 54 ULONG buckets_byte_size, 55 KPROFILE_SOURCE source, 56 DWORD_PTR processor_mask); 57 58 typedef NTSTATUS (NTAPI *ZwStartProfileFunc)(HANDLE); 59 typedef NTSTATUS (NTAPI *ZwStopProfileFunc)(HANDLE); 60 61 // This class is used to lazy-initialize pointers to the native 62 // functions we need to access. 63 class ProfilerFuncs { 64 public: 65 ProfilerFuncs(); 66 67 ZwSetIntervalProfileFunc ZwSetIntervalProfile; 68 ZwQueryIntervalProfileFunc ZwQueryIntervalProfile; 69 ZwCreateProfileFunc ZwCreateProfile; 70 ZwStartProfileFunc ZwStartProfile; 71 ZwStopProfileFunc ZwStopProfile; 72 73 // True iff all of the function pointers above were successfully initialized. 74 bool initialized_; 75 }; 76 77 ProfilerFuncs::ProfilerFuncs() 78 : ZwSetIntervalProfile(NULL), 79 ZwQueryIntervalProfile(NULL), 80 ZwCreateProfile(NULL), 81 ZwStartProfile(NULL), 82 ZwStopProfile(NULL), 83 initialized_(false) { 84 HMODULE ntdll = ::GetModuleHandle(L"ntdll.dll"); 85 if (ntdll != NULL) { 86 ZwSetIntervalProfile = reinterpret_cast<ZwSetIntervalProfileFunc>( 87 ::GetProcAddress(ntdll, "ZwSetIntervalProfile")); 88 ZwQueryIntervalProfile = reinterpret_cast<ZwQueryIntervalProfileFunc>( 89 ::GetProcAddress(ntdll, "ZwQueryIntervalProfile")); 90 ZwCreateProfile = reinterpret_cast<ZwCreateProfileFunc>( 91 ::GetProcAddress(ntdll, "ZwCreateProfile")); 92 ZwStartProfile = reinterpret_cast<ZwStartProfileFunc>( 93 ::GetProcAddress(ntdll, "ZwStartProfile")); 94 ZwStopProfile = reinterpret_cast<ZwStopProfileFunc>( 95 ::GetProcAddress(ntdll, "ZwStopProfile")); 96 97 if (ZwSetIntervalProfile && 98 ZwQueryIntervalProfile && 99 ZwCreateProfile && 100 ZwStartProfile && 101 ZwStopProfile) { 102 initialized_ = true; 103 } 104 } 105 } 106 107 base::LazyInstance<ProfilerFuncs>::Leaky funcs = LAZY_INSTANCE_INITIALIZER; 108 109 } // namespace 110 111 112 namespace base { 113 namespace win { 114 115 SamplingProfiler::SamplingProfiler() : is_started_(false) { 116 } 117 118 SamplingProfiler::~SamplingProfiler() { 119 if (is_started_) { 120 CHECK(Stop()) << 121 "Unable to stop sampling profiler, this will cause memory corruption."; 122 } 123 } 124 125 bool SamplingProfiler::Initialize(HANDLE process, 126 void* start, 127 size_t size, 128 size_t log2_bucket_size) { 129 // You only get to initialize each instance once. 130 DCHECK(!profile_handle_.IsValid()); 131 DCHECK(!is_started_); 132 DCHECK(start != NULL); 133 DCHECK_NE(0U, size); 134 DCHECK_LE(2, log2_bucket_size); 135 DCHECK_GE(32, log2_bucket_size); 136 137 // Bail if the native functions weren't found. 138 if (!funcs.Get().initialized_) 139 return false; 140 141 size_t bucket_size = 1 << log2_bucket_size; 142 size_t num_buckets = (size + bucket_size - 1) / bucket_size; 143 DCHECK(num_buckets != 0); 144 buckets_.resize(num_buckets); 145 146 // Get our affinity mask for the call below. 147 DWORD_PTR process_affinity = 0; 148 DWORD_PTR system_affinity = 0; 149 if (!::GetProcessAffinityMask(process, &process_affinity, &system_affinity)) { 150 LOG(ERROR) << "Failed to get process affinity mask."; 151 return false; 152 } 153 154 HANDLE profile = NULL; 155 NTSTATUS status = 156 funcs.Get().ZwCreateProfile(&profile, 157 process, 158 start, 159 static_cast<ULONG>(size), 160 static_cast<ULONG>(log2_bucket_size), 161 &buckets_[0], 162 static_cast<ULONG>( 163 sizeof(buckets_[0]) * num_buckets), 164 ProfileTime, 165 process_affinity); 166 167 if (!NT_SUCCESS(status)) { 168 // Might as well deallocate the buckets. 169 buckets_.resize(0); 170 LOG(ERROR) << "Failed to create profile, error 0x" << std::hex << status; 171 return false; 172 } 173 174 DCHECK(profile != NULL); 175 profile_handle_.Set(profile); 176 177 return true; 178 } 179 180 bool SamplingProfiler::Start() { 181 DCHECK(profile_handle_.IsValid()); 182 DCHECK(!is_started_); 183 DCHECK(funcs.Get().initialized_); 184 185 NTSTATUS status = funcs.Get().ZwStartProfile(profile_handle_.Get()); 186 if (!NT_SUCCESS(status)) 187 return false; 188 189 is_started_ = true; 190 191 return true; 192 } 193 194 bool SamplingProfiler::Stop() { 195 DCHECK(profile_handle_.IsValid()); 196 DCHECK(is_started_); 197 DCHECK(funcs.Get().initialized_); 198 199 NTSTATUS status = funcs.Get().ZwStopProfile(profile_handle_.Get()); 200 if (!NT_SUCCESS(status)) 201 return false; 202 is_started_ = false; 203 204 return true; 205 } 206 207 bool SamplingProfiler::SetSamplingInterval(base::TimeDelta sampling_interval) { 208 if (!funcs.Get().initialized_) 209 return false; 210 211 // According to Nebbet, the sampling interval is in units of 100ns. 212 ULONG interval = sampling_interval.InMicroseconds() * 10; 213 NTSTATUS status = funcs.Get().ZwSetIntervalProfile(interval, ProfileTime); 214 if (!NT_SUCCESS(status)) 215 return false; 216 217 return true; 218 } 219 220 bool SamplingProfiler::GetSamplingInterval(base::TimeDelta* sampling_interval) { 221 DCHECK(sampling_interval != NULL); 222 223 if (!funcs.Get().initialized_) 224 return false; 225 226 ULONG interval = 0; 227 NTSTATUS status = funcs.Get().ZwQueryIntervalProfile(ProfileTime, &interval); 228 if (!NT_SUCCESS(status)) 229 return false; 230 231 // According to Nebbet, the sampling interval is in units of 100ns. 232 *sampling_interval = base::TimeDelta::FromMicroseconds(interval / 10); 233 234 return true; 235 } 236 237 } // namespace win 238 } // namespace base 239