diff --git a/benchmarking/benchmarking.cc b/benchmarking/benchmarking.cc index c8e5af7ea3dd5..97b7f868e335a 100644 --- a/benchmarking/benchmarking.cc +++ b/benchmarking/benchmarking.cc @@ -4,11 +4,13 @@ #include "benchmarking.h" +#include "flutter/fml/backtrace.h" #include "flutter/fml/icu_util.h" namespace benchmarking { int Main(int argc, char** argv) { + fml::InstallCrashHandler(); benchmark::Initialize(&argc, argv); fml::icu::InitializeICU("icudtl.dat"); ::benchmark::RunSpecifiedBenchmarks(); diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 6056147ddc95f..40a2a0d36b830 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -104,6 +104,10 @@ FILE: ../../../flutter/flow/view_holder.cc FILE: ../../../flutter/flow/view_holder.h FILE: ../../../flutter/flutter_frontend_server/bin/starter.dart FILE: ../../../flutter/flutter_frontend_server/lib/server.dart +FILE: ../../../flutter/fml/backtrace.cc +FILE: ../../../flutter/fml/backtrace.h +FILE: ../../../flutter/fml/backtrace_stub.cc +FILE: ../../../flutter/fml/backtrace_unittests.cc FILE: ../../../flutter/fml/base32.cc FILE: ../../../flutter/fml/base32.h FILE: ../../../flutter/fml/base32_unittest.cc diff --git a/fml/BUILD.gn b/fml/BUILD.gn index d8c7a95d682ef..356b8e2baa79d 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -10,6 +10,7 @@ import("//flutter/testing/testing.gni") source_set("fml") { sources = [ + "backtrace.h", "base32.cc", "base32.h", "build_config.h", @@ -86,6 +87,12 @@ source_set("fml") { "wakeable.h", ] + if (is_mac || is_ios || is_linux) { + sources += [ "backtrace.cc" ] + } else { + sources += [ "backtrace_stub.cc" ] + } + public_deps = [] deps = [ @@ -229,6 +236,7 @@ executable("fml_unittests") { testonly = true sources = [ + "backtrace_unittests.cc", "base32_unittest.cc", "command_line_unittest.cc", "file_unittest.cc", diff --git a/fml/backtrace.cc b/fml/backtrace.cc new file mode 100644 index 0000000000000..66dd0d5e01584 --- /dev/null +++ b/fml/backtrace.cc @@ -0,0 +1,136 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/backtrace.h" + +#include +#include + +#include +#include +#include + +#include "flutter/fml/logging.h" + +namespace fml { + +static std::string kKUnknownFrameName = "Unknown"; + +static std::string DemangleSymbolName(const std::string& mangled) { + if (mangled == kKUnknownFrameName) { + return kKUnknownFrameName; + } + + int status = 0; + size_t length = 0; + char* demangled = __cxxabiv1::__cxa_demangle( + mangled.data(), // mangled name + nullptr, // output buffer (malloc-ed if nullptr) + &length, // demangled length + &status); + + if (demangled == nullptr || status != 0) { + return mangled; + } + + auto demangled_string = std::string{demangled, length}; + free(demangled); + return demangled_string; +} + +static std::string GetSymbolName(void* symbol) { + Dl_info info = {}; + + if (::dladdr(symbol, &info) == 0) { + return kKUnknownFrameName; + } + + return DemangleSymbolName({info.dli_sname}); +} + +std::string BacktraceHere(size_t offset) { + constexpr size_t kMaxFrames = 256; + void* symbols[kMaxFrames]; + const auto available_frames = ::backtrace(symbols, kMaxFrames); + if (available_frames <= 0) { + return ""; + } + + std::stringstream stream; + for (int i = 1 + offset; i < available_frames; ++i) { + stream << "Frame " << i - 1 - offset << ": " << symbols[i] << " " + << GetSymbolName(symbols[i]) << std::endl; + } + return stream.str(); +} + +static size_t kKnownSignalHandlers[] = { + SIGABRT, // abort program + SIGFPE, // floating-point exception + SIGBUS, // bus error + SIGSEGV, // segmentation violation + SIGSYS, // non-existent system call invoked + SIGPIPE, // write on a pipe with no reader + SIGALRM, // real-time timer expired + SIGTERM, // software termination signal +}; + +static std::string SignalNameToString(int signal) { + switch (signal) { + case SIGABRT: + return "SIGABRT"; + case SIGFPE: + return "SIGFPE"; + case SIGBUS: + return "SIGBUS"; + case SIGSEGV: + return "SIGSEGV"; + case SIGSYS: + return "SIGSYS"; + case SIGPIPE: + return "SIGPIPE"; + case SIGALRM: + return "SIGALRM"; + case SIGTERM: + return "SIGTERM"; + }; + return std::to_string(signal); +} + +static void ToggleSignalHandlers(bool set); + +static void SignalHandler(int signal) { + // We are a crash signal handler. This can only happen once. Since we don't + // want to catch crashes while we are generating the crash reports, disable + // all set signal handlers to their default values before reporting the crash + // and re-raising the signal. + ToggleSignalHandlers(false); + + FML_LOG(ERROR) << "Caught signal " << SignalNameToString(signal) + << " during program execution." << std::endl + << BacktraceHere(3); + + ::raise(signal); +} + +static void ToggleSignalHandlers(bool set) { + for (size_t i = 0; i < sizeof(kKnownSignalHandlers) / sizeof(size_t); ++i) { + auto signal_name = kKnownSignalHandlers[i]; + auto handler = set ? &SignalHandler : SIG_DFL; + + if (::signal(signal_name, handler) == SIG_ERR) { + FML_LOG(ERROR) << "Could not attach signal handler for " << signal_name; + } + } +} + +void InstallCrashHandler() { + ToggleSignalHandlers(true); +} + +bool IsCrashHandlingSupported() { + return true; +} + +} // namespace fml diff --git a/fml/backtrace.h b/fml/backtrace.h new file mode 100644 index 0000000000000..18f17933b355b --- /dev/null +++ b/fml/backtrace.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_BACKTRACE_H_ +#define FLUTTER_FML_BACKTRACE_H_ + +#include + +#include "flutter/fml/macros.h" + +namespace fml { + +std::string BacktraceHere(size_t offset = 0); + +void InstallCrashHandler(); + +bool IsCrashHandlingSupported(); + +} // namespace fml + +#endif // FLUTTER_FML_BACKTRACE_H_ diff --git a/fml/backtrace_stub.cc b/fml/backtrace_stub.cc new file mode 100644 index 0000000000000..2328ec45aa2a9 --- /dev/null +++ b/fml/backtrace_stub.cc @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/backtrace.h" + +namespace fml { + +static std::string kKUnknownFrameName = "Unknown"; + +std::string BacktraceHere(size_t offset) { + return ""; +} + +void InstallCrashHandler() { + // Not supported. +} + +bool IsCrashHandlingSupported() { + return false; +} + +} // namespace fml diff --git a/fml/backtrace_unittests.cc b/fml/backtrace_unittests.cc new file mode 100644 index 0000000000000..574fced91c662 --- /dev/null +++ b/fml/backtrace_unittests.cc @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "backtrace.h" +#include "gtest/gtest.h" +#include "logging.h" + +namespace fml { +namespace testing { + +TEST(BacktraceTest, CanGatherBacktrace) { + if (!IsCrashHandlingSupported()) { + GTEST_SKIP(); + return; + } + { + auto trace = BacktraceHere(0); + ASSERT_GT(trace.size(), 0u); + ASSERT_NE(trace.find("Frame 0"), std::string::npos); + } + + { + auto trace = BacktraceHere(1); + ASSERT_GT(trace.size(), 0u); + ASSERT_NE(trace.find("Frame 0"), std::string::npos); + } + + { + auto trace = BacktraceHere(2); + ASSERT_GT(trace.size(), 0u); + ASSERT_NE(trace.find("Frame 0"), std::string::npos); + } +} + +} // namespace testing +} // namespace fml diff --git a/testing/run_all_unittests.cc b/testing/run_all_unittests.cc index 8e579246eb639..eb1ae28256bc3 100644 --- a/testing/run_all_unittests.cc +++ b/testing/run_all_unittests.cc @@ -6,6 +6,7 @@ #include #include +#include "flutter/fml/backtrace.h" #include "flutter/fml/build_config.h" #include "flutter/fml/command_line.h" #include "flutter/testing/debugger_detection.h" @@ -35,6 +36,7 @@ std::optional GetTestTimeoutFromArgs(int argc, char** argv) { } int main(int argc, char** argv) { + fml::InstallCrashHandler(); #ifdef OS_IOS asl_log_descriptor(NULL, NULL, ASL_LEVEL_NOTICE, STDOUT_FILENO, ASL_LOG_DESCRIPTOR_WRITE);