Shell: Make separate binary for child processes.
To do (separately): Slim down the deps for the child process.
For comparison, on an Android Debug build, the stripped binary for the main process is ~2.4 MB whereas the child is ~850 kB (as seen in MojoShell.apk).
R=davemoore@chromium.org
Review URL: https://codereview.chromium.org/1061413002
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
index 4dcf578..b84cba0 100644
--- a/shell/BUILD.gn
+++ b/shell/BUILD.gn
@@ -44,9 +44,7 @@
if (!mojo_use_prebuilt_mojo_shell) {
executable("mojo_shell") {
- sources = [
- "desktop/main.cc",
- ]
+ sources = []
deps = [
":init",
@@ -58,7 +56,11 @@
"//mojo/environment:chromium",
]
- if (is_android) {
+ data_deps = [ ":mojo_shell_child" ]
+
+ if (!is_android) {
+ sources += [ "desktop/main.cc" ]
+ } else {
sources += [
"android/library_loader.cc",
"android/main.cc",
@@ -77,6 +79,26 @@
]
}
}
+
+ executable("mojo_shell_child") {
+ sources = [
+ "child_main.cc",
+ ]
+
+ deps = [
+ # TODO(vtl): Reduce these dependencies (probably mostly in :lib).
+ ":child_controller_bindings",
+ ":init",
+ ":lib",
+ ":native_application_support",
+ "//base",
+ "//base/allocator",
+ "//build/config/sanitizers:deps",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ ]
+ }
} # !mojo_use_prebuilt_mojo_shell
source_set("init") {
@@ -108,8 +130,6 @@
source_set("lib") {
sources = [
- "child_main.cc",
- "child_main.h",
"child_process_host.cc",
"child_process_host.h",
"command_line_util.cc",
@@ -292,6 +312,7 @@
clear_dir = true
dest = mojo_shell_assets_dir
sources = [
+ "$root_out_dir/exe.stripped/mojo_shell_child",
"$root_out_dir/lib.stripped/libbootstrap.so",
"$root_out_dir/network_service.mojo",
"$root_out_dir/obj/shell/bootstrap_java.dex.jar",
@@ -389,7 +410,8 @@
"//shell/application_manager",
]
- datadeps = [
+ data_deps = [
+ ":mojo_shell_child",
"//services/test_service:test_app",
"//services/test_service:test_request_tracker_app",
]
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
index 1f55fa2..7ed3656 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
@@ -27,9 +27,12 @@
private static final String LOCAL_APP_DIRECTORY = "local_apps";
// Individual applications bundled with the shell as assets.
private static final String NETWORK_LIBRARY_APP = "network_service.mojo";
- // The mojo_shell library is also an executable run in forked processes when running
- // multi-process.
+ // Not really an executable, but what we'll use for "argv[0]" (along with the path).
private static final String MOJO_SHELL_EXECUTABLE = "libmojo_shell.so";
+ // Directory where the child executable will be extracted.
+ private static final String CHILD_DIRECTORY = "child";
+ // Name of the child executable.
+ private static final String MOJO_SHELL_CHILD_EXECUTABLE = "mojo_shell_child";
/**
* A guard flag for calling nativeInit() only once.
@@ -46,6 +49,12 @@
getLocalAppsDir(applicationContext), false);
File mojoShell = new File(applicationContext.getApplicationInfo().nativeLibraryDir,
MOJO_SHELL_EXECUTABLE);
+ FileHelper.extractFromAssets(applicationContext, MOJO_SHELL_CHILD_EXECUTABLE,
+ getChildDir(applicationContext), false);
+ File mojoShellChild =
+ new File(getChildDir(applicationContext), MOJO_SHELL_CHILD_EXECUTABLE);
+ // The shell child executable needs to be ... executable.
+ mojoShellChild.setExecutable(true, true);
List<String> parametersList = new ArrayList<String>();
// Program name.
@@ -54,6 +63,7 @@
}
nativeInit(applicationContext, mojoShell.getAbsolutePath(),
+ mojoShellChild.getAbsolutePath(),
parametersList.toArray(new String[parametersList.size()]),
getLocalAppsDir(applicationContext).getAbsolutePath(),
getTmpDir(applicationContext).getAbsolutePath());
@@ -84,6 +94,10 @@
return context.getDir(LOCAL_APP_DIRECTORY, Context.MODE_PRIVATE);
}
+ private static File getChildDir(Context context) {
+ return context.getDir(CHILD_DIRECTORY, Context.MODE_PRIVATE);
+ }
+
private static File getTmpDir(Context context) {
return new File(context.getCacheDir(), "tmp");
}
@@ -97,7 +111,8 @@
* Initializes the native system. This API should be called only once per process.
**/
private static native void nativeInit(Context context, String mojoShellPath,
- String[] parameters, String bundledAppsDirectory, String tmpDir);
+ String mojoShellChildPath, String[] parameters, String bundledAppsDirectory,
+ String tmpDir);
private static native boolean nativeStart();
diff --git a/shell/android/main.cc b/shell/android/main.cc
index aa26a13..221a5d0 100644
--- a/shell/android/main.cc
+++ b/shell/android/main.cc
@@ -46,14 +46,20 @@
class MojoShellRunner : public base::DelegateSimpleThread::Delegate {
public:
- MojoShellRunner(const std::vector<std::string>& parameters)
- : parameters_(parameters) {}
+ MojoShellRunner(const base::FilePath& mojo_shell_path,
+ const base::FilePath& mojo_shell_child_path,
+ const std::vector<std::string>& parameters)
+ : mojo_shell_path_(mojo_shell_path),
+ mojo_shell_child_path_(mojo_shell_child_path),
+ parameters_(parameters) {}
~MojoShellRunner() override {}
private:
void Run() override;
- std::vector<std::string> parameters_;
+ const base::FilePath mojo_shell_path_;
+ const base::FilePath mojo_shell_child_path_;
+ const std::vector<std::string> parameters_;
DISALLOW_COPY_AND_ASSIGN(MojoShellRunner);
};
@@ -105,9 +111,9 @@
base::MessageLoop loop(common::MessagePumpMojo::Create());
Context* context = g_context.Pointer()->get();
ConfigureAndroidServices(context);
- context->Init();
+ context->InitWithPaths(mojo_shell_path_, mojo_shell_child_path_);
- for (auto& args : parameters_)
+ for (const auto& args : parameters_)
ApplyApplicationArgs(context, args);
RunCommandLineApps(context);
@@ -142,6 +148,7 @@
jclass clazz,
jobject activity,
jstring mojo_shell_path,
+ jstring mojo_shell_child_path,
jobjectArray jparameters,
jstring j_local_apps_directory,
jstring j_tmp_dir) {
@@ -164,7 +171,12 @@
¶meters);
base::CommandLine::Init(0, nullptr);
base::CommandLine::ForCurrentProcess()->InitFromArgv(parameters);
- g_shell_runner.Get().reset(new MojoShellRunner(parameters));
+ g_shell_runner.Get().reset(new MojoShellRunner(
+ base::FilePath(
+ base::android::ConvertJavaStringToUTF8(env, mojo_shell_path)),
+ base::FilePath(
+ base::android::ConvertJavaStringToUTF8(env, mojo_shell_child_path)),
+ parameters));
InitializeLogging();
@@ -214,3 +226,9 @@
} // namespace shell
} // namespace mojo
+
+// TODO(vtl): We need a main(), even though it should never be called.
+int main(int argc, char** argv) {
+ NOTREACHED();
+ return 1;
+}
diff --git a/shell/child_main.cc b/shell/child_main.cc
index b3964a0..a420096 100644
--- a/shell/child_main.cc
+++ b/shell/child_main.cc
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "shell/child_main.h"
-
#include <unistd.h>
+#include "base/at_exit.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
@@ -28,7 +27,9 @@
#include "mojo/edk/embedder/simple_platform_support.h"
#include "mojo/public/cpp/system/core.h"
#include "shell/child_controller.mojom.h"
+#include "shell/init.h"
#include "shell/native_application_support.h"
+#include "shell/switches.h"
namespace mojo {
namespace shell {
@@ -276,25 +277,32 @@
} // namespace
-// ChildMain -------------------------------------------------------------------
+} // namespace shell
+} // namespace mojo
-int ChildMain() {
- DVLOG(2) << "ChildMain()";
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit;
+ base::CommandLine::Init(argc, argv);
- DCHECK(!base::MessageLoop::current());
+ mojo::shell::InitializeLogging();
- embedder::ScopedPlatformHandle platform_channel =
- embedder::PlatformChannelPair::PassClientHandleFromParentProcess(
+ // Make sure that we're really meant to be invoked as the child process.
+ CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kChildProcess));
+
+ mojo::embedder::ScopedPlatformHandle platform_channel =
+ mojo::embedder::PlatformChannelPair::PassClientHandleFromParentProcess(
*base::CommandLine::ForCurrentProcess());
CHECK(platform_channel.is_valid());
- AppContext app_context;
+ mojo::shell::AppContext app_context;
app_context.Init();
- Blocker blocker;
+ mojo::shell::Blocker blocker;
app_context.controller_runner()->PostTask(
FROM_HERE,
- base::Bind(&ChildControllerImpl::Init, base::Unretained(&app_context),
+ base::Bind(&mojo::shell::ChildControllerImpl::Init,
+ base::Unretained(&app_context),
base::Passed(&platform_channel), blocker.GetUnblocker()));
// This will block, then run whatever the controller wants.
blocker.Block();
@@ -303,6 +311,3 @@
return 0;
}
-
-} // namespace shell
-} // namespace mojo
diff --git a/shell/child_main.h b/shell/child_main.h
deleted file mode 100644
index 36cb9cd..0000000
--- a/shell/child_main.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SHELL_CHILD_MAIN_H_
-#define SHELL_CHILD_MAIN_H_
-
-namespace mojo {
-namespace shell {
-
-// The "main()" for child processes. This should be called with a
-// the |base::CommandLine| singleton initialized and a |base::AtExitManager|,
-// but without a |base::MessageLoop| for the current thread. Returns the exit
-// code for the process.
-int ChildMain();
-
-} // namespace shell
-} // namespace mojo
-
-#endif // SHELL_CHILD_MAIN_H_
diff --git a/shell/child_process_host.cc b/shell/child_process_host.cc
index cdc8fcd..acab25d 100644
--- a/shell/child_process_host.cc
+++ b/shell/child_process_host.cc
@@ -7,6 +7,7 @@
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
+#include "base/files/file_path.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
diff --git a/shell/context.cc b/shell/context.cc
index 0ad30a4..49e5fa3 100644
--- a/shell/context.cc
+++ b/shell/context.cc
@@ -235,16 +235,24 @@
}
bool Context::Init() {
- TRACE_EVENT0("mojo_shell", "Context::Init");
+ base::FilePath shell_path = base::MakeAbsoluteFilePath(
+ base::CommandLine::ForCurrentProcess()->GetProgram());
+ base::FilePath shell_child_path =
+ shell_path.DirName().AppendASCII("mojo_shell_child");
+ return InitWithPaths(shell_path, shell_child_path);
+}
+
+bool Context::InitWithPaths(const base::FilePath& shell_path,
+ const base::FilePath& shell_child_path) {
+ TRACE_EVENT0("mojo_shell", "Context::InitWithPaths");
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kWaitForDebugger))
base::debug::WaitForDebugger(60, true);
- mojo_shell_path_ = base::MakeAbsoluteFilePath(command_line.GetProgram());
- // TODO(vtl): For the moment, the child is the same as the parent.
- mojo_shell_child_path_ = mojo_shell_path_;
+ mojo_shell_path_ = shell_path;
+ mojo_shell_child_path_ = shell_child_path;
EnsureEmbedderIsInitialized();
task_runners_.reset(
diff --git a/shell/context.h b/shell/context.h
index 0b3522e..ec9ab72 100644
--- a/shell/context.h
+++ b/shell/context.h
@@ -50,6 +50,11 @@
// success.
bool Init();
+ // Like Init(), but specifies values for |mojo_shell_path()| and
+ // |mojo_shell_child_path()| explicitly.
+ bool InitWithPaths(const base::FilePath& shell_path,
+ const base::FilePath& shell_child_path);
+
// If Init() was called and succeeded, this must be called before destruction.
void Shutdown();
diff --git a/shell/desktop/main.cc b/shell/desktop/main.cc
index 81d34dd..2fea883 100644
--- a/shell/desktop/main.cc
+++ b/shell/desktop/main.cc
@@ -17,7 +17,6 @@
#include "base/message_loop/message_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
-#include "shell/child_main.h"
#include "shell/command_line_util.h"
#include "shell/context.h"
#include "shell/init.h"
@@ -112,85 +111,73 @@
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kHelp) ||
+ command_line.GetArgs().empty()) {
+ Usage();
+ return 0;
+ }
- // TODO(vtl): Unify parent and child process cases to the extent possible.
- int exit_code = 0;
- if (command_line.HasSwitch(switches::kChildProcess)) {
- exit_code = mojo::shell::ChildMain();
- } else {
- // Only check the command line for the main process.
- const std::set<std::string> all_switches = switches::GetAllSwitches();
- const base::CommandLine::SwitchMap switches = command_line.GetSwitches();
- bool found_unknown_switch = false;
- for (const auto& s : switches) {
- if (all_switches.find(s.first) == all_switches.end()) {
- std::cerr << "unknown switch: " << s.first << std::endl;
- found_unknown_switch = true;
- }
- }
-
- if (found_unknown_switch || command_line.HasSwitch(switches::kHelp) ||
- command_line.GetArgs().empty()) {
+ const std::set<std::string> all_switches = switches::GetAllSwitches();
+ const base::CommandLine::SwitchMap switches = command_line.GetSwitches();
+ for (const auto& s : switches) {
+ if (all_switches.find(s.first) == all_switches.end()) {
+ std::cerr << "Unknown switch: " << s.first << std::endl;
Usage();
- return 0;
- }
-
- if (command_line.HasSwitch(switches::kTraceStartup)) {
- g_tracing = true;
- base::trace_event::CategoryFilter category_filter(
- command_line.GetSwitchValueASCII(switches::kTraceStartup));
- base::trace_event::TraceLog::GetInstance()->SetEnabled(
- category_filter, base::trace_event::TraceLog::RECORDING_MODE,
- base::trace_event::TraceOptions(
- base::trace_event::RECORD_UNTIL_FULL));
- }
-
- if (command_line.HasSwitch(switches::kCPUProfile)) {
-#if !defined(NDEBUG) || !defined(ENABLE_PROFILING)
- LOG(ERROR) << "Profiling requires is_debug=false and "
- << "enable_profiling=true. Continuing without profiling.";
-// StartProfiling() and StopProfiling() are a no-op.
-#endif
- base::debug::StartProfiling("mojo_shell.pprof");
- }
-
- // We want the shell::Context to outlive the MessageLoop so that pipes are
- // all gracefully closed / error-out before we try to shut the Context down.
- mojo::shell::Context shell_context;
- {
- base::MessageLoop message_loop;
- if (!shell_context.Init()) {
- Usage();
- return 0;
- }
- if (g_tracing) {
- message_loop.PostDelayedTask(FROM_HERE,
- base::Bind(StopTracingAndFlushToDisk),
- base::TimeDelta::FromSeconds(5));
- }
-
- // The mojo_shell --args-for command-line switch is handled specially
- // because it can appear more than once. The base::CommandLine class
- // collapses multiple occurrences of the same switch.
- for (int i = 1; i < argc; i++) {
- ApplyApplicationArgs(&shell_context, argv[i]);
- }
-
- message_loop.PostTask(
- FROM_HERE,
- base::Bind(&mojo::shell::RunCommandLineApps, &shell_context));
- message_loop.Run();
-
- // Must be called before |message_loop| is destroyed.
- shell_context.Shutdown();
- }
-
- if (command_line.HasSwitch(switches::kCPUProfile)) {
- base::debug::StopProfiling();
+ return 1;
}
}
+ if (command_line.HasSwitch(switches::kTraceStartup)) {
+ g_tracing = true;
+ base::trace_event::CategoryFilter category_filter(
+ command_line.GetSwitchValueASCII(switches::kTraceStartup));
+ base::trace_event::TraceLog::GetInstance()->SetEnabled(
+ category_filter, base::trace_event::TraceLog::RECORDING_MODE,
+ base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL));
+ }
+
+ if (command_line.HasSwitch(switches::kCPUProfile)) {
+#if !defined(NDEBUG) || !defined(ENABLE_PROFILING)
+ LOG(ERROR) << "Profiling requires is_debug=false and "
+ << "enable_profiling=true. Continuing without profiling.";
+// StartProfiling() and StopProfiling() are a no-op.
+#endif
+ base::debug::StartProfiling("mojo_shell.pprof");
+ }
+
+ // We want the shell::Context to outlive the MessageLoop so that pipes are all
+ // gracefully closed / error-out before we try to shut the Context down.
+ mojo::shell::Context shell_context;
+ {
+ base::MessageLoop message_loop;
+ if (!shell_context.Init()) {
+ Usage();
+ return 1;
+ }
+ if (g_tracing) {
+ message_loop.PostDelayedTask(FROM_HERE,
+ base::Bind(StopTracingAndFlushToDisk),
+ base::TimeDelta::FromSeconds(5));
+ }
+
+ // The mojo_shell --args-for command-line switch is handled specially
+ // because it can appear more than once. The base::CommandLine class
+ // collapses multiple occurrences of the same switch.
+ for (int i = 1; i < argc; i++)
+ ApplyApplicationArgs(&shell_context, argv[i]);
+
+ message_loop.PostTask(
+ FROM_HERE,
+ base::Bind(&mojo::shell::RunCommandLineApps, &shell_context));
+ message_loop.Run();
+
+ // Must be called before |message_loop| is destroyed.
+ shell_context.Shutdown();
+ }
+
+ if (command_line.HasSwitch(switches::kCPUProfile))
+ base::debug::StopProfiling();
if (g_tracing)
StopTracingAndFlushToDisk();
- return exit_code;
+ return 0;
}
diff --git a/shell/shell_test_main.cc b/shell/shell_test_main.cc
index bad2f07..6df2215 100644
--- a/shell/shell_test_main.cc
+++ b/shell/shell_test_main.cc
@@ -8,19 +8,14 @@
#include "base/logging.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/test_suite.h"
-#include "shell/child_main.h"
#include "shell/switches.h"
#include "testing/gtest/include/gtest/gtest.h"
int main(int argc, char** argv) {
base::CommandLine::Init(argc, argv);
- const base::CommandLine& command_line =
- *base::CommandLine::ForCurrentProcess();
- if (command_line.HasSwitch(switches::kChildProcess)) {
- base::AtExitManager at_exit;
- return mojo::shell::ChildMain();
- }
+ CHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kChildProcess));
base::TestSuite test_suite(argc, argv);
return base::LaunchUnitTests(
diff --git a/shell/switches.cc b/shell/switches.cc
index 0453f8d..518d668 100644
--- a/shell/switches.cc
+++ b/shell/switches.cc
@@ -20,8 +20,7 @@
// --args-for='mojo:wget http://www.google.com'
const char kArgsFor[] = "args-for";
-// Used internally by the main process to indicate that a new process should be
-// a child process. Not for user use.
+// Used only by the child process. Not for user use.
const char kChildProcess[] = "child-process";
// Comma separated list like: