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]

Reply via email to