diff --git a/shell/app/electron_main.cc b/shell/app/electron_main.cc index f72f42c420fc..930061cee2ee 100644 --- a/shell/app/electron_main.cc +++ b/shell/app/electron_main.cc @@ -103,7 +103,75 @@ namespace crash_reporter { extern const char kCrashpadProcess[]; } +// In 32-bit builds, the main thread starts with the default (small) stack size. +// The ARCH_CPU_32_BITS blocks here and below are in support of moving the main +// thread to a fiber with a larger stack size. +#if defined(ARCH_CPU_32_BITS) +// The information needed to transfer control to the large-stack fiber and later +// pass the main routine's exit code back to the small-stack fiber prior to +// termination. +struct FiberState { + HINSTANCE instance; + LPVOID original_fiber; + int fiber_result; +}; + +// A PFIBER_START_ROUTINE function run on a large-stack fiber that calls the +// main routine, stores its return value, and returns control to the small-stack +// fiber. |params| must be a pointer to a FiberState struct. +void WINAPI FiberBinder(void* params) { + auto* fiber_state = static_cast(params); + // Call the wWinMain routine from the fiber. Reusing the entry point minimizes + // confusion when examining call stacks in crash reports - seeing wWinMain on + // the stack is a handy hint that this is the main thread of the process. + fiber_state->fiber_result = + wWinMain(fiber_state->instance, nullptr, nullptr, 0); + // Switch back to the main thread to exit. + ::SwitchToFiber(fiber_state->original_fiber); +} +#endif // defined(ARCH_CPU_32_BITS) + int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { +#if defined(ARCH_CPU_32_BITS) + enum class FiberStatus { kConvertFailed, kCreateFiberFailed, kSuccess }; + FiberStatus fiber_status = FiberStatus::kSuccess; + // GetLastError result if fiber conversion failed. + DWORD fiber_error = ERROR_SUCCESS; + if (!::IsThreadAFiber()) { + // Make the main thread's stack size 4 MiB so that it has roughly the same + // effective size as the 64-bit build's 8 MiB stack. + constexpr size_t kStackSize = 4 * 1024 * 1024; // 4 MiB + // Leak the fiber on exit. + LPVOID original_fiber = + ::ConvertThreadToFiberEx(nullptr, FIBER_FLAG_FLOAT_SWITCH); + if (original_fiber) { + FiberState fiber_state = {instance, original_fiber}; + // Create a fiber with a bigger stack and switch to it. Leak the fiber on + // exit. + LPVOID big_stack_fiber = ::CreateFiberEx( + 0, kStackSize, FIBER_FLAG_FLOAT_SWITCH, FiberBinder, &fiber_state); + if (big_stack_fiber) { + ::SwitchToFiber(big_stack_fiber); + // The fibers must be cleaned up to avoid obscure TLS-related shutdown + // crashes. + ::DeleteFiber(big_stack_fiber); + ::ConvertFiberToThread(); + // Control returns here after Chrome has finished running on FiberMain. + return fiber_state.fiber_result; + } + fiber_status = FiberStatus::kCreateFiberFailed; + } else { + fiber_status = FiberStatus::kConvertFailed; + } + // If we reach here then creating and switching to a fiber has failed. This + // probably means we are low on memory and will soon crash. Try to report + // this error once crash reporting is initialized. + fiber_error = ::GetLastError(); + base::debug::Alias(&fiber_error); + } + // If we are already a fiber then continue normal execution. +#endif // defined(ARCH_CPU_32_BITS) + struct Arguments { int argc = 0; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); @@ -198,6 +266,11 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { return crashpad_status; } +#if defined(ARCH_CPU_32_BITS) + // Intentionally crash if converting to a fiber failed. + CHECK_EQ(fiber_status, FiberStatus::kSuccess); +#endif // defined(ARCH_CPU_32_BITS) + if (!electron::CheckCommandLineArguments(arguments.argc, arguments.argv)) return -1; diff --git a/spec-main/fixtures/crash-cases/quit-on-crashed-event/index.js b/spec-main/fixtures/crash-cases/quit-on-crashed-event/index.js index eef4d676621d..a095bf69f490 100644 --- a/spec-main/fixtures/crash-cases/quit-on-crashed-event/index.js +++ b/spec-main/fixtures/crash-cases/quit-on-crashed-event/index.js @@ -1,6 +1,6 @@ const { app, BrowserWindow } = require('electron'); -app.once('ready', () => { +app.once('ready', async () => { const w = new BrowserWindow({ show: false, webPreferences: { @@ -8,9 +8,12 @@ app.once('ready', () => { nodeIntegration: true } }); - w.webContents.once('crashed', () => { - app.quit(); + w.webContents.once('render-process-gone', (_, details) => { + if (details.reason === 'crashed') { + process.exit(0); + } else { + process.exit(details.exitCode); + } }); - w.webContents.loadURL('about:blank'); - w.webContents.executeJavaScript('process.crash()'); + await w.webContents.loadURL('chrome://checkcrash'); });