• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
PreReadExperimentIsActive()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.
GetPreReadPopulationAndGroup(double * population,double * group)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.
InitPreReadPercentage()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.
LoadModuleWithDirectory(base::string16 * dir,const wchar_t * dll_name,bool pre_read)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 
RecordDidRun(const base::string16 & dll_path)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 
ClearDidRun(const base::string16 & dll_path)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 
InMetroMode()189 bool InMetroMode() {
190   return (wcsstr(
191       ::GetCommandLineW(), L" -ServerName:DefaultBrowserServer") != NULL);
192 }
193 
194 typedef int (*InitMetro)();
195 
196 }  // namespace
197 
GetExecutablePath()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 
GetCurrentModuleVersion()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 
MainDllLoader()220 MainDllLoader::MainDllLoader()
221   : dll_(NULL), metro_mode_(InMetroMode()) {
222 }
223 
~MainDllLoader()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.
Load(base::string16 * version,base::string16 * out_file)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.
Launch(HINSTANCE instance)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 
RelaunchChromeBrowserWithNewCommandLineIfNeeded()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:
OnBeforeLaunch(const base::string16 & dll_path)339   virtual void OnBeforeLaunch(const base::string16& dll_path) {
340     RecordDidRun(dll_path);
341   }
342 
OnBeforeExit(int return_code,const base::string16 & dll_path)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:
OnBeforeLaunch(const base::string16 & dll_path)358   virtual void OnBeforeLaunch(const base::string16& dll_path) OVERRIDE {
359   }
OnBeforeExit(int return_code,const base::string16 & dll_path)360   virtual int OnBeforeExit(int return_code,
361                            const base::string16& dll_path) OVERRIDE {
362     return return_code;
363   }
364 };
365 
MakeMainDllLoader()366 MainDllLoader* MakeMainDllLoader() {
367 #if defined(GOOGLE_CHROME_BUILD)
368   return new ChromeDllLoader();
369 #else
370   return new ChromiumDllLoader();
371 #endif
372 }
373