Coverage for /Syzygy/agent/call_trace/client_rpc.cc

CoverageLines executed / instrumented / missingexe / inst / missLanguageGroup
78.0%1451860.C++source

Line-by-line coverage:

   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

Coverage information generated Thu Jan 14 17:40:38 2016.