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