Coverage for /Syzygy/instrument/instrument_app.cc

CoverageLines executed / instrumented / missingexe / inst / missLanguageGroup
95.9%1631700.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    :  // Defines the InstrumentApp class, which implements the command-line
  16    :  // "instrument" tool.
  17    :  
  18    :  #include "syzygy/instrument/instrument_app.h"
  19    :  
  20    :  #include <algorithm>
  21    :  #include <iostream>
  22    :  
  23    :  #include "base/string_util.h"
  24    :  #include "base/stringprintf.h"
  25    :  #include "syzygy/instrument/mutators/add_bb_ranges_stream.h"
  26    :  #include "syzygy/instrument/transforms/asan_transform.h"
  27    :  #include "syzygy/instrument/transforms/basic_block_entry_hook_transform.h"
  28    :  #include "syzygy/instrument/transforms/coverage_transform.h"
  29    :  #include "syzygy/instrument/transforms/entry_thunk_transform.h"
  30    :  #include "syzygy/instrument/transforms/thunk_import_references_transform.h"
  31    :  #include "syzygy/pe/decomposer.h"
  32    :  #include "syzygy/pe/image_filter.h"
  33    :  #include "syzygy/pe/pe_relinker.h"
  34    :  
  35    :  namespace instrument {
  36    :  
  37    :  namespace {
  38    :  
  39    :  using block_graph::BlockGraph;
  40    :  using pe::Decomposer;
  41    :  using pe::PEFile;
  42    :  
  43    :  static const char kUsageFormatStr[] =
  44    :      "Usage: %ls [options]\n"
  45    :      "  Required arguments:\n"
  46    :      "    --input-image=<path> The input image to instrument.\n"
  47    :      "    --mode=asan|bbentry|calltrace|coverage|profile\n"
  48    :      "                         Specifies which instrumentation mode is to be\n"
  49    :      "                         used. If this is not specified it is equivalent\n"
  50    :      "                         to specifying --mode=calltrace (this default\n"
  51    :      "                         behaviour is DEPRECATED).\n"
  52    :      "    --output-image=<path>\n"
  53    :      "                         The instrumented output image.\n"
  54    :      "  DEPRECATED options:\n"
  55    :      "    --input-dll is aliased to --input-image.\n"
  56    :      "    --output-dll is aliased to --output-image.\n"
  57    :      "    --call-trace-client=RPC\n"
  58    :      "                          Equivalent to --mode=calltrace.\n"
  59    :      "    --call-trace-client=PROFILER\n"
  60    :      "                          Equivalent to --mode=profile.\n"
  61    :      "    --call-trace-client=<path>\n"
  62    :      "                          Equivalent to --mode=calltrace --agent=<path>.\n"
  63    :      "  General options (applicable in all modes):\n"
  64    :      "    --agent=<path>        If specified indicates exactly which DLL to\n"
  65    :      "                          use when instrumenting the provided module.\n"
  66    :      "                          If not specified a default agent library will\n"
  67    :      "                          be used. This is ignored in ASAN mode.\n"
  68    :      "    --debug-friendly      Generate more debugger friendly output by\n"
  69    :      "                          making the thunks resolve to the original\n"
  70    :      "                          function's name. This is at the cost of the\n"
  71    :      "                          uniqueness of address->name resolution.\n"
  72    :      "    --input-pdb=<path>    The PDB for the DLL to instrument. If not\n"
  73    :      "                          explicitly provided will be searched for.\n"
  74    :      "    --filter=<path>       The path of the filter to be used in applying\n"
  75    :      "                          the instrumentation. Ranges marked in the\n"
  76    :      "                          filter will not be instrumented.\n"
  77    :      "    --new-decomposer      Use the new decomposer.\n"
  78    :      "    --no-augment-pdb      Indicates that the relinker should not augment\n"
  79    :      "                          the output PDB with additional metadata.\n"
  80    :      "    --no-parse-debug-info Do not parse debug information. Harder to\n"
  81    :      "                          debug but much more memory and time efficient.\n"
  82    :      "                          Only affects the new decomposer.\n"
  83    :      "    --no-strip-strings    Indicates that the relinker should not strip\n"
  84    :      "                          the strings when augmenting the PDB. They are\n"
  85    :      "                          stripped by default to keep PDB sizes down.\n"
  86    :      "    --output-pdb=<path>   The PDB for the instrumented DLL. If not\n"
  87    :      "                          provided will attempt to generate one.\n"
  88    :      "    --overwrite           Allow output files to be overwritten.\n"
  89    :      "  calltrace mode options:\n"
  90    :      "    --instrument-imports Also instrument calls to imports.\n"
  91    :      "    --module-entry-only   If specified then the per-function entry hook\n"
  92    :      "                          will not be used and only module entry points\n"
  93    :      "                          will be hooked.\n"
  94    :      "    --no-unsafe-refs      Perform no instrumentation of references\n"
  95    :      "                          between code blocks that contain anything but\n"
  96    :      "                          C/C++.\n"
  97    :      "  profile mode options:\n"
  98    :      "    --instrument-imports Also instrument calls to imports.\n"
  99    :      "\n";
 100    :  
 101    :  }  // namespace
 102    :  
 103    :  const char InstrumentApp::kCallTraceClientDllBasicBlockEntry[] =
 104    :      "basic_block_entry_client.dll";
 105    :  const char InstrumentApp::kCallTraceClientDllCoverage[] = "coverage_client.dll";
 106    :  const char InstrumentApp::kCallTraceClientDllProfile[] = "profile_client.dll";
 107    :  const char InstrumentApp::kCallTraceClientDllRpc[] = "call_trace_client.dll";
 108    :  
 109  E :  pe::PERelinker& InstrumentApp::GetRelinker() {
 110  E :    if (relinker_.get() == NULL) {
 111  E :      relinker_.reset(new pe::PERelinker());
 112  E :      CHECK(relinker_.get() != NULL);
 113    :    }
 114  E :    return *(relinker_.get());
 115  E :  }
 116    :  
 117  E :  void InstrumentApp::ParseDeprecatedMode(const CommandLine* cmd_line) {
 118  E :    DCHECK(cmd_line != NULL);
 119    :  
 120  E :    std::string client = cmd_line->GetSwitchValueASCII("call-trace-client");
 121    :  
 122  E :    if (client.empty()) {
 123  E :      LOG(INFO) << "DEPRECATED: No mode specified, using --mode=calltrace.";
 124  E :      mode_ = kInstrumentCallTraceMode;
 125  E :      client_dll_ = kCallTraceClientDllRpc;
 126  E :      return;
 127    :    }
 128    :  
 129  E :    if (LowerCaseEqualsASCII(client, "profiler")) {
 130  E :      LOG(INFO) << "DEPRECATED: Using --mode=profile.";
 131  E :      mode_ = kInstrumentProfileMode;
 132  E :      client_dll_ = kCallTraceClientDllProfile;
 133  E :    } else if (LowerCaseEqualsASCII(client, "rpc")) {
 134  E :      LOG(INFO) << "DEPRECATED: Using --mode=calltrace.";
 135  E :      mode_ = kInstrumentCallTraceMode;
 136  E :      client_dll_ = kCallTraceClientDllRpc;
 137  E :    } else {
 138  E :      LOG(INFO) << "DEPRECATED: Using --mode=calltrace --agent=" << client << ".";
 139  E :      mode_ = kInstrumentCallTraceMode;
 140  E :      client_dll_ = client;
 141    :    }
 142  E :  }
 143    :  
 144  E :  bool InstrumentApp::ParseCommandLine(const CommandLine* cmd_line) {
 145  E :    DCHECK(cmd_line != NULL);
 146    :  
 147  E :    if (cmd_line->HasSwitch("help"))
 148  E :      return Usage(cmd_line, "");
 149    :  
 150    :    // TODO(chrisha): Simplify the input/output image parsing once external
 151    :    //     tools have been updated.
 152    :  
 153    :    // Parse the input image.
 154  E :    if (cmd_line->HasSwitch("input-dll")) {
 155  E :      LOG(WARNING) << "DEPRECATED: Using --input-dll.";
 156  E :      input_dll_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("input-dll"));
 157  E :    } else {
 158  E :      input_dll_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("input-image"));
 159    :    }
 160    :  
 161    :    // Parse the output image.
 162  E :    if (cmd_line->HasSwitch("output-dll")) {
 163  E :      LOG(WARNING) << "DEPRECATED: Using --output-dll.";
 164  E :      output_dll_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("output-dll"));
 165  E :    } else {
 166    :      output_dll_path_ = AbsolutePath(cmd_line->GetSwitchValuePath(
 167  E :          "output-image"));
 168    :    }
 169    :  
 170    :    // Parse the filter path, if one was provided.
 171  E :    filter_path_ = cmd_line->GetSwitchValuePath("filter");
 172    :  
 173    :    // Ensure that both input and output have been specified.
 174  E :    if (input_dll_path_.empty() || output_dll_path_.empty())
 175  E :      return Usage(cmd_line, "You must provide input and output file names.");
 176    :  
 177    :    // Get the mode and the default client DLL.
 178  E :    if (!cmd_line->HasSwitch("mode")) {
 179    :      // TODO(chrisha): Remove this once build scripts and profiling tools have
 180    :      //     been updated.
 181  E :      ParseDeprecatedMode(cmd_line);
 182  E :    } else {
 183  E :      std::string mode = cmd_line->GetSwitchValueASCII("mode");
 184  E :      if (LowerCaseEqualsASCII(mode, "asan")) {
 185  E :        mode_ = kInstrumentAsanMode;
 186  E :      } else if (LowerCaseEqualsASCII(mode, "bbentry")) {
 187  E :        mode_ = kInstrumentBasicBlockEntryMode;
 188  E :        client_dll_ = kCallTraceClientDllBasicBlockEntry;
 189  E :      } else if (LowerCaseEqualsASCII(mode, "calltrace")) {
 190  E :        mode_ = kInstrumentCallTraceMode;
 191  E :        client_dll_ = kCallTraceClientDllRpc;
 192  E :      } else if (LowerCaseEqualsASCII(mode, "coverage")) {
 193  E :        mode_ = kInstrumentCoverageMode;
 194  E :        client_dll_ = kCallTraceClientDllCoverage;
 195  E :      } else if (LowerCaseEqualsASCII(mode, "profile")) {
 196  E :        mode_ = kInstrumentProfileMode;
 197  E :        client_dll_ = kCallTraceClientDllProfile;
 198  E :      } else {
 199    :        return Usage(cmd_line,
 200    :                     base::StringPrintf("Unknown instrumentation mode: %s.",
 201  i :                                        mode.c_str()).c_str());
 202    :      }
 203    :  
 204  E :      LOG(INFO) << "Default agent for mode " << mode << " is \""
 205    :                << client_dll_ << "\".";
 206  E :    }
 207  E :    DCHECK_NE(kInstrumentInvalidMode, mode_);
 208    :  
 209    :    // Parse the custom agent if one is specified.
 210  E :    if (cmd_line->HasSwitch("agent")) {
 211  E :      if (mode_ == kInstrumentAsanMode) {
 212    :        // TODO(siggi): Make this work properly!
 213  E :        LOG(WARNING) << "Ignoring --agent in asan mode.";
 214  E :      } else {
 215  E :        client_dll_ = cmd_line->GetSwitchValueASCII("agent");
 216  E :        LOG(INFO) << "Got custom agent \"" << client_dll_ << "\".";
 217    :      }
 218    :    }
 219    :  
 220    :    // Parse the remaining command line arguments. Not all of these are valid in
 221    :    // all modes, but we don't care too much about ignored arguments.
 222  E :    input_pdb_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("input-pdb"));
 223  E :    output_pdb_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("output-pdb"));
 224  E :    allow_overwrite_ = cmd_line->HasSwitch("overwrite");
 225  E :    new_decomposer_ = cmd_line->HasSwitch("new-decomposer");
 226  E :    no_augment_pdb_ = cmd_line->HasSwitch("no-augment-pdb");
 227  E :    no_parse_debug_info_ = cmd_line->HasSwitch("no-parse-debug-info");
 228  E :    no_strip_strings_ = cmd_line->HasSwitch("no-strip-strings");
 229  E :    debug_friendly_ = cmd_line->HasSwitch("debug-friendly");
 230  E :    thunk_imports_ = cmd_line->HasSwitch("instrument-imports");
 231  E :    instrument_unsafe_references_ = !cmd_line->HasSwitch("no-unsafe-refs");
 232  E :    module_entry_only_ = cmd_line->HasSwitch("module-entry-only");
 233    :  
 234    :    // Set per-mode overrides as necessary.
 235  E :    switch (mode_) {
 236    :      case kInstrumentBasicBlockEntryMode:
 237    :      case kInstrumentCoverageMode: {
 238  E :        thunk_imports_ = false;
 239  E :        instrument_unsafe_references_ = false;
 240  E :        module_entry_only_ = true;
 241  E :      } break;
 242    :  
 243    :      case kInstrumentProfileMode: {
 244  E :        instrument_unsafe_references_ = false;
 245  E :        module_entry_only_ = false;
 246    :      } break;
 247    :  
 248    :      default: break;
 249    :    }
 250    :  
 251  E :    return true;
 252  E :  }
 253    :  
 254  E :  int InstrumentApp::Run() {
 255  E :    DCHECK_NE(kInstrumentInvalidMode, mode_);
 256    :  
 257  E :    pe::PERelinker& relinker = GetRelinker();
 258  E :    relinker.set_input_path(input_dll_path_);
 259  E :    relinker.set_input_pdb_path(input_pdb_path_);
 260  E :    relinker.set_output_path(output_dll_path_);
 261  E :    relinker.set_output_pdb_path(output_pdb_path_);
 262  E :    relinker.set_allow_overwrite(allow_overwrite_);
 263  E :    relinker.set_augment_pdb(!no_augment_pdb_);
 264  E :    relinker.set_parse_debug_info(!no_parse_debug_info_);
 265  E :    relinker.set_use_new_decomposer(new_decomposer_);
 266  E :    relinker.set_strip_strings(!no_strip_strings_);
 267    :  
 268    :    // Parse the filter if one was provided.
 269  E :    scoped_ptr<pe::ImageFilter> filter;
 270  E :    bool filter_used = false;
 271  E :    if (!filter_path_.empty()) {
 272  E :      filter.reset(new pe::ImageFilter());
 273  E :      if (!filter->LoadFromJSON(filter_path_)) {
 274  i :        LOG(ERROR) << "Failed to parse filter file: " << filter_path_.value();
 275  i :        return 1;
 276    :      }
 277    :  
 278    :      // Ensure it is for the input module.
 279  E :      if (!filter->IsForModule(input_dll_path_)) {
 280  E :        LOG(ERROR) << "Filter does not match the input module.";
 281  E :        return 1;
 282    :      }
 283    :    }
 284    :  
 285    :    // Initialize the relinker. This does the decomposition, etc.
 286  E :    if (!relinker.Init()) {
 287  E :      LOG(ERROR) << "Failed to initialize relinker.";
 288  E :      return 1;
 289    :    }
 290    :  
 291    :    // A list of all possible transforms that we will need.
 292  E :    scoped_ptr<instrument::transforms::AsanTransform> asan_transform;
 293    :    scoped_ptr<instrument::transforms::BasicBlockEntryHookTransform>
 294  E :        basic_block_entry_transform;
 295  E :    scoped_ptr<instrument::transforms::EntryThunkTransform> entry_thunk_tx;
 296    :    scoped_ptr<instrument::transforms::ThunkImportReferencesTransform>
 297  E :        import_thunk_tx;
 298    :    scoped_ptr<instrument::transforms::CoverageInstrumentationTransform>
 299  E :        coverage_tx;
 300    :    scoped_ptr<instrument::mutators::AddBasicBlockRangesStreamPdbMutator>
 301  E :        add_bb_addr_stream_mutator;
 302    :  
 303    :    // We are instrumenting in ASAN mode.
 304  E :    if (mode_ == kInstrumentAsanMode) {
 305  E :      asan_transform.reset(new instrument::transforms::AsanTransform);
 306    :  
 307    :      // Set up the filter if one was provided.
 308  E :      if (filter.get()) {
 309  E :        asan_transform->set_filter(&filter->filter);
 310  E :        filter_used = true;
 311    :      }
 312    :  
 313  E :      relinker.AppendTransform(asan_transform.get());
 314  E :    } else if (mode_ == kInstrumentBasicBlockEntryMode) {
 315    :      // If we're in basic-block-entry mode, we need to apply the basic block
 316    :      // entry hook transform (which adds basic-block frequency structures to
 317    :      // the image and thunks the entry points) and we need to augment the PDB
 318    :      // file with the basic block addresses.
 319    :      basic_block_entry_transform.reset(
 320  E :          new instrument::transforms::BasicBlockEntryHookTransform);
 321  E :      basic_block_entry_transform->set_instrument_dll_name(client_dll_);
 322  E :      basic_block_entry_transform->set_src_ranges_for_thunks(debug_friendly_);
 323  E :      relinker.AppendTransform(basic_block_entry_transform.get());
 324    :  
 325    :      add_bb_addr_stream_mutator.reset(
 326    :          new instrument::mutators::AddBasicBlockRangesStreamPdbMutator(
 327  E :              basic_block_entry_transform->bb_ranges()));
 328  E :      relinker.AppendPdbMutator(add_bb_addr_stream_mutator.get());
 329  E :    } else if (mode_ == kInstrumentCoverageMode) {
 330    :      // If we're in coverage mode, we need to add coverage structures to
 331    :      // the image and we need to augment the PDB file with the basic block
 332    :      // addresses.
 333    :      coverage_tx.reset(
 334  E :          new instrument::transforms::CoverageInstrumentationTransform);
 335  E :      coverage_tx->set_instrument_dll_name(client_dll_);
 336  E :      coverage_tx->set_src_ranges_for_thunks(debug_friendly_);
 337  E :      relinker.AppendTransform(coverage_tx.get());
 338    :  
 339    :      add_bb_addr_stream_mutator.reset(
 340    :          new instrument::mutators::AddBasicBlockRangesStreamPdbMutator(
 341  E :              coverage_tx->bb_ranges()));
 342  E :      relinker.AppendPdbMutator(add_bb_addr_stream_mutator.get());
 343  E :    } else {
 344    :      // We're either in calltrace mode or profile mode. Each of these
 345    :      // use the entry_thunk_tx, so we handle them in the same manner.
 346    :      DCHECK(mode_ == kInstrumentCallTraceMode ||
 347  E :             mode_ == kInstrumentProfileMode);
 348    :  
 349    :      // Set up the entry thunk instrumenting transform and add it to the
 350    :      // relinker.
 351  E :      entry_thunk_tx.reset(new instrument::transforms::EntryThunkTransform);
 352  E :      entry_thunk_tx->set_instrument_dll_name(client_dll_);
 353    :      entry_thunk_tx->set_instrument_unsafe_references(
 354  E :          instrument_unsafe_references_);
 355  E :      entry_thunk_tx->set_src_ranges_for_thunks(debug_friendly_);
 356  E :      entry_thunk_tx->set_only_instrument_module_entry(module_entry_only_);
 357  E :      relinker.AppendTransform(entry_thunk_tx.get());
 358    :  
 359    :      // If we are thunking imports then add the appropriate transform.
 360  E :      if (thunk_imports_) {
 361    :        import_thunk_tx.reset(
 362  i :            new instrument::transforms::ThunkImportReferencesTransform);
 363    :        // Use the selected client DLL.
 364  i :        import_thunk_tx->set_instrument_dll_name(client_dll_);
 365  i :        relinker.AppendTransform(import_thunk_tx.get());
 366    :      }
 367    :    }
 368    :  
 369    :    // If a filter was provided but not used output a warning.
 370  E :    if (filter.get() && !filter_used)
 371  i :      LOG(WARNING) << "Not using provided filter.";
 372    :  
 373    :    // We let the PERelinker use the implicit OriginalOrderer.
 374  E :    if (!relinker.Relink()) {
 375  E :      LOG(ERROR) << "Unable to relink input image.";
 376  E :      return 1;
 377    :    }
 378    :  
 379  E :    return 0;
 380  E :  }
 381    :  
 382    :  bool InstrumentApp::Usage(const CommandLine* cmd_line,
 383  E :                            const base::StringPiece& message) const {
 384  E :    if (!message.empty()) {
 385  E :      ::fwrite(message.data(), 1, message.length(), err());
 386  E :      ::fprintf(err(), "\n\n");
 387    :    }
 388    :  
 389    :    ::fprintf(err(),
 390    :              kUsageFormatStr,
 391  E :              cmd_line->GetProgram().BaseName().value().c_str());
 392    :  
 393  E :    return false;
 394  E :  }
 395    :  
 396    :  }  // namespace instrument

Coverage information generated Thu Mar 14 11:53:36 2013.