From dde71520ba7439741c3314b719ea88dc0ed8b9a7 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 19 Dec 2018 09:53:12 +0800 Subject: [PATCH] src: move more process methods initialization in bootstrap/node.js Instead of: - Writing methods onto the process directly in C++ during `SetupProcessObject()` and overwrite with argument checks later - Or, wrapping and writing them in `internal/process/*.js` Do: - Move the C++ implementations in node_process.cc and mark them static wherever possible - Expose the C++ methods through a new `internalBinding('process_methods')` - Wrap the methods in `internal/process/*.js` in a side-effect-free manner and return them back to `internal/bootstrap/node.js` - Centralize the write to the process object based on conditions in `bootstrap/node.js` So it's easier to see what methods are attached to the process object during bootstrap under what condition and in what order. The eventual goal is to figure out the dependency of process methods and the write/read access to the process object during bootstrap, group these access properly and remove the process properties that should not be exposed to users this way. Also correct the NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE milestone which should be marked before code execution. Refs: https://github.com/nodejs/node/issues/24961 PR-URL: https://github.com/nodejs/node/pull/25127 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Backport-PR-URL: https://github.com/nodejs/node/pull/25496 --- lib/internal/bootstrap/node.js | 100 +++++++++--- lib/internal/process/main_thread_only.js | 24 +-- lib/internal/process/per_thread.js | 141 +++++++---------- src/bootstrapper.cc | 14 -- src/node.cc | 155 +------------------ src/node_binding.cc | 1 + src/node_internals.h | 15 +- src/node_process.cc | 187 +++++++++++++++++++++-- test/parallel/test-bootstrap-modules.js | 2 +- 9 files changed, 322 insertions(+), 317 deletions(-) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 4604fadb8bf3d0..6430436eb0609e 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -19,11 +19,7 @@ const { _setupNextTick, - _setupPromises, _chdir, _cpuUsage, - _hrtime, _hrtimeBigInt, - _memoryUsage, _rawDebug, - _umask, - _shouldAbortOnUncaughtToggle + _setupPromises } = bootstrappers; const { internalBinding, NativeModule } = loaderExports; @@ -56,15 +52,57 @@ function startup() { ); } - perThreadSetup.setupAssert(); - perThreadSetup.setupConfig(); + // process.config is serialized config.gypi + process.config = JSON.parse(internalBinding('native_module').config); + const rawMethods = internalBinding('process_methods'); + // Set up methods and events on the process object for the main thread if (isMainThread) { + // This depends on process being an event emitter mainThreadSetup.setupSignalHandlers(internalBinding); + + process.abort = rawMethods.abort; + const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods); + process.umask = wrapped.umask; + process.chdir = wrapped.chdir; + + // TODO(joyeecheung): deprecate and remove these underscore methods + process._debugProcess = rawMethods._debugProcess; + process._debugEnd = rawMethods._debugEnd; + process._startProfilerIdleNotifier = + rawMethods._startProfilerIdleNotifier; + process._stopProfilerIdleNotifier = rawMethods._stopProfilerIdleNotifier; } - perThreadSetup.setupUncaughtExceptionCapture(exceptionHandlerState, - _shouldAbortOnUncaughtToggle); + // Set up methods on the process object for all threads + { + process.cwd = rawMethods.cwd; + process.dlopen = rawMethods.dlopen; + process.uptime = rawMethods.uptime; + + // TODO(joyeecheung): either remove them or make them public + process._getActiveRequests = rawMethods._getActiveRequests; + process._getActiveHandles = rawMethods._getActiveHandles; + + // TODO(joyeecheung): remove these + process.reallyExit = rawMethods.reallyExit; + process._kill = rawMethods._kill; + + const wrapped = perThreadSetup.wrapProcessMethods( + rawMethods, exceptionHandlerState + ); + process._rawDebug = wrapped._rawDebug; + process.hrtime = wrapped.hrtime; + process.hrtime.bigint = wrapped.hrtimeBigInt; + process.cpuUsage = wrapped.cpuUsage; + process.memoryUsage = wrapped.memoryUsage; + process.kill = wrapped.kill; + process.exit = wrapped.exit; + process.setUncaughtExceptionCaptureCallback = + wrapped.setUncaughtExceptionCaptureCallback; + process.hasUncaughtExceptionCaptureCallback = + wrapped.hasUncaughtExceptionCaptureCallback; + } NativeModule.require('internal/process/warning').setup(); NativeModule.require('internal/process/next_tick').setup(_setupNextTick, @@ -90,22 +128,10 @@ function startup() { if (isMainThread) { mainThreadSetup.setupStdio(); - mainThreadSetup.setupProcessMethods(_chdir, _umask); } else { workerThreadSetup.setupStdio(); } - const perf = internalBinding('performance'); - const { - NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE, - } = perf.constants; - - perThreadSetup.setupRawDebug(_rawDebug); - perThreadSetup.setupHrtime(_hrtime, _hrtimeBigInt); - perThreadSetup.setupCpuUsage(_cpuUsage); - perThreadSetup.setupMemoryUsage(_memoryUsage); - perThreadSetup.setupKillAndExit(); - if (global.__coverage__) NativeModule.require('internal/process/write-coverage').setup(); @@ -237,9 +263,37 @@ function startup() { } } - perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); + // process.allowedNodeEnvironmentFlags + Object.defineProperty(process, 'allowedNodeEnvironmentFlags', { + get() { + const flags = perThreadSetup.buildAllowedFlags(); + process.allowedNodeEnvironmentFlags = flags; + return process.allowedNodeEnvironmentFlags; + }, + // If the user tries to set this to another value, override + // this completely to that value. + set(value) { + Object.defineProperty(this, 'allowedNodeEnvironmentFlags', { + value, + configurable: true, + enumerable: true, + writable: true + }); + }, + enumerable: true, + configurable: true + }); + // process.assert + process.assert = deprecate( + perThreadSetup.assert, + 'process.assert() is deprecated. Please use the `assert` module instead.', + 'DEP0100'); - perThreadSetup.setupAllowedFlags(); + const perf = internalBinding('performance'); + const { + NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE, + } = perf.constants; + perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); startExecution(); } diff --git a/lib/internal/process/main_thread_only.js b/lib/internal/process/main_thread_only.js index 479ebd1433c8ff..862194ae46e27e 100644 --- a/lib/internal/process/main_thread_only.js +++ b/lib/internal/process/main_thread_only.js @@ -25,21 +25,25 @@ function setupStdio() { setupProcessStdio(getMainThreadStdio()); } -// Non-POSIX platforms like Windows don't have certain methods. -// Workers also lack these methods since they change process-global state. -function setupProcessMethods(_chdir, _umask) { - process.chdir = function chdir(directory) { +// The execution of this function itself should not cause any side effects. +function wrapProcessMethods(binding) { + function chdir(directory) { validateString(directory, 'directory'); - return _chdir(directory); - }; + return binding.chdir(directory); + } - process.umask = function umask(mask) { + function umask(mask) { if (mask === undefined) { // Get the mask - return _umask(mask); + return binding.umask(mask); } mask = validateMode(mask, 'mask'); - return _umask(mask); + return binding.umask(mask); + } + + return { + chdir, + umask }; } @@ -171,7 +175,7 @@ function setupChildProcessIpcChannel() { module.exports = { setupStdio, - setupProcessMethods, + wrapProcessMethods, setupSignalHandlers, setupChildProcessIpcChannel, wrapPosixCredentialSetters diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index 57cc9c38143f79..f1629e3a97c336 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -18,25 +18,30 @@ const { } = require('internal/errors'); const util = require('util'); const constants = internalBinding('constants').os.signals; -const { deprecate } = require('internal/util'); - -function setupAssert() { - process.assert = deprecate( - function(x, msg) { - if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); - }, - 'process.assert() is deprecated. Please use the `assert` module instead.', - 'DEP0100'); + +function assert(x, msg) { + if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); } -// Set up the process.cpuUsage() function. -function setupCpuUsage(_cpuUsage) { +// The execution of this function itself should not cause any side effects. +function wrapProcessMethods(binding, exceptionHandlerState) { + const { + hrtime: _hrtime, + hrtimeBigInt: _hrtimeBigInt, + cpuUsage: _cpuUsage, + memoryUsage: _memoryUsage + } = binding; + + function _rawDebug(...args) { + binding._rawDebug(util.format.apply(null, args)); + } + // Create the argument array that will be passed to the native function. const cpuValues = new Float64Array(2); // Replace the native function with the JS version that calls the native // function. - process.cpuUsage = function cpuUsage(prevValue) { + function cpuUsage(prevValue) { // If a previous value was passed in, ensure it has the correct shape. if (prevValue) { if (!previousValueIsValid(prevValue.user)) { @@ -80,7 +85,7 @@ function setupCpuUsage(_cpuUsage) { user: cpuValues[0], system: cpuValues[1] }; - }; + } // Ensure that a previously passed in value is valid. Currently, the native // implementation always returns numbers <= Number.MAX_SAFE_INTEGER. @@ -89,15 +94,13 @@ function setupCpuUsage(_cpuUsage) { num <= Number.MAX_SAFE_INTEGER && num >= 0; } -} -// The 3 entries filled in by the original process.hrtime contains -// the upper/lower 32 bits of the second part of the value, -// and the remaining nanoseconds of the value. -function setupHrtime(_hrtime, _hrtimeBigInt) { + // The 3 entries filled in by the original process.hrtime contains + // the upper/lower 32 bits of the second part of the value, + // and the remaining nanoseconds of the value. const hrValues = new Uint32Array(3); - process.hrtime = function hrtime(time) { + function hrtime(time) { _hrtime(hrValues); if (time !== undefined) { @@ -118,21 +121,18 @@ function setupHrtime(_hrtime, _hrtimeBigInt) { hrValues[0] * 0x100000000 + hrValues[1], hrValues[2] ]; - }; + } // Use a BigUint64Array in the closure because V8 does not have an API for // creating a BigInt out of a uint64_t yet. const hrBigintValues = new BigUint64Array(1); - process.hrtime.bigint = function() { + function hrtimeBigInt() { _hrtimeBigInt(hrBigintValues); return hrBigintValues[0]; - }; -} + } -function setupMemoryUsage(_memoryUsage) { const memValues = new Float64Array(4); - - process.memoryUsage = function memoryUsage() { + function memoryUsage() { _memoryUsage(memValues); return { rss: memValues[0], @@ -140,18 +140,9 @@ function setupMemoryUsage(_memoryUsage) { heapUsed: memValues[2], external: memValues[3] }; - }; -} - -function setupConfig() { - // Serialized config.gypi - process.config = JSON.parse(internalBinding('native_module').config); -} - - -function setupKillAndExit() { + } - process.exit = function(code) { + function exit(code) { if (code || code === 0) process.exitCode = code; @@ -159,10 +150,10 @@ function setupKillAndExit() { process._exiting = true; process.emit('exit', process.exitCode || 0); } - process.reallyExit(process.exitCode || 0); - }; + binding.reallyExit(process.exitCode || 0); + } - process.kill = function(pid, sig) { + function kill(pid, sig) { var err; if (process.env.NODE_V8_COVERAGE) { const { writeCoverage } = require('internal/process/coverage'); @@ -176,6 +167,8 @@ function setupKillAndExit() { // preserve null signal if (sig === (sig | 0)) { + // XXX(joyeecheung): we have to use process._kill here because + // it's monkey-patched by tests. err = process._kill(pid, sig); } else { sig = sig || 'SIGTERM'; @@ -190,22 +183,13 @@ function setupKillAndExit() { throw errnoException(err, 'kill'); return true; - }; -} - -function setupRawDebug(_rawDebug) { - process._rawDebug = function() { - _rawDebug(util.format.apply(null, arguments)); - }; -} - + } -function setupUncaughtExceptionCapture(exceptionHandlerState, - shouldAbortOnUncaughtToggle) { // shouldAbortOnUncaughtToggle is a typed array for faster // communication with JS. + const { shouldAbortOnUncaughtToggle } = binding; - process.setUncaughtExceptionCaptureCallback = function(fn) { + function setUncaughtExceptionCaptureCallback(fn) { if (fn === null) { exceptionHandlerState.captureFn = fn; shouldAbortOnUncaughtToggle[0] = 1; @@ -219,10 +203,22 @@ function setupUncaughtExceptionCapture(exceptionHandlerState, } exceptionHandlerState.captureFn = fn; shouldAbortOnUncaughtToggle[0] = 0; - }; + } - process.hasUncaughtExceptionCaptureCallback = function() { + function hasUncaughtExceptionCaptureCallback() { return exceptionHandlerState.captureFn !== null; + } + + return { + _rawDebug, + hrtime, + hrtimeBigInt, + cpuUsage, + memoryUsage, + kill, + exit, + setUncaughtExceptionCaptureCallback, + hasUncaughtExceptionCaptureCallback }; } @@ -326,38 +322,13 @@ function buildAllowedFlags() { Object.freeze(NodeEnvironmentFlagsSet.prototype.constructor); Object.freeze(NodeEnvironmentFlagsSet.prototype); - return process.allowedNodeEnvironmentFlags = Object.freeze( - new NodeEnvironmentFlagsSet( - allowedNodeEnvironmentFlags - )); -} - -function setupAllowedFlags() { - Object.defineProperty(process, 'allowedNodeEnvironmentFlags', { - get: buildAllowedFlags, - set(value) { - // If the user tries to set this to another value, override - // this completely to that value. - Object.defineProperty(this, 'allowedNodeEnvironmentFlags', { - value, - configurable: true, - enumerable: true, - writable: true - }); - }, - enumerable: true, - configurable: true - }); + return Object.freeze(new NodeEnvironmentFlagsSet( + allowedNodeEnvironmentFlags + )); } module.exports = { - setupAllowedFlags, - setupAssert, - setupCpuUsage, - setupHrtime, - setupMemoryUsage, - setupConfig, - setupKillAndExit, - setupRawDebug, - setupUncaughtExceptionCapture + assert, + buildAllowedFlags, + wrapProcessMethods }; diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index de3eca8ba464a2..b46ba5badab226 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -132,20 +132,6 @@ void SetupBootstrapObject(Environment* env, Local bootstrapper) { BOOTSTRAP_METHOD(_setupNextTick, SetupNextTick); BOOTSTRAP_METHOD(_setupPromises, SetupPromises); - BOOTSTRAP_METHOD(_chdir, Chdir); - BOOTSTRAP_METHOD(_cpuUsage, CPUUsage); - BOOTSTRAP_METHOD(_hrtime, Hrtime); - BOOTSTRAP_METHOD(_hrtimeBigInt, HrtimeBigInt); - BOOTSTRAP_METHOD(_memoryUsage, MemoryUsage); - BOOTSTRAP_METHOD(_rawDebug, RawDebug); - BOOTSTRAP_METHOD(_umask, Umask); - - Local should_abort_on_uncaught_toggle = - FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle"); - CHECK(bootstrapper->Set(env->context(), - should_abort_on_uncaught_toggle, - env->should_abort_on_uncaught_toggle().GetJSArray()) - .FromJust()); } #undef BOOTSTRAP_METHOD diff --git a/src/node.cc b/src/node.cc index 9975ff9d23f5b7..524785297eb503 100644 --- a/src/node.cc +++ b/src/node.cc @@ -95,8 +95,6 @@ #if defined(_MSC_VER) #include #include -#define umask _umask -typedef int mode_t; #else #include #include // getrlimit, setrlimit @@ -700,8 +698,7 @@ static void WaitForInspectorDisconnect(Environment* env) { #endif } - -static void Exit(const FunctionCallbackInfo& args) { +void Exit(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); WaitForInspectorDisconnect(env); v8_platform.StopTracingAgent(); @@ -1080,29 +1077,6 @@ void SetupProcessObject(Environment* env, DebugPortGetter, env->is_main_thread() ? DebugPortSetter : nullptr, env->as_external()).FromJust()); - - // define various internal methods - if (env->is_main_thread()) { - env->SetMethod(process, "_debugProcess", DebugProcess); - env->SetMethod(process, "_debugEnd", DebugEnd); - env->SetMethod(process, - "_startProfilerIdleNotifier", - StartProfilerIdleNotifier); - env->SetMethod(process, - "_stopProfilerIdleNotifier", - StopProfilerIdleNotifier); - env->SetMethod(process, "abort", Abort); - env->SetMethod(process, "chdir", Chdir); - env->SetMethod(process, "umask", Umask); - } - env->SetMethod(process, "_getActiveRequests", GetActiveRequests); - env->SetMethod(process, "_getActiveHandles", GetActiveHandles); - env->SetMethod(process, "_kill", Kill); - - env->SetMethodNoSideEffect(process, "cwd", Cwd); - env->SetMethod(process, "dlopen", binding::DLOpen); - env->SetMethod(process, "reallyExit", Exit); - env->SetMethodNoSideEffect(process, "uptime", Uptime); } @@ -1244,135 +1218,8 @@ void RegisterSignalHandler(int signal, CHECK_EQ(sigaction(signal, &sa, nullptr), 0); } - -void DebugProcess(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - if (args.Length() != 1) { - return env->ThrowError("Invalid number of arguments."); - } - - CHECK(args[0]->IsNumber()); - pid_t pid = args[0].As()->Value(); - int r = kill(pid, SIGUSR1); - - if (r != 0) { - return env->ThrowErrnoException(errno, "kill"); - } -} #endif // __POSIX__ - -#ifdef _WIN32 -static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf, - size_t buf_len) { - return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid); -} - - -static void DebugProcess(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = args.GetIsolate(); - - if (args.Length() != 1) { - env->ThrowError("Invalid number of arguments."); - return; - } - - HANDLE process = nullptr; - HANDLE thread = nullptr; - HANDLE mapping = nullptr; - wchar_t mapping_name[32]; - LPTHREAD_START_ROUTINE* handler = nullptr; - DWORD pid = 0; - - OnScopeLeave cleanup([&]() { - if (process != nullptr) - CloseHandle(process); - if (thread != nullptr) - CloseHandle(thread); - if (handler != nullptr) - UnmapViewOfFile(handler); - if (mapping != nullptr) - CloseHandle(mapping); - }); - - CHECK(args[0]->IsNumber()); - pid = args[0].As()->Value(); - - process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | - PROCESS_VM_OPERATION | PROCESS_VM_WRITE | - PROCESS_VM_READ, - FALSE, - pid); - if (process == nullptr) { - isolate->ThrowException( - WinapiErrnoException(isolate, GetLastError(), "OpenProcess")); - return; - } - - if (GetDebugSignalHandlerMappingName(pid, - mapping_name, - arraysize(mapping_name)) < 0) { - env->ThrowErrnoException(errno, "sprintf"); - return; - } - - mapping = OpenFileMappingW(FILE_MAP_READ, FALSE, mapping_name); - if (mapping == nullptr) { - isolate->ThrowException(WinapiErrnoException(isolate, - GetLastError(), - "OpenFileMappingW")); - return; - } - - handler = reinterpret_cast( - MapViewOfFile(mapping, - FILE_MAP_READ, - 0, - 0, - sizeof *handler)); - if (handler == nullptr || *handler == nullptr) { - isolate->ThrowException( - WinapiErrnoException(isolate, GetLastError(), "MapViewOfFile")); - return; - } - - thread = CreateRemoteThread(process, - nullptr, - 0, - *handler, - nullptr, - 0, - nullptr); - if (thread == nullptr) { - isolate->ThrowException(WinapiErrnoException(isolate, - GetLastError(), - "CreateRemoteThread")); - return; - } - - // Wait for the thread to terminate - if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0) { - isolate->ThrowException(WinapiErrnoException(isolate, - GetLastError(), - "WaitForSingleObject")); - return; - } -} -#endif // _WIN32 - - -static void DebugEnd(const FunctionCallbackInfo& args) { -#if HAVE_INSPECTOR - Environment* env = Environment::GetCurrent(args); - if (env->inspector_agent()->IsListening()) { - env->inspector_agent()->Stop(); - } -#endif -} - - inline void PlatformInit() { #ifdef __POSIX__ #if HAVE_INSPECTOR diff --git a/src/node_binding.cc b/src/node_binding.cc index 0758741ffcb852..46ae5f6840f658 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -44,6 +44,7 @@ V(performance) \ V(pipe_wrap) \ V(process_wrap) \ + V(process_methods) \ V(serdes) \ V(signal_wrap) \ V(spawn_sync) \ diff --git a/src/node_internals.h b/src/node_internals.h index 0d07eb5d907b57..46a6c9e3e0fb6b 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -119,6 +119,7 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(err); } +void Exit(const v8::FunctionCallbackInfo& args); void SignalExit(int signo); #ifdef __POSIX__ void RegisterSignalHandler(int signal, @@ -699,21 +700,7 @@ static inline const char* errno_string(int errorno) { extern double prog_start_time; -void Abort(const v8::FunctionCallbackInfo& args); -void Chdir(const v8::FunctionCallbackInfo& args); -void CPUUsage(const v8::FunctionCallbackInfo& args); -void Cwd(const v8::FunctionCallbackInfo& args); -void GetActiveHandles(const v8::FunctionCallbackInfo& args); -void GetActiveRequests(const v8::FunctionCallbackInfo& args); -void Hrtime(const v8::FunctionCallbackInfo& args); -void HrtimeBigInt(const v8::FunctionCallbackInfo& args); -void Kill(const v8::FunctionCallbackInfo& args); -void MemoryUsage(const v8::FunctionCallbackInfo& args); void RawDebug(const v8::FunctionCallbackInfo& args); -void StartProfilerIdleNotifier(const v8::FunctionCallbackInfo& args); -void StopProfilerIdleNotifier(const v8::FunctionCallbackInfo& args); -void Umask(const v8::FunctionCallbackInfo& args); -void Uptime(const v8::FunctionCallbackInfo& args); void DebugPortGetter(v8::Local property, const v8::PropertyCallbackInfo& info); diff --git a/src/node_process.cc b/src/node_process.cc index b9376953e241be..477ac2adc8eb6c 100644 --- a/src/node_process.cc +++ b/src/node_process.cc @@ -8,6 +8,8 @@ #include "uv.h" #include "v8.h" +#include + #if HAVE_INSPECTOR #include "inspector_io.h" #endif @@ -36,10 +38,12 @@ using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::HeapStatistics; +using v8::Integer; using v8::Isolate; using v8::Local; using v8::Name; using v8::NewStringType; +using v8::Object; using v8::PropertyCallbackInfo; using v8::String; using v8::Uint32; @@ -61,11 +65,11 @@ Mutex environ_mutex; #define CHDIR_BUFSIZE (PATH_MAX) #endif -void Abort(const FunctionCallbackInfo& args) { +static void Abort(const FunctionCallbackInfo& args) { Abort(); } -void Chdir(const FunctionCallbackInfo& args) { +static void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->is_main_thread()); @@ -88,7 +92,7 @@ void Chdir(const FunctionCallbackInfo& args) { // which are uv_timeval_t structs (long tv_sec, long tv_usec). // Returns those values as Float64 microseconds in the elements of the array // passed to the function. -void CPUUsage(const FunctionCallbackInfo& args) { +static void CPUUsage(const FunctionCallbackInfo& args) { uv_rusage_t rusage; // Call libuv to get the values we'll return. @@ -111,7 +115,7 @@ void CPUUsage(const FunctionCallbackInfo& args) { fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; } -void Cwd(const FunctionCallbackInfo& args) { +static void Cwd(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); char buf[CHDIR_BUFSIZE]; size_t cwd_len = sizeof(buf); @@ -138,7 +142,7 @@ void Cwd(const FunctionCallbackInfo& args) { // broken into the upper/lower 32 bits to be converted back in JS, // because there is no Uint64Array in JS. // The third entry contains the remaining nanosecond part of the value. -void Hrtime(const FunctionCallbackInfo& args) { +static void Hrtime(const FunctionCallbackInfo& args) { uint64_t t = uv_hrtime(); Local ab = args[0].As()->Buffer(); @@ -149,13 +153,13 @@ void Hrtime(const FunctionCallbackInfo& args) { fields[2] = t % NANOS_PER_SEC; } -void HrtimeBigInt(const FunctionCallbackInfo& args) { +static void HrtimeBigInt(const FunctionCallbackInfo& args) { Local ab = args[0].As()->Buffer(); uint64_t* fields = static_cast(ab->GetContents().Data()); fields[0] = uv_hrtime(); } -void Kill(const FunctionCallbackInfo& args) { +static void Kill(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); @@ -170,8 +174,7 @@ void Kill(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(err); } - -void MemoryUsage(const FunctionCallbackInfo& args) { +static void MemoryUsage(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); size_t rss; @@ -209,18 +212,17 @@ void RawDebug(const FunctionCallbackInfo& args) { fflush(stderr); } -void StartProfilerIdleNotifier(const FunctionCallbackInfo& args) { +static void StartProfilerIdleNotifier(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); env->StartProfilerIdleNotifier(); } - -void StopProfilerIdleNotifier(const FunctionCallbackInfo& args) { +static void StopProfilerIdleNotifier(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); env->StopProfilerIdleNotifier(); } -void Umask(const FunctionCallbackInfo& args) { +static void Umask(const FunctionCallbackInfo& args) { uint32_t old; CHECK_EQ(args.Length(), 1); @@ -237,7 +239,7 @@ void Umask(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(old); } -void Uptime(const FunctionCallbackInfo& args) { +static void Uptime(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); double uptime; @@ -255,7 +257,6 @@ void ProcessTitleGetter(Local property, NewStringType::kNormal).ToLocalChecked()); } - void ProcessTitleSetter(Local property, Local value, const PropertyCallbackInfo& info) { @@ -270,7 +271,7 @@ void GetParentProcessId(Local property, info.GetReturnValue().Set(uv_os_getppid()); } -void GetActiveRequests(const FunctionCallbackInfo& args) { +static void GetActiveRequests(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); std::vector> request_v; @@ -315,5 +316,159 @@ void DebugPortSetter(Local property, env->inspector_host_port()->set_port(static_cast(port)); } +#ifdef __POSIX__ +static void DebugProcess(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() != 1) { + return env->ThrowError("Invalid number of arguments."); + } + + CHECK(args[0]->IsNumber()); + pid_t pid = args[0].As()->Value(); + int r = kill(pid, SIGUSR1); + + if (r != 0) { + return env->ThrowErrnoException(errno, "kill"); + } +} +#endif // __POSIX__ + +#ifdef _WIN32 +static int GetDebugSignalHandlerMappingName(DWORD pid, + wchar_t* buf, + size_t buf_len) { + return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid); +} + +static void DebugProcess(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = args.GetIsolate(); + + if (args.Length() != 1) { + env->ThrowError("Invalid number of arguments."); + return; + } + + HANDLE process = nullptr; + HANDLE thread = nullptr; + HANDLE mapping = nullptr; + wchar_t mapping_name[32]; + LPTHREAD_START_ROUTINE* handler = nullptr; + DWORD pid = 0; + + OnScopeLeave cleanup([&]() { + if (process != nullptr) CloseHandle(process); + if (thread != nullptr) CloseHandle(thread); + if (handler != nullptr) UnmapViewOfFile(handler); + if (mapping != nullptr) CloseHandle(mapping); + }); + + CHECK(args[0]->IsNumber()); + pid = args[0].As()->Value(); + + process = + OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, + FALSE, + pid); + if (process == nullptr) { + isolate->ThrowException( + WinapiErrnoException(isolate, GetLastError(), "OpenProcess")); + return; + } + + if (GetDebugSignalHandlerMappingName( + pid, mapping_name, arraysize(mapping_name)) < 0) { + env->ThrowErrnoException(errno, "sprintf"); + return; + } + + mapping = OpenFileMappingW(FILE_MAP_READ, FALSE, mapping_name); + if (mapping == nullptr) { + isolate->ThrowException( + WinapiErrnoException(isolate, GetLastError(), "OpenFileMappingW")); + return; + } + + handler = reinterpret_cast( + MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, sizeof *handler)); + if (handler == nullptr || *handler == nullptr) { + isolate->ThrowException( + WinapiErrnoException(isolate, GetLastError(), "MapViewOfFile")); + return; + } + + thread = + CreateRemoteThread(process, nullptr, 0, *handler, nullptr, 0, nullptr); + if (thread == nullptr) { + isolate->ThrowException( + WinapiErrnoException(isolate, GetLastError(), "CreateRemoteThread")); + return; + } + + // Wait for the thread to terminate + if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0) { + isolate->ThrowException( + WinapiErrnoException(isolate, GetLastError(), "WaitForSingleObject")); + return; + } +} +#endif // _WIN32 + +static void DebugEnd(const FunctionCallbackInfo& args) { +#if HAVE_INSPECTOR + Environment* env = Environment::GetCurrent(args); + if (env->inspector_agent()->IsListening()) { + env->inspector_agent()->Stop(); + } +#endif +} + +static void InitializeProcessMethods(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + + // define various internal methods + if (env->is_main_thread()) { + env->SetMethod(target, "_debugProcess", DebugProcess); + env->SetMethod(target, "_debugEnd", DebugEnd); + env->SetMethod( + target, "_startProfilerIdleNotifier", StartProfilerIdleNotifier); + env->SetMethod( + target, "_stopProfilerIdleNotifier", StopProfilerIdleNotifier); + env->SetMethod(target, "abort", Abort); + env->SetMethod(target, "chdir", Chdir); + env->SetMethod(target, "umask", Umask); + } + + env->SetMethod(target, "_rawDebug", RawDebug); + env->SetMethod(target, "memoryUsage", MemoryUsage); + env->SetMethod(target, "cpuUsage", CPUUsage); + env->SetMethod(target, "hrtime", Hrtime); + env->SetMethod(target, "hrtimeBigInt", HrtimeBigInt); + + env->SetMethod(target, "_getActiveRequests", GetActiveRequests); + env->SetMethod(target, "_getActiveHandles", GetActiveHandles); + env->SetMethod(target, "_kill", Kill); + + env->SetMethodNoSideEffect(target, "cwd", Cwd); + env->SetMethod(target, "dlopen", binding::DLOpen); + env->SetMethod(target, "reallyExit", Exit); + env->SetMethodNoSideEffect(target, "uptime", Uptime); + + Local should_abort_on_uncaught_toggle = + FIXED_ONE_BYTE_STRING(env->isolate(), "shouldAbortOnUncaughtToggle"); + CHECK(target + ->Set(env->context(), + should_abort_on_uncaught_toggle, + env->should_abort_on_uncaught_toggle().GetJSArray()) + .FromJust()); +} } // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods, + node::InitializeProcessMethods) diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 9816aa05466ff3..905be701e9f1a1 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -9,7 +9,7 @@ const common = require('../common'); const assert = require('assert'); const isMainThread = common.isMainThread; -const kMaxModuleCount = isMainThread ? 61 : 82; +const kMaxModuleCount = isMainThread ? 62 : 82; assert(list.length <= kMaxModuleCount, `Total length: ${list.length}\n` + list.join('\n')