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 : // Implementation of the Call Trace Client DLL.
16 :
17 : #include "syzygy/agent/call_trace/client_rpc.h"
18 :
19 : #include <windows.h> // NOLINT
20 : #include <psapi.h>
21 : #include <tlhelp32.h>
22 : #include <vector>
23 :
24 : #include "base/at_exit.h"
25 : #include "base/command_line.h"
26 : #include "base/environment.h"
27 : #include "base/lazy_instance.h"
28 : #include "base/logging.h"
29 : #include "base/strings/utf_string_conversions.h"
30 : #include "base/win/pe_image.h"
31 : #include "syzygy/agent/common/agent.h"
32 : #include "syzygy/agent/common/process_utils.h"
33 : #include "syzygy/agent/common/scoped_last_error_keeper.h"
34 : #include "syzygy/common/com_utils.h"
35 : #include "syzygy/common/logging.h"
36 : #include "syzygy/common/path_util.h"
37 : #include "syzygy/trace/client/client_utils.h"
38 : #include "syzygy/trace/protocol/call_trace_defs.h"
39 :
40 : using agent::client::Client;
41 : using agent::common::ScopedLastErrorKeeper;
42 :
43 : namespace {
44 :
45 : // All tracing runs through this object.
46 : base::LazyInstance<Client> static_client_instance = LAZY_INSTANCE_INITIALIZER;
47 :
48 : // Copies the arguments under an SEH handler so we don't crash by under-running
49 : // the stack.
50 : void CopyArguments(ArgumentWord *dst, const ArgumentWord *src, size_t num) {
51 : __try {
52 : for (size_t i = 0; i < num; ++i)
53 : *dst++ = *src++;
54 : } __except(EXCEPTION_EXECUTE_HANDLER) {
55 : }
56 : }
57 :
58 : } // namespace
59 :
60 E : BOOL WINAPI DllMain(HMODULE instance, DWORD reason, LPVOID reserved) {
61 : // Our AtExit manager required by base.
62 : static base::AtExitManager* at_exit = NULL;
63 :
64 E : agent::common::InitializeCrt();
65 :
66 E : if (reason == DLL_PROCESS_ATTACH) {
67 E : DCHECK(at_exit == NULL);
68 E : at_exit = new base::AtExitManager();
69 : }
70 :
71 E : BOOL ret = Client::Instance()->DllMain(instance, reason, reserved);
72 :
73 E : if (reason == DLL_PROCESS_DETACH) {
74 E : base::CommandLine::Reset();
75 E : DCHECK(at_exit != NULL);
76 E : delete at_exit;
77 E : at_exit = NULL;
78 : }
79 :
80 E : return ret;
81 E : }
82 :
83 : // This instrumentation hook is used for all function calls except for
84 : // calls to a DLL entry point.
85 : //
86 : // Note that the calling convention to this function is non-conventional.
87 : // This function is invoked by a generated stub that does:
88 : //
89 : // push <original function>
90 : // jmp _indirect_penter
91 : //
92 : // This function will trace the entry to <original function>, and then on
93 : // exit, will arrange for execution to jump to <original function>. If
94 : // required, it will also arrange for the return from <original function>
95 : // to be captured.
96 i : extern "C" void __declspec(naked) _cdecl _indirect_penter() {
97 : __asm {
98 : // Stash volatile registers.
99 i : push eax
100 i : push ecx
101 i : push edx
102 i : pushfd
103 :
104 : // Retrieve the address pushed by our caller.
105 i : mov eax, DWORD PTR[esp + 0x10]
106 i : push eax
107 :
108 : // Calculate the position of the return address on stack, and
109 : // push it. This becomes the EntryFrame argument.
110 i : lea eax, DWORD PTR[esp + 0x18]
111 i : push eax
112 i : call Client::FunctionEntryHook
113 :
114 : // Restore volatile registers.
115 i : popfd
116 i : pop edx
117 i : pop ecx
118 i : pop eax
119 :
120 : // Return to the address pushed by our caller.
121 i : ret
122 : }
123 : }
124 :
125 : // This instrumentation hook is used for calls to a DLL's entry point.
126 : //
127 : // Note that the calling convention to this function is non-conventional.
128 : // This function is invoked by a generated stub that does:
129 : //
130 : // push <original dllmain>
131 : // jmp _indirect_penter_dllmain
132 : //
133 : // This function will trace the entry to <original dllmain>, capture the
134 : // nature of the module event being generated, and then on exit, will
135 : // arrange for execution to jump to <original dllmain>. If required,
136 : // it will also arrange for the return from <original dllmain> to be
137 : // captured.
138 i : extern "C" void __declspec(naked) _cdecl _indirect_penter_dllmain() {
139 : __asm {
140 : // Stash volatile registers.
141 i : push eax
142 i : push ecx
143 i : push edx
144 i : pushfd
145 :
146 : // Retrieve the address pushed by our caller.
147 i : mov eax, DWORD PTR[esp + 0x10]
148 i : push eax
149 :
150 : // Calculate the position of the return address on stack, and
151 : // push it. This becomes the EntryFrame argument.
152 i : lea eax, DWORD PTR[esp + 0x18]
153 i : push eax
154 i : call Client::DllMainEntryHook
155 :
156 : // Restore volatile registers.
157 i : popfd
158 i : pop edx
159 i : pop ecx
160 i : pop eax
161 :
162 : // Return to the address pushed by our caller.
163 i : ret
164 : }
165 : }
166 :
167 : namespace agent {
168 : namespace client {
169 :
170 : class Client::ThreadLocalData {
171 : public:
172 : explicit ThreadLocalData(Client* module);
173 :
174 E : bool IsInitialized() const {
175 E : return segment.header != NULL;
176 E : }
177 :
178 : // Allocates a new enter event.
179 : TraceEnterEventData* AllocateEnterEvent();
180 :
181 : // Flushes the current trace file segment.
182 : bool FlushSegment();
183 :
184 : // The call trace client to which this data belongs.
185 : // TODO(rogerm): This field isn't necessary, it's only used in DCHECKs.
186 : Client* const client;
187 :
188 : // The owning thread's current trace-file segment, if any.
189 : trace::client::TraceFileSegment segment;
190 :
191 : // The current batch record we're extending, if any.
192 : // This will point into the associated trace file segment's buffer.
193 : TraceBatchEnterData* batch;
194 : };
195 :
196 E : Client::Client() {
197 E : }
198 :
199 E : Client::~Client() {
200 E : }
201 :
202 E : Client* Client::Instance() {
203 E : return static_client_instance.Pointer();
204 E : }
205 :
206 : BOOL Client::DllMain(HMODULE /* module */,
207 : DWORD reason,
208 E : LPVOID /* reserved */) {
209 E : switch (reason) {
210 : case DLL_PROCESS_ATTACH:
211 : // Initialize logging ASAP.
212 E : base::CommandLine::Init(0, NULL);
213 E : ::common::InitLoggingForDll(L"call_trace");
214 E : break;
215 :
216 : case DLL_THREAD_ATTACH:
217 : // Session creation and thread-local data allocation are performed
218 : // just-in-time when the first instrumented entry point is invoked.
219 E : break;
220 :
221 : case DLL_PROCESS_DETACH:
222 E : OnClientProcessDetach();
223 E : break;
224 :
225 : case DLL_THREAD_DETACH:
226 E : OnClientThreadDetach();
227 E : break;
228 :
229 : default:
230 i : NOTREACHED() << "Unrecognized reason in DllMain: " << reason << ".";
231 : }
232 :
233 E : return TRUE;
234 E : }
235 :
236 E : void Client::OnClientProcessDetach() {
237 E : if (!session_.IsTracing())
238 E : return;
239 :
240 E : session_.CloseSession();
241 E : FreeThreadData();
242 E : session_.FreeSharedMemory();
243 E : }
244 :
245 E : void Client::OnClientThreadDetach() {
246 E : if (!session_.IsTracing())
247 i : return;
248 :
249 : // Get the thread data. If this thread has never called an instrumented
250 : // function, no thread local call trace data will be associated with it.
251 E : ThreadLocalData* data = GetThreadData();
252 E : if (data != NULL) {
253 E : session_.ReturnBuffer(&data->segment);
254 E : FreeThreadData(data);
255 : }
256 E : }
257 :
258 E : void Client::DllMainEntryHook(EntryFrame *entry_frame, FuncAddr function) {
259 E : ScopedLastErrorKeeper save_and_restore_last_error;
260 :
261 E : Client* client = Instance();
262 E : CHECK(client != NULL) << "Failed to get call trace client instance.";
263 :
264 E : if (client->session_.IsDisabled())
265 E : return;
266 :
267 E : HMODULE module = reinterpret_cast<HMODULE>(entry_frame->args[0]);
268 E : DWORD reason = entry_frame->args[1];
269 :
270 E : client->LogEvent_FunctionEntry(entry_frame, function, module, reason);
271 E : }
272 :
273 E : void Client::FunctionEntryHook(EntryFrame *entry_frame, FuncAddr function) {
274 E : ScopedLastErrorKeeper save_and_restore_last_error;
275 :
276 E : Client* client = Instance();
277 E : CHECK(client != NULL) << "Failed to get call trace client instance.";
278 :
279 E : if (client->session_.IsDisabled())
280 E : return;
281 :
282 E : client->LogEvent_FunctionEntry(entry_frame, function, NULL, SIZE_MAX);
283 E : }
284 :
285 : void Client::LogEvent_ModuleEvent(ThreadLocalData *data,
286 : HMODULE module,
287 E : DWORD reason) {
288 E : DCHECK(data != NULL);
289 E : DCHECK(module != NULL);
290 E : DCHECK(session_.IsTracing());
291 :
292 : // Perform a sanity check.
293 E : switch (reason) {
294 : case DLL_PROCESS_ATTACH:
295 : case DLL_PROCESS_DETACH:
296 E : break;
297 :
298 : case DLL_THREAD_ATTACH:
299 : case DLL_THREAD_DETACH:
300 : // We don't log these.
301 E : return;
302 i : break;
303 :
304 : default:
305 i : LOG(WARNING) << "Unrecognized module event: " << reason << ".";
306 i : return;
307 : }
308 :
309 : // This already logs verbosely.
310 E : if (!agent::common::LogModule(module, &session_, &data->segment))
311 i : return;
312 :
313 : // We need to flush module events right away, so that the module is
314 : // defined in the trace file before events using that module start to
315 : // occur (in another thread).
316 E : if (reason == DLL_PROCESS_ATTACH)
317 E : data->FlushSegment();
318 E : }
319 :
320 :
321 : void Client::LogEvent_FunctionEntry(EntryFrame *entry_frame,
322 : FuncAddr function,
323 : HMODULE module,
324 E : DWORD reason ) {
325 : // TODO(rogerm): Split this up so that we don't have to pass unused
326 : // module and reason parameters on every call. This is really
327 : // sub-optimal, so address it ASAP.
328 :
329 : // If we're not currently tracing then this is (one of) the first calls
330 : // to an instrumented function. We attempt to initialize a session. If
331 : // we're not able to initialize a session, we disable the call trace
332 : // client.
333 E : ThreadLocalData *data = GetOrAllocateThreadData();
334 E : CHECK(data != NULL) << "Failed to get call trace thread context.";
335 :
336 E : if (!session_.IsTracing() && !session_.IsDisabled()) {
337 E : base::AutoLock scoped_lock(init_lock_);
338 E : if (session_.IsDisabled())
339 i : return;
340 :
341 E : if (!session_.IsTracing()) {
342 E : if (!trace::client::InitializeRpcSession(&session_, &data->segment))
343 E : return;
344 : }
345 E : }
346 :
347 E : DCHECK(!session_.IsDisabled());
348 E : DCHECK(session_.IsTracing());
349 :
350 E : if (!data->IsInitialized()) {
351 E : CHECK(session_.AllocateBuffer(&data->segment))
352 : << "Failed to allocate trace buffer.";
353 : }
354 :
355 : if ((module != NULL) &&
356 E : (reason == DLL_PROCESS_ATTACH || reason == DLL_THREAD_ATTACH)) {
357 E : LogEvent_ModuleEvent(data, module, reason);
358 : }
359 :
360 : // TODO(chrisha): Add buffer flushing to permit some kind of guarantee on
361 : // the accuracy of the time for batch entry events. Do this before adding
362 : // this event to the buffer in order to guarantee precision.
363 :
364 : // Capture the basic call info and timestamp.
365 E : TraceEnterEventData* enter = data->AllocateEnterEvent();
366 E : if (enter != NULL) {
367 E : enter->retaddr = entry_frame->retaddr;
368 E : enter->function = function;
369 : }
370 E : }
371 :
372 E : Client::ThreadLocalData* Client::GetThreadData() {
373 E : return tls_.Get();
374 E : }
375 :
376 E : Client::ThreadLocalData* Client::GetOrAllocateThreadData() {
377 E : ThreadLocalData *data = tls_.Get();
378 E : if (data != NULL)
379 E : return data;
380 :
381 E : data = new ThreadLocalData(this);
382 E : if (data == NULL) {
383 i : LOG(ERROR) << "Unable to allocate per-thread data";
384 i : return NULL;
385 : }
386 :
387 E : tls_.Set(data);
388 E : return data;
389 E : }
390 :
391 E : void Client::FreeThreadData(ThreadLocalData *data) {
392 E : DCHECK(data != NULL);
393 :
394 E : delete data;
395 E : tls_.Set(NULL);
396 E : }
397 :
398 E : void Client::FreeThreadData() {
399 E : ThreadLocalData* data = GetThreadData();
400 E : if (data != NULL)
401 E : FreeThreadData(data);
402 E : }
403 :
404 E : Client::ThreadLocalData::ThreadLocalData(Client* c) : client(c), batch(NULL) {
405 E : }
406 :
407 E : TraceEnterEventData* Client::ThreadLocalData::AllocateEnterEvent() {
408 : // Do we have a batch record that we can grow?
409 E : if (batch != NULL && segment.CanAllocateRaw(sizeof(TraceEnterEventData))) {
410 : TraceEnterEventData* enter =
411 E : reinterpret_cast<TraceEnterEventData*>(segment.write_ptr);
412 : // The order of operations from here is pretty important. The issue is that
413 : // threads can be terminated at any point, and this happens as a matter of
414 : // fact at process exit, for any other threads than the one calling
415 : // ExitProcess. We want our shared memory buffers to be in a self-consistent
416 : // state at all times, so we proceed here by:
417 : // - allocating and initializing a new record first.
418 : // - then update the bookkeeping for the enclosures from the outermost,
419 : // inward. E.g. first we grow the file segment, then the record enclosure,
420 : // and lastly update the record itself.
421 :
422 : // Initialize the new record.
423 E : memset(enter, 0, sizeof(*enter));
424 :
425 : // Update the file segment size.
426 E : segment.write_ptr += sizeof(TraceEnterEventData);
427 E : segment.header->segment_length += sizeof(TraceEnterEventData);
428 :
429 : // Extend the record enclosure.
430 E : RecordPrefix* prefix = trace::client::GetRecordPrefix(batch);
431 E : prefix->size += sizeof(TraceEnterEventData);
432 :
433 : // And lastly update the inner counter.
434 E : DCHECK(enter == batch->calls + batch->num_calls);
435 E : batch->num_calls += 1;
436 :
437 E : return enter;
438 : }
439 :
440 : // Do we need to scarf a new buffer?
441 E : if (batch != NULL || !segment.CanAllocate(sizeof(TraceBatchEnterData))) {
442 i : if (!client->session_.ExchangeBuffer(&segment)) {
443 i : return NULL;
444 : }
445 : }
446 :
447 E : batch = segment.AllocateTraceRecord<TraceBatchEnterData>();
448 E : batch->thread_id = segment.header->thread_id;
449 E : batch->num_calls = 1;
450 :
451 E : return &batch->calls[0];
452 E : }
453 :
454 E : bool Client::ThreadLocalData::FlushSegment() {
455 E : DCHECK(IsInitialized());
456 :
457 E : batch = NULL;
458 E : return client->session_.ExchangeBuffer(&segment);
459 E : }
460 :
461 : } // namespace client
462 : } // namespace agent
|