Skip to content

Commit fa3b67c

Browse files
travisdownsxemul
authored andcommitted
Print incrementally in sigsegv handler
If we crash due to segfault, we want to print the backtrace incrementally, frame-by-frame, rather than all at once. This is because the backtrace call may crash at some problematic frame, in which case we get no output at all: these are very hard to diagnose. Instead, introduce an "immediate" mode to backtrace buffer which prints all appended strings immediately, and add an incremental mode to the backtrace function which calls ::backtrace repeatedly, increasing the number of requested frames by 1 each time. If we crash at some frame N we will have printed all frames 1..N-1 already. Before this change the backtrace looks like this using the reproducer in #2697: ``` Backtrace: ``` After it looks like: ``` Backtrace: /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x36ce6a /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x32fbba /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x342d0b /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x35cf6c /lib/x86_64-linux-gnu/libc.so.6+0x4251f /lib/x86_64-linux-gnu/libgcc_s.so.1+0x17585 /lib/x86_64-linux-gnu/libc.so.6+0x133bb2 /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x36d0d7 /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x32fb84 /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x32f0e7 /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x330281 /lib/x86_64-linux-gnu/libc.so.6+0x4251f /lib/x86_64-linux-gnu/libgcc_s.so.1+0x14789 /lib/x86_64-linux-gnu/libgcc_s.so.1+0x16f54 /home/tdowns/dev/seastar/build-clang-18/dev/libseastar.so+0x2b98a8 /lib/x86_64-linux-gnu/libgcc_s.so.1+0x173b8 ``` Fixes #2710. Closes #2712
1 parent b843f5a commit fa3b67c

File tree

2 files changed

+56
-15
lines changed

2 files changed

+56
-15
lines changed

Diff for: include/seastar/util/backtrace.hh

+25-5
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,36 @@ bool operator==(const frame& a, const frame& b) noexcept;
6161
frame decorate(uintptr_t addr) noexcept;
6262

6363
// Invokes func for each frame passing it as argument.
64+
// incremental=false is the default mode and simply calls ::backtrace once
65+
// and then calls func for each frame. If the ::backtrace call crashes,
66+
// func will not be called.
67+
// incremental=true will call ::backtrace in a loop, increasing the number of
68+
// frames to capture by one each time. This is useful for cases where
69+
// ::backtrace itself may crash, e.g., in a crash handler, in this case the
70+
// func will be called for each frame which didn't crash. See #2710. This
71+
// is much slower, however.
6472
SEASTAR_MODULE_EXPORT
6573
template<typename Func>
66-
void backtrace(Func&& func) noexcept(noexcept(func(frame()))) {
74+
void backtrace(Func&& func, bool incremental = false) noexcept(noexcept(func(frame()))) {
6775
#ifdef HAVE_EXECINFO
6876
constexpr size_t max_backtrace = 100;
6977
void* buffer[max_backtrace];
70-
int n = ::backtrace(buffer, max_backtrace);
71-
for (int i = 0; i < n; ++i) {
72-
auto ip = reinterpret_cast<uintptr_t>(buffer[i]);
73-
func(decorate(ip - 1));
78+
79+
if (incremental) {
80+
for (size_t last_frame = 1; last_frame <= max_backtrace; ++last_frame) {
81+
int n = ::backtrace(buffer, last_frame);
82+
if (n < static_cast<int>(last_frame)) {
83+
return;
84+
}
85+
auto ip = reinterpret_cast<uintptr_t>(buffer[last_frame-1]);
86+
func(decorate(ip - 1));
87+
}
88+
} else {
89+
int n = ::backtrace(buffer, max_backtrace);
90+
for (int i = 0; i < n; ++i) {
91+
auto ip = reinterpret_cast<uintptr_t>(buffer[i]);
92+
func(decorate(ip - 1));
93+
}
7494
}
7595
#else
7696
// Not implemented yet

Diff for: src/core/reactor.cc

+31-10
Original file line numberDiff line numberDiff line change
@@ -749,8 +749,11 @@ class backtrace_buffer {
749749
static constexpr unsigned _max_size = 8 << 10;
750750
unsigned _pos = 0;
751751
char _buf[_max_size];
752+
bool _immediate;
752753
public:
753-
backtrace_buffer() = default;
754+
backtrace_buffer(bool immediate = false)
755+
: _immediate{immediate} {}
756+
754757
~backtrace_buffer() {
755758
flush();
756759
}
@@ -762,23 +765,30 @@ class backtrace_buffer {
762765
backtrace_buffer &operator = (backtrace_buffer &&) = delete;
763766

764767
void flush() noexcept {
765-
if (_pos > 0) {
768+
if (!_immediate && _pos > 0) {
766769
print_safe(_buf, _pos);
767770
_pos = 0;
768771
}
769772
}
770773

771774
void reserve(size_t len) noexcept {
775+
if (_immediate) {
776+
return;
777+
}
772778
SEASTAR_ASSERT(len < _max_size);
773779
if (_pos + len >= _max_size) {
774780
flush();
775781
}
776782
}
777783

778784
void append(const char* str, size_t len) noexcept {
779-
reserve(len);
780-
memcpy(_buf + _pos, str, len);
781-
_pos += len;
785+
if (_immediate) {
786+
print_safe(str, len);
787+
} else {
788+
reserve(len);
789+
memcpy(_buf + _pos, str, len);
790+
_pos += len;
791+
}
782792
}
783793

784794
void append(const char* str) noexcept { append(str, strlen(str)); }
@@ -808,15 +818,15 @@ class backtrace_buffer {
808818
append("0x");
809819
append_hex(f.addr);
810820
append("\n");
811-
});
821+
}, _immediate);
812822
}
813823

814824
void append_backtrace_oneline() noexcept {
815825
backtrace([this] (frame f) noexcept {
816826
reserve(3 + sizeof(f.addr) * 2);
817827
append(" 0x");
818828
append_hex(f.addr);
819-
});
829+
}, _immediate);
820830
}
821831
};
822832

@@ -839,8 +849,17 @@ static void print_with_backtrace(backtrace_buffer& buf, bool oneline) noexcept {
839849
}
840850
}
841851

842-
static void print_with_backtrace(const char* cause, bool oneline = false) noexcept {
843-
backtrace_buffer buf;
852+
// Print the current backtrace to stdout with the given cause.
853+
// If oneline is true, backtrace is printed entirely on one line,
854+
// otherwise it is printed with 1 line per frame.
855+
// If immediate is true, the backtrace is printed frame by frame
856+
// with a call to write(2), otherwise it is printed in a single
857+
// call to write(2). The former strategy is more robust in cases
858+
// where the backtrace itself may crash at some point down the stack
859+
// while the latter is more efficient and avoids splitting output
860+
// in the face of concurrent logging by other shards.
861+
static void print_with_backtrace(const char* cause, bool oneline = false, bool immediate = false) noexcept {
862+
backtrace_buffer buf(immediate);
844863
buf.append(cause);
845864
print_with_backtrace(buf, oneline);
846865
}
@@ -4027,7 +4046,9 @@ static void sigsegv_action(siginfo_t *info, ucontext_t* uc) noexcept {
40274046
print_zero_padded_hex_safe(f.so->end - f.so->begin);
40284047
print_safe("]\n");
40294048

4030-
print_with_backtrace("Segmentation fault");
4049+
// print the backtrace in immediate mode, so if we crash
4050+
// during the backtrace we get as much output as possible
4051+
print_with_backtrace("Segmentation fault", false, true);
40314052
reraise_signal(SIGSEGV);
40324053
}
40334054

0 commit comments

Comments
 (0)