1 : // Copyright 2012 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 : // Declares utility functions used by the call trace client and its unit
16 : // tests.
17 :
18 : #include "syzygy/trace/client/client_utils.h"
19 :
20 : #include <psapi.h>
21 :
22 : #include "base/environment.h"
23 : #include "base/file_util.h"
24 : #include "base/logging.h"
25 : #include "base/string_number_conversions.h"
26 : #include "base/utf_string_conversions.h"
27 : #include "base/strings/string_split.h"
28 : #include "base/win/pe_image.h"
29 : #include "sawbuck/common/com_utils.h"
30 : #include "syzygy/common/path_util.h"
31 : #include "syzygy/core/file_util.h"
32 : #include "syzygy/trace/client/rpc_session.h"
33 :
34 : // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
35 : extern "C" IMAGE_DOS_HEADER __ImageBase;
36 :
37 : namespace trace {
38 : namespace client {
39 :
40 : namespace {
41 :
42 : // Loads the environment variable @p env_var and splits it at semi-colons. Each
43 : // substring is treated as a comma-separated "path,value" pair, with the first
44 : // substring being allowed to be a "value" singleton interpreted as a default
45 : // value. Looks for the presence of @p module_path in the pairs, with more
46 : // exact matches taking higher priority (highest is exact path matching,
47 : // than basename matching and finally the default value).
48 : //
49 : // Returns true if a value has been found via the environment variable, false
50 : // if no environment variable exists or no match was found. If no match is
51 : // found @p value is left unmodified.
52 : template<typename ReturnType, typename ConversionFunctor>
53 : bool GetModuleValueFromEnvVar(const char* env_var_name,
54 : const base::FilePath& module_path,
55 : const ReturnType& default_value,
56 : const ConversionFunctor& convert,
57 E : ReturnType* value) {
58 E : size_t best_score = 0;
59 E : ReturnType best_value = default_value;
60 :
61 : // Get the environment variable. If it's empty, we can return early.
62 E : scoped_ptr<base::Environment> env(base::Environment::Create());
63 E : std::string env_var;
64 E : env->GetVar(env_var_name, &env_var);
65 E : if (env_var.empty())
66 E : return false;
67 :
68 : // Get the absolute path and the basename of the module. We will use these
69 : // for matching.
70 E : base::FilePath abs_module_path(base::MakeAbsoluteFilePath(module_path));
71 : // TODO(chrisha): Is this wise? There's all kinds of environmental trouble
72 : // that can lead to path normalization failing, and there is infact no
73 : // guarantee that an arbitrary file path can be normalized given an
74 : // arbitrary process' permissions.
75 E : CHECK(!abs_module_path.empty());
76 E : base::FilePath base_module_path = module_path.BaseName();
77 :
78 E : std::vector<std::string> pairs;
79 E : base::SplitString(env_var, ';', &pairs);
80 :
81 E : for (size_t i = 0; i < pairs.size(); ++i) {
82 E : if (pairs[i].empty())
83 i : continue;
84 :
85 E : std::vector<std::string> path_value;
86 E : base::SplitString(pairs[i], ',', &path_value);
87 :
88 E : size_t score = 0;
89 :
90 : // Ignore malformed fields.
91 E : if (path_value.size() > 2)
92 i : continue;
93 :
94 : // Ignore entries with improperly formatted values.
95 E : ReturnType value = default_value;
96 E : if (!convert(path_value.back(), &value))
97 E : continue;
98 :
99 E : if (path_value.size() == 1) {
100 : // This is a default value specified without a path.
101 E : score = 1;
102 E : } else if (path_value.size() == 2) {
103 E : base::FilePath path(UTF8ToWide(path_value[0]));
104 :
105 : // Ignore improperly formatted paths.
106 E : if (path.empty())
107 i : continue;
108 :
109 E : if (base_module_path == path) {
110 : // The basename of the module matches the path.
111 E : score = 2;
112 E : } else if (abs_module_path == path) {
113 : // The full path of the module matches.
114 E : score = 3;
115 E : } else {
116 : // Due to mounting files in different locations we can often get
117 : // differing but equivalent paths to the same file. Thus, we pull out
118 : // the big guns and do a file-system level comparison to see if they
119 : // do in fact refer to the same file.
120 : core::FilePathCompareResult result = core::CompareFilePaths(
121 E : abs_module_path, path);
122 E : if (result == core::kEquivalentFilePaths)
123 i : score = 3;
124 : }
125 E : }
126 :
127 E : if (score > best_score) {
128 E : best_score = score;
129 E : best_value = value;
130 : }
131 E : }
132 :
133 E : if (best_score > 0) {
134 E : *value = best_value;
135 E : return true;
136 : }
137 :
138 E : return false;
139 E : }
140 :
141 : struct KeepAsString {
142 E : bool operator()(const std::string& s1, std::string* s2) const {
143 E : DCHECK(s2 != NULL);
144 E : *s2 = s1;
145 E : return true;
146 E : }
147 : };
148 :
149 : struct ToInt {
150 E : bool operator()(const std::string& s, int* i) const {
151 E : DCHECK(i != NULL);
152 E : if (!base::StringToInt(s, i))
153 E : return false;
154 E : return true;
155 E : }
156 : };
157 :
158 : } // namespace
159 :
160 : int ReasonToEventType(DWORD reason) {
161 : switch (reason) {
162 : case DLL_PROCESS_ATTACH:
163 : return TRACE_PROCESS_ATTACH_EVENT;
164 :
165 : case DLL_PROCESS_DETACH:
166 : return TRACE_PROCESS_DETACH_EVENT;
167 :
168 : case DLL_THREAD_ATTACH:
169 : return TRACE_THREAD_ATTACH_EVENT;
170 :
171 : case DLL_THREAD_DETACH:
172 : return TRACE_THREAD_DETACH_EVENT;
173 :
174 : default:
175 : NOTREACHED() << "Invalid reason: " << reason << ".";
176 : return -1;
177 : }
178 : }
179 :
180 E : RecordPrefix* GetRecordPrefix(void *record) {
181 E : DCHECK(record != NULL);
182 :
183 E : return reinterpret_cast<RecordPrefix*>(record) - 1;
184 E : }
185 :
186 : TraceFileSegment::TraceFileSegment()
187 : : header(NULL),
188 : base_ptr(NULL),
189 : write_ptr(NULL),
190 E : end_ptr(NULL) {
191 : // Zero the RPC buffer.
192 E : memset(&buffer_info, 0, sizeof(buffer_info));
193 E : }
194 :
195 : // Returns true if there's enough space left in the given segment to write
196 : // num_bytes of raw data.
197 E : bool TraceFileSegment::CanAllocateRaw(size_t num_bytes) const {
198 E : DCHECK(write_ptr != NULL);
199 E : DCHECK(end_ptr != NULL);
200 E : DCHECK(num_bytes != 0);
201 E : return (write_ptr + num_bytes) <= end_ptr;
202 E : }
203 :
204 : // Returns true if there's enough space left in the given segment to write
205 : // a prefixed record of length num_bytes.
206 E : bool TraceFileSegment::CanAllocate(size_t num_bytes) const {
207 E : DCHECK(num_bytes != 0);
208 E : return CanAllocateRaw(num_bytes + sizeof(RecordPrefix));
209 E : }
210 :
211 E : void FillPrefix(RecordPrefix* prefix, int type, size_t size) {
212 E : prefix->size = size;
213 E : prefix->version.hi = TRACE_VERSION_HI;
214 E : prefix->version.lo = TRACE_VERSION_LO;
215 E : prefix->type = static_cast<uint16>(type);
216 E : prefix->timestamp = ::GetTickCount();
217 E : }
218 :
219 : // Writes the segment header at the top of a segment, updating the bytes
220 : // consumed and initializing the segment header structures.
221 E : void TraceFileSegment::WriteSegmentHeader(SessionHandle session_handle) {
222 E : DCHECK(header == NULL);
223 E : DCHECK(write_ptr != NULL);
224 E : DCHECK(CanAllocate(sizeof(TraceFileSegmentHeader)));
225 :
226 : // The trace record allocation will write the record prefix and update
227 : // the number of bytes consumed within the buffer.
228 :
229 E : RecordPrefix* prefix = reinterpret_cast<RecordPrefix*>(write_ptr);
230 : FillPrefix(prefix,
231 : TraceFileSegmentHeader::kTypeId,
232 E : sizeof(TraceFileSegmentHeader));
233 :
234 E : header = reinterpret_cast<TraceFileSegmentHeader*>(prefix + 1);
235 E : header->thread_id = ::GetCurrentThreadId();
236 E : header->segment_length = 0;
237 :
238 E : write_ptr = reinterpret_cast<uint8*>(header + 1);
239 E : }
240 :
241 : void* TraceFileSegment::AllocateTraceRecordImpl(int record_type,
242 E : size_t record_size) {
243 E : DCHECK(header != NULL);
244 E : DCHECK(write_ptr != NULL);
245 E : DCHECK(record_size != 0);
246 :
247 E : const size_t total_size = sizeof(RecordPrefix) + record_size;
248 :
249 E : DCHECK(CanAllocateRaw(total_size));
250 :
251 : // Clear the memory we're about to allocate. If this thread gets killed
252 : // before it can finish updating the trace record we want the allocated
253 : // record to have a somewhat consistent state.
254 E : ::memset(write_ptr, 0, total_size);
255 :
256 E : RecordPrefix* prefix = reinterpret_cast<RecordPrefix*>(write_ptr);
257 E : FillPrefix(prefix, record_type, record_size);
258 :
259 E : write_ptr += total_size;
260 E : header->segment_length += total_size;
261 :
262 E : return prefix + 1;
263 E : }
264 :
265 E : bool GetModuleBaseAddress(void* address_in_module, void** module_base) {
266 E : DCHECK(address_in_module != NULL);
267 E : DCHECK(module_base != NULL);
268 :
269 : // Get the address of the module. We do this by querying for the allocation
270 : // that contains the address of the function we intercepted. This must lie
271 : // within the instrumented module, and be part of the single allocation in
272 : // which the image of the module lies. The base of the module will be the
273 : // base address of the allocation.
274 E : MEMORY_BASIC_INFORMATION mem_info = {};
275 E : if (::VirtualQuery(address_in_module, &mem_info, sizeof(mem_info)) == 0) {
276 i : DWORD error = ::GetLastError();
277 i : LOG(ERROR) << "VirtualQuery failed: " << com::LogWe(error) << ".";
278 i : return false;
279 : }
280 :
281 E : *module_base = mem_info.AllocationBase;
282 :
283 : #ifndef NDEBUG
284 E : base::win::PEImage image(*module_base);
285 E : DCHECK(image.VerifyMagic());
286 : #endif
287 :
288 E : return true;
289 E : }
290 :
291 E : bool GetModulePath(void* module_base, base::FilePath* module_path) {
292 E : DCHECK(module_base != NULL);
293 E : DCHECK(module_path != NULL);
294 :
295 E : HMODULE module = reinterpret_cast<HMODULE>(module_base);
296 :
297 : wchar_t buffer[1024];
298 : if (::GetMappedFileName(::GetCurrentProcess(), module_base, buffer,
299 E : arraysize(buffer)) == 0) {
300 i : DWORD error = ::GetLastError();
301 i : LOG(ERROR) << "GetMappedFileName failed: " << com::LogWe(error) << ".";
302 i : return false;
303 : }
304 :
305 E : base::FilePath device_path(buffer);
306 E : if (!common::ConvertDevicePathToDrivePath(device_path, module_path))
307 i : return false;
308 :
309 E : return true;
310 E : }
311 :
312 E : std::string GetInstanceIdForModule(const base::FilePath& module_path) {
313 E : std::string id;
314 : // We don't care if the search is successful or not.
315 : GetModuleValueFromEnvVar(::kSyzygyRpcInstanceIdEnvVar, module_path,
316 E : id, KeepAsString(), &id);
317 E : return id;
318 E : }
319 :
320 E : std::string GetInstanceIdForThisModule() {
321 E : base::FilePath module_path;
322 E : CHECK(GetModulePath(&__ImageBase, &module_path));
323 :
324 E : std::string instance_id = GetInstanceIdForModule(module_path);
325 :
326 E : return instance_id;
327 E : }
328 :
329 E : bool IsRpcSessionMandatory(const base::FilePath& module_path) {
330 E : int value = 0;
331 : if (!GetModuleValueFromEnvVar(kSyzygyRpcSessionMandatoryEnvVar, module_path,
332 E : value, ToInt(), &value)) {
333 E : return false;
334 : }
335 :
336 E : if (value == 0)
337 E : return false;
338 :
339 : // Anything non-zero is treated as 'true'.
340 E : return true;
341 E : }
342 :
343 E : bool IsRpcSessionMandatoryForThisModule() {
344 E : base::FilePath module_path;
345 E : CHECK(GetModulePath(&__ImageBase, &module_path));
346 :
347 E : if (IsRpcSessionMandatory(module_path))
348 E : return true;
349 :
350 E : return false;
351 E : }
352 :
353 E : bool InitializeRpcSession(RpcSession* rpc_session, TraceFileSegment* segment) {
354 E : DCHECK(rpc_session != NULL);
355 :
356 E : std::string id = trace::client::GetInstanceIdForThisModule();
357 E : rpc_session->set_instance_id(UTF8ToWide(id));
358 E : if (rpc_session->CreateSession(segment))
359 E : return true;
360 :
361 : // If the session is not mandatory then return and indicate that we failed
362 : // to initialize properly.
363 E : if (!IsRpcSessionMandatoryForThisModule())
364 E : return false;
365 :
366 : // If you're seeing this error message it's because the process was unable
367 : // to initialize an RPC session, and the state of the
368 : // SYZYGY_RPC_SESSION_MANDATORY environment variable indicated that it was
369 : // required. Make sure the call-trace service is running with the appropriate
370 : // instance ID!
371 i : LOG(ERROR) << "RPC session is mandatory, but unable to be created.";
372 :
373 : // Dump some context regarding the decision to abort.
374 i : base::FilePath module_path;
375 i : if (GetModulePath(&__ImageBase, &module_path))
376 i : LOG(ERROR) << "Module path: " << module_path.value();
377 :
378 i : LOG(ERROR) << "RPC instance ID is \"" << id << "\".";
379 :
380 i : base::Environment* env = base::Environment::Create();
381 i : if (env) {
382 i : std::string var;
383 i : if (env->GetVar(::kSyzygyRpcInstanceIdEnvVar, &var)) {
384 i : LOG(ERROR) << ::kSyzygyRpcInstanceIdEnvVar << " is \"" << var << "\".";
385 i : } else {
386 i : LOG(ERROR) << ::kSyzygyRpcInstanceIdEnvVar << " is not set.";
387 : }
388 :
389 i : if (env->GetVar(::kSyzygyRpcSessionMandatoryEnvVar, &var)) {
390 i : LOG(ERROR) << ::kSyzygyRpcSessionMandatoryEnvVar << " is \"" << var
391 : << "\".";
392 i : } else {
393 i : LOG(ERROR) << ::kSyzygyRpcSessionMandatoryEnvVar << " is not set.";
394 : }
395 i : }
396 :
397 : // Kill this process with prejudice. We need to be heavy handed here because
398 : // we are typically running under the loader lock, and most things won't
399 : // actually convince it to stop the entire process.
400 i : ::TerminateProcess(::GetCurrentProcess(), 255);
401 :
402 : // We need this to avoid getting complaints about control paths missing a
403 : // return statement.
404 i : return false;
405 E : }
406 :
407 : } // namespace trace::client
408 : } // namespace trace
|