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