aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@google.com>2017-10-11 18:35:09 -0700
committerDan Willemsen <dwillemsen@google.com>2017-10-13 13:50:55 -0700
commitfcc71f888a7aaf65ce344386731b90dc2ef61e18 (patch)
treebca3e345fe366b511d6ff22c045b499103f8477c
parent36e5729db554afaea6fe9b23f3caa87b6c8cc80d (diff)
downloadkati-fcc71f888a7aaf65ce344386731b90dc2ef61e18.tar.gz
Add segfault handler
For easier debugging, print out the last evaluated line when we hit a segfault. Preserves existing segfault handlers, so that LD_PRELOAD of libSegFault.so can still provide a backtrace. Change-Id: Ie003378179102b22ef83f31d08f01d85040da5f1
-rw-r--r--main.cc112
-rw-r--r--testcase/segfault_stack_overflow.sh33
2 files changed, 135 insertions, 10 deletions
diff --git a/main.cc b/main.cc
index 160468b..e89e17b 100644
--- a/main.cc
+++ b/main.cc
@@ -15,6 +15,7 @@
// +build ignore
#include <limits.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -46,7 +47,7 @@
// We know that there are leaks in Kati. Turn off LeakSanitizer by default.
extern "C" const char* __asan_default_options() {
- return "detect_leaks=0";
+ return "detect_leaks=0:allow_user_segv_handler=1";
}
static void Init() {
@@ -121,6 +122,97 @@ static void SetVar(StringPiece l, VarOrigin origin) {
extern "C" char** environ;
+class SegfaultHandler {
+ public:
+ explicit SegfaultHandler(Evaluator* ev);
+ ~SegfaultHandler();
+
+ void handle(int, siginfo_t*, void*);
+
+ private:
+ static SegfaultHandler* global_handler;
+
+ void dumpstr(const char* s) const {
+ (void)write(STDERR_FILENO, s, strlen(s));
+ }
+ void dumpint(int i) const {
+ char buf[11];
+ char* ptr = buf + sizeof(buf) - 1;
+
+ if (i < 0) {
+ i = -i;
+ dumpstr("-");
+ } else if (i == 0) {
+ dumpstr("0");
+ return;
+ }
+
+ *ptr = '\0';
+ while (ptr > buf && i > 0) {
+ *--ptr = '0' + (i % 10);
+ i = i / 10;
+ }
+
+ dumpstr(ptr);
+ }
+
+ Evaluator* ev_;
+
+ struct sigaction orig_action_;
+ struct sigaction new_action_;
+};
+
+SegfaultHandler* SegfaultHandler::global_handler = nullptr;
+
+SegfaultHandler::SegfaultHandler(Evaluator* ev) : ev_(ev) {
+ CHECK(global_handler == nullptr);
+ global_handler = this;
+
+ // Construct an alternate stack, so that we can handle stack overflows.
+ stack_t ss;
+ ss.ss_sp = malloc(SIGSTKSZ * 2);
+ CHECK(ss.ss_sp != nullptr);
+ ss.ss_size = SIGSTKSZ * 2;
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, nullptr) == -1) {
+ PERROR("sigaltstack");
+ }
+
+ // Register our segfault handler using the alternate stack, falling
+ // back to the default handler.
+ sigemptyset(&new_action_.sa_mask);
+ new_action_.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESETHAND;
+ new_action_.sa_sigaction = [](int sig, siginfo_t* info, void* context) {
+ if (global_handler != nullptr) {
+ global_handler->handle(sig, info, context);
+ }
+
+ raise(SIGSEGV);
+ };
+ sigaction(SIGSEGV, &new_action_, &orig_action_);
+}
+
+void SegfaultHandler::handle(int sig, siginfo_t* info, void* context) {
+ // Avoid fprintf in case it allocates or tries to do anything else that may
+ // hang.
+ dumpstr("*kati*: Segmentation fault, last evaluated line was ");
+ dumpstr(ev_->loc().filename);
+ dumpstr(":");
+ dumpint(ev_->loc().lineno);
+ dumpstr("\n");
+
+ // Run the original handler, in case we've been preloaded with libSegFault
+ // or similar.
+ if (orig_action_.sa_sigaction != nullptr) {
+ orig_action_.sa_sigaction(sig, info, context);
+ }
+}
+
+SegfaultHandler::~SegfaultHandler() {
+ sigaction(SIGSEGV, &orig_action_, nullptr);
+ global_handler = nullptr;
+}
+
static int Run(const vector<Symbol>& targets,
const vector<StringPiece>& cl_vars,
const string& orig_args) {
@@ -149,14 +241,15 @@ static int Run(const vector<Symbol>& targets,
for (char** p = environ; *p; p++) {
SetVar(*p, VarOrigin::ENVIRONMENT);
}
- Evaluator* ev = new Evaluator();
+ unique_ptr<Evaluator> ev(new Evaluator());
+ SegfaultHandler segfault(ev.get());
vector<Stmt*> bootstrap_asts;
ReadBootstrapMakefile(targets, &bootstrap_asts);
ev->set_is_bootstrap(true);
for (Stmt* stmt : bootstrap_asts) {
LOG("%s", stmt->DebugString().c_str());
- stmt->Eval(ev);
+ stmt->Eval(ev.get());
}
ev->set_is_bootstrap(false);
@@ -165,7 +258,7 @@ static int Run(const vector<Symbol>& targets,
vector<Stmt*> asts;
Parse(Intern(l).str(), Loc("*bootstrap*", 0), &asts);
CHECK(asts.size() == 1);
- asts[0]->Eval(ev);
+ asts[0]->Eval(ev.get());
}
ev->set_is_commandline(false);
@@ -174,7 +267,7 @@ static int Run(const vector<Symbol>& targets,
Makefile* mk = cache_mgr->ReadMakefile(g_flags.makefile);
for (Stmt* stmt : mk->stmts()) {
LOG("%s", stmt->DebugString().c_str());
- stmt->Eval(ev);
+ stmt->Eval(ev.get());
}
}
@@ -186,7 +279,7 @@ static int Run(const vector<Symbol>& targets,
vector<DepNode*> nodes;
{
ScopedTimeReporter tr("make dep time");
- MakeDep(ev, ev->rules(), ev->rule_vars(), targets, &nodes);
+ MakeDep(ev.get(), ev->rules(), ev->rule_vars(), targets, &nodes);
}
if (g_flags.is_syntax_check_only)
@@ -194,7 +287,7 @@ static int Run(const vector<Symbol>& targets,
if (g_flags.generate_ninja) {
ScopedTimeReporter tr("generate ninja time");
- GenerateNinja(nodes, ev, orig_args, start_time);
+ GenerateNinja(nodes, ev.get(), orig_args, start_time);
ev->DumpStackStats();
return 0;
}
@@ -203,7 +296,7 @@ static int Run(const vector<Symbol>& targets,
const Symbol name = p.first;
if (p.second) {
Var* v = ev->LookupVar(name);
- const string&& value = v->Eval(ev);
+ const string&& value = v->Eval(ev.get());
LOG("setenv(%s, %s)", name.c_str(), value.c_str());
setenv(name.c_str(), value.c_str(), 1);
} else {
@@ -214,14 +307,13 @@ static int Run(const vector<Symbol>& targets,
{
ScopedTimeReporter tr("exec time");
- Exec(nodes, ev);
+ Exec(nodes, ev.get());
}
ev->DumpStackStats();
for (Stmt* stmt : bootstrap_asts)
delete stmt;
- delete ev;
delete cache_mgr;
return 0;
diff --git a/testcase/segfault_stack_overflow.sh b/testcase/segfault_stack_overflow.sh
new file mode 100644
index 0000000..2ba425b
--- /dev/null
+++ b/testcase/segfault_stack_overflow.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Copyright 2017 Google Inc. All rights reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -u
+
+mk="$@"
+
+cat <<EOF > Makefile
+a = \$(a) \$(a) \$(a) \$(a)
+\$(a)
+EOF
+
+if echo "${mk}" | grep -qv "kati"; then
+ # Make detects this differently
+ echo 'Segmentation fault, last evaluated line was Makefile:2'
+else
+ # runtest.rb strips *kati*: lines, so strip that prefix to test
+ # Grab only *kati* lines, since asan may print a backtrace
+ ${mk} 2>&1 | grep "*kati*" | sed "s/^\*kati\*: //"
+fi