1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <windows.h> 6 #include <shlwapi.h> 7 8 #include "base/command_line.h" 9 #include "base/compiler_specific.h" 10 #include "base/debug/trace_event.h" 11 #include "base/environment.h" 12 #include "base/file_version_info.h" 13 #include "base/lazy_instance.h" 14 #include "base/logging.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/rand_util.h" // For PreRead experiment. 17 #include "base/sha1.h" // For PreRead experiment. 18 #include "base/strings/string16.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "base/version.h" 23 #include "base/win/windows_version.h" 24 #include "chrome/app/chrome_crash_reporter_client.h" 25 #include "chrome/app/client_util.h" 26 #include "chrome/app/image_pre_reader_win.h" 27 #include "chrome/common/chrome_constants.h" 28 #include "chrome/common/chrome_result_codes.h" 29 #include "chrome/common/chrome_switches.h" 30 #include "chrome/common/env_vars.h" 31 #include "chrome/installer/util/google_update_constants.h" 32 #include "chrome/installer/util/google_update_settings.h" 33 #include "chrome/installer/util/install_util.h" 34 #include "chrome/installer/util/util_constants.h" 35 #include "components/crash/app/breakpad_win.h" 36 #include "components/crash/app/crash_reporter_client.h" 37 #include "components/metrics/client_info.h" 38 #include "content/public/app/startup_helper_win.h" 39 #include "sandbox/win/src/sandbox.h" 40 41 namespace { 42 // The entry point signature of chrome.dll. 43 typedef int (*DLL_MAIN)(HINSTANCE, sandbox::SandboxInterfaceInfo*); 44 45 typedef void (*RelaunchChromeBrowserWithNewCommandLineIfNeededFunc)(); 46 47 base::LazyInstance<chrome::ChromeCrashReporterClient>::Leaky 48 g_chrome_crash_client = LAZY_INSTANCE_INITIALIZER; 49 50 // Returns true if the build date for this module precedes the expiry date 51 // for the pre-read experiment. 52 bool PreReadExperimentIsActive() { 53 const int kPreReadExpiryYear = 2014; 54 const int kPreReadExpiryMonth = 7; 55 const int kPreReadExpiryDay = 1; 56 const char kBuildTimeStr[] = __DATE__ " " __TIME__; 57 58 // Get the timestamp of the build. 59 base::Time build_time; 60 bool result = base::Time::FromString(kBuildTimeStr, &build_time); 61 DCHECK(result); 62 63 // Get the timestamp at which the experiment expires. 64 base::Time::Exploded exploded = {0}; 65 exploded.year = kPreReadExpiryYear; 66 exploded.month = kPreReadExpiryMonth; 67 exploded.day_of_month = kPreReadExpiryDay; 68 base::Time expiration_time = base::Time::FromLocalExploded(exploded); 69 70 // Return true if the build time predates the expiration time.. 71 return build_time < expiration_time; 72 } 73 74 // Get random unit values, i.e., in the range (0, 1), denoting a die-toss for 75 // being in an experiment population and experimental group thereof. 76 void GetPreReadPopulationAndGroup(double* population, double* group) { 77 // By default we use the metrics id for the user as stable pseudo-random 78 // input to a hash. 79 scoped_ptr<metrics::ClientInfo> client_info = 80 GoogleUpdateSettings::LoadMetricsClientInfo(); 81 82 // If this user has no metrics id, we fall back to a purely random value per 83 // browser session. 84 const size_t kLength = 16; 85 std::string random_value(client_info ? client_info->client_id 86 : base::RandBytesAsString(kLength)); 87 88 // To interpret the value as a random number we hash it and read the first 8 89 // bytes of the hash as a unit-interval representing a die-toss for being in 90 // the experiment population and the second 8 bytes as a die-toss for being 91 // in various experiment groups. 92 unsigned char sha1_hash[base::kSHA1Length]; 93 base::SHA1HashBytes( 94 reinterpret_cast<const unsigned char*>(random_value.c_str()), 95 random_value.size() * sizeof(random_value[0]), 96 sha1_hash); 97 COMPILE_ASSERT(2 * sizeof(uint64) < sizeof(sha1_hash), need_more_data); 98 const uint64* random_bits = reinterpret_cast<uint64*>(&sha1_hash[0]); 99 100 // Convert the bits into unit-intervals and return. 101 *population = base::BitsToOpenEndedUnitInterval(random_bits[0]); 102 *group = base::BitsToOpenEndedUnitInterval(random_bits[1]); 103 } 104 105 // Gets the amount of pre-read to use as well as the experiment group in which 106 // the user falls. 107 size_t InitPreReadPercentage() { 108 // By default use the old behaviour: read 100%. 109 const int kDefaultPercentage = 100; 110 const char kDefaultFormatStr[] = "%d-pct-default"; 111 const char kControlFormatStr[] = "%d-pct-control"; 112 const char kGroupFormatStr[] = "%d-pct"; 113 114 COMPILE_ASSERT(kDefaultPercentage <= 100, default_percentage_too_large); 115 COMPILE_ASSERT(kDefaultPercentage % 5 == 0, default_percentage_not_mult_5); 116 117 // Roll the dice to determine if this user is in the experiment and if so, 118 // in which experimental group. 119 double population = 0.0; 120 double group = 0.0; 121 GetPreReadPopulationAndGroup(&population, &group); 122 123 // We limit experiment populations to 1% of the Stable and 10% of each of 124 // the other channels. 125 const base::string16 channel(GoogleUpdateSettings::GetChromeChannel( 126 GoogleUpdateSettings::IsSystemInstall())); 127 double threshold = (channel == installer::kChromeChannelStable) ? 0.01 : 0.10; 128 129 // If the experiment has expired use the default pre-read level. Otherwise, 130 // those not in the experiment population also use the default pre-read level. 131 size_t value = kDefaultPercentage; 132 const char* format_str = kDefaultFormatStr; 133 if (PreReadExperimentIsActive() && (population <= threshold)) { 134 // We divide the experiment population into groups pre-reading at 5 percent 135 // increments in the range [0, 100]. 136 value = static_cast<size_t>(group * 21.0) * 5; 137 DCHECK_LE(value, 100u); 138 DCHECK_EQ(0u, value % 5); 139 format_str = 140 (value == kDefaultPercentage) ? kControlFormatStr : kGroupFormatStr; 141 } 142 143 // Generate the group name corresponding to this percentage value. 144 std::string group_name; 145 base::SStringPrintf(&group_name, format_str, value); 146 147 // Persist the group name to the environment so that it can be used for 148 // reporting. 149 scoped_ptr<base::Environment> env(base::Environment::Create()); 150 env->SetVar(chrome::kPreReadEnvironmentVariable, group_name); 151 152 // Return the percentage value to be used. 153 return value; 154 } 155 156 // Expects that |dir| has a trailing backslash. |dir| is modified so it 157 // contains the full path that was tried. Caller must check for the return 158 // value not being null to determine if this path contains a valid dll. 159 HMODULE LoadModuleWithDirectory(base::string16* dir, 160 const wchar_t* dll_name, 161 bool pre_read) { 162 ::SetCurrentDirectoryW(dir->c_str()); 163 dir->append(dll_name); 164 165 if (pre_read) { 166 #if !defined(WIN_DISABLE_PREREAD) 167 // We pre-read the binary to warm the memory caches (fewer hard faults to 168 // page parts of the binary in). 169 const size_t kStepSize = 1024 * 1024; 170 size_t percentage = InitPreReadPercentage(); 171 ImagePreReader::PartialPreReadImage(dir->c_str(), percentage, kStepSize); 172 #endif 173 } 174 175 return ::LoadLibraryExW(dir->c_str(), NULL, 176 LOAD_WITH_ALTERED_SEARCH_PATH); 177 } 178 179 void RecordDidRun(const base::string16& dll_path) { 180 bool system_level = !InstallUtil::IsPerUserInstall(dll_path.c_str()); 181 GoogleUpdateSettings::UpdateDidRunState(true, system_level); 182 } 183 184 void ClearDidRun(const base::string16& dll_path) { 185 bool system_level = !InstallUtil::IsPerUserInstall(dll_path.c_str()); 186 GoogleUpdateSettings::UpdateDidRunState(false, system_level); 187 } 188 189 bool InMetroMode() { 190 return (wcsstr( 191 ::GetCommandLineW(), L" -ServerName:DefaultBrowserServer") != NULL); 192 } 193 194 typedef int (*InitMetro)(); 195 196 } // namespace 197 198 base::string16 GetExecutablePath() { 199 wchar_t path[MAX_PATH]; 200 ::GetModuleFileNameW(NULL, path, MAX_PATH); 201 if (!::PathRemoveFileSpecW(path)) 202 return base::string16(); 203 base::string16 exe_path(path); 204 return exe_path.append(1, L'\\'); 205 } 206 207 base::string16 GetCurrentModuleVersion() { 208 scoped_ptr<FileVersionInfo> file_version_info( 209 FileVersionInfo::CreateFileVersionInfoForCurrentModule()); 210 if (file_version_info.get()) { 211 base::string16 version_string(file_version_info->file_version()); 212 if (Version(base::UTF16ToASCII(version_string)).IsValid()) 213 return version_string; 214 } 215 return base::string16(); 216 } 217 218 //============================================================================= 219 220 MainDllLoader::MainDllLoader() 221 : dll_(NULL), metro_mode_(InMetroMode()) { 222 } 223 224 MainDllLoader::~MainDllLoader() { 225 } 226 227 // Loading chrome is an interesting affair. First we try loading from the 228 // current directory to support run-what-you-compile and other development 229 // scenarios. 230 // If that fails then we look at the version resource in the current 231 // module. This is the expected path for chrome.exe browser instances in an 232 // installed build. 233 HMODULE MainDllLoader::Load(base::string16* version, 234 base::string16* out_file) { 235 const base::string16 executable_dir(GetExecutablePath()); 236 *out_file = executable_dir; 237 238 const wchar_t* dll_name = metro_mode_ ? 239 installer::kChromeMetroDll : 240 #if !defined(CHROME_MULTIPLE_DLL) 241 installer::kChromeDll; 242 #else 243 (process_type_ == "service") || process_type_.empty() ? 244 installer::kChromeDll : 245 installer::kChromeChildDll; 246 #endif 247 const bool pre_read = !metro_mode_; 248 HMODULE dll = LoadModuleWithDirectory(out_file, dll_name, pre_read); 249 if (!dll) { 250 base::string16 version_string(GetCurrentModuleVersion()); 251 if (version_string.empty()) { 252 LOG(ERROR) << "No valid Chrome version found"; 253 return NULL; 254 } 255 *out_file = executable_dir; 256 *version = version_string; 257 out_file->append(version_string).append(1, L'\\'); 258 dll = LoadModuleWithDirectory(out_file, dll_name, pre_read); 259 if (!dll) { 260 PLOG(ERROR) << "Failed to load Chrome DLL from " << *out_file; 261 return NULL; 262 } 263 } 264 265 DCHECK(dll); 266 return dll; 267 } 268 269 // Launching is a matter of loading the right dll, setting the CHROME_VERSION 270 // environment variable and just calling the entry point. Derived classes can 271 // add custom code in the OnBeforeLaunch callback. 272 int MainDllLoader::Launch(HINSTANCE instance) { 273 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 274 process_type_ = cmd_line.GetSwitchValueASCII(switches::kProcessType); 275 276 base::string16 version; 277 base::string16 file; 278 279 if (metro_mode_) { 280 HMODULE metro_dll = Load(&version, &file); 281 if (!metro_dll) 282 return chrome::RESULT_CODE_MISSING_DATA; 283 284 InitMetro chrome_metro_main = 285 reinterpret_cast<InitMetro>(::GetProcAddress(metro_dll, "InitMetro")); 286 return chrome_metro_main(); 287 } 288 289 // Initialize the sandbox services. 290 sandbox::SandboxInterfaceInfo sandbox_info = {0}; 291 content::InitializeSandboxInfo(&sandbox_info); 292 293 crash_reporter::SetCrashReporterClient(g_chrome_crash_client.Pointer()); 294 bool exit_now = true; 295 if (process_type_.empty()) { 296 if (breakpad::ShowRestartDialogIfCrashed(&exit_now)) { 297 // We restarted because of a previous crash. Ask user if we should 298 // Relaunch. Only for the browser process. See crbug.com/132119. 299 if (exit_now) 300 return content::RESULT_CODE_NORMAL_EXIT; 301 } 302 } 303 breakpad::InitCrashReporter(process_type_); 304 305 dll_ = Load(&version, &file); 306 if (!dll_) 307 return chrome::RESULT_CODE_MISSING_DATA; 308 309 scoped_ptr<base::Environment> env(base::Environment::Create()); 310 env->SetVar(chrome::kChromeVersionEnvVar, base::WideToUTF8(version)); 311 312 OnBeforeLaunch(file); 313 DLL_MAIN chrome_main = 314 reinterpret_cast<DLL_MAIN>(::GetProcAddress(dll_, "ChromeMain")); 315 int rc = chrome_main(instance, &sandbox_info); 316 return OnBeforeExit(rc, file); 317 } 318 319 void MainDllLoader::RelaunchChromeBrowserWithNewCommandLineIfNeeded() { 320 if (!dll_) 321 return; 322 323 RelaunchChromeBrowserWithNewCommandLineIfNeededFunc relaunch_function = 324 reinterpret_cast<RelaunchChromeBrowserWithNewCommandLineIfNeededFunc>( 325 ::GetProcAddress(dll_, 326 "RelaunchChromeBrowserWithNewCommandLineIfNeeded")); 327 if (!relaunch_function) { 328 LOG(ERROR) << "Could not find exported function " 329 << "RelaunchChromeBrowserWithNewCommandLineIfNeeded"; 330 } else { 331 relaunch_function(); 332 } 333 } 334 335 //============================================================================= 336 337 class ChromeDllLoader : public MainDllLoader { 338 protected: 339 virtual void OnBeforeLaunch(const base::string16& dll_path) { 340 RecordDidRun(dll_path); 341 } 342 343 virtual int OnBeforeExit(int return_code, const base::string16& dll_path) { 344 // NORMAL_EXIT_CANCEL is used for experiments when the user cancels 345 // so we need to reset the did_run signal so omaha does not count 346 // this run as active usage. 347 if (chrome::RESULT_CODE_NORMAL_EXIT_CANCEL == return_code) { 348 ClearDidRun(dll_path); 349 } 350 return return_code; 351 } 352 }; 353 354 //============================================================================= 355 356 class ChromiumDllLoader : public MainDllLoader { 357 protected: 358 virtual void OnBeforeLaunch(const base::string16& dll_path) OVERRIDE { 359 } 360 virtual int OnBeforeExit(int return_code, 361 const base::string16& dll_path) OVERRIDE { 362 return return_code; 363 } 364 }; 365 366 MainDllLoader* MakeMainDllLoader() { 367 #if defined(GOOGLE_CHROME_BUILD) 368 return new ChromeDllLoader(); 369 #else 370 return new ChromiumDllLoader(); 371 #endif 372 } 373