Skip to content

Commit 7c2d975

Browse files
authored
Gather demangled stack traces and report the same to console on crashes. (flutter#16450)
These should only be used on host binaries for more detailed crash reports. Installing the handler on targets (iOS/Android) may cause use to break existing crash reporting mechanisms users may have installed themselves in the process. This should work on Darwin & Linux for now. Doing something like int* a = nullptr; *a = 12; or abort or tripping an assertion should print something the following before program termination. We can tweak the report further if necessary. ``` [ERROR:flutter/fml/backtrace.cc(110)] Caught signal SIGSEGV during program execution. Frame 0: 0x10658342c void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) Frame 1: 0x106555070 void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) Frame 2: 0x106554f81 testing::Test::Run() Frame 3: 0x106555dc3 testing::TestInfo::Run() Frame 4: 0x1065570a1 testing::TestSuite::Run() Frame 5: 0x106562a55 testing::internal::UnitTestImpl::RunAllTests() Frame 6: 0x10658c22c bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) Frame 7: 0x1065625c3 bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) Frame 8: 0x106562445 testing::UnitTest::Run() Frame 9: 0x105c8dc33 RUN_ALL_TESTS() Frame 10: 0x105c8dbe6 main Frame 11: 0x7fff7c2dc3d5 start ``` Known issue: This routines that generate the stack trace are not signal safe. But since we only use the same before the process is terminating, this ought to be fine. I’ll work in a separate patch to convert all the internals to be signal safe. In the meantime, this will help us better identify the causes of flakes on our bots. Fixes flutter/flutter#50244
1 parent 17e07c5 commit 7c2d975

File tree

8 files changed

+234
-0
lines changed

8 files changed

+234
-0
lines changed

benchmarking/benchmarking.cc

+2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
#include "benchmarking.h"
66

7+
#include "flutter/fml/backtrace.h"
78
#include "flutter/fml/icu_util.h"
89

910
namespace benchmarking {
1011

1112
int Main(int argc, char** argv) {
13+
fml::InstallCrashHandler();
1214
benchmark::Initialize(&argc, argv);
1315
fml::icu::InitializeICU("icudtl.dat");
1416
::benchmark::RunSpecifiedBenchmarks();

ci/licenses_golden/licenses_flutter

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ FILE: ../../../flutter/flow/view_holder.cc
107107
FILE: ../../../flutter/flow/view_holder.h
108108
FILE: ../../../flutter/flutter_frontend_server/bin/starter.dart
109109
FILE: ../../../flutter/flutter_frontend_server/lib/server.dart
110+
FILE: ../../../flutter/fml/backtrace.cc
111+
FILE: ../../../flutter/fml/backtrace.h
112+
FILE: ../../../flutter/fml/backtrace_stub.cc
113+
FILE: ../../../flutter/fml/backtrace_unittests.cc
110114
FILE: ../../../flutter/fml/base32.cc
111115
FILE: ../../../flutter/fml/base32.h
112116
FILE: ../../../flutter/fml/base32_unittest.cc

fml/BUILD.gn

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import("//flutter/testing/testing.gni")
1010

1111
source_set("fml") {
1212
sources = [
13+
"backtrace.h",
1314
"base32.cc",
1415
"base32.h",
1516
"build_config.h",
@@ -86,6 +87,12 @@ source_set("fml") {
8687
"wakeable.h",
8788
]
8889

90+
if (is_mac || is_ios || is_linux) {
91+
sources += [ "backtrace.cc" ]
92+
} else {
93+
sources += [ "backtrace_stub.cc" ]
94+
}
95+
8996
public_deps = []
9097

9198
deps = [
@@ -229,6 +236,7 @@ executable("fml_unittests") {
229236
testonly = true
230237

231238
sources = [
239+
"backtrace_unittests.cc",
232240
"base32_unittest.cc",
233241
"command_line_unittest.cc",
234242
"file_unittest.cc",

fml/backtrace.cc

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/fml/backtrace.h"
6+
7+
#include <cxxabi.h>
8+
#include <sstream>
9+
10+
#include <dlfcn.h>
11+
#include <execinfo.h>
12+
#include <signal.h>
13+
14+
#include "flutter/fml/logging.h"
15+
16+
namespace fml {
17+
18+
static std::string kKUnknownFrameName = "Unknown";
19+
20+
static std::string DemangleSymbolName(const std::string& mangled) {
21+
if (mangled == kKUnknownFrameName) {
22+
return kKUnknownFrameName;
23+
}
24+
25+
int status = 0;
26+
size_t length = 0;
27+
char* demangled = __cxxabiv1::__cxa_demangle(
28+
mangled.data(), // mangled name
29+
nullptr, // output buffer (malloc-ed if nullptr)
30+
&length, // demangled length
31+
&status);
32+
33+
if (demangled == nullptr || status != 0) {
34+
return mangled;
35+
}
36+
37+
auto demangled_string = std::string{demangled, length};
38+
free(demangled);
39+
return demangled_string;
40+
}
41+
42+
static std::string GetSymbolName(void* symbol) {
43+
Dl_info info = {};
44+
45+
if (::dladdr(symbol, &info) == 0) {
46+
return kKUnknownFrameName;
47+
}
48+
49+
return DemangleSymbolName({info.dli_sname});
50+
}
51+
52+
std::string BacktraceHere(size_t offset) {
53+
constexpr size_t kMaxFrames = 256;
54+
void* symbols[kMaxFrames];
55+
const auto available_frames = ::backtrace(symbols, kMaxFrames);
56+
if (available_frames <= 0) {
57+
return "";
58+
}
59+
60+
std::stringstream stream;
61+
for (int i = 1 + offset; i < available_frames; ++i) {
62+
stream << "Frame " << i - 1 - offset << ": " << symbols[i] << " "
63+
<< GetSymbolName(symbols[i]) << std::endl;
64+
}
65+
return stream.str();
66+
}
67+
68+
static size_t kKnownSignalHandlers[] = {
69+
SIGABRT, // abort program
70+
SIGFPE, // floating-point exception
71+
SIGBUS, // bus error
72+
SIGSEGV, // segmentation violation
73+
SIGSYS, // non-existent system call invoked
74+
SIGPIPE, // write on a pipe with no reader
75+
SIGALRM, // real-time timer expired
76+
SIGTERM, // software termination signal
77+
};
78+
79+
static std::string SignalNameToString(int signal) {
80+
switch (signal) {
81+
case SIGABRT:
82+
return "SIGABRT";
83+
case SIGFPE:
84+
return "SIGFPE";
85+
case SIGBUS:
86+
return "SIGBUS";
87+
case SIGSEGV:
88+
return "SIGSEGV";
89+
case SIGSYS:
90+
return "SIGSYS";
91+
case SIGPIPE:
92+
return "SIGPIPE";
93+
case SIGALRM:
94+
return "SIGALRM";
95+
case SIGTERM:
96+
return "SIGTERM";
97+
};
98+
return std::to_string(signal);
99+
}
100+
101+
static void ToggleSignalHandlers(bool set);
102+
103+
static void SignalHandler(int signal) {
104+
// We are a crash signal handler. This can only happen once. Since we don't
105+
// want to catch crashes while we are generating the crash reports, disable
106+
// all set signal handlers to their default values before reporting the crash
107+
// and re-raising the signal.
108+
ToggleSignalHandlers(false);
109+
110+
FML_LOG(ERROR) << "Caught signal " << SignalNameToString(signal)
111+
<< " during program execution." << std::endl
112+
<< BacktraceHere(3);
113+
114+
::raise(signal);
115+
}
116+
117+
static void ToggleSignalHandlers(bool set) {
118+
for (size_t i = 0; i < sizeof(kKnownSignalHandlers) / sizeof(size_t); ++i) {
119+
auto signal_name = kKnownSignalHandlers[i];
120+
auto handler = set ? &SignalHandler : SIG_DFL;
121+
122+
if (::signal(signal_name, handler) == SIG_ERR) {
123+
FML_LOG(ERROR) << "Could not attach signal handler for " << signal_name;
124+
}
125+
}
126+
}
127+
128+
void InstallCrashHandler() {
129+
ToggleSignalHandlers(true);
130+
}
131+
132+
bool IsCrashHandlingSupported() {
133+
return true;
134+
}
135+
136+
} // namespace fml

fml/backtrace.h

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FML_BACKTRACE_H_
6+
#define FLUTTER_FML_BACKTRACE_H_
7+
8+
#include <string>
9+
10+
#include "flutter/fml/macros.h"
11+
12+
namespace fml {
13+
14+
std::string BacktraceHere(size_t offset = 0);
15+
16+
void InstallCrashHandler();
17+
18+
bool IsCrashHandlingSupported();
19+
20+
} // namespace fml
21+
22+
#endif // FLUTTER_FML_BACKTRACE_H_

fml/backtrace_stub.cc

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/fml/backtrace.h"
6+
7+
namespace fml {
8+
9+
static std::string kKUnknownFrameName = "Unknown";
10+
11+
std::string BacktraceHere(size_t offset) {
12+
return "";
13+
}
14+
15+
void InstallCrashHandler() {
16+
// Not supported.
17+
}
18+
19+
bool IsCrashHandlingSupported() {
20+
return false;
21+
}
22+
23+
} // namespace fml

fml/backtrace_unittests.cc

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "backtrace.h"
6+
#include "gtest/gtest.h"
7+
#include "logging.h"
8+
9+
namespace fml {
10+
namespace testing {
11+
12+
TEST(BacktraceTest, CanGatherBacktrace) {
13+
if (!IsCrashHandlingSupported()) {
14+
GTEST_SKIP();
15+
return;
16+
}
17+
{
18+
auto trace = BacktraceHere(0);
19+
ASSERT_GT(trace.size(), 0u);
20+
ASSERT_NE(trace.find("Frame 0"), std::string::npos);
21+
}
22+
23+
{
24+
auto trace = BacktraceHere(1);
25+
ASSERT_GT(trace.size(), 0u);
26+
ASSERT_NE(trace.find("Frame 0"), std::string::npos);
27+
}
28+
29+
{
30+
auto trace = BacktraceHere(2);
31+
ASSERT_GT(trace.size(), 0u);
32+
ASSERT_NE(trace.find("Frame 0"), std::string::npos);
33+
}
34+
}
35+
36+
} // namespace testing
37+
} // namespace fml

testing/run_all_unittests.cc

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <optional>
77
#include <string>
88

9+
#include "flutter/fml/backtrace.h"
910
#include "flutter/fml/build_config.h"
1011
#include "flutter/fml/command_line.h"
1112
#include "flutter/testing/debugger_detection.h"
@@ -35,6 +36,7 @@ std::optional<fml::TimeDelta> GetTestTimeoutFromArgs(int argc, char** argv) {
3536
}
3637

3738
int main(int argc, char** argv) {
39+
fml::InstallCrashHandler();
3840
#ifdef OS_IOS
3941
asl_log_descriptor(NULL, NULL, ASL_LEVEL_NOTICE, STDOUT_FILENO,
4042
ASL_LOG_DESCRIPTOR_WRITE);

0 commit comments

Comments
 (0)