diff --git a/b2g/app/B2GLoader.cpp b/b2g/app/B2GLoader.cpp new file mode 100644 index 0000000000000..2eb2b14f784c1 --- /dev/null +++ b/b2g/app/B2GLoader.cpp @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXULAppAPI.h" +#include "nsXPCOMGlue.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "BinaryPath.h" +#include "nsAutoPtr.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL + +#define ASSERT(x) if (!(x)) { MOZ_CRASH(); } + + +// Functions being loaded by XPCOMGlue +XRE_ProcLoaderServiceRunType XRE_ProcLoaderServiceRun; +XRE_ProcLoaderClientInitType XRE_ProcLoaderClientInit; + +static const nsDynamicFunctionLoad kXULFuncs[] = { + { "XRE_ProcLoaderServiceRun", (NSFuncPtr*) &XRE_ProcLoaderServiceRun }, + { "XRE_ProcLoaderClientInit", (NSFuncPtr*) &XRE_ProcLoaderClientInit }, + { nullptr, nullptr } +}; + +static int +GetDirnameSlash(const char *aPath, char *aOutDir, int aMaxLen) +{ + char *lastSlash = strrchr(aPath, XPCOM_FILE_PATH_SEPARATOR[0]); + if (lastSlash == nullptr) { + return 0; + } + int cpsz = lastSlash - aPath + 1; // include slash + if (aMaxLen <= cpsz) { + return 0; + } + strncpy(aOutDir, aPath, cpsz); + aOutDir[cpsz] = 0; + return cpsz; +} + +static bool +GetXPCOMPath(const char *aProgram, char *aOutPath, int aMaxLen) +{ + nsAutoArrayPtr progBuf(new char[aMaxLen]); + nsresult rv = mozilla::BinaryPath::Get(aProgram, progBuf); + NS_ENSURE_SUCCESS(rv, false); + + int len = GetDirnameSlash(progBuf, aOutPath, aMaxLen); + NS_ENSURE_TRUE(!!len, false); + + NS_ENSURE_TRUE((len + sizeof(XPCOM_DLL)) < aMaxLen, false); + char *afterSlash = aOutPath + len; + strcpy(afterSlash, XPCOM_DLL); + return true; +} + +static bool +LoadLibxul(const char *aXPCOMPath) +{ + nsresult rv; + + XPCOMGlueEnablePreload(); + rv = XPCOMGlueStartup(aXPCOMPath); + NS_ENSURE_SUCCESS(rv, false); + + rv = XPCOMGlueLoadXULFunctions(kXULFuncs); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +static bool +LoadStaticData(const char *aProgram) +{ + char xpcomPath[MAXPATHLEN]; + bool ok = GetXPCOMPath(aProgram, xpcomPath, MAXPATHLEN); + NS_ENSURE_TRUE(ok, false); + + ok = LoadLibxul(xpcomPath); + return ok; +} + +/** + * Fork and run parent and child process. + * + * The parent is the b2g process and child for Nuwa. + */ +static int +RunProcesses(int argc, const char *argv[]) +{ + /* + * The original main() of the b2g process. It is renamed to + * b2g_main() for the b2g loader. + */ + int b2g_main(int argc, const char *argv[]); + + int ipcSockets[2] = {-1, -1}; + int r = socketpair(AF_LOCAL, SOCK_STREAM, 0, ipcSockets); + ASSERT(r == 0); + int parentSock = ipcSockets[0]; + int childSock = ipcSockets[1]; + + r = fcntl(parentSock, F_SETFL, O_NONBLOCK); + ASSERT(r != -1); + r = fcntl(childSock, F_SETFL, O_NONBLOCK); + ASSERT(r != -1); + + pid_t pid = fork(); + ASSERT(pid >= 0); + bool isChildProcess = pid == 0; + + close(isChildProcess ? parentSock : childSock); + + if (isChildProcess) { + /* The Nuwa process */ + /* This provides the IPC service of loading Nuwa at the process. + * The b2g process would send a IPC message of loading Nuwa + * as the replacement of forking and executing plugin-container. + */ + return XRE_ProcLoaderServiceRun(getppid(), childSock, argc, argv); + } + + // The b2g process + int childPid = pid; + XRE_ProcLoaderClientInit(childPid, parentSock); + return b2g_main(argc, argv); +} + +/** + * B2G Loader is responsible for loading the b2g process and the + * Nuwa process. It forks into the parent process, for the b2g + * process, and the child process, for the Nuwa process. + * + * The loader loads libxul and performs initialization of static data + * before forking, so relocation of libxul and static data can be + * shared between the b2g process, the Nuwa process, and the content + * processes. + */ +int +main(int argc, const char* argv[]) +{ + const char *program = argv[0]; + /* + * Before fork(), libxul and static data of Gecko are loaded for + * sharing. + */ + bool ok = LoadStaticData(program); + if (!ok) { + return 255; + } + + return RunProcesses(argc, argv); +} diff --git a/b2g/app/moz.build b/b2g/app/moz.build index 77879ddb44405..82b60ad6db310 100644 --- a/b2g/app/moz.build +++ b/b2g/app/moz.build @@ -9,6 +9,11 @@ if not CONFIG['LIBXUL_SDK']: PROGRAM = CONFIG['MOZ_APP_NAME'] + "-bin" else: PROGRAM = CONFIG['MOZ_APP_NAME'] + if CONFIG['MOZ_B2G_LOADER']: + SOURCES += [ + 'B2GLoader.cpp', + ] + SOURCES += [ 'nsBrowserApp.cpp', ] diff --git a/b2g/app/nsBrowserApp.cpp b/b2g/app/nsBrowserApp.cpp index bf57ef445744d..f4b3e8e45a824 100644 --- a/b2g/app/nsBrowserApp.cpp +++ b/b2g/app/nsBrowserApp.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "nsCOMPtr.h" #include "nsIFile.h" @@ -163,9 +164,22 @@ static int do_main(int argc, char* argv[]) return XRE_main(argc, argv, &sAppData, 0); } -int main(int argc, char* argv[]) +#ifdef MOZ_B2G_LOADER +/* + * The main() in B2GLoader.cpp is the new main function instead of the + * main() here if it is enabled. So, rename it to b2g_man(). + */ +#define main b2g_main +#define _CONST const +#else +#define _CONST +#endif + +int main(int argc, _CONST char* argv[]) { +#ifndef MOZ_B2G_LOADER char exePath[MAXPATHLEN]; +#endif #ifdef MOZ_WIDGET_GONK // This creates a ThreadPool for binder ipc. A ThreadPool is necessary to @@ -175,7 +189,9 @@ int main(int argc, char* argv[]) android::ProcessState::self()->startThreadPool(); #endif - nsresult rv = mozilla::BinaryPath::Get(argv[0], exePath); + nsresult rv; +#ifndef MOZ_B2G_LOADER + rv = mozilla::BinaryPath::Get(argv[0], exePath); if (NS_FAILED(rv)) { Output("Couldn't calculate the application directory.\n"); return 255; @@ -186,6 +202,7 @@ int main(int argc, char* argv[]) return 255; strcpy(++lastSlash, XPCOM_DLL); +#endif // MOZ_B2G_LOADER #if defined(XP_UNIX) // If the b2g app is launched from adb shell, then the shell will wind @@ -209,6 +226,9 @@ int main(int argc, char* argv[]) DllBlocklist_Initialize(); #endif + // B2G loader has already initialized Gecko so we can't initialize + // it again here. +#ifndef MOZ_B2G_LOADER // We do this because of data in bug 771745 XPCOMGlueEnablePreload(); @@ -219,6 +239,7 @@ int main(int argc, char* argv[]) } // Reset exePath so that it is the directory name and not the xpcom dll name *lastSlash = 0; +#endif // MOZ_B2G_LOADER rv = XPCOMGlueLoadXULFunctions(kXULFuncs); if (NS_FAILED(rv)) { @@ -253,7 +274,25 @@ int main(int argc, char* argv[]) int result; { ScopedLogging log; - result = do_main(argc, argv); + char **_argv; + + /* + * Duplicate argument vector to conform non-const argv of + * do_main() since XRE_main() is very stupid with non-const argv. + */ + _argv = new char *[argc + 1]; + for (int i = 0; i < argc; i++) { + _argv[i] = strdup(argv[i]); + MOZ_ASSERT(_argv[i] != nullptr); + } + _argv[argc] = nullptr; + + result = do_main(argc, _argv); + + for (int i = 0; i < argc; i++) { + free(_argv[i]); + } + delete[] _argv; } return result; diff --git a/b2g/confvars.sh b/b2g/confvars.sh index f77d791fb0ddc..98ef5283328ab 100644 --- a/b2g/confvars.sh +++ b/b2g/confvars.sh @@ -59,6 +59,7 @@ MOZ_B2G=1 if test "$OS_TARGET" = "Android"; then MOZ_NUWA_PROCESS=1 +MOZ_B2G_LOADER=1 fi MOZ_FOLD_LIBS=1 diff --git a/configure.in b/configure.in index dcc60a70d995a..9c2a508d54f83 100644 --- a/configure.in +++ b/configure.in @@ -8631,6 +8631,14 @@ if test "$MOZ_WIDGET_TOOLKIT" = gonk -a -n "$MOZ_NUWA_PROCESS"; then AC_DEFINE(MOZ_NUWA_PROCESS) fi AC_SUBST(MOZ_NUWA_PROCESS) +if test "$MOZ_WIDGET_TOOLKIT" = gonk -a -n "$MOZ_B2G_LOADER"; then + if test -z "$MOZ_NUWA_PROCESS"; then + AC_MSG_ERROR([B2G loader works with Nuwa]); + fi + export MOZ_B2G_LOADER + AC_DEFINE(MOZ_B2G_LOADER) +fi +AC_SUBST(MOZ_B2G_LOADER) AC_SUBST(NSPR_CFLAGS) AC_SUBST(NSPR_LIBS) diff --git a/ipc/app/Makefile.in b/ipc/app/Makefile.in index 5cc4d7a2b41cf..6c18864b7029f 100644 --- a/ipc/app/Makefile.in +++ b/ipc/app/Makefile.in @@ -37,6 +37,10 @@ include $(topsrcdir)/config/config.mk include $(topsrcdir)/config/rules.mk +ifneq ($(MOZ_WIDGET_TOOLKIT),android) +#LIBS += ../contentproc/$(LIB_PREFIX)plugin-container.$(LIB_SUFFIX) +endif + ifeq ($(OS_ARCH),WINNT) #{ # Note the manifest file exists in the tree, so we use the explicit filename # here. diff --git a/ipc/app/MozillaRuntimeMain.cpp b/ipc/app/MozillaRuntimeMain.cpp index 4bdb6a56dd8a2..67b636333145f 100644 --- a/ipc/app/MozillaRuntimeMain.cpp +++ b/ipc/app/MozillaRuntimeMain.cpp @@ -4,148 +4,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "nsXPCOM.h" -#include "nsXULAppAPI.h" - -// FIXME/cjones testing -#if !defined(OS_WIN) -#include -#endif - -#ifdef XP_WIN -#include -// we want a wmain entry point -// but we don't want its DLL load protection, because we'll handle it here -#define XRE_DONT_PROTECT_DLL_LOAD -#include "nsWindowsWMain.cpp" -#include "nsSetDllDirectory.h" -#endif - -#if defined(XP_WIN) -#include "sandbox/chromium/base/basictypes.h" -#include "sandbox/win/src/sandbox.h" -#include "sandbox/win/src/sandbox_factory.h" -#include "mozilla/sandboxTarget.h" -#endif - -#ifdef MOZ_WIDGET_GONK -# include -# include - -# include - -# ifdef LOGE_IF -# undef LOGE_IF -# endif - -# include -# define LOGE_IF(cond, ...) \ - ( (CONDITION(cond)) \ - ? ((void)__android_log_print(ANDROID_LOG_ERROR, \ - "Gecko:MozillaRntimeMain", __VA_ARGS__)) \ - : (void)0 ) - -#endif - -#ifdef MOZ_NUWA_PROCESS -#include -#include "ipc/Nuwa.h" -#endif - -#ifdef MOZ_WIDGET_GONK -static void -InitializeBinder(void *aDummy) { - // Change thread priority to 0 only during calling ProcessState::self(). - // The priority is registered to binder driver and used for default Binder - // Thread's priority. - // To change the process's priority to small value need's root permission. - int curPrio = getpriority(PRIO_PROCESS, 0); - int err = setpriority(PRIO_PROCESS, 0, 0); - MOZ_ASSERT(!err); - LOGE_IF(err, "setpriority failed. Current process needs root permission."); - android::ProcessState::self()->startThreadPool(); - setpriority(PRIO_PROCESS, 0, curPrio); -} -#endif - -#if defined(XP_WIN) -static bool gIsSandboxEnabled = false; -void StartSandboxCallback() -{ - if (gIsSandboxEnabled) { - sandbox::TargetServices* target_service = - sandbox::SandboxFactory::GetTargetServices(); - target_service->LowerToken(); - } -} -#endif - +#include "../contentproc/plugin-container.cpp" + int -main(int argc, char* argv[]) -{ - bool isNuwa = false; - for (int i = 1; i < argc; i++) { - isNuwa |= strcmp(argv[i], "-nuwa") == 0; -#if defined(XP_WIN) - gIsSandboxEnabled |= strcmp(argv[i], "-sandbox") == 0; -#endif - } - -#ifdef MOZ_NUWA_PROCESS - if (isNuwa) { - PrepareNuwaProcess(); - } -#endif - -#ifdef MOZ_WIDGET_GONK - // This creates a ThreadPool for binder ipc. A ThreadPool is necessary to - // receive binder calls, though not necessary to send binder calls. - // ProcessState::Self() also needs to be called once on the main thread to - // register the main thread with the binder driver. - -#ifdef MOZ_NUWA_PROCESS - if (!isNuwa) { - InitializeBinder(nullptr); - } else { - NuwaAddFinalConstructor(&InitializeBinder, nullptr); - } -#else - InitializeBinder(nullptr); -#endif -#endif - - // Check for the absolute minimum number of args we need to move - // forward here. We expect the last arg to be the child process type. - if (argc < 1) - return 3; - GeckoProcessType proctype = XRE_StringToChildProcessType(argv[--argc]); - -#ifdef XP_WIN - // For plugins, this is done in PluginProcessChild::Init, as we need to - // avoid it for unsupported plugins. See PluginProcessChild::Init for - // the details. - if (proctype != GeckoProcessType_Plugin) { - mozilla::SanitizeEnvironmentVariables(); - SetDllDirectory(L""); - } - - if (gIsSandboxEnabled) { - sandbox::TargetServices* target_service = - sandbox::SandboxFactory::GetTargetServices(); - if (!target_service) { - return 1; - } - - sandbox::ResultCode result = target_service->Init(); - if (result != sandbox::SBOX_ALL_OK) { - return 2; - } - mozilla::SandboxTarget::Instance()->SetStartSandboxCallback(StartSandboxCallback); - } -#endif - - nsresult rv = XRE_InitChildProcess(argc, argv, proctype); - NS_ENSURE_SUCCESS(rv, 1); - - return 0; +main(int argc, char *argv[]) { + return content_process_main(argc, argv); } diff --git a/ipc/chromium/src/base/process_util_linux.cc b/ipc/chromium/src/base/process_util_linux.cc index 0be1b7edd770b..741e912b8d9d3 100644 --- a/ipc/chromium/src/base/process_util_linux.cc +++ b/ipc/chromium/src/base/process_util_linux.cc @@ -1,3 +1,5 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 autoindent cindent expandtab: */ // Copyright (c) 2008 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. @@ -18,6 +20,13 @@ #include "base/logging.h" #include "base/string_tokenizer.h" #include "base/string_util.h" +#include "nsLiteralString.h" + +#ifdef MOZ_B2G_LOADER +#include "ProcessUtils.h" + +using namespace mozilla::ipc; +#endif // MOZ_B2G_LOADER #ifdef MOZ_WIDGET_GONK /* @@ -188,12 +197,71 @@ bool LaunchApp(const std::vector& argv, wait, process_handle); } +#ifdef MOZ_B2G_LOADER +/** + * Launch an app using B2g Loader. + */ +static bool +LaunchAppProcLoader(const std::vector& argv, + const file_handle_mapping_vector& fds_to_remap, + const environment_map& env_vars_to_set, + ChildPrivileges privs, + ProcessHandle* process_handle) { + size_t i; + scoped_array argv_cstr(new char*[argv.size() + 1]); + for (i = 0; i < argv.size(); i++) { + argv_cstr[i] = const_cast(argv[i].c_str()); + } + argv_cstr[argv.size()] = nullptr; + + scoped_array env_cstr(new char*[env_vars_to_set.size() + 1]); + i = 0; + for (environment_map::const_iterator it = env_vars_to_set.begin(); + it != env_vars_to_set.end(); ++it) { + env_cstr[i++] = strdup((it->first + "=" + it->second).c_str()); + } + env_cstr[env_vars_to_set.size()] = nullptr; + + bool ok = ProcLoaderLoad((const char **)argv_cstr.get(), + (const char **)env_cstr.get(), + fds_to_remap, privs, + process_handle); + MOZ_ASSERT(ok, "ProcLoaderLoad() failed"); + + for (size_t i = 0; i < env_vars_to_set.size(); i++) { + free(env_cstr[i]); + } + + return ok; +} + +static bool +IsLaunchingNuwa(const std::vector& argv) { + std::vector::const_iterator it; + for (it = argv.begin(); it != argv.end(); ++it) { + if (*it == std::string("-nuwa")) { + return true; + } + } + return false; +} +#endif // MOZ_B2G_LOADER + bool LaunchApp(const std::vector& argv, const file_handle_mapping_vector& fds_to_remap, const environment_map& env_vars_to_set, ChildPrivileges privs, bool wait, ProcessHandle* process_handle, ProcessArchitecture arch) { +#ifdef MOZ_B2G_LOADER + static bool beforeFirstNuwaLaunch = true; + if (!wait && beforeFirstNuwaLaunch && IsLaunchingNuwa(argv)) { + beforeFirstNuwaLaunch = false; + return LaunchAppProcLoader(argv, fds_to_remap, env_vars_to_set, + privs, process_handle); + } +#endif // MOZ_B2G_LOADER + scoped_array argv_cstr(new char*[argv.size() + 1]); // Illegal to allocate memory after fork and before execvp InjectiveMultimap fd_shuffle1, fd_shuffle2; diff --git a/ipc/contentproc/moz.build b/ipc/contentproc/moz.build new file mode 100644 index 0000000000000..39b5d3273791b --- /dev/null +++ b/ipc/contentproc/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LIBRARY_NAME = 'plugin-container' +if CONFIG['MOZ_B2G_LOADER']: + FINAL_LIBRARY = 'xul' + +SOURCES += [ + 'plugin-container.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +if CONFIG['OS_ARCH'] == 'WINNT': + LOCAL_INCLUDES += [ + '/toolkit/xre', + '/xpcom/base', + ] + +if CONFIG['OS_ARCH'] == 'WINNT': + # For sandbox includes and the include dependencies those have + LOCAL_INCLUDES += [ + '/security', + '/security/sandbox', + '/security/sandbox/chromium', + ] diff --git a/ipc/contentproc/plugin-container.cpp b/ipc/contentproc/plugin-container.cpp new file mode 100644 index 0000000000000..e8fe2cc2549fa --- /dev/null +++ b/ipc/contentproc/plugin-container.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" + +// FIXME/cjones testing +#if !defined(OS_WIN) +#include +#endif + +#ifdef XP_WIN +#include +// we want a wmain entry point +// but we don't want its DLL load protection, because we'll handle it here +#define XRE_DONT_PROTECT_DLL_LOAD +#include "nsWindowsWMain.cpp" +#include "nsSetDllDirectory.h" +#endif + +#if defined(XP_WIN) +#include "sandbox/chromium/base/basictypes.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "mozilla/sandboxTarget.h" +#endif + +#ifdef MOZ_WIDGET_GONK +# include +# include + +# include + +# ifdef LOGE_IF +# undef LOGE_IF +# endif + +# include +# define LOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)__android_log_print(ANDROID_LOG_ERROR, \ + "Gecko:MozillaRntimeMain", __VA_ARGS__)) \ + : (void)0 ) + +#endif + +#ifdef MOZ_NUWA_PROCESS +#include +#include "ipc/Nuwa.h" +#endif + +#ifdef MOZ_WIDGET_GONK +static void +InitializeBinder(void *aDummy) { + // Change thread priority to 0 only during calling ProcessState::self(). + // The priority is registered to binder driver and used for default Binder + // Thread's priority. + // To change the process's priority to small value need's root permission. + int curPrio = getpriority(PRIO_PROCESS, 0); + int err = setpriority(PRIO_PROCESS, 0, 0); + MOZ_ASSERT(!err); + LOGE_IF(err, "setpriority failed. Current process needs root permission."); + android::ProcessState::self()->startThreadPool(); + setpriority(PRIO_PROCESS, 0, curPrio); +} +#endif + +#if defined(XP_WIN) +static bool gIsSandboxEnabled = false; +void StartSandboxCallback() +{ + if (gIsSandboxEnabled) { + sandbox::TargetServices* target_service = + sandbox::SandboxFactory::GetTargetServices(); + target_service->LowerToken(); + } +} +#endif + +int +content_process_main(int argc, char* argv[]) +{ + bool isNuwa = false; + for (int i = 1; i < argc; i++) { + isNuwa |= strcmp(argv[i], "-nuwa") == 0; +#if defined(XP_WIN) + gIsSandboxEnabled |= strcmp(argv[i], "-sandbox") == 0; +#endif + } + +#ifdef MOZ_NUWA_PROCESS + if (isNuwa) { + PrepareNuwaProcess(); + } +#endif + +#ifdef MOZ_WIDGET_GONK + // This creates a ThreadPool for binder ipc. A ThreadPool is necessary to + // receive binder calls, though not necessary to send binder calls. + // ProcessState::Self() also needs to be called once on the main thread to + // register the main thread with the binder driver. + +#ifdef MOZ_NUWA_PROCESS + if (!isNuwa) { + InitializeBinder(nullptr); + } else { + NuwaAddFinalConstructor(&InitializeBinder, nullptr); + } +#else + InitializeBinder(nullptr); +#endif +#endif + + // Check for the absolute minimum number of args we need to move + // forward here. We expect the last arg to be the child process type. + if (argc < 1) + return 3; + GeckoProcessType proctype = XRE_StringToChildProcessType(argv[--argc]); + +#ifdef XP_WIN + // For plugins, this is done in PluginProcessChild::Init, as we need to + // avoid it for unsupported plugins. See PluginProcessChild::Init for + // the details. + if (proctype != GeckoProcessType_Plugin) { + mozilla::SanitizeEnvironmentVariables(); + SetDllDirectory(L""); + } + + if (gIsSandboxEnabled) { + sandbox::TargetServices* target_service = + sandbox::SandboxFactory::GetTargetServices(); + if (!target_service) { + return 1; + } + + sandbox::ResultCode result = target_service->Init(); + if (result != sandbox::SBOX_ALL_OK) { + return 2; + } + mozilla::SandboxTarget::Instance()->SetStartSandboxCallback(StartSandboxCallback); + } +#endif + + nsresult rv = XRE_InitChildProcess(argc, argv, proctype); + NS_ENSURE_SUCCESS(rv, 1); + + return 0; +} diff --git a/ipc/glue/PProcLoader.ipdl b/ipc/glue/PProcLoader.ipdl new file mode 100644 index 0000000000000..d295d8b7221a5 --- /dev/null +++ b/ipc/glue/PProcLoader.ipdl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace mozilla { +namespace ipc { + +struct FDRemap { + FileDescriptor fd; + int mapto; +}; + +protocol PProcLoader +{ +child: + /** + * Request B2G loader service to load content process. + * + * It actually calls the main() function of plugin-container. + */ + async Load(nsCString[] argv, nsCString[] env, + FDRemap[] fdsRemap, uint32_t privs, + int32_t cookie); + +parent: + /** + * The acknowledgement of Load(). + */ + async LoadComplete(int32_t pid, int32_t cookie); +}; + +} +} diff --git a/ipc/glue/ProcessUtils.h b/ipc/glue/ProcessUtils.h index fc17609dcadca..99d0f532dd8b6 100644 --- a/ipc/glue/ProcessUtils.h +++ b/ipc/glue/ProcessUtils.h @@ -1,3 +1,5 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -5,6 +7,10 @@ #ifndef mozilla_ipc_ProcessUtils_h #define mozilla_ipc_ProcessUtils_h +#ifdef MOZ_B2G_LOADER +#include "base/process_util.h" +#endif + namespace mozilla { namespace ipc { @@ -12,6 +18,17 @@ namespace ipc { // this directly. void SetThisProcessName(const char *aName); +#ifdef MOZ_B2G_LOADER +// see ProcessUtils_linux.cpp for explaination. +void ProcLoaderClientGeckoInit(); + +bool ProcLoaderLoad(const char *aArgv[], + const char *aEnvp[], + const base::file_handle_mapping_vector &aFdsRemap, + const base::ChildPrivileges aPrivs, + base::ProcessHandle *aProcessHandle); +#endif /* MOZ_B2G_LOADER */ + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/ProcessUtils_linux.cpp b/ipc/glue/ProcessUtils_linux.cpp index d5b8c092e0f38..10156e63a2c44 100644 --- a/ipc/glue/ProcessUtils_linux.cpp +++ b/ipc/glue/ProcessUtils_linux.cpp @@ -1,3 +1,5 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -8,6 +10,37 @@ #include +#ifdef MOZ_B2G_LOADER + +#include +#include + +#include "nsAutoPtr.h" + +#include "mozilla/Assertions.h" +#include "mozilla/ipc/PProcLoaderParent.h" +#include "mozilla/ipc/PProcLoaderChild.h" +#include "mozilla/ipc/Transport.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/dom/ContentProcess.h" +#include "base/file_descriptor_shuffle.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/DebugOnly.h" +#include "base/process_util.h" + +#include "prenv.h" + +#include "nsXULAppAPI.h" // export XRE_* functions + +#include "nsAppRunner.h" + +int content_process_main(int argc, char *argv[]); + +extern bool gDisableAndroidLog; + +#endif /* MOZ_B2G_LOADER */ + namespace mozilla { namespace ipc { @@ -16,5 +49,522 @@ void SetThisProcessName(const char *aName) prctl(PR_SET_NAME, (unsigned long)aName, 0uL, 0uL, 0uL); } +#ifdef MOZ_B2G_LOADER +/** + * How does B2G Loader Work? + * + * <> <> + * ProcLoaderParent -----> ProcLoaderChild + * ^ | + * | load() | content_process_main() + * | V + * ProcLoaderClient Nuwa/plugin-container + * ^ + * | ProcLoaderLoad() + * ... + * ContentParent + * + * + * B2G loader includes an IPC protocol PProcLoader for communication + * between client (parent) and server (child). The b2g process is the + * client. It requests the server to load/start the Nuwa process with + * the given arguments, env variables, and file descriptors. + * + * ProcLoaderClientInit() is called by B2G loader to initialize the + * client side, the b2g process. Then the b2g_main() is called to + * start b2g process. + * + * ProcLoaderClientGeckoInit() is called by XRE_main() to create the + * parent actor, |ProcLoaderParent|, of PProcLoader for servicing the + * request to run Nuwa process later once Gecko has been initialized. + * + * ProcLoaderServiceRun() is called by the server process. It starts + * an IOThread and event loop to serve the |ProcLoaderChild| + * implmentation of PProcLoader protocol as the child actor. Once it + * recieves a load() request, it stops the IOThread and event loop, + * then starts running the main function of the content process with + * the given arguments. + * + * NOTE: The server process serves at most one load() request. + */ + +using namespace base; +using namespace mozilla::dom; + +static bool sProcLoaderClientOnDeinit = false; +static DebugOnly sProcLoaderClientInitialized = false; +static DebugOnly sProcLoaderClientGeckoInitialized = false; +static pid_t sProcLoaderPid = 0; +static int sProcLoaderChannelFd = -1; +static PProcLoaderParent *sProcLoaderParent = nullptr; +static MessageLoop *sProcLoaderLoop = nullptr; + +static void ProcLoaderClientDeinit(); + + +class ProcLoaderParent : public PProcLoaderParent +{ +private: + nsAutoPtr mChannelFd; // To keep a reference. + +public: + ProcLoaderParent(FileDescriptor *aFd) : mChannelFd(aFd) {} + + virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; + + virtual bool RecvLoadComplete(const int32_t &aPid, + const int32_t &aCookie) MOZ_OVERRIDE; + + virtual void OnChannelError() MOZ_OVERRIDE; +}; + +void +ProcLoaderParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +static void +_ProcLoaderParentDestroy(PProcLoaderParent *aLoader) +{ + aLoader->Close(); + delete aLoader; + sProcLoaderClientOnDeinit = false; +} + +bool +ProcLoaderParent::RecvLoadComplete(const int32_t &aPid, + const int32_t &aCookie) +{ + ProcLoaderClientDeinit(); + return true; +} + +void +ProcLoaderParent::OnChannelError() +{ + if (sProcLoaderClientOnDeinit) { + // Get error for closing while the channel is already error. + return; + } + NS_WARNING("ProcLoaderParent is in channel error"); + ProcLoaderClientDeinit(); +} + +/** + * Initialize the client of B2G loader for loader itself. + * + * The initialization of B2G loader are divided into two stages. First + * stage is to collect child info passed from the main program of the + * loader. Second stage is to initialize Gecko according to info from the + * first stage and make the client of loader service ready. + * + * \param aPeerPid is the pid of the child. + * \param aChannelFd is the file descriptor of the socket used for IPC. + */ +static void +ProcLoaderClientInit(pid_t aPeerPid, int aChannelFd) +{ + MOZ_ASSERT(!sProcLoaderClientInitialized, "call ProcLoaderClientInit() more than once"); + MOZ_ASSERT(aPeerPid != 0 && aChannelFd != -1, "invalid argument"); + sProcLoaderPid = aPeerPid; + sProcLoaderChannelFd = aChannelFd; + sProcLoaderClientInitialized = true; +} + +/** + * Initialize the client of B2G loader for Gecko. + */ +void +ProcLoaderClientGeckoInit() +{ + MOZ_ASSERT(sProcLoaderClientInitialized, "call ProcLoaderClientInit() at first"); + MOZ_ASSERT(!sProcLoaderClientGeckoInitialized, + "call ProcLoaderClientGeckoInit() more than once"); + + sProcLoaderClientGeckoInitialized = true; + + FileDescriptor *fd = new FileDescriptor(sProcLoaderChannelFd); + close(sProcLoaderChannelFd); + sProcLoaderChannelFd = -1; + Transport *transport = OpenDescriptor(*fd, Transport::MODE_CLIENT); + sProcLoaderParent = new ProcLoaderParent(fd); + sProcLoaderParent->Open(transport, + sProcLoaderPid, + XRE_GetIOMessageLoop(), + ParentSide); + sProcLoaderLoop = MessageLoop::current(); +} + +/** + * Shutdown and destroy the client of B2G loader service. + */ +static void +ProcLoaderClientDeinit() +{ + MOZ_ASSERT(sProcLoaderClientGeckoInitialized && sProcLoaderClientInitialized); + sProcLoaderClientGeckoInitialized = false; + sProcLoaderClientInitialized = false; + + sProcLoaderClientOnDeinit = true; + + MOZ_ASSERT(sProcLoaderParent != nullptr); + PProcLoaderParent *procLoaderParent = sProcLoaderParent; + sProcLoaderParent = nullptr; + sProcLoaderLoop = nullptr; + + MessageLoop::current()-> + PostTask(FROM_HERE, + NewRunnableFunction(&_ProcLoaderParentDestroy, + procLoaderParent)); +} + +struct AsyncSendLoadData +{ + nsTArray mArgv; + nsTArray mEnv; + nsTArray mFdsremap; + ChildPrivileges mPrivs; + int mCookie; +}; + +static void +AsyncSendLoad(AsyncSendLoadData *aLoad) +{ + PProcLoaderParent *loader = sProcLoaderParent; + DebugOnly ok = + loader->SendLoad(aLoad->mArgv, aLoad->mEnv, aLoad->mFdsremap, + aLoad->mPrivs, aLoad->mCookie); + MOZ_ASSERT(ok); + delete aLoad; +} + +/** + * Request the loader service, the server, to load Nuwa. + */ +bool +ProcLoaderLoad(const char *aArgv[], + const char *aEnvp[], + const file_handle_mapping_vector &aFdsRemap, + const ChildPrivileges aPrivs, + ProcessHandle *aProcessHandle) +{ + static int cookie=0; + int i; + + if (sProcLoaderParent == nullptr || sProcLoaderPid == 0) { + return false; + } + + AsyncSendLoadData *load = new AsyncSendLoadData(); + nsTArray &argv = load->mArgv; + for (i = 0; aArgv[i] != nullptr; i++) { + argv.AppendElement(nsCString(aArgv[i])); + } + nsTArray &env = load->mEnv; + for (i = 0; aEnvp[i] != nullptr; i++) { + env.AppendElement(nsCString(aEnvp[i])); + } + nsTArray &fdsremap = load->mFdsremap; + for (file_handle_mapping_vector::const_iterator fdmap = + aFdsRemap.begin(); + fdmap != aFdsRemap.end(); + fdmap++) { + fdsremap.AppendElement(FDRemap(fdmap->first, fdmap->second)); + } + load->mPrivs = aPrivs; + load->mCookie = cookie++; + + *aProcessHandle = sProcLoaderPid; + sProcLoaderPid = 0; + + sProcLoaderLoop->PostTask(FROM_HERE, + NewRunnableFunction(AsyncSendLoad, load)); + return true; +} + + +class ProcLoaderRunnerBase; + +static bool sProcLoaderServing = false; +static ProcLoaderRunnerBase *sProcLoaderDispatchedTask = nullptr; + +class ProcLoaderRunnerBase +{ +public: + virtual int DoWork() = 0; +}; + + +class ProcLoaderNoopRunner : public ProcLoaderRunnerBase { +public: + virtual int DoWork(); +}; + +int +ProcLoaderNoopRunner::DoWork() { + return 0; +} + + +/** + * The runner to load Nuwa at the current process. + */ +class ProcLoaderLoadRunner : public ProcLoaderRunnerBase { +private: + const nsTArray mArgv; + const nsTArray mEnv; + const nsTArray mFdsRemap; + const ChildPrivileges mPrivs; + + void ShuffleFds(); + +public: + ProcLoaderLoadRunner(const InfallibleTArray& aArgv, + const InfallibleTArray& aEnv, + const InfallibleTArray& aFdsRemap, + const ChildPrivileges aPrivs) + : mArgv(aArgv) + , mEnv(aEnv) + , mFdsRemap(aFdsRemap) + , mPrivs(aPrivs) {} + + int DoWork(); +}; + +void +ProcLoaderLoadRunner::ShuffleFds() +{ + unsigned int i; + + InjectiveMultimap fd_shuffle1, fd_shuffle2; + fd_shuffle1.reserve(mFdsRemap.Length()); + fd_shuffle2.reserve(mFdsRemap.Length()); + for (i = 0; i < mFdsRemap.Length(); i++) { + const FDRemap *map = &mFdsRemap[i]; + int fd = map->fd().PlatformHandle(); + int tofd = map->mapto(); + + fd_shuffle1.push_back(InjectionArc(fd, tofd, false)); + fd_shuffle2.push_back(InjectionArc(fd, tofd, false)); + } + + DebugOnly ok = ShuffleFileDescriptors(&fd_shuffle1); + MOZ_ASSERT(ok, "ShuffleFileDescriptors failed"); + + CloseSuperfluousFds(fd_shuffle2); +} + +int +ProcLoaderLoadRunner::DoWork() +{ + unsigned int i; + + ShuffleFds(); + + unsigned int argc = mArgv.Length(); + char **argv = new char *[argc + 1]; + for (i = 0; i < argc; i++) { + argv[i] = ::strdup(mArgv[i].get()); + } + argv[argc] = nullptr; + + unsigned int envc = mEnv.Length(); + for (i = 0; i < envc; i++) { + PR_SetEnv(mEnv[i].get()); + } + + SetCurrentProcessPrivileges(mPrivs); + + MOZ_ASSERT(content_process_main != nullptr, + "content_process_main not found"); + // Start Nuwa (main function) + int ret = content_process_main(argc, argv); + + for (i = 0; i < argc; i++) { + free(argv[i]); + } + delete[] argv; + + return ret; +} + + +class ProcLoaderChild : public PProcLoaderChild +{ + pid_t mPeerPid; + +public: + ProcLoaderChild(pid_t aPeerPid) : mPeerPid(aPeerPid) {} + + virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; + + virtual bool RecvLoad(const InfallibleTArray& aArgv, + const InfallibleTArray& aEnv, + const InfallibleTArray& aFdsremap, + const uint32_t& aPrivs, + const int32_t& aCookie); + + virtual void OnChannelError(); +}; + +void +ProcLoaderChild::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +static void +_ProcLoaderChildDestroy(ProcLoaderChild *aChild) +{ + aChild->Close(); + delete aChild; + MessageLoop::current()->Quit(); +} + +bool +ProcLoaderChild::RecvLoad(const InfallibleTArray& aArgv, + const InfallibleTArray& aEnv, + const InfallibleTArray& aFdsRemap, + const uint32_t& aPrivs, + const int32_t& aCookie) { + if (!sProcLoaderServing) { + return true; + } + sProcLoaderServing = false; + + MOZ_ASSERT(sProcLoaderDispatchedTask == nullptr); + ChildPrivileges privs = static_cast(aPrivs); + sProcLoaderDispatchedTask = + new ProcLoaderLoadRunner(aArgv, aEnv, aFdsRemap, privs); + + SendLoadComplete(mPeerPid, aCookie); + + MessageLoop::current()->PostTask(FROM_HERE, + NewRunnableFunction(_ProcLoaderChildDestroy, + this)); + return true; +} + +void +ProcLoaderChild::OnChannelError() +{ + if (!sProcLoaderServing) { + return; + } + sProcLoaderServing = false; + + PProcLoaderChild::OnChannelError(); + + MOZ_ASSERT(sProcLoaderDispatchedTask == nullptr); + sProcLoaderDispatchedTask = new ProcLoaderNoopRunner(); + + MessageLoop::current()->PostTask(FROM_HERE, + NewRunnableFunction(_ProcLoaderChildDestroy, + this)); +} + +/** + * A helper class which calls NS_LogInit/NS_LogTerm in its scope. + */ +class ScopedLogging +{ +public: + ScopedLogging() { NS_LogInit(); } + ~ScopedLogging() { NS_LogTerm(); } +}; + +/** + * Run service of ProcLoader. + * + * \param aPeerPid is the pid of the parent. + * \param aFd is the file descriptor of the socket for IPC. + * + * See the comment near the head of this file. + */ +static int +ProcLoaderServiceRun(pid_t aPeerPid, int aFd, + int aArgc, const char *aArgv[]) +{ + ScopedLogging logging; + + char **_argv; + _argv = new char *[aArgc + 1]; + for (int i = 0; i < aArgc; i++) { + _argv[i] = ::strdup(aArgv[i]); + MOZ_ASSERT(_argv[i] != nullptr); + } + _argv[aArgc] = nullptr; + + gArgv = _argv; + gArgc = aArgc; + + { + gDisableAndroidLog = true; + + nsresult rv = XRE_InitCommandLine(aArgc, _argv); + if (NS_FAILED(rv)) { + gDisableAndroidLog = false; + MOZ_CRASH(); + } + + FileDescriptor fd(aFd); + close(aFd); + + MOZ_ASSERT(!sProcLoaderServing); + MessageLoop loop; + + nsAutoPtr process; + process = new ContentProcess(aPeerPid); + ChildThread *iothread = process->child_thread(); + + Transport *transport = OpenDescriptor(fd, Transport::MODE_CLIENT); + ProcLoaderChild *loaderChild = new ProcLoaderChild(aPeerPid); + // Pass a message loop to initialize (connect) the channel + // (connection). + loaderChild->Open(transport, aPeerPid, iothread->message_loop()); + + BackgroundHangMonitor::Prohibit(); + + sProcLoaderServing = true; + loop.Run(); + + BackgroundHangMonitor::Allow(); + + XRE_DeinitCommandLine(); + + gDisableAndroidLog = false; + } + + MOZ_ASSERT(sProcLoaderDispatchedTask != nullptr); + ProcLoaderRunnerBase *task = sProcLoaderDispatchedTask; + sProcLoaderDispatchedTask = nullptr; + int ret = task->DoWork(); + delete task; + + for (int i = 0; i < aArgc; i++) { + free(_argv[i]); + } + delete[] _argv; + + return ret; +} + +#endif /* MOZ_B2G_LOADER */ + } // namespace ipc } // namespace mozilla + +#ifdef MOZ_B2G_LOADER +void +XRE_ProcLoaderClientInit(pid_t aPeerPid, int aChannelFd) +{ + mozilla::ipc::ProcLoaderClientInit(aPeerPid, aChannelFd); +} + +int +XRE_ProcLoaderServiceRun(pid_t aPeerPid, int aFd, + int aArgc, const char *aArgv[]) +{ + return mozilla::ipc::ProcLoaderServiceRun(aPeerPid, aFd, + aArgc, aArgv); +} +#endif /* MOZ_B2G_LOADER */ diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build index 08e50d92cfc4e..af3e146b62927 100644 --- a/ipc/glue/moz.build +++ b/ipc/glue/moz.build @@ -130,12 +130,14 @@ IPDL_SOURCES = [ 'PBackground.ipdl', 'PBackgroundSharedTypes.ipdlh', 'PBackgroundTest.ipdl', + 'PProcLoader.ipdl', 'ProtocolTypes.ipdlh', 'URIParams.ipdlh', ] LOCAL_INCLUDES += [ + '/toolkit/xre', '/xpcom/threads', ] diff --git a/ipc/moz.build b/ipc/moz.build index ff5df8d550e76..0c2bceb89cae9 100644 --- a/ipc/moz.build +++ b/ipc/moz.build @@ -26,4 +26,7 @@ if CONFIG['MOZ_B2G_RIL'] or CONFIG['MOZ_B2G_BT'] or CONFIG['MOZ_NFC'] or CONFIG[ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': DIRS += ['keystore', 'netd'] +if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': + DIRS += ['contentproc'] + TOOL_DIRS += ['app'] diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 36e7aea2bc5ea..fe8ed3c449e95 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -185,6 +185,10 @@ #include "GTestRunner.h" #endif +#ifdef MOZ_B2G_LOADER +#include "ProcessUtils.h" +#endif + #ifdef MOZ_WIDGET_ANDROID #include "AndroidBridge.h" #endif @@ -3772,6 +3776,10 @@ XREMain::XRE_mainRun() nsresult rv = NS_OK; NS_ASSERTION(mScopedXPCom, "Scoped xpcom not initialized."); +#ifdef MOZ_B2G_LOADER + mozilla::ipc::ProcLoaderClientGeckoInit(); +#endif + #ifdef NS_FUNCTION_TIMER // initialize some common services, so we don't pay the cost for these at odd times later on; // SetWindowCreator -> ChromeRegistry -> IOService -> SocketTransportService -> (nspr wspm init), Prefs diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp index 0befeeece8739..10735b25d5bd1 100644 --- a/xpcom/base/nsDebugImpl.cpp +++ b/xpcom/base/nsDebugImpl.cpp @@ -101,6 +101,13 @@ Break(const char* aMsg); #include #endif +#ifdef MOZ_B2G_LOADER +/* Avoid calling Android logger/logd temporarily while running + * B2GLoader to start the child process. + */ +bool gDisableAndroidLog = false; +#endif + using namespace mozilla; static const char* sMultiprocessDescription = nullptr; @@ -392,6 +399,9 @@ NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, #endif #ifdef ANDROID +#ifdef MOZ_B2G_LOADER + if (!gDisableAndroidLog) +#endif __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer); #endif diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h index 6f320744fd60e..e5ace7e9439ee 100644 --- a/xpcom/build/nsXULAppAPI.h +++ b/xpcom/build/nsXULAppAPI.h @@ -467,6 +467,13 @@ XRE_API(WindowsEnvironmentType, XRE_GetWindowsEnvironment, ()) #endif // XP_WIN +#ifdef MOZ_B2G_LOADER +XRE_API(int, + XRE_ProcLoaderServiceRun, (pid_t, int, int argc, const char *argv[])); +XRE_API(void, + XRE_ProcLoaderClientInit, (pid_t, int)); +#endif // MOZ_B2G_LOADER + XRE_API(int, XRE_XPCShellMain, (int argc, char** argv, char** envp)) diff --git a/xpcom/threads/BackgroundHangMonitor.cpp b/xpcom/threads/BackgroundHangMonitor.cpp index 7f4328b415735..93b38f50f547c 100644 --- a/xpcom/threads/BackgroundHangMonitor.cpp +++ b/xpcom/threads/BackgroundHangMonitor.cpp @@ -65,6 +65,7 @@ class BackgroundHangManager public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundHangManager) static StaticRefPtr sInstance; + static bool sProhibited; // Lock for access to members of this class Monitor mLock; @@ -162,6 +163,7 @@ class BackgroundHangThread : public LinkedListElement StaticRefPtr BackgroundHangManager::sInstance; +bool BackgroundHangManager::sProhibited = false; ThreadLocal BackgroundHangThread::sTlsKey; @@ -409,8 +411,16 @@ BackgroundHangThread* BackgroundHangThread::FindThread() { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sInstance == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sProhibited, + "BackgroundHandleManager is not initialized"); + return nullptr; + } + if (sTlsKey.initialized()) { // Use TLS if available + MOZ_ASSERT(!BackgroundHangManager::sProhibited, + "BackgroundHandleManager is not initialized"); return sTlsKey.get(); } // If TLS is unavailable, we can search through the thread list @@ -436,6 +446,7 @@ void BackgroundHangMonitor::Startup() { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(!BackgroundHangManager::sProhibited, "Prohibited"); MOZ_ASSERT(!BackgroundHangManager::sInstance, "Already initialized"); ThreadStackHelper::Startup(); BackgroundHangThread::Startup(); @@ -447,6 +458,7 @@ void BackgroundHangMonitor::Shutdown() { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(!BackgroundHangManager::sProhibited, "Prohibited"); MOZ_ASSERT(BackgroundHangManager::sInstance, "Not initialized"); /* Scope our lock inside Shutdown() because the sInstance object can be destroyed as soon as we set sInstance to nullptr below, and @@ -463,7 +475,8 @@ BackgroundHangMonitor::BackgroundHangMonitor(const char* aName, : mThread(BackgroundHangThread::FindThread()) { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR - if (!mThread) { + if (!BackgroundHangManager::sProhibited && !mThread) { + // If sProhibit is true, mThread would be null, and no monitoring. mThread = new BackgroundHangThread(aName, aTimeoutMs, aMaxTimeoutMs); } #endif @@ -473,7 +486,8 @@ BackgroundHangMonitor::BackgroundHangMonitor() : mThread(BackgroundHangThread::FindThread()) { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR - MOZ_ASSERT(mThread, "Thread not initialized for hang monitoring"); + MOZ_ASSERT(!BackgroundHangManager::sProhibited || mThread, + "This thread is not initialized for hang monitoring"); #endif } @@ -485,6 +499,11 @@ void BackgroundHangMonitor::NotifyActivity() { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (mThread == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sProhibited, + "This thread is not initialized for hang monitoring"); + return; + } mThread->NotifyActivity(); #endif } @@ -493,18 +512,49 @@ void BackgroundHangMonitor::NotifyWait() { #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (mThread == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sProhibited, + "This thread is not initialized for hang monitoring"); + return; + } mThread->NotifyWait(); #endif } +void +BackgroundHangMonitor::Prohibit() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(BackgroundHangManager::sInstance == nullptr, + "The background hang monitor is already initialized"); + BackgroundHangManager::sProhibited = true; +#endif +} + +void +BackgroundHangMonitor::Allow() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(BackgroundHangManager::sInstance == nullptr, + "The background hang monitor is already initialized"); + BackgroundHangManager::sProhibited = false; +#endif +} + /* Because we are iterating through the BackgroundHangThread linked list, we need to take a lock. Using MonitorAutoLock as a base class makes sure all of that is taken care of for us. */ BackgroundHangMonitor::ThreadHangStatsIterator::ThreadHangStatsIterator() : MonitorAutoLock(BackgroundHangManager::sInstance->mLock) - , mThread(BackgroundHangManager::sInstance->mHangThreads.getFirst()) + , mThread(BackgroundHangManager::sInstance ? + BackgroundHangManager::sInstance->mHangThreads.getFirst() : + nullptr) { +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(BackgroundHangManager::sInstance || BackgroundHangManager::sProhibited, + "Inconsistent state"); +#endif } Telemetry::ThreadHangStats* diff --git a/xpcom/threads/BackgroundHangMonitor.h b/xpcom/threads/BackgroundHangMonitor.h index 76d517fce4ddc..059fb0a08ff94 100644 --- a/xpcom/threads/BackgroundHangMonitor.h +++ b/xpcom/threads/BackgroundHangMonitor.h @@ -107,6 +107,8 @@ class BackgroundHangThread; * } * } * + * Prohibit() and Allow() make the background hang monitor work safely + * before Startup(). */ class BackgroundHangMonitor { @@ -204,6 +206,27 @@ class BackgroundHangMonitor * NotifyActivity when subsequently exiting the wait state. */ void NotifyWait(); + + /** + * Prohibit the hang monitor from activating. + * + * Startup() should not be called between Prohibit() and Allow(). + * This function makes the background hang monitor stop monitoring + * threads. + * + * Prohibit() and Allow() can be called before XPCOM is ready. If + * we don't stop monitoring threads it could case errors. + */ + static void Prohibit(); + + /** + * Allow the hang monitor to run. + * + * Allow() and Prohibit() should be called in pair. + * + * \see Prohibit() + */ + static void Allow(); }; } // namespace mozilla