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