This is an automated email from the ASF dual-hosted git repository.
zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 2ec58216929 [fix](be) Guard empty stack trace formatting (#65731)
2ec58216929 is described below
commit 2ec58216929c12ff9aff9f043627ab8984628dad
Author: zclllyybb <[email protected]>
AuthorDate: Fri Jul 17 10:31:20 2026 +0800
[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.
---
be/src/common/stack_trace.cpp | 23 ++++++++++++++++++-----
be/test/common/status_test.cpp | 29 +++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/be/src/common/stack_trace.cpp b/be/src/common/stack_trace.cpp
index 9ff2b3e7931..8ab6d76414a 100644
--- a/be/src/common/stack_trace.cpp
+++ b/be/src/common/stack_trace.cpp
@@ -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,
diff --git a/be/test/common/status_test.cpp b/be/test/common/status_test.cpp
index f607e56f701..be555a0912c 100644
--- a/be/test/common/status_test.cpp
+++ b/be/test/common/status_test.cpp
@@ -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();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]