Home | History | Annotate | Download | only in win
      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