Skip to content

Commit 05d9f80

Browse files
committed
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.
1 parent 528264d commit 05d9f80

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
@@ -766,8 +766,11 @@ class backtrace_buffer {
766766
static constexpr unsigned _max_size = 8 << 10;
767767
unsigned _pos = 0;
768768
char _buf[_max_size];
769+
bool _immediate;
769770
public:
770-
backtrace_buffer() = default;
771+
backtrace_buffer(bool immediate = false)
772+
: _immediate{immediate} {}
773+
771774
~backtrace_buffer() {
772775
flush();
773776
}
@@ -779,23 +782,30 @@ class backtrace_buffer {
779782
backtrace_buffer &operator = (backtrace_buffer &&) = delete;
780783

781784
void flush() noexcept {
782-
if (_pos > 0) {
785+
if (!_immediate && _pos > 0) {
783786
print_safe(_buf, _pos);
784787
_pos = 0;
785788
}
786789
}
787790

788791
void reserve(size_t len) noexcept {
792+
if (_immediate) {
793+
return;
794+
}
789795
SEASTAR_ASSERT(len < _max_size);
790796
if (_pos + len >= _max_size) {
791797
flush();
792798
}
793799
}
794800

795801
void append(const char* str, size_t len) noexcept {
796-
reserve(len);
797-
memcpy(_buf + _pos, str, len);
798-
_pos += len;
802+
if (_immediate) {
803+
print_safe(str, len);
804+
} else {
805+
reserve(len);
806+
memcpy(_buf + _pos, str, len);
807+
_pos += len;
808+
}
799809
}
800810

801811
void append(const char* str) noexcept { append(str, strlen(str)); }
@@ -825,15 +835,15 @@ class backtrace_buffer {
825835
append("0x");
826836
append_hex(f.addr);
827837
append("\n");
828-
});
838+
}, _immediate);
829839
}
830840

831841
void append_backtrace_oneline() noexcept {
832842
backtrace([this] (frame f) noexcept {
833843
reserve(3 + sizeof(f.addr) * 2);
834844
append(" 0x");
835845
append_hex(f.addr);
836-
});
846+
}, _immediate);
837847
}
838848
};
839849

@@ -856,8 +866,17 @@ static void print_with_backtrace(backtrace_buffer& buf, bool oneline) noexcept {
856866
}
857867
}
858868

859-
static void print_with_backtrace(const char* cause, bool oneline = false) noexcept {
860-
backtrace_buffer buf;
869+
// Print the current backtrace to stdout with the given cause.
870+
// If oneline is true, backtrace is printed entirely on one line,
871+
// otherwise it is printed with 1 line per frame.
872+
// If immediate is true, the backtrace is printed frame by frame
873+
// with a call to write(2), otherwise it is printed in a single
874+
// call to write(2). The former strategy is more robust in cases
875+
// where the backtrace itself may crash at some point down the stack
876+
// while the latter is more efficient and avoids splitting output
877+
// in the face of concurrent logging by other shards.
878+
static void print_with_backtrace(const char* cause, bool oneline = false, bool immediate = false) noexcept {
879+
backtrace_buffer buf(immediate);
861880
buf.append(cause);
862881
print_with_backtrace(buf, oneline);
863882
}
@@ -4053,7 +4072,9 @@ static void sigsegv_action(siginfo_t *info, ucontext_t* uc) noexcept {
40534072
print_zero_padded_hex_safe(f.so->end - f.so->begin);
40544073
print_safe("]\n");
40554074

4056-
print_with_backtrace("Segmentation fault");
4075+
// print the backtrace in immediate mode, so if we crash
4076+
// during the backtrace we get as much output as possible
4077+
print_with_backtrace("Segmentation fault", false, true);
40574078
reraise_signal(SIGSEGV);
40584079
}
40594080

0 commit comments

Comments
 (0)