Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: print backtrace on fatal error #6734

Merged
merged 8 commits into from
Jun 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@

[ 'OS=="win"', {
'sources': [
'src/backtrace_win32.cc',
'src/res/node.rc',
],
'defines!': [
Expand All @@ -480,6 +481,7 @@
'libraries': [ '-lpsapi.lib' ]
}, { # POSIX
'defines': [ '__POSIX__' ],
'sources': [ 'src/backtrace_posix.cc' ],
}],
[ 'OS=="mac"', {
# linking Corefoundation is needed since certain OSX debugging tools
Expand Down
50 changes: 50 additions & 0 deletions src/backtrace_posix.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "node.h"

#if defined(__linux__)
#include <features.h>
#endif

#if defined(__linux__) && !defined(__GLIBC__)
#define HAVE_EXECINFO_H 0
#else
#define HAVE_EXECINFO_H 1
#endif

#if HAVE_EXECINFO_H
#include <cxxabi.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <stdio.h>
#endif

namespace node {

void DumpBacktrace(FILE* fp) {
#if HAVE_EXECINFO_H
void* frames[256];
const int size = backtrace(frames, arraysize(frames));
if (size <= 0) {
return;
}
for (int i = 1; i < size; i += 1) {
void* frame = frames[i];
fprintf(fp, "%2d: ", i);
Dl_info info;
const bool have_info = dladdr(frame, &info);
if (!have_info || info.dli_sname == nullptr) {
fprintf(fp, "%p", frame);
} else if (char* demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, 0)) {
fprintf(fp, "%s", demangled);
free(demangled);
} else {
fprintf(fp, "%s", info.dli_sname);
}
if (have_info && info.dli_fname != nullptr) {
fprintf(fp, " [%s]", info.dli_fname);
}
fprintf(fp, "\n");
}
#endif // HAVE_EXECINFO_H
}

} // namespace node
8 changes: 8 additions & 0 deletions src/backtrace_win32.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "node.h"

namespace node {

void DumpBacktrace(FILE* fp) {
}

} // namespace node
34 changes: 33 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1734,8 +1734,40 @@ void GetActiveHandles(const FunctionCallbackInfo<Value>& args) {
}


NO_RETURN void Abort() {
DumpBacktrace(stderr);
fflush(stderr);
ABORT_NO_BACKTRACE();
}


NO_RETURN void Assert(const char* const (*args)[4]) {
auto filename = (*args)[0];
auto linenum = (*args)[1];
auto message = (*args)[2];
auto function = (*args)[3];

char exepath[256];
size_t exepath_size = sizeof(exepath);
if (uv_exepath(exepath, &exepath_size))
snprintf(exepath, sizeof(exepath), "node");

char pid[12] = {0};
#ifndef _WIN32
snprintf(pid, sizeof(pid), "[%u]", getpid());
#endif

fprintf(stderr, "%s%s: %s:%s:%s%s Assertion `%s' failed.\n",
exepath, pid, filename, linenum,
function, *function ? ":" : "", message);
fflush(stderr);

Abort();
}


static void Abort(const FunctionCallbackInfo<Value>& args) {
ABORT();
Abort();
}


Expand Down
4 changes: 1 addition & 3 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,10 @@ constexpr size_t arraysize(const T(&)[N]) { return N; }
# define ROUND_UP(a, b) ((a) % (b) ? ((a) + (b)) - ((a) % (b)) : (a))
#endif

#if defined(__GNUC__) && __GNUC__ >= 4
#ifdef __GNUC__
# define MUST_USE_RESULT __attribute__((warn_unused_result))
# define NO_RETURN __attribute__((noreturn))
#else
# define MUST_USE_RESULT
# define NO_RETURN
#endif

bool IsExceptionDecorated(Environment* env, v8::Local<v8::Value> er);
Expand Down
56 changes: 48 additions & 8 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <assert.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __APPLE__
Expand All @@ -18,6 +19,18 @@

namespace node {

#ifdef __GNUC__
#define NO_RETURN __attribute__((noreturn))
#else
#define NO_RETURN
#endif

// The slightly odd function signature for Assert() is to ease
// instruction cache pressure in calls from ASSERT and CHECK.
NO_RETURN void Abort();
NO_RETURN void Assert(const char* const (*args)[4]);
void DumpBacktrace(FILE* fp);

#ifdef __APPLE__
template <typename T> using remove_reference = std::tr1::remove_reference<T>;
#else
Expand All @@ -35,20 +48,47 @@ template <typename T> using remove_reference = std::remove_reference<T>;

// Windows 8+ does not like abort() in Release mode
#ifdef _WIN32
#define ABORT() raise(SIGABRT)
#define ABORT_NO_BACKTRACE() raise(SIGABRT)
#else
#define ABORT_NO_BACKTRACE() abort()
#endif

#define ABORT() node::Abort()

#ifdef __GNUC__
#define LIKELY(expr) __builtin_expect(!!(expr), 1)
#define UNLIKELY(expr) __builtin_expect(!!(expr), 0)
#define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__
#else
#define ABORT() abort()
#define LIKELY(expr) expr
#define UNLIKELY(expr) expr
#define PRETTY_FUNCTION_NAME ""
#endif

#if defined(NDEBUG)
# define ASSERT(expression)
# define CHECK(expression) \
#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)

#define CHECK(expr) \
do { \
if (!(expression)) ABORT(); \
if (UNLIKELY(!(expr))) { \
static const char* const args[] = { __FILE__, STRINGIFY(__LINE__), \
#expr, PRETTY_FUNCTION_NAME }; \
node::Assert(&args); \
} \
} while (0)

// FIXME(bnoordhuis) cctests don't link in node::Abort() and node::Assert().
#ifdef GTEST_DONT_DEFINE_ASSERT_EQ
#undef ABORT
#undef CHECK
#define ABORT ABORT_NO_BACKTRACE
#define CHECK assert
#endif

#ifdef NDEBUG
#define ASSERT(expr)
#else
# define ASSERT(expression) assert(expression)
# define CHECK(expression) assert(expression)
#define ASSERT(expr) CHECK(expr)
#endif

#define ASSERT_EQ(a, b) ASSERT((a) == (b))
Expand Down
24 changes: 24 additions & 0 deletions test/abort/test-abort-backtrace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const cp = require('child_process');

if (common.isWindows) {
common.skip('Backtraces unimplemented on Windows.');
return;
}

if (process.argv[2] === 'child') {
process.abort();
} else {
const child = cp.spawnSync(`${process.execPath}`, [`${__filename}`, 'child']);
const frames =
child.stderr.toString().trimRight().split('\n').map((s) => s.trim());

assert.strictEqual(child.stdout.toString(), '');
assert.ok(frames.length > 0);
// All frames should start with a frame number.
assert.ok(frames.every((frame, index) => frame.startsWith(`${index + 1}:`)));
// At least some of the frames should include the binary name.
assert.ok(frames.some((frame) => frame.includes(`[${process.execPath}]`)));
}
15 changes: 9 additions & 6 deletions test/abort/test-abort-uncaught-exception.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@ if (process.argv[2] === 'child') {
throw new Error('child error');
} else {
run('', null);
run('--abort-on-uncaught-exception', 'SIGABRT');
run('--abort-on-uncaught-exception', ['SIGABRT', 'SIGILL']);
}

function run(flags, signal) {
function run(flags, signals) {
const args = [__filename, 'child'];
if (flags)
args.unshift(flags);

const child = spawn(node, args);
child.on('exit', common.mustCall(function(code, sig) {
if (!common.isWindows) {
assert.strictEqual(sig, signal);
} else {
if (signal)
if (common.isWindows) {
if (signals)
assert.strictEqual(code, 3);
else
assert.strictEqual(code, 1);
} else {
if (signals)
assert.strictEqual(signals.includes(sig), true);
else
assert.strictEqual(sig, null);
}
}));
}
6 changes: 6 additions & 0 deletions test/abort/testcfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import testpy

def GetConfiguration(context, root):
return testpy.SimpleTestConfiguration(context, root, 'abort')
5 changes: 3 additions & 2 deletions test/addons/buffer-free-callback/binding.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#include <node.h>
#include <node_buffer.h>
#include <util.h>
#include <v8.h>

#include <assert.h>

static int alive;
static char buf[1024];

Expand Down Expand Up @@ -32,7 +33,7 @@ void Check(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
CHECK_GT(alive, 0);
assert(alive > 0);
}

void init(v8::Local<v8::Object> target) {
Expand Down
5 changes: 1 addition & 4 deletions test/addons/buffer-free-callback/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
'targets': [
{
'target_name': 'binding',
'defines': [
'NODE_WANT_INTERNALS=1',
'V8_DEPRECATION_WARNINGS=1',
],
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
'sources': [ 'binding.cc' ]
}
]
Expand Down
6 changes: 3 additions & 3 deletions test/addons/make-callback-recurse/binding.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "node.h"
#include "v8.h"

#include "../../../src/util.h"
#include <assert.h>

using v8::Function;
using v8::FunctionCallbackInfo;
Expand All @@ -13,8 +13,8 @@ using v8::Value;
namespace {

void MakeCallback(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsObject());
CHECK(args[1]->IsFunction());
assert(args[0]->IsObject());
assert(args[1]->IsFunction());
Isolate* isolate = args.GetIsolate();
Local<Object> recv = args[0].As<Object>();
Local<Function> method = args[1].As<Function>();
Expand Down
5 changes: 1 addition & 4 deletions test/addons/make-callback-recurse/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
'targets': [
{
'target_name': 'binding',
'defines': [
'NODE_WANT_INTERNALS=1',
'V8_DEPRECATION_WARNINGS=1',
],
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
'sources': [ 'binding.cc' ]
}
]
Expand Down
9 changes: 4 additions & 5 deletions test/addons/make-callback/binding.cc
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
#include "node.h"
#include "v8.h"

#include "../../../src/util.h"

#include <assert.h>
#include <vector>

namespace {

void MakeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK(args[0]->IsObject());
CHECK(args[1]->IsFunction() || args[1]->IsString());
assert(args[0]->IsObject());
assert(args[1]->IsFunction() || args[1]->IsString());
auto isolate = args.GetIsolate();
auto recv = args[0].As<v8::Object>();
std::vector<v8::Local<v8::Value>> argv;
Expand All @@ -26,7 +25,7 @@ void MakeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
result =
node::MakeCallback(isolate, recv, method, argv.size(), argv.data());
} else {
UNREACHABLE();
assert(0 && "unreachable");
}
args.GetReturnValue().Set(result);
}
Expand Down
5 changes: 1 addition & 4 deletions test/addons/make-callback/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
'targets': [
{
'target_name': 'binding',
'defines': [
'NODE_WANT_INTERNALS=1',
'V8_DEPRECATION_WARNINGS=1',
],
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
'sources': [ 'binding.cc' ]
}
]
Expand Down
Loading