mirror of
https://github.com/apache/doris.git
synced 2026-11-04 07:31:17 +00:00
[fix](be) Guard empty stack trace formatting (#65731)
An empty or too-short libunwind stack trace underflows the remaining-frame count in `StackTrace::toString()` after it skips its internal frames. Formatting an error `Status` then reads beyond the fixed frame array and can crash the BE. Treat non-positive stack-capture results as empty, render a trace with fewer frames than the requested skip as the existing `<Empty trace>`, and preserve signal-context offsets after slicing. A debug-point BEUT injects an empty trace into the real `Status::NotFound` capture/logging path.
This commit is contained in:
1 parent
4df43e95c3
commit
2ec5821692
2 files changed
+47
-5
No files matched your search
@@ -44,6 +44,7 @@
|
||||
#include "common/memory_sanitizer.h"
|
||||
#include "common/symbol_index.h"
|
||||
#include "exec/common/hex.h"
|
||||
#include "util/debug_points.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace {
|
||||
@@ -304,11 +305,17 @@ StackTrace::StackTrace(const ucontext_t& signal_context) {
|
||||
}
|
||||
|
||||
void StackTrace::tryCapture() {
|
||||
int frame_count = 0;
|
||||
#if defined(USE_UNWIND) && USE_UNWIND && defined(__x86_64__)
|
||||
size = unw_backtrace(frame_pointers.data(), capacity);
|
||||
frame_count = unw_backtrace(frame_pointers.data(), capacity);
|
||||
#else
|
||||
size = backtrace(frame_pointers.data(), capacity);
|
||||
frame_count = backtrace(frame_pointers.data(), capacity);
|
||||
#endif
|
||||
size = frame_count > 0 ? static_cast<size_t>(frame_count) : 0;
|
||||
if (doris::config::enable_debug_points &&
|
||||
doris::DebugPoints::instance()->is_enable("StackTrace::tryCapture.empty_trace")) {
|
||||
size = 0;
|
||||
}
|
||||
__msan_unpoison(frame_pointers.data(), size * sizeof(frame_pointers[0]));
|
||||
}
|
||||
|
||||
@@ -480,11 +487,17 @@ std::string StackTrace::toString(int start_pointers_index,
|
||||
const std::string& dwarf_location_info_mode) const {
|
||||
// Default delete the first three frame pointers, which are inside the stack_trace.cpp.
|
||||
start_pointers_index += 3;
|
||||
if (start_pointers_index < 0 || static_cast<size_t>(start_pointers_index) >= size) {
|
||||
// Unwind can legitimately return fewer frames than the internal frames we normally hide.
|
||||
return toStringCached({}, 0, 0, dwarf_location_info_mode);
|
||||
}
|
||||
|
||||
const auto first_frame = static_cast<size_t>(start_pointers_index);
|
||||
StackTrace::FramePointers frame_pointers_raw {};
|
||||
std::copy(frame_pointers.begin() + start_pointers_index, frame_pointers.end(),
|
||||
std::copy(frame_pointers.begin() + first_frame, frame_pointers.end(),
|
||||
frame_pointers_raw.begin());
|
||||
return toStringCached(frame_pointers_raw, offset, size - start_pointers_index,
|
||||
dwarf_location_info_mode);
|
||||
return toStringCached(frame_pointers_raw, offset > first_frame ? offset - first_frame : 0,
|
||||
size - first_frame, dwarf_location_info_mode);
|
||||
}
|
||||
|
||||
std::string StackTrace::toString(void** frame_pointers_raw, size_t offset, size_t size,
|
||||
|
||||
@@ -21,11 +21,33 @@
|
||||
#include <gtest/gtest-test-part.h>
|
||||
|
||||
#include "gtest/gtest_pred_impl.h"
|
||||
#include "util/debug_points.h"
|
||||
|
||||
namespace doris {
|
||||
|
||||
class StatusTest : public testing::Test {};
|
||||
|
||||
class ScopedEmptyStackTraceFault {
|
||||
public:
|
||||
ScopedEmptyStackTraceFault()
|
||||
: _enable_debug_points(config::enable_debug_points),
|
||||
_enable_stacktrace(config::enable_stacktrace) {
|
||||
config::enable_debug_points = true;
|
||||
config::enable_stacktrace = true;
|
||||
DebugPoints::instance()->add("StackTrace::tryCapture.empty_trace");
|
||||
}
|
||||
|
||||
~ScopedEmptyStackTraceFault() {
|
||||
DebugPoints::instance()->remove("StackTrace::tryCapture.empty_trace");
|
||||
config::enable_stacktrace = _enable_stacktrace;
|
||||
config::enable_debug_points = _enable_debug_points;
|
||||
}
|
||||
|
||||
private:
|
||||
const bool _enable_debug_points;
|
||||
const bool _enable_stacktrace;
|
||||
};
|
||||
|
||||
TEST_F(StatusTest, OK) {
|
||||
// default
|
||||
Status st;
|
||||
@@ -117,6 +139,13 @@ TEST_F(StatusTest, Error) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(StatusTest, EmptyCapturedStackTrace) {
|
||||
ScopedEmptyStackTraceFault fault;
|
||||
|
||||
Status status = Status::NotFound("fault-injected empty stack trace");
|
||||
EXPECT_NE(status.to_string().find("<Empty trace>"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(StatusTest /*unused*/, Format /*unused*/) {
|
||||
// status == ok
|
||||
Status st_ok = Status::OK();
|
||||
|
||||
Reference in new issue
Block a user