Chromium Code Reviews| 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 |