ok: basic crash handling and stack trace dumps
This seems to work pretty nicely for each engine in the expected case of
few crashes. The serial and thread engines just dump the first crash
stack to stderr before dying, while the fork engine saves all crashes to
a temporary file, then prints that to stderr once everything's finished.
I'm not sold on this TLS solution as being the best way to know what
was running when we crashed, but it's better than printing nothing.
Change-Id: I0aca66529301b1ad9bd51ec728848817586c606d
Reviewed-on: https://skia-review.googlesource.com/10102
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Mike Klein <mtklein@chromium.org>
diff --git a/tools/ok.cpp b/tools/ok.cpp
index d0d3960..06c417a 100644
--- a/tools/ok.cpp
+++ b/tools/ok.cpp
@@ -26,6 +26,65 @@
#include <stdlib.h>
#include <thread>
+#if !defined(__has_include)
+ #define __has_include(x) 0
+#endif
+
+static thread_local const char* tls_name = "";
+
+#if __has_include(<execinfo.h>) && __has_include(<fcntl.h>) && __has_include(<signal.h>)
+ #include <execinfo.h>
+ #include <fcntl.h>
+ #include <signal.h>
+
+ static int crash_stacktrace_fd = 2/*stderr*/;
+
+ static void setup_crash_handler() {
+ static void (*original_handlers[32])(int);
+
+ for (int sig : std::vector<int>{ SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV }) {
+ original_handlers[sig] = signal(sig, [](int sig) {
+ auto ez_write = [](const char* str) {
+ write(crash_stacktrace_fd, str, strlen(str));
+ };
+ ez_write("\ncaught signal ");
+ switch (sig) {
+ #define CASE(s) case s: ez_write(#s); break
+ CASE(SIGABRT);
+ CASE(SIGBUS);
+ CASE(SIGFPE);
+ CASE(SIGILL);
+ CASE(SIGSEGV);
+ #undef CASE
+ }
+ ez_write(" while running '");
+ ez_write(tls_name);
+ ez_write("'\n");
+
+ void* stack[128];
+ int frames = backtrace(stack, sizeof(stack)/sizeof(*stack));
+ backtrace_symbols_fd(stack, frames, crash_stacktrace_fd);
+ signal(sig, original_handlers[sig]);
+ raise(sig);
+ });
+ }
+ }
+
+ static void defer_crash_stacktraces() {
+ crash_stacktrace_fd = fileno(tmpfile());
+ atexit([] {
+ lseek(crash_stacktrace_fd, 0, SEEK_SET);
+ char buf[1024];
+ while (size_t bytes = read(crash_stacktrace_fd, buf, sizeof(buf))) {
+ write(2, buf, bytes);
+ }
+ });
+ }
+#else
+ static void setup_crash_handler() {}
+ static void defer_crash_stacktraces() {}
+#endif
+
enum class Status { OK, Failed, Crashed, Skipped, None };
struct Engine {
@@ -83,7 +142,7 @@
struct ForkEngine : Engine {
bool spawn(std::function<Status(void)> fn) override {
switch (fork()) {
- case 0: exit((int)fn());
+ case 0: _exit((int)fn());
case -1: return false;
default: return true;
}
@@ -227,6 +286,7 @@
int main(int argc, char** argv) {
SkGraphics::Init();
+ setup_crash_handler();
int jobs {1};
std::regex match {".*"};
@@ -279,9 +339,9 @@
if (!stream) { return help(); }
std::unique_ptr<Engine> engine;
- if (jobs == 0) { engine.reset(new SerialEngine); }
- if (jobs > 0) { engine.reset(new ForkEngine); }
- if (jobs < 0) { engine.reset(new ThreadEngine); jobs = -jobs; }
+ if (jobs == 0) { engine.reset(new SerialEngine); }
+ if (jobs > 0) { engine.reset(new ForkEngine); defer_crash_stacktraces(); }
+ if (jobs < 0) { engine.reset(new ThreadEngine); jobs = -jobs; }
if (jobs == 1) { jobs = std::thread::hardware_concurrency(); }
@@ -328,6 +388,7 @@
std::unique_ptr<Src> src{raw};
auto name = src->name();
+ tls_name = name.c_str();
if (!std::regex_match (name, match) ||
!std::regex_search(name, search)) {
return Status::Skipped;