diff --git a/src/module.cc b/src/module.cc index e9de130..2aa7a57 100644 --- a/src/module.cc +++ b/src/module.cc @@ -303,6 +303,7 @@ void Initialize(v8::Local exports) { SetLoadTime(); SetVersionString(isolate); + SetCommandLine(); const char* verbose_switch = secure_getenv("NODEREPORT_VERBOSE"); if (verbose_switch != nullptr) { diff --git a/src/node_report.cc b/src/node_report.cc index c55ffd1..b296a60 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -29,6 +29,8 @@ #include #ifndef _AIX #include +#else +#include #endif #include #endif @@ -41,6 +43,11 @@ #define UNKNOWN_NODEVERSION_STRING "Unable to determine Node.js version\n" #endif +#ifdef __APPLE__ +// Include _NSGetArgv and _NSGetArgc for command line arguments. +#include +#endif + #ifndef _WIN32 extern char** environ; #endif @@ -58,6 +65,7 @@ using v8::String; using v8::V8; // Internal/static function declarations +static void PrintCommandLine(FILE* fp); static void PrintVersionInformation(FILE* fp, Isolate* isolate); static void PrintJavaScriptStack(FILE* fp, Isolate* isolate, DumpEvent event, const char* location); static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event); @@ -77,6 +85,7 @@ static bool report_active = false; // recursion protection static char report_filename[NR_MAXNAME + 1] = ""; static char report_directory[NR_MAXPATH + 1] = ""; // defaults to current working directory static std::string version_string = UNKNOWN_NODEVERSION_STRING; +static std::string commandline_string = ""; #ifdef _WIN32 static SYSTEMTIME loadtime_tm_struct; // module load time #else // UNIX, OSX @@ -299,6 +308,67 @@ void SetLoadTime() { localtime_r(&time_val.tv_sec, &loadtime_tm_struct); #endif } + +/******************************************************************************* + * Function to save the process command line + *******************************************************************************/ +void SetCommandLine() { +#ifdef __linux__ + // Read the command line from /proc/self/cmdline + char buf[64]; + FILE* cmdline_fd = fopen("/proc/self/cmdline", "r"); + if (cmdline_fd == nullptr) { + return; + } + commandline_string = ""; + int bytesread = fread(buf, 1, sizeof(buf), cmdline_fd); + while (bytesread > 0) { + for (int i = 0; i < bytesread; i++) { + // Arguments are null separated. + if (buf[i] == '\0') { + commandline_string += " "; + } else { + commandline_string += buf[i]; + } + } + bytesread = fread(buf, 1, sizeof(buf), cmdline_fd); + } + fclose(cmdline_fd); +#elif __APPLE__ + char **argv = *_NSGetArgv(); + int argc = *_NSGetArgc(); + + commandline_string = ""; + std::string separator = ""; + for (int i = 0; i < argc; i++) { + commandline_string += separator + argv[i]; + separator = " "; + } +#elif _AIX + // Read the command line from /proc/self/cmdline + char procbuf[64]; + snprintf(procbuf, sizeof(procbuf), "/proc/%d/psinfo", getpid()); + FILE* psinfo_fd = fopen(procbuf, "r"); + if (psinfo_fd == nullptr) { + return; + } + psinfo_t info; + int bytesread = fread(&info, 1, sizeof(psinfo_t), psinfo_fd); + fclose(psinfo_fd); + if (bytesread == sizeof(psinfo_t)) { + commandline_string = ""; + std::string separator = ""; + char **argv = *((char ***) info.pr_argv); + for (uint32_t i = 0; i < info.pr_argc; i++) { + commandline_string += separator + argv[i]; + separator = " "; + } + } +#elif _WIN32 + commandline_string = GetCommandLine(); +#endif +} + /******************************************************************************* * Main API function to write a NodeReport to file. * @@ -311,7 +381,7 @@ void SetLoadTime() { ******************************************************************************/ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name) { // Recursion check for NodeReport in progress, bail out - if (report_active) return; + if (report_active) return; report_active = true; // Obtain the current time and the pid (platform dependent) @@ -414,6 +484,10 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c fprintf(fp, "Process ID: %d\n", pid); fflush(fp); + // Print out the command line. + PrintCommandLine(fp); + fflush(fp); + // Print Node.js and OS version information PrintVersionInformation(fp, isolate); fflush(fp); @@ -463,6 +537,16 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c report_active = false; } +/******************************************************************************* + * Function to print process command line. + * + ******************************************************************************/ +static void PrintCommandLine(FILE* fp) { + if (commandline_string != "") { + fprintf(fp, "Command line: %s\n", commandline_string.c_str()); + } +} + /******************************************************************************* * Function to print Node.js version, OS version and machine information * @@ -688,7 +772,7 @@ void PrintNativeStack(FILE* fp) { SymInitialize(hProcess, nullptr, TRUE); WORD numberOfFrames = CaptureStackBackTrace(2, 64, frames, nullptr); - + // Walk the frames printing symbolic information if available for (int i = 0; i < numberOfFrames; i++) { DWORD64 dwOffset64 = 0; diff --git a/src/node_report.h b/src/node_report.h index 210669e..95148e7 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -44,6 +44,7 @@ unsigned int ProcessNodeReportVerboseSwitch(const char* args); void SetLoadTime(); void SetVersionString(Isolate* isolate); +void SetCommandLine(); // Local implementation of secure_getenv() inline const char* secure_getenv(const char* key) { diff --git a/test/common.js b/test/common.js index bab9cf8..bc11f42 100644 --- a/test/common.js +++ b/test/common.js @@ -36,16 +36,16 @@ exports.validate = (t, report, options) => { const expectedVersions = options ? options.expectedVersions || nodeComponents : nodeComponents; - const plan = REPORT_SECTIONS.length + nodeComponents.length + 2; + var plan = REPORT_SECTIONS.length + nodeComponents.length + 2; + if (options.commandline) plan++; t.plan(plan); - // Check all sections are present REPORT_SECTIONS.forEach((section) => { t.match(reportContents, new RegExp('==== ' + section), 'Checking report contains ' + section + ' section'); }); - // Check NodeReport section + // Check NodeReport header section const nodeReportSection = getSection(reportContents, 'NodeReport'); t.match(nodeReportSection, new RegExp('Process ID: ' + pid), 'NodeReport section contains expected process ID'); @@ -57,6 +57,20 @@ exports.validate = (t, report, options) => { new RegExp('Node.js version: ' + process.version), 'NodeReport section contains expected Node.js version'); } + if (options && options.commandline) { + if (this.isWindows()) { + // On Windows we need to strip double quotes from the command line in + // the report, and escape backslashes in the regex comparison string. + t.match(nodeReportSection.replace(/"/g,''), + new RegExp('Command line: ' + + (options.commandline).replace(/\\/g,'\\\\')), + 'Checking report contains expected command line'); + } else { + t.match(nodeReportSection, + new RegExp('Command line: ' + options.commandline), + 'Checking report contains expected command line'); + } + } nodeComponents.forEach((c) => { if (c !== 'node') { if (expectedVersions.indexOf(c) === -1) { diff --git a/test/test-api-bad-processobj.js b/test/test-api-bad-processobj.js index 4d32941..652bbfd 100644 --- a/test/test-api-bad-processobj.js +++ b/test/test-api-bad-processobj.js @@ -18,7 +18,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - const validateOpts = { pid: child.pid, expectedVersions: [] }; + const validateOpts = { pid: child.pid, expectedVersions: [], + commandline: child.spawnargs.join(' '), }; common.validate(tap, report, validateOpts); }); } diff --git a/test/test-api-bad-processversion.js b/test/test-api-bad-processversion.js index a84d9cb..6d9cede 100644 --- a/test/test-api-bad-processversion.js +++ b/test/test-api-bad-processversion.js @@ -18,7 +18,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - const validateOpts = { pid: child.pid, expectNodeVersion: true }; + const validateOpts = { pid: child.pid, expectNodeVersion: true, + commandline: child.spawnargs.join(' '), }; common.validate(tap, report, validateOpts); }); } diff --git a/test/test-api-bad-processversions.js b/test/test-api-bad-processversions.js index a1099a1..387a5f7 100644 --- a/test/test-api-bad-processversions.js +++ b/test/test-api-bad-processversions.js @@ -19,7 +19,8 @@ if (process.argv[2] === 'child') { tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; const validateOpts = { pid: child.pid, - expectedVersions: Object.keys(process.versions).filter((c) => c !== 'uv') + expectedVersions: Object.keys(process.versions).filter((c) => c !== 'uv'), + commandline: child.spawnargs.join(' ') }; common.validate(tap, report, validateOpts); }); diff --git a/test/test-api-nohooks.js b/test/test-api-nohooks.js index 3ca3dd4..186f642 100644 --- a/test/test-api-nohooks.js +++ b/test/test-api-nohooks.js @@ -17,6 +17,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - common.validate(tap, report, {pid: child.pid}); + common.validate(tap, report, {pid: child.pid, + commandline: child.spawnargs.join(' ') + }); }); } diff --git a/test/test-api-noversioninfo.js b/test/test-api-noversioninfo.js index 717eb80..2941e7c 100644 --- a/test/test-api-noversioninfo.js +++ b/test/test-api-noversioninfo.js @@ -22,7 +22,8 @@ if (process.argv[2] === 'child') { tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; const validateOpts = { pid: child.pid, expectNodeVersion: false, - expectedVersions: [] }; + expectedVersions: [], commandline: child.spawnargs.join(' ') + }; common.validate(tap, report, validateOpts); }); } diff --git a/test/test-api.js b/test/test-api.js index 98f365c..5e0ffe1 100644 --- a/test/test-api.js +++ b/test/test-api.js @@ -16,6 +16,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - common.validate(tap, report, {pid: child.pid}); + common.validate(tap, report, {pid: child.pid, + commandline: child.spawnargs.join(' ') + }); }); } diff --git a/test/test-exception.js b/test/test-exception.js index 04f16a9..cb26ce0 100644 --- a/test/test-exception.js +++ b/test/test-exception.js @@ -32,6 +32,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - common.validate(tap, report, {pid: child.pid}); + common.validate(tap, report, {pid: child.pid, + commandline: child.spawnargs.join(' ') + }); }); } diff --git a/test/test-fatal-error.js b/test/test-fatal-error.js index 563a578..628d4d6 100644 --- a/test/test-fatal-error.js +++ b/test/test-fatal-error.js @@ -28,6 +28,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - common.validate(tap, report, {pid: child.pid}); + common.validate(tap, report, {pid: child.pid, + commandline: child.spawnargs.join(' ') + }); }); } diff --git a/test/test-signal.js b/test/test-signal.js index d1a0761..85bffba 100644 --- a/test/test-signal.js +++ b/test/test-signal.js @@ -59,6 +59,8 @@ if (process.argv[2] === 'child') { const reports = common.findReports(child.pid); tap.equal(reports.length, 1, 'Found reports ' + reports); const report = reports[0]; - common.validate(tap, report, {pid: child.pid}); + common.validate(tap, report, {pid: child.pid, + commandline: child.spawnargs.join(' ') + }); }); }