diff options
author | Dan Willemsen <dwillemsen@google.com> | 2017-10-11 18:35:09 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@google.com> | 2017-10-13 13:50:55 -0700 |
commit | fcc71f888a7aaf65ce344386731b90dc2ef61e18 (patch) | |
tree | bca3e345fe366b511d6ff22c045b499103f8477c | |
parent | 36e5729db554afaea6fe9b23f3caa87b6c8cc80d (diff) | |
download | kati-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.cc | 112 | ||||
-rw-r--r-- | testcase/segfault_stack_overflow.sh | 33 |
2 files changed, 135 insertions, 10 deletions
@@ -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 |