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
|