OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 #include "syzygy/runlaa/runlaa_app.h" | |
16 | |
17 #include "base/path_service.h" | |
18 #include "base/files/file_util.h" | |
19 #include "base/files/scoped_temp_dir.h" | |
20 #include "base/process/launch.h" | |
21 #include "base/win/pe_image.h" | |
22 #include "syzygy/core/file_util.h" | |
23 #include "syzygy/pe/pe_file.h" | |
24 | |
25 namespace runlaa { | |
26 | |
27 namespace { | |
28 | |
29 static const char kUsageFormatStr[] = | |
30 "Usage: %ls [options] -- [options for child process]\n" | |
31 "Required Options:\n" | |
32 " --image=<FILE> Path of the image to run." | |
33 " --mode=<MODE> Runs the provided executable with the given mode.\n" | |
34 " MODE must be one of 'laa' or 'nolaa'.\n" | |
35 "Optional Options:\n" | |
36 " --expect-mode=<MODE>\n" | |
37 " If specified then returns 0 if the currently running\n" | |
38 " mode matches the expected mode, 1 otherwise. This is\n" | |
39 " to allow self-unittesting.\n" | |
40 " --in-place Modifies the image in-place if necessary. Returns the\n" | |
41 " image to its original state when completed.\n" | |
42 " --keep-temp-dir If specified then the temp directory will not be\n" | |
43 " deleted.\n" | |
44 "\n"; | |
45 | |
46 static const char kInPlace[] = "in-place"; | |
47 static const char kImage[] = "image"; | |
48 static const char kKeepTempDir[] = "keep-temp-dir"; | |
49 static const char kMode[] = "mode"; | |
50 static const char kModeLaa[] = "laa"; | |
51 static const char kModeNoLaa[] = "nolaa"; | |
52 | |
53 // Gets the status of the LargeAddressAware bit for the given image. | |
54 bool GetLaaBit(const base::FilePath& image_path, bool* is_laa) { | |
55 DCHECK_NE(static_cast<bool*>(nullptr), is_laa); | |
56 | |
57 if (!base::PathExists(image_path)) { | |
58 LOG(ERROR) << "Image does not exist: " << image_path.value(); | |
59 return false; | |
60 } | |
61 | |
62 pe::PEFile image; | |
63 if (!image.Init(image_path)) { | |
64 LOG(ERROR) << "Unable to open PE file: " << image_path.value(); | |
65 return false; | |
66 } | |
67 | |
68 *is_laa = (image.nt_headers()->FileHeader.Characteristics & | |
69 IMAGE_FILE_LARGE_ADDRESS_AWARE) != 0; | |
70 | |
71 return true; | |
72 } | |
73 | |
74 // Toggles the LargeAddressAware bit for the given image. | |
75 bool ToggleLaaBit(const base::FilePath& image_path) { | |
76 base::ScopedFILE file(base::OpenFile(image_path, "r+b")); | |
77 if (file.get() == nullptr) { | |
78 LOG(ERROR) << "Unable to open for reading and writing: " | |
79 << image_path.value(); | |
80 return false; | |
81 } | |
82 | |
83 // Read the DOS header. | |
84 IMAGE_DOS_HEADER dos_header = {}; | |
85 if (fread(&dos_header, sizeof(dos_header), 1, file.get()) != 1) { | |
86 LOG(ERROR) << "Unable to read DOS header:" << image_path.value(); | |
87 return false; | |
88 } | |
89 | |
90 // Get the offset of the image characteristics. | |
91 size_t characteristics_offset = dos_header.e_lfanew + | |
92 offsetof(IMAGE_NT_HEADERS, FileHeader) + | |
93 offsetof(IMAGE_FILE_HEADER, Characteristics); | |
94 WORD characteristics = 0; | |
95 if (::fseek(file.get(), characteristics_offset, SEEK_SET) != 0 || | |
96 ::fread(&characteristics, sizeof(characteristics), 1, file.get()) != 1) { | |
97 LOG(ERROR) << "Unable to read image characteristics: " | |
98 << image_path.value(); | |
99 return false; | |
100 } | |
101 | |
102 // Toggle the bit and write it back to the image. | |
103 characteristics ^= IMAGE_FILE_LARGE_ADDRESS_AWARE; | |
104 if (::fseek(file.get(), characteristics_offset, SEEK_SET) != 0 || | |
105 ::fwrite(&characteristics, sizeof(characteristics), 1, file.get()) != 1) { | |
106 LOG(ERROR) << "Unable to write image characteristics: " | |
107 << image_path.value(); | |
108 return false; | |
109 } | |
110 | |
111 return true; | |
112 } | |
113 | |
114 bool CurrentProcessIsLargeAddressAware() { | |
115 const base::win::PEImage image(::GetModuleHandle(NULL)); | |
116 | |
117 bool process_is_large_address_aware = | |
118 (image.GetNTHeaders()->FileHeader.Characteristics & | |
119 IMAGE_FILE_LARGE_ADDRESS_AWARE) != 0; | |
120 | |
121 return process_is_large_address_aware; | |
122 } | |
123 | |
124 bool SelfTest(const std::string& expect_mode) { | |
125 bool is_laa = CurrentProcessIsLargeAddressAware(); | |
126 if (expect_mode == kModeLaa) | |
127 return is_laa; | |
128 if (expect_mode == kModeNoLaa) | |
129 return !is_laa; | |
130 return false; | |
131 } | |
132 | |
133 } // namespace | |
134 | |
135 bool RunLaaApp::ParseCommandLine(const base::CommandLine* command_line) { | |
136 DCHECK_NE(static_cast<const base::CommandLine*>(nullptr), command_line); | |
137 | |
138 if (command_line->HasSwitch("help")) { | |
139 ::fprintf(err(), kUsageFormatStr, | |
140 command_line->GetProgram().BaseName().value().c_str()); | |
141 return false; | |
142 } | |
143 | |
144 // If the executable is running a self-hosted test, then don't bother parsing | |
145 // anything else. | |
146 expect_mode_ = command_line->GetSwitchValueASCII("expect-mode"); | |
147 if (!expect_mode_.empty()) | |
148 return true; | |
149 | |
150 // Parse the image. | |
151 if (!command_line->HasSwitch(kImage)) { | |
152 LOG(ERROR) << "Must specify --" << kImage << "."; | |
153 return false; | |
154 } | |
155 image_ = base::MakeAbsoluteFilePath(command_line->GetSwitchValuePath(kImage)); | |
156 | |
157 // Parse the mode. | |
158 if (!command_line->HasSwitch(kMode)) { | |
159 LOG(ERROR) << "Must specify --" << kMode << "."; | |
160 return false; | |
161 } | |
162 std::string mode; | |
163 mode = command_line->GetSwitchValueASCII(kMode); | |
164 if (mode == kModeLaa) { | |
165 is_laa_ = true; | |
166 } else if (mode == kModeNoLaa) { | |
167 is_laa_ = false; | |
168 } else { | |
169 LOG(ERROR) << "Unrecognized mode: " << mode; | |
170 return false; | |
171 } | |
172 | |
173 // Parse optional options. | |
174 in_place_ = command_line->HasSwitch(kInPlace); | |
175 keep_temp_dir_ = command_line->HasSwitch(kKeepTempDir); | |
176 | |
177 // Copy the child process arguments. | |
178 child_argv_ = command_line->GetArgs(); | |
179 | |
180 return true; | |
181 } | |
182 | |
183 int RunLaaApp::Run() { | |
184 // If an expected mode has been specified then run a self-test and return | |
185 // the result. | |
186 if (!expect_mode_.empty()) { | |
187 if (SelfTest(expect_mode_)) | |
188 return 0; | |
189 return 1; | |
190 } | |
191 | |
192 bool is_laa = false; | |
Sigurður Ásgeirsson
2015/08/03 10:27:28
nit: was_laa to make subsequent code more readable
chrisha
2015/08/03 13:13:46
Done.
| |
193 if (!GetLaaBit(image_, &is_laa)) | |
194 return 1; | |
195 | |
196 base::ScopedTempDir scoped_temp_dir; | |
197 base::FilePath child_image(image_); | |
198 bool toggle_back = false; | |
199 | |
200 if (is_laa == is_laa_) { | |
201 LOG(INFO) << "Image already in desired mode, running directly."; | |
202 } else { | |
203 // The image is not in the desired mode. It needs to be toggled. | |
204 if (in_place_) { | |
205 // Try our best not to modify the currently running executable. | |
206 base::FilePath exe_path; | |
207 if (PathService::Get(base::FILE_EXE, &exe_path)) { | |
208 exe_path = base::MakeAbsoluteFilePath(exe_path); | |
209 core::FilePathCompareResult result = | |
210 core::CompareFilePaths(exe_path, image_); | |
211 if (result == core::kEquivalentFilePaths) { | |
212 LOG(ERROR) << "Unable to modify running executable in-place."; | |
213 return 1; | |
214 } | |
215 } | |
216 | |
217 // The work is occurring in place and the image needs to be toggled back. | |
218 toggle_back = true; | |
219 } else { | |
220 // The work is not to happen in place. Create a temp directory and copy | |
221 // the | |
Sigurður Ásgeirsson
2015/08/03 10:27:28
nit: yuck!
chrisha
2015/08/03 13:13:46
Done.
| |
222 // image. | |
223 if (!scoped_temp_dir.CreateUniqueTempDir()) { | |
224 LOG(ERROR) << "Failed to create temp directory."; | |
225 return 1; | |
226 } | |
227 | |
228 // Take ownership of the temp directory if it is to be left around. | |
229 base::FilePath temp_dir = scoped_temp_dir.path(); | |
230 if (keep_temp_dir_) { | |
231 temp_dir = scoped_temp_dir.Take(); | |
232 LOG(INFO) << "Temporary directory will be preserved: " | |
233 << temp_dir.value(); | |
234 } | |
235 | |
236 child_image = temp_dir.Append(image_.BaseName()); | |
237 LOG(INFO) << "Creating copy of image: " << child_image.value(); | |
238 if (!base::CopyFile(image_, child_image)) { | |
239 LOG(ERROR) << "Failed to copy image."; | |
240 return 1; | |
241 } | |
242 } | |
243 | |
244 // Toggle the image. | |
245 LOG(INFO) << "Toggling LargeAddressAware bit: " << child_image.value(); | |
246 if (!ToggleLaaBit(child_image)) | |
247 return 1; | |
248 } | |
249 | |
250 // Run the child process. | |
251 base::CommandLine::StringVector child_argv(child_argv_); | |
252 child_argv.insert(child_argv.begin(), child_image.value()); | |
253 base::CommandLine child_command_line(child_argv); | |
254 LOG(INFO) << "Launching child process: " | |
255 << child_command_line.GetCommandLineString(); | |
256 base::LaunchOptions launch_options; | |
257 base::Process child_process = | |
258 base::LaunchProcess(child_command_line, launch_options); | |
259 DCHECK(child_process.IsValid()); | |
260 int exit_code = 0; | |
261 child_process.WaitForExit(&exit_code); | |
262 LOG(INFO) << "Child process returned " << exit_code; | |
263 | |
264 // Toggle the image back if need be. | |
265 if (toggle_back) { | |
266 // The assumption is that work was in place and the bit was previously | |
267 // toggled. | |
268 DCHECK_NE(is_laa, is_laa_); | |
269 DCHECK_EQ(child_image.value(), image_.value()); | |
270 LOG(INFO) << "Toggling back LargeAddressAware bit."; | |
271 if (!ToggleLaaBit(child_image)) | |
272 return 1; | |
273 } | |
274 | |
275 // Return the exit code of the child process. | |
276 return exit_code; | |
277 } | |
278 | |
279 } // namespace runlaa | |
OLD | NEW |