You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by yi...@apache.org on 2023/07/19 14:59:47 UTC

[doris] branch branch-2.0 updated: [branch-2.0](cherry-pick) introduce libunwind speed up stack trace (#21981)

This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-2.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-2.0 by this push:
     new e8410d61fe [branch-2.0](cherry-pick) introduce libunwind speed up stack trace (#21981)
e8410d61fe is described below

commit e8410d61fe77fa15335c39fed4a712b5b9013dfb
Author: Xinyi Zou <zo...@gmail.com>
AuthorDate: Wed Jul 19 22:59:36 2023 +0800

    [branch-2.0](cherry-pick) introduce libunwind speed up stack trace (#21981)
    
    * [chore](third-party) Introduce libunwind (#21938)
    
    * [fix](stacktrace) Speed up stack trace (#21755)
    
    Introduce libunwind get stack trace, cost is negligible and has line numbers.
    use StackTraceCache, PHDRCache speed up, is customizable and has some optimizations.
    Other stack trace tools remain: glog, boost, glibc, in case for need.
    
    TODO:
    
    currently support linux __x86_64__, __arm__, __powerpc__, not supported __FreeBSD__, APPLE
    Note: __arm__, __powerpc__ not been verified
    Support signal handle
    libunwid support unw_backtrace for jemalloc
    Use of undefined compile option USE_MUSL for later
---
 be/CMakeLists.txt                |   20 +
 be/src/common/config.cpp         |    2 +
 be/src/common/config.h           |    3 +
 be/src/common/dwarf.cpp          | 2035 ++++++++++++++++++++++++++++++++++++++
 be/src/common/dwarf.h            |  479 +++++++++
 be/src/common/elf.cpp            |  243 +++++
 be/src/common/elf.h              |   95 ++
 be/src/common/memory_sanitizer.h |   50 +
 be/src/common/multi_version.h    |   66 ++
 be/src/common/phdr_cache.cpp     |  147 +++
 be/src/common/phdr_cache.h       |   40 +
 be/src/common/stack_trace.cpp    |  455 +++++++++
 be/src/common/stack_trace.h      |   97 ++
 be/src/common/status.h           |    2 +-
 be/src/common/symbol_index.cpp   |  559 +++++++++++
 be/src/common/symbol_index.h     |  114 +++
 be/src/service/doris_main.cpp    |    5 +
 be/src/util/defer_op.h           |    2 +-
 be/src/util/stack_util.cpp       |   59 +-
 be/src/util/stack_util.h         |   33 +-
 build.sh                         |   10 +
 run-be-ut.sh                     |    9 +
 thirdparty/build-thirdparty.sh   |   27 +
 thirdparty/vars.sh               |    7 +
 24 files changed, 4550 insertions(+), 9 deletions(-)

diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt
index 5844e80d42..0e5aab86c9 100644
--- a/be/CMakeLists.txt
+++ b/be/CMakeLists.txt
@@ -71,10 +71,12 @@ if (NOT OS_MACOSX)
     option(GLIBC_COMPATIBILITY "Enable compatibility with older glibc libraries." ON)
     option(USE_LIBCPP "Use libc++" OFF)
     option(USE_MEM_TRACKER, "Use memory tracker" ON)
+    option(USE_UNWIND "Use libunwind" ON)
 else()
     option(GLIBC_COMPATIBILITY "Enable compatibility with older glibc libraries." OFF)
     option(USE_LIBCPP "Use libc++" ON)
     option(USE_MEM_TRACKER, "Use memory tracker" OFF)
+    option(USE_UNWIND "Use libunwind" OFF)
 endif()
 option(USE_JEMALLOC "Use jemalloc" ON)
 
@@ -86,6 +88,7 @@ message(STATUS "GLIBC_COMPATIBILITY is ${GLIBC_COMPATIBILITY}")
 message(STATUS "USE_LIBCPP is ${USE_LIBCPP}")
 message(STATUS "USE_MEM_TRACKER is ${USE_MEM_TRACKER}")
 message(STATUS "USE_JEMALLOC is ${USE_JEMALLOC}")
+message(STATUS "USE_UNWIND is ${USE_UNWIND}")
 message(STATUS "ENABLE_PCH is ${ENABLE_PCH}")
 
 # set CMAKE_BUILD_TYPE
@@ -251,6 +254,11 @@ set_target_properties(jemalloc PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/li
 add_library(jemalloc_arrow STATIC IMPORTED)
 set_target_properties(jemalloc_arrow PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib64/libjemalloc.a)
 
+if (USE_UNWIND)
+    add_library(libunwind STATIC IMPORTED)
+    set_target_properties(libunwind PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib64/libunwind.a)
+endif()
+
 add_library(brotlicommon STATIC IMPORTED)
 set_target_properties(brotlicommon PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib64/libbrotlicommon.a)
 
@@ -597,6 +605,14 @@ if (USE_JEMALLOC)
     add_compile_options(-DUSE_JEMALLOC)
 endif()
 
+# Compile with libunwind
+if (USE_UNWIND)
+    add_compile_options(-DUSE_UNWIND)
+    if (COMPILER_CLANG)
+        add_compile_options(-gdwarf-aranges)
+    endif()
+endif()
+
 # Use Bthread to separate computation and IO
 # Adding the option `USE_BTHREAD_SCANNER=ON sh build.sh` when comliling can enable this feature
 if (USE_BTHREAD_SCANNER)
@@ -879,6 +895,10 @@ if (WITH_MYSQL)
     set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} mysql)
 endif()
 
+if (USE_UNWIND)
+    set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} libunwind)
+endif()
+
 set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} orc)
 set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} ic)
 set(DORIS_DEPENDENCIES ${DORIS_DEPENDENCIES} clucene-core-static)
diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index d4eaeb6b09..d5cace0c31 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -1032,6 +1032,8 @@ DEFINE_Int32(rocksdb_max_write_buffer_number, "5");
 DEFINE_Bool(allow_invalid_decimalv2_literal, "false");
 DEFINE_mInt64(kerberos_expiration_time_seconds, "43200");
 
+DEFINE_mString(get_stack_trace_tool, "libunwind");
+
 #ifdef BE_TEST
 // test s3
 DEFINE_String(test_s3_resource, "resource");
diff --git a/be/src/common/config.h b/be/src/common/config.h
index d2adce91f1..7e63e87a6d 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -1053,6 +1053,9 @@ DECLARE_mBool(allow_invalid_decimalv2_literal);
 // than this time, it will be expired.
 DECLARE_mInt64(kerberos_expiration_time_seconds);
 
+// Values include `none`, `glog`, `boost`, `glibc`, `libunwind`
+DECLARE_mString(get_stack_trace_tool);
+
 #ifdef BE_TEST
 // test s3
 DECLARE_String(test_s3_resource);
diff --git a/be/src/common/dwarf.cpp b/be/src/common/dwarf.cpp
new file mode 100644
index 0000000000..fb5a3739d6
--- /dev/null
+++ b/be/src/common/dwarf.cpp
@@ -0,0 +1,2035 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/Dwarf.cpp
+// and modified by Doris
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+/*
+ * Copyright 2012-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** This file was edited for ClickHouse.
+  */
+
+#include "common/dwarf.h"
+
+#include <cstring>
+
+#include "common/elf.h"
+#include "common/logging.h"
+
+#define DW_CHILDREN_no 0
+
+#define DW_FORM_addr 1
+#define DW_FORM_block1 0x0a
+#define DW_FORM_block2 3
+#define DW_FORM_block4 4
+#define DW_FORM_block 9
+#define DW_FORM_exprloc 0x18
+#define DW_FORM_data1 0x0b
+#define DW_FORM_ref1 0x11
+#define DW_FORM_data2 0x05
+#define DW_FORM_ref2 0x12
+#define DW_FORM_data4 0x06
+#define DW_FORM_ref4 0x13
+#define DW_FORM_data8 0x07
+#define DW_FORM_ref8 0x14
+#define DW_FORM_ref_sig8 0x20
+#define DW_FORM_sdata 0x0d
+#define DW_FORM_udata 0x0f
+#define DW_FORM_ref_udata 0x15
+#define DW_FORM_flag 0x0c
+#define DW_FORM_flag_present 0x19
+#define DW_FORM_sec_offset 0x17
+#define DW_FORM_ref_addr 0x10
+#define DW_FORM_string 0x08
+#define DW_FORM_strp 0x0e
+#define DW_FORM_indirect 0x16
+#define DW_FORM_strx 0x1a
+#define DW_FORM_addrx 0x1b
+#define DW_FORM_ref_sup4 0x1c
+#define DW_FORM_strp_sup 0x1d
+#define DW_FORM_data16 0x1e
+#define DW_FORM_line_strp 0x1f
+#define DW_FORM_implicit_const 0x21
+#define DW_FORM_rnglistx 0x23
+#define DW_FORM_loclistx 0x22
+#define DW_FORM_ref_sup8 0x24
+#define DW_FORM_strx1 0x25
+#define DW_FORM_strx2 0x26
+#define DW_FORM_strx3 0x27
+#define DW_FORM_strx4 0x28
+#define DW_FORM_addrx1 0x29
+#define DW_FORM_addrx2 0x2a
+#define DW_FORM_addrx3 0x2b
+#define DW_FORM_addrx4 0x2c
+
+#define DW_TAG_compile_unit 0x11
+#define DW_TAG_subprogram 0x2e
+#define DW_TAG_try_block 0x32
+#define DW_TAG_catch_block 0x25
+#define DW_TAG_entry_point 0x03
+#define DW_TAG_common_block 0x1a
+#define DW_TAG_lexical_block 0x0b
+
+#define DW_AT_stmt_list 0x10
+#define DW_AT_comp_dir 0x1b
+#define DW_AT_name 0x03
+#define DW_AT_high_pc 0x12
+#define DW_AT_low_pc 0x11
+#define DW_AT_entry_pc 0x52
+#define DW_AT_ranges 0x55
+#define DW_AT_abstract_origin 0x31
+#define DW_AT_call_line 0x59
+#define DW_AT_call_file 0x58
+#define DW_AT_linkage_name 0x6e
+#define DW_AT_specification 0x47
+#define DW_AT_str_offsets_base 0x72
+#define DW_AT_addr_base 0x73
+#define DW_AT_rnglists_base 0x74
+#define DW_AT_loclists_base 0x8c
+#define DW_AT_GNU_ranges_base 0x2132
+#define DW_AT_GNU_addr_base 0x2133
+
+#define DW_LNE_define_file 0x03
+#define DW_LNS_copy 0x01
+#define DW_LNS_advance_pc 0x02
+#define DW_LNS_advance_line 0x03
+#define DW_LNS_set_file 0x04
+#define DW_LNS_set_column 0x05
+#define DW_LNS_negate_stmt 0x06
+#define DW_LNS_set_basic_block 0x07
+#define DW_LNS_const_add_pc 0x08
+#define DW_LNS_fixed_advance_pc 0x09
+#define DW_LNS_set_prologue_end 0x0a
+#define DW_LNS_set_epilogue_begin 0x0b
+#define DW_LNS_set_isa 0x0c
+#define DW_LNE_end_sequence 0x01
+#define DW_LNE_set_address 0x02
+#define DW_LNE_set_discriminator 0x04
+
+#define DW_LNCT_path 0x1
+#define DW_LNCT_directory_index 0x2
+#define DW_LNCT_timestamp 0x3
+#define DW_LNCT_size 0x4
+#define DW_LNCT_MD5 0x5
+
+#define DW_RLE_end_of_list 0x0
+#define DW_RLE_base_addressx 0x1
+#define DW_RLE_startx_endx 0x2
+#define DW_RLE_startx_length 0x3
+#define DW_RLE_offset_pair 0x4
+#define DW_RLE_base_address 0x5
+#define DW_RLE_start_end 0x6
+#define DW_RLE_start_length 0x7
+
+namespace doris {
+
+Dwarf::Dwarf(const std::shared_ptr<Elf>& elf)
+        : elf_(elf),
+          abbrev_(getSection(".debug_abbrev")),
+          addr_(getSection(".debug_addr")),
+          aranges_(getSection(".debug_aranges")),
+          info_(getSection(".debug_info")),
+          line_(getSection(".debug_line")),
+          line_str_(getSection(".debug_line_str")),
+          loclists_(getSection(".debug_loclists")),
+          ranges_(getSection(".debug_ranges")),
+          rnglists_(getSection(".debug_rnglists")),
+          str_(getSection(".debug_str")),
+          str_offsets_(getSection(".debug_str_offsets")) {
+    // Optional sections:
+    //  - debugAranges_: for fast address range lookup.
+    //     If missing .debug_info can be used - but it's much slower (linear
+    //     scan).
+    //  - debugRanges_ (DWARF 4) / debugRnglists_ (DWARF 5): non-contiguous
+    //    address ranges of debugging information entries.
+    //    Used for inline function address lookup.
+    if (info_.empty() || abbrev_.empty() || line_.empty() || str_.empty()) {
+        elf_ = nullptr;
+    }
+}
+
+Dwarf::Section::Section(std::string_view d) : is64_bit(false), data(d) {}
+
+#define SAFE_CHECK(cond, ...)                                \
+    do {                                                     \
+        if (!(cond)) LOG(FATAL) << fmt::format(__VA_ARGS__); \
+    } while (false)
+
+namespace {
+// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to
+// speed up inline function lookup.
+const uint32_t kMaxAbbreviationEntries = 1000;
+
+// All following read* functions read from a std::string_view, advancing the
+// std::string_view, and aborting if there's not enough room.
+
+// Read (bitwise) one object of type T
+template <typename T>
+    requires std::is_trivial_v<T> && std::is_standard_layout_v<T>
+T read(std::string_view& sp) {
+    SAFE_CHECK(sp.size() >= sizeof(T), "underflow: expected bytes {}, got bytes {}", sizeof(T),
+               sp.size());
+    T x;
+    memcpy(&x, sp.data(), sizeof(T));
+    sp.remove_prefix(sizeof(T));
+    return x;
+}
+
+// Read (bitwise) an unsigned number of N bytes (N in 1, 2, 3, 4).
+template <size_t N>
+uint64_t readU64(std::string_view& sp) {
+    SAFE_CHECK(sp.size() >= N, "underflow");
+    uint64_t x = 0;
+    memcpy(&x, sp.data(), N);
+    sp.remove_prefix(N);
+    return x;
+}
+
+// Read ULEB (unsigned) varint value; algorithm from the DWARF spec
+uint64_t readULEB(std::string_view& sp, uint8_t& shift, uint8_t& val) {
+    uint64_t r = 0;
+    shift = 0;
+    do {
+        val = read<uint8_t>(sp);
+        r |= (uint64_t(val & 0x7f) << shift);
+        shift += 7;
+    } while (val & 0x80);
+    return r;
+}
+
+uint64_t readULEB(std::string_view& sp) {
+    uint8_t shift;
+    uint8_t val;
+    return readULEB(sp, shift, val);
+}
+
+// Read SLEB (signed) varint value; algorithm from the DWARF spec
+int64_t readSLEB(std::string_view& sp) {
+    uint8_t shift;
+    uint8_t val;
+    uint64_t r = readULEB(sp, shift, val);
+
+    if (shift < 64 && (val & 0x40)) {
+        r |= -(1ULL << shift); // sign extend
+    }
+
+    return r;
+}
+
+// Read a value of "section offset" type, which may be 4 or 8 bytes
+uint64_t readOffset(std::string_view& sp, bool is64_bit) {
+    return is64_bit ? read<uint64_t>(sp) : read<uint32_t>(sp);
+}
+
+// Read "len" bytes
+std::string_view readBytes(std::string_view& sp, uint64_t len) {
+    SAFE_CHECK(len <= sp.size(), "invalid string length: {} vs. {}", len, sp.size());
+    std::string_view ret(sp.data(), len);
+    sp.remove_prefix(len);
+    return ret;
+}
+
+// Read a null-terminated string
+std::string_view readNullTerminated(std::string_view& sp) {
+    const char* p = static_cast<const char*>(memchr(sp.data(), 0, sp.size()));
+    SAFE_CHECK(p, "invalid null-terminated string");
+    std::string_view ret(sp.data(), p - sp.data());
+    sp = std::string_view(p + 1, sp.size());
+    return ret;
+}
+
+// Get a string from the section
+std::string_view getStringFromStringSection(std::string_view section, uint64_t offset) {
+    SAFE_CHECK(offset < section.size(), "invalid section offset");
+    std::string_view sp(section);
+    sp.remove_prefix(offset);
+    return readNullTerminated(sp);
+}
+
+// Skip over padding until sp.data() - start is a multiple of alignment
+void skipPadding(std::string_view& sp, const char* start, size_t alignment) {
+    size_t remainder = (sp.data() - start) % alignment;
+    if (remainder) {
+        SAFE_CHECK(alignment - remainder <= sp.size(), "invalid padding");
+        sp.remove_prefix(alignment - remainder);
+    }
+}
+
+} // namespace
+
+Dwarf::Path::Path(std::string_view baseDir, std::string_view subDir, std::string_view file)
+        : baseDir_(baseDir), subDir_(subDir), file_(file) {
+    using std::swap;
+
+    // Normalize
+    if (file_.empty()) {
+        baseDir_ = {};
+        subDir_ = {};
+        return;
+    }
+
+    if (file_[0] == '/') {
+        // file_ is absolute
+        baseDir_ = {};
+        subDir_ = {};
+    }
+
+    if (!subDir_.empty() && subDir_[0] == '/') {
+        baseDir_ = {}; // subDir_ is absolute
+    }
+
+    // Make sure it's never the case that baseDir_ is empty, but subDir_ isn't.
+    if (baseDir_.empty()) {
+        swap(baseDir_, subDir_);
+    }
+}
+
+size_t Dwarf::Path::size() const {
+    size_t size = 0;
+    bool needs_slash = false;
+
+    if (!baseDir_.empty()) {
+        size += baseDir_.size();
+        needs_slash = baseDir_.back() != '/';
+    }
+
+    if (!subDir_.empty()) {
+        size += needs_slash;
+        size += subDir_.size();
+        needs_slash = subDir_.back() != '/';
+    }
+
+    if (!file_.empty()) {
+        size += needs_slash;
+        size += file_.size();
+    }
+
+    return size;
+}
+
+size_t Dwarf::Path::toBuffer(char* buf, size_t bufSize) const {
+    size_t total_size = 0;
+    bool needs_slash = false;
+
+    auto append = [&](std::string_view sp) {
+        if (bufSize >= 2) {
+            size_t to_copy = std::min(sp.size(), bufSize - 1);
+            memcpy(buf, sp.data(), to_copy);
+            buf += to_copy;
+            bufSize -= to_copy;
+        }
+        total_size += sp.size();
+    };
+
+    if (!baseDir_.empty()) {
+        append(baseDir_);
+        needs_slash = baseDir_.back() != '/';
+    }
+    if (!subDir_.empty()) {
+        if (needs_slash) {
+            append("/");
+        }
+        append(subDir_);
+        needs_slash = subDir_.back() != '/';
+    }
+    if (!file_.empty()) {
+        if (needs_slash) {
+            append("/");
+        }
+        append(file_);
+    }
+    if (bufSize) {
+        *buf = '\0';
+    }
+
+    SAFE_CHECK(total_size == size(), "Size mismatch");
+    return total_size;
+}
+
+void Dwarf::Path::toString(std::string& dest) const {
+    size_t initial_size = dest.size();
+    dest.reserve(initial_size + size());
+    if (!baseDir_.empty()) {
+        dest.append(baseDir_.begin(), baseDir_.end());
+    }
+    if (!subDir_.empty()) {
+        if (!dest.empty() && dest.back() != '/') {
+            dest.push_back('/');
+        }
+        dest.append(subDir_.begin(), subDir_.end());
+    }
+    if (!file_.empty()) {
+        if (!dest.empty() && dest.back() != '/') {
+            dest.push_back('/');
+        }
+        dest.append(file_.begin(), file_.end());
+    }
+    SAFE_CHECK(dest.size() == initial_size + size(), "Size mismatch");
+}
+
+// Next chunk in section
+bool Dwarf::Section::next(std::string_view& chunk) {
+    chunk = data;
+    if (chunk.empty()) {
+        return false;
+    }
+
+    // Initial length is a uint32_t value for a 32-bit section, and
+    // a 96-bit value (0xffffffff followed by the 64-bit length) for a 64-bit
+    // section.
+    auto initial_length = read<uint32_t>(chunk);
+    is64_bit = (initial_length == uint32_t(-1));
+    auto length = is64_bit ? read<uint64_t>(chunk) : initial_length;
+    SAFE_CHECK(length <= chunk.size(), "invalid DWARF section");
+    chunk = std::string_view(chunk.data(), length);
+    data = std::string_view(chunk.end(), data.end() - chunk.end());
+    return true;
+}
+
+std::string_view Dwarf::getSection(const char* name) const {
+    std::optional<Elf::Section> elf_section = elf_->findSectionByName(name);
+    if (!elf_section) {
+        return {};
+    }
+
+#ifdef SHF_COMPRESSED
+    if (elf_section->header.sh_flags & SHF_COMPRESSED) {
+        return {};
+    }
+#endif
+
+    return {elf_section->begin(), elf_section->size()};
+}
+
+// static
+bool Dwarf::readAbbreviation(std::string_view& section, DIEAbbreviation& abbr) {
+    // abbreviation code
+    abbr.code = readULEB(section);
+    if (abbr.code == 0) {
+        return false;
+    }
+
+    // abbreviation tag
+    abbr.tag = readULEB(section);
+
+    // does this entry have children?
+    abbr.has_children = (read<uint8_t>(section) != DW_CHILDREN_no);
+
+    // attributes
+    const char* attribute_begin = section.data();
+    for (;;) {
+        SAFE_CHECK(!section.empty(), "invalid attribute section");
+        auto attr = readAttributeSpec(section);
+        if (attr.name == 0 && attr.form == 0) {
+            break;
+        }
+    }
+
+    abbr.attributes = std::string_view(attribute_begin, section.data() - attribute_begin);
+    return true;
+}
+
+// static
+void Dwarf::readCompilationUnitAbbrs(std::string_view abbrev, CompilationUnit& cu) {
+    abbrev.remove_prefix(cu.abbrev_offset);
+
+    DIEAbbreviation abbr;
+    while (readAbbreviation(abbrev, abbr)) {
+        // Abbreviation code 0 is reserved for null debugging information entries.
+        if (abbr.code != 0 && abbr.code <= kMaxAbbreviationEntries) {
+            cu.abbr_cache[abbr.code - 1] = abbr;
+        }
+    }
+}
+
+size_t Dwarf::forEachChild(const CompilationUnit& cu, const Die& die,
+                           std::function<bool(const Die& die)> f) const {
+    size_t next_die_offset = forEachAttribute(cu, die, [&](const Attribute&) { return true; });
+    if (!die.abbr.has_children) {
+        return next_die_offset;
+    }
+
+    auto child_die = getDieAtOffset(cu, next_die_offset);
+    while (child_die.code != 0) {
+        if (!f(child_die)) {
+            return child_die.offset;
+        }
+
+        // NOTE: Don't run `f` over grandchildren, just skip over them.
+        size_t sibling_offset = forEachChild(cu, child_die, [](const Die&) { return true; });
+        child_die = getDieAtOffset(cu, sibling_offset);
+    }
+
+    // childDie is now a dummy die whose offset is to the code 0 marking the
+    // end of the children. Need to add one to get the offset of the next die.
+    return child_die.offset + 1;
+}
+
+/*
+ * Iterate over all attributes of the given DIE, calling the given callable
+ * for each. Iteration is stopped early if any of the calls return false.
+ */
+size_t Dwarf::forEachAttribute(const CompilationUnit& cu, const Die& die,
+                               std::function<bool(const Attribute& die)> f) const {
+    auto attrs = die.abbr.attributes;
+    auto values = std::string_view {info_.data() + die.offset + die.attr_offset,
+                                    cu.offset + cu.size - die.offset - die.attr_offset};
+    while (auto spec = readAttributeSpec(attrs)) {
+        auto attr = readAttribute(cu, die, spec, values);
+        if (!f(attr)) {
+            return static_cast<size_t>(-1);
+        }
+    }
+    return values.data() - info_.data();
+}
+
+Dwarf::Attribute Dwarf::readAttribute(const CompilationUnit& cu, const Die& die, AttributeSpec spec,
+                                      std::string_view& info) const {
+    // DWARF 5 introduces new FORMs whose values are relative to some base attrs:
+    // DW_AT_str_offsets_base, DW_AT_rnglists_base, DW_AT_addr_base.
+    // Debug Fission DWARF 4 uses GNU DW_AT_GNU_ranges_base & DW_AT_GNU_addr_base.
+    //
+    // The order in which attributes appear in a CU is not defined.
+    // The DW_AT_*_base attrs may appear after attributes that need them.
+    // The DW_AT_*_base attrs are CU specific; so we read them just after
+    // reading the CU header. During this first pass return empty values
+    // when encountering a FORM that depends on DW_AT_*_base.
+    auto get_string_using_offset_table = [&](uint64_t index) {
+        if (!cu.str_offsets_base.has_value()) {
+            return std::string_view();
+        }
+        // DWARF 5: 7.26 String Offsets Table
+        // The DW_AT_str_offsets_base attribute points to the first entry following
+        // the header. The entries are indexed sequentially from this base entry,
+        // starting from 0.
+        auto sp = str_offsets_.substr(*cu.str_offsets_base +
+                                      index * (cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t)));
+        uint64_t str_offset = readOffset(sp, cu.is64Bit);
+        return getStringFromStringSection(str_, str_offset);
+    };
+
+    auto read_debug_addr = [&](uint64_t index) {
+        if (!cu.addr_base.has_value()) {
+            return uint64_t(0);
+        }
+        // DWARF 5: 7.27 Address Table
+        // The DW_AT_addr_base attribute points to the first entry following the
+        // header. The entries are indexed sequentially from this base entry,
+        // starting from 0.
+        auto sp = addr_.substr(*cu.addr_base + index * sizeof(uint64_t));
+        return read<uint64_t>(sp);
+    };
+
+    switch (spec.form) {
+    case DW_FORM_addr:
+        return {spec, die, read<uintptr_t>(info)};
+    case DW_FORM_block1:
+        return {spec, die, readBytes(info, read<uint8_t>(info))};
+    case DW_FORM_block2:
+        return {spec, die, readBytes(info, read<uint16_t>(info))};
+    case DW_FORM_block4:
+        return {spec, die, readBytes(info, read<uint32_t>(info))};
+    case DW_FORM_block:
+        [[fallthrough]];
+    case DW_FORM_exprloc:
+        return {spec, die, readBytes(info, readULEB(info))};
+    case DW_FORM_data1:
+        [[fallthrough]];
+    case DW_FORM_ref1:
+        return {spec, die, read<uint8_t>(info)};
+    case DW_FORM_data2:
+        [[fallthrough]];
+    case DW_FORM_ref2:
+        return {spec, die, read<uint16_t>(info)};
+    case DW_FORM_data4:
+        [[fallthrough]];
+    case DW_FORM_ref4:
+        return {spec, die, read<uint32_t>(info)};
+    case DW_FORM_data8:
+        [[fallthrough]];
+    case DW_FORM_ref8:
+        [[fallthrough]];
+    case DW_FORM_ref_sig8:
+        return {spec, die, read<uint64_t>(info)};
+    case DW_FORM_sdata:
+        return {spec, die, static_cast<uint64_t>(readSLEB(info))};
+    case DW_FORM_udata:
+        [[fallthrough]];
+    case DW_FORM_ref_udata:
+        return {spec, die, readULEB(info)};
+    case DW_FORM_flag:
+        return {spec, die, read<uint8_t>(info)};
+    case DW_FORM_flag_present:
+        return {spec, die, 1ULL};
+    case DW_FORM_sec_offset:
+        [[fallthrough]];
+    case DW_FORM_ref_addr:
+        return {spec, die, readOffset(info, die.is64Bit)};
+    case DW_FORM_string:
+        return {spec, die, readNullTerminated(info)};
+    case DW_FORM_strp:
+        return {spec, die, getStringFromStringSection(str_, readOffset(info, die.is64Bit))};
+    case DW_FORM_indirect: // form is explicitly specified
+        // Update spec with the actual FORM.
+        spec.form = readULEB(info);
+        return readAttribute(cu, die, spec, info);
+
+    // DWARF 5:
+    case DW_FORM_implicit_const: // form is explicitly specified
+        // For attributes with this form, the attribute specification contains a
+        // third part, which is a signed LEB128 number. The value of this number
+        // is used as the value of the attribute, and no value is stored in the
+        // .debug_info section.
+        return {spec, die, static_cast<uint64_t>(spec.implicitConst)};
+
+    case DW_FORM_addrx:
+        return {spec, die, read_debug_addr(readULEB(info))};
+    case DW_FORM_addrx1:
+        return {spec, die, read_debug_addr(readU64<1>(info))};
+    case DW_FORM_addrx2:
+        return {spec, die, read_debug_addr(readU64<2>(info))};
+    case DW_FORM_addrx3:
+        return {spec, die, read_debug_addr(readU64<3>(info))};
+    case DW_FORM_addrx4:
+        return {spec, die, read_debug_addr(readU64<4>(info))};
+
+    case DW_FORM_line_strp:
+        return {spec, die, getStringFromStringSection(line_str_, readOffset(info, die.is64Bit))};
+
+    case DW_FORM_strx:
+        return {spec, die, get_string_using_offset_table(readULEB(info))};
+    case DW_FORM_strx1:
+        return {spec, die, get_string_using_offset_table(readU64<1>(info))};
+    case DW_FORM_strx2:
+        return {spec, die, get_string_using_offset_table(readU64<2>(info))};
+    case DW_FORM_strx3:
+        return {spec, die, get_string_using_offset_table(readU64<3>(info))};
+    case DW_FORM_strx4:
+        return {spec, die, get_string_using_offset_table(readU64<4>(info))};
+
+    case DW_FORM_rnglistx: {
+        auto index = readULEB(info);
+        if (!cu.rnglists_base.has_value()) {
+            return {spec, die, 0ULL};
+        }
+        const uint64_t offset_size = cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
+        auto sp = rnglists_.substr(*cu.rnglists_base + index * offset_size);
+        auto offset = readOffset(sp, cu.is64Bit);
+        return {spec, die, *cu.rnglists_base + offset};
+    }
+
+    case DW_FORM_loclistx: {
+        auto index = readULEB(info);
+        if (!cu.loclists_base.has_value()) {
+            return {spec, die, 0ULL};
+        }
+        const uint64_t offset_size = cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
+        auto sp = loclists_.substr(*cu.loclists_base + index * offset_size);
+        auto offset = readOffset(sp, cu.is64Bit);
+        return {spec, die, *cu.loclists_base + offset};
+    }
+
+    case DW_FORM_data16:
+        return {spec, die, readBytes(info, 16)};
+
+    case DW_FORM_ref_sup4:
+    case DW_FORM_ref_sup8:
+    case DW_FORM_strp_sup:
+        SAFE_CHECK(false, "Unexpected DWARF5 supplimentary object files");
+
+    default:
+        SAFE_CHECK(false, "invalid attribute form");
+    }
+    return {spec, die, 0ULL};
+}
+
+// static
+Dwarf::AttributeSpec Dwarf::readAttributeSpec(std::string_view& sp) {
+    Dwarf::AttributeSpec spec;
+    spec.name = readULEB(sp);
+    spec.form = readULEB(sp);
+    if (spec.form == DW_FORM_implicit_const) {
+        spec.implicitConst = readSLEB(sp);
+    }
+    return spec;
+}
+
+Dwarf::CompilationUnit Dwarf::getCompilationUnit(uint64_t offset) const {
+    // SAFE_CHECK(offset < info_.size(), "unexpected offset");
+    CompilationUnit cu;
+    std::string_view chunk(info_);
+    cu.offset = offset;
+    chunk.remove_prefix(offset);
+
+    // 1) unit_length
+    auto initial_length = read<uint32_t>(chunk);
+    cu.is64Bit = (initial_length == uint32_t(-1));
+    cu.size = cu.is64Bit ? read<uint64_t>(chunk) : initial_length;
+    SAFE_CHECK(cu.size <= chunk.size(), "invalid chunk size");
+    cu.size += cu.is64Bit ? 12 : 4;
+
+    // 2) version
+    cu.version = read<uint16_t>(chunk);
+    SAFE_CHECK(cu.version >= 2 && cu.version <= 5, "invalid info version");
+
+    if (cu.version == 5) {
+        // DWARF5: 7.5.1.1 Full and Partial Compilation Unit Headers
+        // 3) unit_type (new DWARF 5)
+        cu.unit_type = read<uint8_t>(chunk);
+        if (cu.unit_type != DW_UT_compile && cu.unit_type != DW_UT_skeleton) {
+            return cu;
+        }
+        // 4) address_size
+        cu.addr_size = read<uint8_t>(chunk);
+        SAFE_CHECK(cu.addr_size == sizeof(uintptr_t), "invalid address size");
+
+        // 5) debug_abbrev_offset
+        cu.abbrev_offset = readOffset(chunk, cu.is64Bit);
+
+        if (cu.unit_type == DW_UT_skeleton) {
+            // 6) dwo_id
+            read<uint64_t>(chunk);
+        }
+    } else {
+        // DWARF4 has a single type of unit in .debug_info
+        cu.unit_type = DW_UT_compile;
+        // 3) debug_abbrev_offset
+        cu.abbrev_offset = readOffset(chunk, cu.is64Bit);
+        // 4) address_size
+        cu.addr_size = read<uint8_t>(chunk);
+        SAFE_CHECK(cu.addr_size == sizeof(uintptr_t), "invalid address size");
+    }
+    cu.first_die = chunk.data() - info_.data();
+    if (cu.version < 5) {
+        return cu;
+    }
+
+    Die die = getDieAtOffset(cu, cu.first_die);
+    if (die.abbr.tag != DW_TAG_compile_unit) {
+        return cu;
+    }
+
+    // Read the DW_AT_*_base attributes.
+    // Attributes which use FORMs relative to these base attrs
+    // will not have valid values during this first pass!
+    forEachAttribute(cu, die, [&](const Attribute& attr) {
+        switch (attr.spec.name) {
+        case DW_AT_addr_base:
+        case DW_AT_GNU_addr_base:
+            cu.addr_base = std::get<uint64_t>(attr.attr_value);
+            break;
+        case DW_AT_loclists_base:
+            cu.loclists_base = std::get<uint64_t>(attr.attr_value);
+            break;
+        case DW_AT_rnglists_base:
+        case DW_AT_GNU_ranges_base:
+            cu.rnglists_base = std::get<uint64_t>(attr.attr_value);
+            break;
+        case DW_AT_str_offsets_base:
+            cu.str_offsets_base = std::get<uint64_t>(attr.attr_value);
+            break;
+        }
+        return true; // continue forEachAttribute
+    });
+    return cu;
+}
+
+// Finds the Compilation Unit starting at offset.
+Dwarf::CompilationUnit Dwarf::findCompilationUnit(uint64_t targetOffset) const {
+    // SAFE_CHECK(targetOffset < info_.size(), "unexpected target address");
+    uint64_t offset = 0;
+    while (offset < info_.size()) {
+        std::string_view chunk(info_);
+        chunk.remove_prefix(offset);
+
+        auto initial_length = read<uint32_t>(chunk);
+        auto is64_bit = (initial_length == static_cast<uint32_t>(-1));
+        auto size = is64_bit ? read<uint64_t>(chunk) : initial_length;
+        SAFE_CHECK(size <= chunk.size(), "invalid chunk size");
+        size += is64_bit ? 12 : 4;
+
+        if (offset + size > targetOffset) {
+            break;
+        }
+        offset += size;
+    }
+    return getCompilationUnit(offset);
+}
+
+Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset) const {
+    // Linear search in the .debug_abbrev section, starting at offset
+    std::string_view section = abbrev_;
+    section.remove_prefix(offset);
+
+    Dwarf::DIEAbbreviation abbr;
+    while (readAbbreviation(section, abbr)) {
+        if (abbr.code == code) {
+            return abbr;
+        }
+    }
+
+    SAFE_CHECK(false, "could not find abbreviation code");
+}
+
+Dwarf::AttributeValue Dwarf::readAttributeValue(std::string_view& sp, uint64_t form,
+                                                bool is64_bit) const {
+    switch (form) {
+    case DW_FORM_addr:
+        return uint64_t(read<uintptr_t>(sp));
+    case DW_FORM_block1:
+        return readBytes(sp, read<uint8_t>(sp));
+    case DW_FORM_block2:
+        return readBytes(sp, read<uint16_t>(sp));
+    case DW_FORM_block4:
+        return readBytes(sp, read<uint32_t>(sp));
+    case DW_FORM_block:
+        [[fallthrough]];
+    case DW_FORM_exprloc:
+        return readBytes(sp, readULEB(sp));
+    case DW_FORM_data1:
+        [[fallthrough]];
+    case DW_FORM_ref1:
+        return uint64_t(read<uint8_t>(sp));
+    case DW_FORM_data2:
+        [[fallthrough]];
+    case DW_FORM_ref2:
+        return uint64_t(read<uint16_t>(sp));
+    case DW_FORM_data4:
+        [[fallthrough]];
+    case DW_FORM_ref4:
+        return uint64_t(read<uint32_t>(sp));
+    case DW_FORM_data8:
+        [[fallthrough]];
+    case DW_FORM_ref8:
+        return read<uint64_t>(sp);
+    case DW_FORM_sdata:
+        return uint64_t(readSLEB(sp));
+    case DW_FORM_udata:
+        [[fallthrough]];
+    case DW_FORM_ref_udata:
+        return readULEB(sp);
+    case DW_FORM_flag:
+        return uint64_t(read<uint8_t>(sp));
+    case DW_FORM_flag_present:
+        return uint64_t(1);
+    case DW_FORM_sec_offset:
+        [[fallthrough]];
+    case DW_FORM_ref_addr:
+        return readOffset(sp, is64_bit);
+    case DW_FORM_string:
+        return readNullTerminated(sp);
+    case DW_FORM_strp:
+        return getStringFromStringSection(str_, readOffset(sp, is64_bit));
+    case DW_FORM_indirect: // form is explicitly specified
+        return readAttributeValue(sp, readULEB(sp), is64_bit);
+    default:
+        SAFE_CHECK(false, "invalid attribute form");
+        return uint64_t(1);
+        ;
+    }
+}
+
+/**
+ * Find @address in .debug_aranges and return the offset in
+ * .debug_info for compilation unit to which this address belongs.
+ */
+bool Dwarf::findDebugInfoOffset(uintptr_t address, std::string_view aranges, uint64_t& offset) {
+    Section aranges_section(aranges);
+    std::string_view chunk;
+    while (aranges_section.next(chunk)) {
+        auto version = read<uint16_t>(chunk);
+        SAFE_CHECK(version == 2, "invalid aranges version");
+
+        offset = readOffset(chunk, aranges_section.is64Bit());
+        auto address_size = read<uint8_t>(chunk);
+        SAFE_CHECK(address_size == sizeof(uintptr_t), "invalid address size");
+        auto segment_size = read<uint8_t>(chunk);
+        SAFE_CHECK(segment_size == 0, "segmented architecture not supported");
+
+        // Padded to a multiple of 2 addresses.
+        // Strangely enough, this is the only place in the DWARF spec that requires
+        // padding.
+        skipPadding(chunk, aranges.data(), 2 * sizeof(uintptr_t));
+        for (;;) {
+            auto start = read<uintptr_t>(chunk);
+            auto length = read<uintptr_t>(chunk);
+
+            if (start == 0 && length == 0) {
+                break;
+            }
+
+            // Is our address in this range?
+            if (address >= start && address < start + length) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+Dwarf::Die Dwarf::getDieAtOffset(const CompilationUnit& cu, uint64_t offset) const {
+    SAFE_CHECK(offset < info_.size(), "unexpected offset {}, info size {}", offset, info_.size());
+    Die die;
+    std::string_view sp {info_.data() + offset, cu.offset + cu.size - offset};
+    die.offset = offset;
+    die.is64Bit = cu.is64Bit;
+    auto code = readULEB(sp);
+    die.code = code;
+    if (code == 0) {
+        return die;
+    }
+    die.attr_offset = sp.data() - info_.data() - offset;
+    die.abbr = !cu.abbr_cache.empty() && die.code < kMaxAbbreviationEntries
+                       ? cu.abbr_cache[die.code - 1]
+                       : getAbbreviation(die.code, cu.abbrev_offset);
+
+    return die;
+}
+
+/**
+ * Find the @locationInfo for @address in the compilation unit represented
+ * by the @sp .debug_info entry.
+ * Returns whether the address was found.
+ * Advances @sp to the next entry in .debug_info.
+ */
+bool Dwarf::findLocation(uintptr_t address, const LocationInfoMode mode, CompilationUnit& cu,
+                         LocationInfo& info, std::vector<SymbolizedFrame>& inline_frames) const {
+    Die die = getDieAtOffset(cu, cu.first_die);
+    // Partial compilation unit (DW_TAG_partial_unit) is not supported.
+    SAFE_CHECK(die.abbr.tag == DW_TAG_compile_unit, "expecting compile unit entry");
+
+    // Offset in .debug_line for the line number VM program for this CU
+    std::optional<uint64_t> line_offset = 0;
+    std::string_view compilation_directory;
+    std::optional<std::string_view> main_file_name;
+    std::optional<uint64_t> base_addr_cu;
+
+    forEachAttribute(cu, die, [&](const Attribute& attr) {
+        switch (attr.spec.name) {
+        case DW_AT_stmt_list:
+            // Offset in .debug_line for the line number VM program for this
+            // compilation unit
+            line_offset = std::get<uint64_t>(attr.attr_value);
+            break;
+        case DW_AT_comp_dir:
+            // Compilation directory
+            compilation_directory = std::get<std::string_view>(attr.attr_value);
+            break;
+        case DW_AT_name:
+            // File name of main file being compiled
+            main_file_name = std::get<std::string_view>(attr.attr_value);
+            break;
+        case DW_AT_low_pc:
+        case DW_AT_entry_pc:
+            // 2.17.1: historically DW_AT_low_pc was used. DW_AT_entry_pc was
+            // introduced in DWARF3. Support either to determine the base address of
+            // the CU.
+            base_addr_cu = std::get<uint64_t>(attr.attr_value);
+            break;
+        }
+        // Iterate through all attributes until find all above.
+        return true;
+    });
+
+    if (main_file_name) {
+        info.has_main_file = true;
+        info.main_file = Path(compilation_directory, "", *main_file_name);
+    }
+
+    if (!line_offset) {
+        return false;
+    }
+
+    std::string_view line_section(line_);
+    line_section.remove_prefix(*line_offset);
+    LineNumberVM line_vm(line_section, compilation_directory, str_, line_str_);
+
+    // Execute line number VM program to find file and line
+    info.has_file_and_line = line_vm.findAddress(address, info.file, info.line);
+
+    bool check_inline = (mode == LocationInfoMode::FULL_WITH_INLINE);
+
+    if (info.has_file_and_line && check_inline) {
+        // Re-get the compilation unit with abbreviation cached.
+        cu.abbr_cache.clear();
+        cu.abbr_cache.resize(kMaxAbbreviationEntries);
+        readCompilationUnitAbbrs(abbrev_, cu);
+
+        // Find the subprogram that matches the given address.
+        Die subprogram;
+        findSubProgramDieForAddress(cu, die, address, base_addr_cu, subprogram);
+
+        // Subprogram is the DIE of caller function.
+        if (/*check_inline &&*/ subprogram.abbr.has_children) {
+            // Use an extra location and get its call file and call line, so that
+            // they can be used for the second last location when we don't have
+            // enough inline frames for all inline functions call stack.
+            const size_t max_size = Dwarf::kMaxInlineLocationInfoPerFrame + 1;
+            std::vector<CallLocation> call_locations;
+            call_locations.reserve(Dwarf::kMaxInlineLocationInfoPerFrame + 1);
+
+            findInlinedSubroutineDieForAddress(cu, subprogram, line_vm, address, base_addr_cu,
+                                               call_locations, max_size);
+            size_t num_found = call_locations.size();
+
+            if (num_found > 0) {
+                const auto inner_most_file = info.file;
+                const auto inner_most_line = info.line;
+
+                // Earlier we filled in locationInfo:
+                // - mainFile: the path to the CU -- the file where the non-inlined
+                //   call is made from.
+                // - file + line: the location of the inner-most inlined call.
+                // Here we already find inlined info so mainFile would be redundant.
+                info.has_main_file = false;
+                info.main_file = Path {};
+                // @findInlinedSubroutineDieForAddress fills inlineLocations[0] with the
+                // file+line of the non-inlined outer function making the call.
+                // locationInfo.name is already set by the caller by looking up the
+                // non-inlined function @address belongs to.
+                info.has_file_and_line = true;
+                info.file = call_locations[0].file;
+                info.line = call_locations[0].line;
+
+                // The next inlined subroutine's call file and call line is the current
+                // caller's location.
+                for (size_t i = 0; i < num_found - 1; ++i) {
+                    call_locations[i].file = call_locations[i + 1].file;
+                    call_locations[i].line = call_locations[i + 1].line;
+                }
+                // CallLocation for the inner-most inlined function:
+                // - will be computed if enough space was available in the passed
+                //   buffer.
+                // - will have a .name, but no !.file && !.line
+                // - its corresponding file+line is the one returned by LineVM based
+                //   on @address.
+                // Use the inner-most inlined file+line info we got from the LineVM.
+                call_locations[num_found - 1].file = inner_most_file;
+                call_locations[num_found - 1].line = inner_most_line;
+
+                // Fill in inline frames in reverse order (as expected by the caller).
+                std::reverse(call_locations.begin(), call_locations.end());
+                for (const auto& call_location : call_locations) {
+                    SymbolizedFrame inline_frame;
+                    inline_frame.found = true;
+                    inline_frame.addr = address;
+                    if (!call_location.name.empty()) {
+                        inline_frame.name = call_location.name.data();
+                    } else {
+                        inline_frame.name = nullptr;
+                    }
+                    inline_frame.location.has_file_and_line = true;
+                    inline_frame.location.file = call_location.file;
+                    inline_frame.location.line = call_location.line;
+                    inline_frames.push_back(inline_frame);
+                }
+            }
+        }
+    }
+
+    return info.has_file_and_line;
+}
+
+void Dwarf::findSubProgramDieForAddress(const CompilationUnit& cu, const Die& die, uint64_t address,
+                                        std::optional<uint64_t> base_addr_cu,
+                                        Die& subprogram) const {
+    forEachChild(cu, die, [&](const Die& child_die) {
+        if (child_die.abbr.tag == DW_TAG_subprogram) {
+            std::optional<uint64_t> low_pc;
+            std::optional<uint64_t> high_pc;
+            std::optional<bool> is_high_pc_addr;
+            std::optional<uint64_t> range_offset;
+            forEachAttribute(cu, child_die, [&](const Attribute& attr) {
+                switch (attr.spec.name) {
+                case DW_AT_ranges:
+                    range_offset = std::get<uint64_t>(attr.attr_value);
+                    break;
+                case DW_AT_low_pc:
+                    low_pc = std::get<uint64_t>(attr.attr_value);
+                    break;
+                case DW_AT_high_pc:
+                    // The value of the DW_AT_high_pc attribute can be
+                    // an address (DW_FORM_addr*) or an offset (DW_FORM_data*).
+                    is_high_pc_addr = attr.spec.form == DW_FORM_addr ||   //
+                                      attr.spec.form == DW_FORM_addrx ||  //
+                                      attr.spec.form == DW_FORM_addrx1 || //
+                                      attr.spec.form == DW_FORM_addrx2 || //
+                                      attr.spec.form == DW_FORM_addrx3 || //
+                                      attr.spec.form == DW_FORM_addrx4;
+                    high_pc = std::get<uint64_t>(attr.attr_value);
+                    break;
+                }
+                // Iterate through all attributes until find all above.
+                return true;
+            });
+            bool pc_match = low_pc && high_pc && is_high_pc_addr && address >= *low_pc &&
+                            (address < (*is_high_pc_addr ? *high_pc : *low_pc + *high_pc));
+            bool range_match =
+                    range_offset && isAddrInRangeList(cu, address, base_addr_cu,
+                                                      range_offset.value(), cu.addr_size);
+            if (pc_match || range_match) {
+                subprogram = child_die;
+                return false;
+            }
+        }
+
+        findSubProgramDieForAddress(cu, child_die, address, base_addr_cu, subprogram);
+
+        // Iterates through children until find the inline subprogram.
+        return true;
+    });
+}
+
+/**
+ * Find DW_TAG_inlined_subroutine child DIEs that contain @address and
+ * then extract:
+ * - Where was it called from (DW_AT_call_file & DW_AT_call_line):
+ *   the statement or expression that caused the inline expansion.
+ * - The inlined function's name. As a function may be inlined multiple
+ *   times, common attributes like DW_AT_linkage_name or DW_AT_name
+ *   are only stored in its "concrete out-of-line instance" (a
+ *   DW_TAG_subprogram) which we find using DW_AT_abstract_origin.
+ */
+void Dwarf::findInlinedSubroutineDieForAddress(const CompilationUnit& cu, const Die& die,
+                                               const LineNumberVM& line_vm, uint64_t address,
+                                               std::optional<uint64_t> base_addr_cu,
+                                               std::vector<CallLocation>& locations,
+                                               const size_t max_size) const {
+    if (locations.size() >= max_size) {
+        return;
+    }
+
+    forEachChild(cu, die, [&](const Die& child_die) {
+        // Between a DW_TAG_subprogram and and DW_TAG_inlined_subroutine we might
+        // have arbitrary intermediary "nodes", including DW_TAG_common_block,
+        // DW_TAG_lexical_block, DW_TAG_try_block, DW_TAG_catch_block and
+        // DW_TAG_with_stmt, etc.
+        // We can't filter with locationhere since its range may be not specified.
+        // See section 2.6.2: A location list containing only an end of list entry
+        // describes an object that exists in the source code but not in the
+        // executable program.
+        if (child_die.abbr.tag == DW_TAG_try_block || child_die.abbr.tag == DW_TAG_catch_block ||
+            child_die.abbr.tag == DW_TAG_entry_point || child_die.abbr.tag == DW_TAG_common_block ||
+            child_die.abbr.tag == DW_TAG_lexical_block) {
+            findInlinedSubroutineDieForAddress(cu, child_die, line_vm, address, base_addr_cu,
+                                               locations, max_size);
+            return true;
+        }
+
+        std::optional<uint64_t> low_pc;
+        std::optional<uint64_t> high_pc;
+        std::optional<bool> is_high_pc_addr;
+        std::optional<uint64_t> abstract_origin;
+        std::optional<uint64_t> abstract_origin_ref_type;
+        std::optional<uint64_t> call_file;
+        std::optional<uint64_t> call_line;
+        std::optional<uint64_t> range_offset;
+        forEachAttribute(cu, child_die, [&](const Attribute& attr) {
+            switch (attr.spec.name) {
+            case DW_AT_ranges:
+                range_offset = std::get<uint64_t>(attr.attr_value);
+                break;
+            case DW_AT_low_pc:
+                low_pc = std::get<uint64_t>(attr.attr_value);
+                break;
+            case DW_AT_high_pc:
+                // The value of the DW_AT_high_pc attribute can be
+                // an address (DW_FORM_addr*) or an offset (DW_FORM_data*).
+                is_high_pc_addr = attr.spec.form == DW_FORM_addr ||   //
+                                  attr.spec.form == DW_FORM_addrx ||  //
+                                  attr.spec.form == DW_FORM_addrx1 || //
+                                  attr.spec.form == DW_FORM_addrx2 || //
+                                  attr.spec.form == DW_FORM_addrx3 || //
+                                  attr.spec.form == DW_FORM_addrx4;
+                high_pc = std::get<uint64_t>(attr.attr_value);
+                break;
+            case DW_AT_abstract_origin:
+                abstract_origin_ref_type = attr.spec.form;
+                abstract_origin = std::get<uint64_t>(attr.attr_value);
+                break;
+            case DW_AT_call_line:
+                call_line = std::get<uint64_t>(attr.attr_value);
+                break;
+            case DW_AT_call_file:
+                call_file = std::get<uint64_t>(attr.attr_value);
+                break;
+            }
+            // Iterate through all until find all above attributes.
+            return true;
+        });
+
+        // 2.17 Code Addresses and Ranges
+        // Any debugging information entry describing an entity that has a
+        // machine code address or range of machine code addresses,
+        // which includes compilation units, module initialization, subroutines,
+        // ordinary blocks, try/catch blocks, labels and the like, may have
+        //  - A DW_AT_low_pc attribute for a single address,
+        //  - A DW_AT_low_pc and DW_AT_high_pc pair of attributes for a
+        //    single contiguous range of addresses, or
+        //  - A DW_AT_ranges attribute for a non-contiguous range of addresses.
+        // TODO: Support DW_TAG_entry_point and DW_TAG_common_block that don't
+        // have DW_AT_low_pc/DW_AT_high_pc pairs and DW_AT_ranges.
+        // TODO: Support relocated address which requires lookup in relocation map.
+        bool pc_match = low_pc && high_pc && is_high_pc_addr && address >= *low_pc &&
+                        (address < (*is_high_pc_addr ? *high_pc : *low_pc + *high_pc));
+        bool range_match = range_offset && isAddrInRangeList(cu, address, base_addr_cu,
+                                                             range_offset.value(), cu.addr_size);
+        if (!pc_match && !range_match) {
+            // Address doesn't match. Keep searching other children.
+            return true;
+        }
+
+        if (!abstract_origin || !abstract_origin_ref_type || !call_line || !call_file) {
+            // We expect a single sibling DIE to match on addr, but it's missing
+            // required fields. Stop searching for other DIEs.
+            return false;
+        }
+
+        CallLocation location;
+        location.file = line_vm.getFullFileName(*call_file);
+        location.line = *call_line;
+
+        /// Something wrong with receiving debug info about inline.
+        /// If set to true we stop parsing DWARF.
+        bool die_for_inline_broken = false;
+
+        auto get_function_name = [&](const CompilationUnit& srcu, uint64_t die_offset) {
+            Die decl_die = getDieAtOffset(srcu, die_offset);
+            auto& die_to_look_for_name = decl_die;
+
+            Die def_die;
+            // Jump to the actual function definition instead of declaration for name
+            // and line info.
+            // DW_AT_specification: Incomplete, non-defining, or separate declaration
+            // corresponding to a declaration
+            auto offset = getAttribute<uint64_t>(srcu, decl_die, DW_AT_specification);
+            if (offset) {
+                /// FIXME: actually it's a bug in our DWARF parser.
+                ///
+                /// Most of the times compilation unit offset (srcu.offset) is some big number inside .debug_info (like 434782255).
+                /// Offset of DIE definition is some small relative number to srcu.offset (like 3518).
+                /// However in some unknown cases offset looks like global, non relative number (like 434672579) and in this
+                /// case we obviously doing something wrong parsing DWARF.
+                ///
+                /// What is important -- this bug? reproduces only with -flto=thin in release mode.
+                /// Also llvm-dwarfdump --verify ./clickhouse says that our DWARF is ok, so it's another prove
+                /// that we just doing something wrong.
+                ///
+                /// FIXME: Currently we just give up parsing DWARF for inlines when we got into this situation.
+                if (srcu.offset + offset.value() >= info_.size()) {
+                    die_for_inline_broken = true;
+                } else {
+                    def_die = getDieAtOffset(srcu, srcu.offset + offset.value());
+                    die_to_look_for_name = def_die;
+                }
+            }
+
+            std::string_view name;
+
+            if (die_for_inline_broken) {
+                return name;
+            }
+
+            // The file and line will be set in the next inline subroutine based on
+            // its DW_AT_call_file and DW_AT_call_line.
+            forEachAttribute(srcu, die_to_look_for_name, [&](const Attribute& attr) {
+                switch (attr.spec.name) {
+                case DW_AT_linkage_name:
+                    name = std::get<std::string_view>(attr.attr_value);
+                    break;
+                case DW_AT_name:
+                    // NOTE: when DW_AT_linkage_name and DW_AT_name match, dwarf
+                    // emitters omit DW_AT_linkage_name (to save space). If present
+                    // DW_AT_linkage_name should always be preferred (mangled C++ name
+                    // vs just the function name).
+                    if (name.empty()) {
+                        name = std::get<std::string_view>(attr.attr_value);
+                    }
+                    break;
+                }
+                return true;
+            });
+            return name;
+        };
+
+        // DW_AT_abstract_origin is a reference. There a 3 types of references:
+        // - the reference can identify any debugging information entry within the
+        //   compilation unit (DW_FORM_ref1, DW_FORM_ref2, DW_FORM_ref4,
+        //   DW_FORM_ref8, DW_FORM_ref_udata). This type of reference is an offset
+        //   from the first byte of the compilation header for the compilation unit
+        //   containing the reference.
+        // - the reference can identify any debugging information entry within a
+        //   .debug_info section; in particular, it may refer to an entry in a
+        //   different compilation unit (DW_FORM_ref_addr)
+        // - the reference can identify any debugging information type entry that
+        //   has been placed in its own type unit.
+        //   Not applicable for DW_AT_abstract_origin.
+        location.name = (*abstract_origin_ref_type != DW_FORM_ref_addr)
+                                ? get_function_name(cu, cu.offset + *abstract_origin)
+                                : get_function_name(findCompilationUnit(*abstract_origin),
+                                                    *abstract_origin);
+
+        /// FIXME: see comment above
+        if (die_for_inline_broken) {
+            return false;
+        }
+
+        locations.push_back(location);
+
+        findInlinedSubroutineDieForAddress(cu, child_die, line_vm, address, base_addr_cu, locations,
+                                           max_size);
+
+        return false;
+    });
+}
+
+bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo, LocationInfoMode mode,
+                        std::vector<SymbolizedFrame>& inline_frames) const {
+    locationInfo = LocationInfo();
+
+    if (mode == LocationInfoMode::DISABLED) {
+        return false;
+    }
+
+    if (!elf_) { // No file.
+        return false;
+    }
+
+    if (!aranges_.empty()) {
+        // Fast path: find the right .debug_info entry by looking up the
+        // address in .debug_aranges.
+        uint64_t offset = 0;
+        if (findDebugInfoOffset(address, aranges_, offset)) {
+            // Read compilation unit header from .debug_info
+            auto unit = getCompilationUnit(offset);
+            if (unit.unit_type != DW_UT_compile && unit.unit_type != DW_UT_skeleton) {
+                return false;
+            }
+            findLocation(address, mode, unit, locationInfo, inline_frames);
+            return locationInfo.has_file_and_line;
+        } else if (mode == LocationInfoMode::FAST) {
+            // NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries
+            // in .debug_aranges for some functions, but always generates
+            // .debug_info entries.  Scanning .debug_info is slow, so fall back to
+            // it only if such behavior is requested via LocationInfoMode.
+            return false;
+        } else {
+            SAFE_CHECK(mode == LocationInfoMode::FULL || mode == LocationInfoMode::FULL_WITH_INLINE,
+                       "unexpected mode");
+            // Fall back to the linear scan.
+        }
+    }
+
+    // Slow path (linear scan): Iterate over all .debug_info entries
+    // and look for the address in each compilation unit.
+    uint64_t offset = 0;
+    while (offset < info_.size() && !locationInfo.has_file_and_line) {
+        auto unit = getCompilationUnit(offset);
+        offset += unit.size;
+        if (unit.unit_type != DW_UT_compile && unit.unit_type != DW_UT_skeleton) {
+            continue;
+        }
+        findLocation(address, mode, unit, locationInfo, inline_frames);
+    }
+
+    return locationInfo.has_file_and_line;
+}
+
+bool Dwarf::isAddrInRangeList(const CompilationUnit& cu, uint64_t address,
+                              std::optional<uint64_t> base_addr, size_t offset,
+                              uint8_t addr_size) const {
+    SAFE_CHECK(addr_size == 4 || addr_size == 8, "wrong address size");
+    if (cu.version <= 4 && !ranges_.empty()) {
+        const bool is64_bit_addr = addr_size == 8;
+        std::string_view sp = ranges_;
+        sp.remove_prefix(offset);
+        const uint64_t max_addr = is64_bit_addr ? std::numeric_limits<uint64_t>::max()
+                                                : std::numeric_limits<uint32_t>::max();
+        while (!sp.empty()) {
+            uint64_t begin = readOffset(sp, is64_bit_addr);
+            uint64_t end = readOffset(sp, is64_bit_addr);
+            // The range list entry is a base address selection entry.
+            if (begin == max_addr) {
+                base_addr = end;
+                continue;
+            }
+            // The range list entry is an end of list entry.
+            if (begin == 0 && end == 0) {
+                break;
+            }
+
+            // Check if the given address falls in the range list entry.
+            // 2.17.3 Non-Contiguous Address Ranges
+            // The applicable base address of a range list entry is determined by the
+            // closest preceding base address selection entry (see below) in the same
+            // range list. If there is no such selection entry, then the applicable
+            // base address defaults to the base address of the compilation unit.
+            if (base_addr && address >= begin + *base_addr && address < end + *base_addr) {
+                return true;
+            }
+        }
+    }
+
+    if (cu.version == 5 && !rnglists_.empty() && cu.addr_base.has_value()) {
+        auto rnglists = rnglists_;
+        rnglists.remove_prefix(offset);
+
+        while (!rnglists.empty()) {
+            auto kind = read<uint8_t>(rnglists);
+            switch (kind) {
+            case DW_RLE_end_of_list:
+                return false;
+            case DW_RLE_base_addressx: {
+                auto index = readULEB(rnglists);
+                auto sp = addr_.substr(*cu.addr_base + index * sizeof(uint64_t));
+                base_addr = read<uint64_t>(sp);
+            } break;
+
+            case DW_RLE_startx_endx: {
+                auto index_start = readULEB(rnglists);
+                auto index_end = readULEB(rnglists);
+                auto sp_start = addr_.substr(*cu.addr_base + index_start * sizeof(uint64_t));
+                auto start = read<uint64_t>(sp_start);
+
+                auto sp_end = addr_.substr(*cu.addr_base + index_end * sizeof(uint64_t));
+                auto end = read<uint64_t>(sp_end);
+                if (address >= start && address < end) {
+                    return true;
+                }
+            } break;
+
+            case DW_RLE_startx_length: {
+                auto index_start = readULEB(rnglists);
+                auto length = readULEB(rnglists);
+                auto sp_start = addr_.substr(*cu.addr_base + index_start * sizeof(uint64_t));
+                auto start = read<uint64_t>(sp_start);
+
+                auto sp_end = addr_.substr(*cu.addr_base + index_start * sizeof(uint64_t) + length);
+                auto end = read<uint64_t>(sp_end);
+                if (start != end && address >= start && address < end) {
+                    return true;
+                }
+            } break;
+
+            case DW_RLE_offset_pair: {
+                auto offset_start = readULEB(rnglists);
+                auto offset_end = readULEB(rnglists);
+                if (base_addr && address >= (*base_addr + offset_start) &&
+                    address < (*base_addr + offset_end)) {
+                    return true;
+                }
+            } break;
+
+            case DW_RLE_base_address:
+                base_addr = read<uint64_t>(rnglists);
+                break;
+
+            case DW_RLE_start_end: {
+                uint64_t start = read<uint64_t>(rnglists);
+                uint64_t end = read<uint64_t>(rnglists);
+                if (address >= start && address < end) {
+                    return true;
+                }
+            } break;
+
+            case DW_RLE_start_length: {
+                uint64_t start = read<uint64_t>(rnglists);
+                uint64_t end = start + readULEB(rnglists);
+                if (address >= start && address < end) {
+                    return true;
+                }
+            } break;
+
+            default:
+                SAFE_CHECK(false, "Unexpected debug_rnglists entry kind");
+            }
+        }
+    }
+    return false;
+}
+
+Dwarf::LineNumberVM::LineNumberVM(std::string_view data, std::string_view compilationDirectory,
+                                  std::string_view debugStr, std::string_view debugLineStr)
+        : compilationDirectory_(compilationDirectory),
+          debugStr_(debugStr),
+          debugLineStr_(debugLineStr) {
+    Section section(data);
+    SAFE_CHECK(section.next(data_), "invalid line number VM");
+    is64Bit_ = section.is64Bit();
+    init();
+    reset();
+}
+
+void Dwarf::LineNumberVM::reset() {
+    address_ = 0;
+    file_ = 1;
+    line_ = 1;
+    column_ = 0;
+    isStmt_ = defaultIsStmt_;
+    basicBlock_ = false;
+    endSequence_ = false;
+    prologueEnd_ = false;
+    epilogueBegin_ = false;
+    isa_ = 0;
+    discriminator_ = 0;
+}
+
+struct LineNumberAttribute {
+    uint64_t content_type_code;
+    uint64_t form_code;
+    std::variant<uint64_t, std::string_view> attr_value;
+};
+
+LineNumberAttribute readLineNumberAttribute(bool is64_bit, std::string_view& format,
+                                            std::string_view& entries, std::string_view debugStr,
+                                            std::string_view debugLineStr) {
+    uint64_t content_type_code = readULEB(format);
+    uint64_t form_code = readULEB(format);
+    std::variant<uint64_t, std::string_view> attr_value;
+
+    switch (content_type_code) {
+    case DW_LNCT_path: {
+        switch (form_code) {
+        case DW_FORM_string:
+            attr_value = readNullTerminated(entries);
+            break;
+        case DW_FORM_line_strp: {
+            auto off = readOffset(entries, is64_bit);
+            attr_value = getStringFromStringSection(debugLineStr, off);
+        } break;
+        case DW_FORM_strp:
+            attr_value = getStringFromStringSection(debugStr, readOffset(entries, is64_bit));
+            break;
+        case DW_FORM_strp_sup:
+            SAFE_CHECK(false, "Unexpected DW_FORM_strp_sup");
+            break;
+        default:
+            SAFE_CHECK(false, "Unexpected form for DW_LNCT_path");
+            break;
+        }
+    } break;
+
+    case DW_LNCT_directory_index: {
+        switch (form_code) {
+        case DW_FORM_data1:
+            attr_value = read<uint8_t>(entries);
+            break;
+        case DW_FORM_data2:
+            attr_value = read<uint16_t>(entries);
+            break;
+        case DW_FORM_udata:
+            attr_value = readULEB(entries);
+            break;
+        default:
+            SAFE_CHECK(false, "Unexpected form for DW_LNCT_directory_index");
+            break;
+        }
+    } break;
+
+    case DW_LNCT_timestamp: {
+        switch (form_code) {
+        case DW_FORM_udata:
+            attr_value = readULEB(entries);
+            break;
+        case DW_FORM_data4:
+            attr_value = read<uint32_t>(entries);
+            break;
+        case DW_FORM_data8:
+            attr_value = read<uint64_t>(entries);
+            break;
+        case DW_FORM_block:
+            attr_value = readBytes(entries, readULEB(entries));
+            break;
+        default:
+            SAFE_CHECK(false, "Unexpected form for DW_LNCT_timestamp");
+        }
+    } break;
+
+    case DW_LNCT_size: {
+        switch (form_code) {
+        case DW_FORM_udata:
+            attr_value = readULEB(entries);
+            break;
+        case DW_FORM_data1:
+            attr_value = read<uint8_t>(entries);
+            break;
+        case DW_FORM_data2:
+            attr_value = read<uint16_t>(entries);
+            break;
+        case DW_FORM_data4:
+            attr_value = read<uint32_t>(entries);
+            break;
+        case DW_FORM_data8:
+            attr_value = read<uint64_t>(entries);
+            break;
+        default:
+            SAFE_CHECK(false, "Unexpected form for DW_LNCT_size");
+            break;
+        }
+    } break;
+
+    case DW_LNCT_MD5: {
+        switch (form_code) {
+        case DW_FORM_data16:
+            attr_value = readBytes(entries, 16);
+            break;
+        default:
+            SAFE_CHECK(false, "Unexpected form for DW_LNCT_MD5");
+            break;
+        }
+    } break;
+
+    default:
+        // TODO: skip over vendor data as specified by the form instead.
+        SAFE_CHECK(false, "Unexpected vendor content type code");
+        break;
+    }
+    return {
+            .content_type_code = content_type_code,
+            .form_code = form_code,
+            .attr_value = attr_value,
+    };
+}
+
+void Dwarf::LineNumberVM::init() {
+    version_ = read<uint16_t>(data_);
+    SAFE_CHECK(version_ >= 2 && version_ <= 5, "invalid version in line number VM: {}", version_);
+    if (version_ == 5) {
+        auto address_size = read<uint8_t>(data_);
+        SAFE_CHECK(address_size == sizeof(uintptr_t), "Unexpected Line Number Table address_size");
+        auto segment_selector_size = read<uint8_t>(data_);
+        SAFE_CHECK(segment_selector_size == 0, "Segments not supported");
+    }
+    uint64_t header_length = readOffset(data_, is64Bit_);
+    SAFE_CHECK(header_length <= data_.size(), "invalid line number VM header length");
+    std::string_view header(data_.data(), header_length);
+    data_ = std::string_view(header.end(), data_.end() - header.end());
+
+    minLength_ = read<uint8_t>(header);
+    if (version_ >= 4) { // Version 2 and 3 records don't have this
+        uint8_t max_ops_per_instruction = read<uint8_t>(header);
+        SAFE_CHECK(max_ops_per_instruction == 1, "VLIW not supported");
+    }
+    defaultIsStmt_ = read<uint8_t>(header);
+    lineBase_ = read<int8_t>(header); // yes, signed
+    lineRange_ = read<uint8_t>(header);
+    opcodeBase_ = read<uint8_t>(header);
+    SAFE_CHECK(opcodeBase_ != 0, "invalid opcode base");
+    standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
+    header.remove_prefix(opcodeBase_ - 1);
+
+    if (version_ <= 4) {
+        // We don't want to use heap, so we don't keep an unbounded amount of state.
+        // We'll just skip over include directories and file names here, and
+        // we'll loop again when we actually need to retrieve one.
+        std::string_view sp;
+        const char* tmp = header.data();
+        v4_.includeDirectoryCount = 0;
+        while (!(sp = readNullTerminated(header)).empty()) {
+            ++v4_.includeDirectoryCount;
+        }
+        v4_.includeDirectories = {tmp, header.data()};
+
+        tmp = header.data();
+        FileName fn;
+        v4_.fileNameCount = 0;
+        while (readFileName(header, fn)) {
+            ++v4_.fileNameCount;
+        }
+        v4_.fileNames = {tmp, header.data()};
+    } else if (version_ == 5) {
+        v5_.directoryEntryFormatCount = read<uint8_t>(header);
+        const char* tmp = header.data();
+        for (uint8_t i = 0; i < v5_.directoryEntryFormatCount; i++) {
+            // A sequence of directory entry format descriptions. Each description
+            // consists of a pair of ULEB128 values:
+            readULEB(header); // A content type code
+            readULEB(header); // A form code using the attribute form codes
+        }
+        v5_.directoryEntryFormat = {tmp, header.data()};
+        v5_.directoriesCount = readULEB(header);
+        tmp = header.data();
+        for (uint64_t i = 0; i < v5_.directoriesCount; i++) {
+            std::string_view format = v5_.directoryEntryFormat;
+            for (uint8_t f = 0; f < v5_.directoryEntryFormatCount; f++) {
+                readLineNumberAttribute(is64Bit_, format, header, debugStr_, debugLineStr_);
+            }
+        }
+        v5_.directories = {tmp, header.data()};
+
+        v5_.fileNameEntryFormatCount = read<uint8_t>(header);
+        tmp = header.data();
+        for (uint8_t i = 0; i < v5_.fileNameEntryFormatCount; i++) {
+            // A sequence of file entry format descriptions. Each description
+            // consists of a pair of ULEB128 values:
+            readULEB(header); // A content type code
+            readULEB(header); // A form code using the attribute form codes
+        }
+        v5_.fileNameEntryFormat = {tmp, header.data()};
+        v5_.fileNamesCount = readULEB(header);
+        tmp = header.data();
+        for (uint64_t i = 0; i < v5_.fileNamesCount; i++) {
+            std::string_view format = v5_.fileNameEntryFormat;
+            for (uint8_t f = 0; f < v5_.fileNameEntryFormatCount; f++) {
+                readLineNumberAttribute(is64Bit_, format, header, debugStr_, debugLineStr_);
+            }
+        }
+        v5_.fileNames = {tmp, header.data()};
+    }
+}
+
+bool Dwarf::LineNumberVM::next(std::string_view& program) {
+    Dwarf::LineNumberVM::StepResult ret;
+    do {
+        ret = step(program);
+    } while (ret == CONTINUE);
+
+    return (ret == COMMIT);
+}
+
+Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index) const {
+    if (version_ <= 4) {
+        SAFE_CHECK(index != 0, "invalid file index 0");
+        FileName fn;
+        if (index <= v4_.fileNameCount) {
+            std::string_view file_names = v4_.fileNames;
+            for (; index; --index) {
+                if (!readFileName(file_names, fn)) {
+                    abort();
+                }
+            }
+            return fn;
+        }
+
+        index -= v4_.fileNameCount;
+
+        std::string_view program = data_;
+        for (; index; --index) {
+            SAFE_CHECK(nextDefineFile(program, fn), "invalid file index");
+        }
+
+        return fn;
+    } else {
+        FileName fn;
+        SAFE_CHECK(index < v5_.fileNamesCount, "invalid file index");
+        std::string_view file_names = v5_.fileNames;
+        for (uint64_t i = 0; i < v5_.fileNamesCount; i++) {
+            std::string_view format = v5_.fileNameEntryFormat;
+            for (uint8_t f = 0; f < v5_.fileNameEntryFormatCount; f++) {
+                auto attr = readLineNumberAttribute(is64Bit_, format, file_names, debugStr_,
+                                                    debugLineStr_);
+                if (i == index) {
+                    switch (attr.content_type_code) {
+                    case DW_LNCT_path:
+                        fn.relativeName = std::get<std::string_view>(attr.attr_value);
+                        break;
+                    case DW_LNCT_directory_index:
+                        fn.directoryIndex = std::get<uint64_t>(attr.attr_value);
+                        break;
+                    }
+                }
+            }
+        }
+        return fn;
+    }
+}
+
+std::string_view Dwarf::LineNumberVM::getIncludeDirectory(uint64_t index) const {
+    if (version_ <= 4) {
+        if (index == 0) {
+            // In DWARF <= 4 the current directory is not represented in the
+            // directories field and a directory index of 0 implicitly referred to
+            // that directory as found in the DW_AT_comp_dir attribute of the
+            // compilation unit debugging information entry.
+            return {};
+        }
+
+        SAFE_CHECK(index <= v4_.includeDirectoryCount, "invalid include directory");
+
+        std::string_view include_directories = v4_.includeDirectories;
+        std::string_view dir;
+        for (; index; --index) {
+            dir = readNullTerminated(include_directories);
+            if (dir.empty()) {
+                abort(); // BUG
+            }
+        }
+
+        return dir;
+    } else {
+        SAFE_CHECK(index < v5_.directoriesCount, "invalid file index");
+        std::string_view directories = v5_.directories;
+        for (uint64_t i = 0; i < v5_.directoriesCount; i++) {
+            std::string_view format = v5_.directoryEntryFormat;
+            for (uint8_t f = 0; f < v5_.directoryEntryFormatCount; f++) {
+                auto attr = readLineNumberAttribute(is64Bit_, format, directories, debugStr_,
+                                                    debugLineStr_);
+                if (i == index && attr.content_type_code == DW_LNCT_path) {
+                    return std::get<std::string_view>(attr.attr_value);
+                }
+            }
+        }
+        // This could only happen if DWARF5's directory_entry_format doesn't contain
+        // a DW_LNCT_path. Highly unlikely, but we shouldn't crash.
+        return std::string_view("<directory not found>");
+    }
+}
+
+bool Dwarf::LineNumberVM::readFileName(std::string_view& program, FileName& fn) {
+    fn.relativeName = readNullTerminated(program);
+    if (fn.relativeName.empty()) {
+        return false;
+    }
+    fn.directoryIndex = readULEB(program);
+    // Skip over file size and last modified time
+    readULEB(program);
+    readULEB(program);
+    return true;
+}
+
+bool Dwarf::LineNumberVM::nextDefineFile(std::string_view& program, FileName& fn) const {
+    while (!program.empty()) {
+        auto opcode = read<uint8_t>(program);
+
+        if (opcode >= opcodeBase_) { // special opcode
+            continue;
+        }
+
+        if (opcode != 0) { // standard opcode
+            // Skip, slurp the appropriate number of LEB arguments
+            uint8_t arg_count = standardOpcodeLengths_[opcode - 1];
+            while (arg_count--) {
+                readULEB(program);
+            }
+            continue;
+        }
+
+        // Extended opcode
+        auto length = readULEB(program);
+        // the opcode itself should be included in the length, so length >= 1
+        SAFE_CHECK(length != 0, "invalid extended opcode length");
+        read<uint8_t>(program); // extended opcode
+        --length;
+
+        if (opcode == DW_LNE_define_file) {
+            SAFE_CHECK(version_ < 5, "DW_LNE_define_file deprecated in DWARF5");
+            SAFE_CHECK(readFileName(program, fn), "invalid empty file in DW_LNE_define_file");
+            return true;
+        }
+
+        program.remove_prefix(length);
+    }
+
+    return false;
+}
+
+Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step(std::string_view& program) {
+    auto opcode = read<uint8_t>(program);
+
+    if (opcode >= opcodeBase_) { // special opcode
+        uint8_t adjusted_opcode = opcode - opcodeBase_;
+        uint8_t op_advance = adjusted_opcode / lineRange_;
+
+        address_ += minLength_ * op_advance;
+        line_ += lineBase_ + adjusted_opcode % lineRange_;
+
+        basicBlock_ = false;
+        prologueEnd_ = false;
+        epilogueBegin_ = false;
+        discriminator_ = 0;
+        return COMMIT;
+    }
+
+    if (opcode != 0) { // standard opcode
+        // Only interpret opcodes that are recognized by the version we're parsing;
+        // the others are vendor extensions and we should ignore them.
+        switch (opcode) {
+        case DW_LNS_copy:
+            basicBlock_ = false;
+            prologueEnd_ = false;
+            epilogueBegin_ = false;
+            discriminator_ = 0;
+            return COMMIT;
+        case DW_LNS_advance_pc:
+            address_ += minLength_ * readULEB(program);
+            return CONTINUE;
+        case DW_LNS_advance_line:
+            line_ += readSLEB(program);
+            return CONTINUE;
+        case DW_LNS_set_file:
+            file_ = readULEB(program);
+            return CONTINUE;
+        case DW_LNS_set_column:
+            column_ = readULEB(program);
+            return CONTINUE;
+        case DW_LNS_negate_stmt:
+            isStmt_ = !isStmt_;
+            return CONTINUE;
+        case DW_LNS_set_basic_block:
+            basicBlock_ = true;
+            return CONTINUE;
+        case DW_LNS_const_add_pc:
+            address_ += minLength_ * ((255 - opcodeBase_) / lineRange_);
+            return CONTINUE;
+        case DW_LNS_fixed_advance_pc:
+            address_ += read<uint16_t>(program);
+            return CONTINUE;
+        case DW_LNS_set_prologue_end:
+            if (version_ == 2) {
+                break; // not supported in version 2
+            }
+            prologueEnd_ = true;
+            return CONTINUE;
+        case DW_LNS_set_epilogue_begin:
+            if (version_ == 2) {
+                break; // not supported in version 2
+            }
+            epilogueBegin_ = true;
+            return CONTINUE;
+        case DW_LNS_set_isa:
+            if (version_ == 2) {
+                break; // not supported in version 2
+            }
+            isa_ = readULEB(program);
+            return CONTINUE;
+        }
+
+        // Unrecognized standard opcode, slurp the appropriate number of LEB
+        // arguments.
+        uint8_t arg_count = standardOpcodeLengths_[opcode - 1];
+        while (arg_count--) {
+            readULEB(program);
+        }
+        return CONTINUE;
+    }
+
+    // Extended opcode
+    auto length = readULEB(program);
+    // the opcode itself should be included in the length, so length >= 1
+    SAFE_CHECK(length != 0, "invalid extended opcode length");
+    auto extended_opcode = read<uint8_t>(program);
+    --length;
+
+    switch (extended_opcode) {
+    case DW_LNE_end_sequence:
+        return END;
+    case DW_LNE_set_address:
+        address_ = read<uintptr_t>(program);
+        return CONTINUE;
+    case DW_LNE_define_file:
+        SAFE_CHECK(version_ < 5, "DW_LNE_define_file deprecated in DWARF5");
+        // We can't process DW_LNE_define_file here, as it would require us to
+        // use unbounded amounts of state (ie. use the heap).  We'll do a second
+        // pass (using nextDefineFile()) if necessary.
+        break;
+    case DW_LNE_set_discriminator:
+        discriminator_ = readULEB(program);
+        return CONTINUE;
+    }
+
+    // Unrecognized extended opcode
+    program.remove_prefix(length);
+    return CONTINUE;
+}
+
+Dwarf::Path Dwarf::LineNumberVM::getFullFileName(uint64_t index) const {
+    auto fn = getFileName(index);
+    // DWARF <= 4: the current dir is not represented in the CU's Line Number
+    // Program Header and relies on the CU's DW_AT_comp_dir.
+    // DWARF 5: the current directory is explicitly present.
+    const std::string_view base_dir = version_ == 5 ? "" : compilationDirectory_;
+    return Path(base_dir, getIncludeDirectory(fn.directoryIndex), fn.relativeName);
+}
+
+bool Dwarf::LineNumberVM::findAddress(uintptr_t target, Path& file, uint64_t& line) {
+    std::string_view program = data_;
+
+    // Within each sequence of instructions, the address may only increase.
+    // Unfortunately, within the same compilation unit, sequences may appear
+    // in any order.  So any sequence is a candidate if it starts at an address
+    // <= the target address, and we know we've found the target address if
+    // a candidate crosses the target address.
+    enum State {
+        START,
+        LOW_SEQ, // candidate
+        HIGH_SEQ
+    };
+    State state = START;
+    reset();
+
+    uint64_t prev_file = 0;
+    uint64_t prev_line = 0;
+    while (!program.empty()) {
+        bool seq_end = !next(program);
+
+        if (state == START) {
+            if (!seq_end) {
+                state = address_ <= target ? LOW_SEQ : HIGH_SEQ;
+            }
+        }
+
+        if (state == LOW_SEQ) {
+            if (address_ > target) {
+                // Found it!  Note that ">" is indeed correct (not ">="), as each
+                // sequence is guaranteed to have one entry past-the-end (emitted by
+                // DW_LNE_end_sequence)
+                //
+                // NOTE: In DWARF <= 4 the file register is non-zero.
+                //   See DWARF 4: 6.2.4 The Line Number Program Header
+                //   "The line number program assigns numbers to each of the file
+                //   entries in order, beginning with 1, and uses those numbers instead
+                //   of file names in the file register."
+                // DWARF 5 has a different include directory/file header and 0 is valid.
+                if (version_ <= 4 && prev_file == 0) {
+                    return false;
+                }
+                file = getFullFileName(prev_file);
+                line = prev_line;
+                return true;
+            }
+            prev_file = file_;
+            prev_line = line_;
+        }
+
+        if (seq_end) {
+            state = START;
+            reset();
+        }
+    }
+
+    return false;
+}
+
+} // namespace doris
+
+#endif
diff --git a/be/src/common/dwarf.h b/be/src/common/dwarf.h
new file mode 100644
index 0000000000..6807721856
--- /dev/null
+++ b/be/src/common/dwarf.h
@@ -0,0 +1,479 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/Dwarf.h
+// and modified by Doris
+
+#pragma once
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+/*
+ * Copyright 2012-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** This file was edited for ClickHouse.
+ *  Original is from folly library.
+  */
+
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <variant>
+#include <vector>
+
+namespace doris {
+
+class Elf;
+
+/**
+ * DWARF record parser.
+ *
+ * We only implement enough DWARF functionality to convert from PC address
+ * to file and line number information.
+ *
+ * This means (although they're not part of the public API of this class), we
+ * can parse Debug Information Entries (DIEs), abbreviations, attributes (of
+ * all forms), and we can interpret bytecode for the line number VM.
+ *
+ * We can interpret DWARF records of version 2, 3, or 4, although we don't
+ * actually support many of the version 4 features (such as VLIW, multiple
+ * operations per instruction)
+ *
+ * Note that the DWARF record parser does not allocate heap memory at all.
+ * This is on purpose: you can use the parser from
+ * memory-constrained situations (such as an exception handler for
+ * std::out_of_memory)  If it weren't for this requirement, some things would
+ * be much simpler: the Path class would be unnecessary and would be replaced
+ * with a std::string; the list of file names in the line number VM would be
+ * kept as a vector of strings instead of re-executing the program to look for
+ * DW_LNE_define_file instructions, etc.
+ */
+class Dwarf final {
+    // Note that Dwarf uses (and returns) std::string_view a lot.
+    // The std::string_view point within sections in the ELF file, and so will
+    // be live for as long as the passed-in Elf is live.
+public:
+    /** Create a DWARF parser around an ELF file. */
+    explicit Dwarf(const std::shared_ptr<Elf>& elf);
+
+    /**
+     * More than one location info may exist if current frame is an inline
+     * function call.
+     */
+    static constexpr uint32_t kMaxInlineLocationInfoPerFrame = 10;
+
+    /**
+      * Represent a file path a s collection of three parts (base directory,
+      * subdirectory, and file).
+      */
+    class Path {
+    public:
+        Path() = default;
+
+        Path(std::string_view baseDir, std::string_view subDir, std::string_view file);
+
+        [[nodiscard]] std::string_view baseDir() const { return baseDir_; }
+        [[nodiscard]] std::string_view subDir() const { return subDir_; }
+        [[nodiscard]] std::string_view file() const { return file_; }
+
+        [[nodiscard]] size_t size() const;
+
+        /**
+        * Copy the Path to a buffer of size bufSize.
+        *
+        * toBuffer behaves like snprintf: It will always null-terminate the
+        * buffer (so it will copy at most bufSize-1 bytes), and it will return
+        * the number of bytes that would have been written if there had been
+        * enough room, so, if toBuffer returns a value >= bufSize, the output
+        * was truncated.
+        */
+        size_t toBuffer(char* buf, size_t bufSize) const;
+
+        void toString(std::string& dest) const;
+        [[nodiscard]] std::string toString() const {
+            std::string s;
+            toString(s);
+            return s;
+        }
+
+        // TODO(tudorb): Implement operator==, operator!=; not as easy as it
+        // seems as the same path can be represented in multiple ways
+    private:
+        std::string_view baseDir_; /// NOLINT
+        std::string_view subDir_;  /// NOLINT
+        std::string_view file_;    /// NOLINT
+    };
+
+    // Indicates inline function `name` is called  at `line@file`.
+    struct CallLocation {
+        Path file = {};
+        uint64_t line = 0;
+        std::string_view name;
+    };
+
+    enum class LocationInfoMode {
+        // Don't resolve location info.
+        DISABLED,
+        // Perform CU lookup using .debug_aranges (might be incomplete).
+        FAST,
+        // Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
+        FULL,
+        // Scan .debug_info (super slower, use with caution) for inline functions in
+        // addition to FULL.
+        FULL_WITH_INLINE,
+    };
+
+    struct LocationInfo {
+        bool has_main_file = false;
+        Path main_file;
+
+        bool has_file_and_line = false;
+        Path file;
+        uint64_t line = 0;
+    };
+
+    /**
+     * Frame information: symbol name and location.
+     */
+    struct SymbolizedFrame {
+        bool found = false;
+        uintptr_t addr = 0;
+        // Mangled symbol name. Use `folly::demangle()` to demangle it.
+        const char* name = nullptr;
+        LocationInfo location;
+        std::shared_ptr<const Elf> file;
+
+        void clear() { *this = SymbolizedFrame(); }
+    };
+
+    /** Find the file and line number information corresponding to address.
+      * The address must be physical - offset in object file without offset in virtual memory where the object is loaded.
+      */
+    bool findAddress(uintptr_t address, LocationInfo& locationInfo, LocationInfoMode mode,
+                     std::vector<SymbolizedFrame>& inline_frames) const;
+
+private:
+    static bool findDebugInfoOffset(uintptr_t address, std::string_view aranges, uint64_t& offset);
+
+    std::shared_ptr<const Elf> elf_; /// NOLINT
+
+    // DWARF section made up of chunks, each prefixed with a length header.
+    // The length indicates whether the chunk is DWARF-32 or DWARF-64, which
+    // guides interpretation of "section offset" records.
+    // (yes, DWARF-32 and DWARF-64 sections may coexist in the same file)
+    class Section {
+    public:
+        Section() : is64_bit(false) {}
+
+        explicit Section(std::string_view d);
+
+        // Return next chunk, if any; the 4- or 12-byte length was already
+        // parsed and isn't part of the chunk.
+        bool next(std::string_view& chunk);
+
+        // Is the current chunk 64 bit?
+        [[nodiscard]] bool is64Bit() const { return is64_bit; }
+
+    private:
+        // Yes, 32- and 64- bit sections may coexist.  Yikes!
+        bool is64_bit;
+        std::string_view data;
+    };
+
+    // Abbreviation for a Debugging Information Entry.
+    struct DIEAbbreviation {
+        uint64_t code = 0;
+        uint64_t tag = 0;
+        bool has_children = false;
+
+        std::string_view attributes;
+    };
+
+    // Debugging information entry to define a low-level representation of a
+    // source program. Each debugging information entry consists of an identifying
+    // tag and a series of attributes. An entry, or group of entries together,
+    // provide a description of a corresponding entity in the source program.
+    struct Die {
+        bool is64Bit; /// NOLINT
+        // Offset from start to first attribute
+        uint8_t attr_offset;
+        // Offset within debug info.
+        uint64_t offset;
+        uint64_t code;
+        DIEAbbreviation abbr;
+    };
+
+    struct AttributeSpec {
+        uint64_t name = 0;
+        uint64_t form = 0;
+        int64_t implicitConst = 0; // only set when form=DW_FORM_implicit_const
+
+        explicit operator bool() const { return name != 0 || form != 0; }
+    };
+
+    struct Attribute {
+        AttributeSpec spec;
+        const Die& die;
+        std::variant<uint64_t, std::string_view> attr_value;
+    };
+
+    enum {
+        DW_UT_compile = 0x01,
+        DW_UT_skeleton = 0x04,
+    };
+
+    struct CompilationUnit {
+        bool is64Bit = false; /// NOLINT
+        uint8_t version = 0;
+        uint8_t unit_type = DW_UT_compile; // DW_UT_compile or DW_UT_skeleton
+        uint8_t addr_size = 0;
+        // Offset in .debug_info of this compilation unit.
+        uint64_t offset = 0;
+        uint64_t size = 0;
+        // Offset in .debug_info for the first DIE in this compilation unit.
+        uint64_t first_die = 0;
+        uint64_t abbrev_offset = 0;
+
+        // The beginning of the CU's contribution to .debug_addr
+        std::optional<uint64_t> addr_base; // DW_AT_addr_base (DWARF 5)
+        // The beginning of the offsets table (immediately following the
+        // header) of the CU's contribution to .debug_loclists
+        std::optional<uint64_t> loclists_base; // DW_AT_loclists_base (DWARF 5)
+        // The beginning of the offsets table (immediately following the
+        // header) of the CU's contribution to .debug_rnglists
+        std::optional<uint64_t> rnglists_base; // DW_AT_rnglists_base (DWARF 5)
+        // Points to the first string offset of the compilation unit’s
+        // contribution to the .debug_str_offsets (or .debug_str_offsets.dwo) section.
+        std::optional<uint64_t> str_offsets_base; // DW_AT_str_offsets_base (DWARF 5)
+
+        // Only the CompilationUnit that contains the caller functions needs this cache.
+        // Indexed by (abbr.code - 1) if (abbr.code - 1) < abbrCache.size();
+        std::vector<DIEAbbreviation> abbr_cache;
+    };
+
+    /** cu must exist during the life cycle of created Die. */
+    [[nodiscard]] Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset) const;
+
+    bool findLocation(uintptr_t address, const LocationInfoMode mode, CompilationUnit& cu,
+                      LocationInfo& info, std::vector<SymbolizedFrame>& inline_frames) const;
+
+    /**
+     * Finds a subprogram debugging info entry that contains a given address among
+     * children of given die. Depth first search.
+     */
+    void findSubProgramDieForAddress(const CompilationUnit& cu, const Die& die, uint64_t address,
+                                     std::optional<uint64_t> base_addr_cu, Die& subprogram) const;
+
+    // Interpreter for the line number bytecode VM
+    class LineNumberVM {
+    public:
+        LineNumberVM(std::string_view data, std::string_view compilationDirectory,
+                     std::string_view debugStr, std::string_view debugLineStr);
+
+        bool findAddress(uintptr_t target, Path& file, uint64_t& line);
+
+        /** Gets full file name at given index including directory. */
+        [[nodiscard]] Path getFullFileName(uint64_t index) const;
+
+    private:
+        void init();
+        void reset();
+
+        // Execute until we commit one new row to the line number matrix
+        bool next(std::string_view& program);
+        enum StepResult {
+            CONTINUE, // Continue feeding opcodes
+            COMMIT,   // Commit new <address, file, line> tuple
+            END,      // End of sequence
+        };
+        // Execute one opcode
+        StepResult step(std::string_view& program);
+
+        struct FileName {
+            std::string_view relativeName; /// NOLINT
+            // 0 = current compilation directory
+            // otherwise, 1-based index in the list of include directories
+            uint64_t directoryIndex; /// NOLINT
+        };
+        // Read one FileName object, remove_prefix program
+        static bool readFileName(std::string_view& program, FileName& fn);
+
+        // Get file name at given index; may be in the initial table
+        // (fileNames_) or defined using DW_LNE_define_file (and we reexecute
+        // enough of the program to find it, if so)
+        [[nodiscard]] FileName getFileName(uint64_t index) const;
+
+        // Get include directory at given index
+        [[nodiscard]] std::string_view getIncludeDirectory(uint64_t index) const;
+
+        // Execute opcodes until finding a DW_LNE_define_file and return true;
+        // return file at the end.
+        bool nextDefineFile(std::string_view& program, FileName& fn) const;
+
+        // Initialization
+        bool is64Bit_;                          /// NOLINT
+        std::string_view data_;                 /// NOLINT
+        std::string_view compilationDirectory_; /// NOLINT
+        std::string_view debugStr_;             // needed for DWARF 5 /// NOLINT
+        std::string_view debugLineStr_;         // DWARF 5        /// NOLINT
+
+        // Header
+        uint16_t version_;                     /// NOLINT
+        uint8_t minLength_;                    /// NOLINT
+        bool defaultIsStmt_;                   /// NOLINT
+        int8_t lineBase_;                      /// NOLINT
+        uint8_t lineRange_;                    /// NOLINT
+        uint8_t opcodeBase_;                   /// NOLINT
+        const uint8_t* standardOpcodeLengths_; /// NOLINT
+
+        // 6.2.4 The Line Number Program Header.
+        struct {
+            size_t includeDirectoryCount;
+            std::string_view includeDirectories;
+            size_t fileNameCount;
+            std::string_view fileNames;
+        } v4_;
+
+        struct {
+            uint8_t directoryEntryFormatCount;
+            std::string_view directoryEntryFormat;
+            uint64_t directoriesCount;
+            std::string_view directories;
+
+            uint8_t fileNameEntryFormatCount;
+            std::string_view fileNameEntryFormat;
+            uint64_t fileNamesCount;
+            std::string_view fileNames;
+        } v5_;
+
+        // State machine registers
+        uint64_t address_;       /// NOLINT
+        uint64_t file_;          /// NOLINT
+        uint64_t line_;          /// NOLINT
+        uint64_t column_;        /// NOLINT
+        bool isStmt_;            /// NOLINT
+        bool basicBlock_;        /// NOLINT
+        bool endSequence_;       /// NOLINT
+        bool prologueEnd_;       /// NOLINT
+        bool epilogueBegin_;     /// NOLINT
+        uint64_t isa_;           /// NOLINT
+        uint64_t discriminator_; /// NOLINT
+    };
+
+    /**
+     * Finds inlined subroutine DIEs and their caller lines that contains a given
+     * address among children of given die. Depth first search.
+     */
+    void findInlinedSubroutineDieForAddress(const CompilationUnit& cu, const Die& die,
+                                            const LineNumberVM& line_vm, uint64_t address,
+                                            std::optional<uint64_t> base_addr_cu,
+                                            std::vector<CallLocation>& locations,
+                                            const size_t max_size) const;
+
+    // Read an abbreviation from a std::string_view, return true if at end; remove_prefix section
+    static bool readAbbreviation(std::string_view& section, DIEAbbreviation& abbr);
+
+    static void readCompilationUnitAbbrs(std::string_view abbrev, CompilationUnit& cu);
+
+    /**
+     * Iterates over all children of a debugging info entry, calling the given
+     * callable for each. Iteration is stopped early if any of the calls return
+     * false. Returns the offset of next DIE after iterations.
+     */
+    size_t forEachChild(const CompilationUnit& cu, const Die& die,
+                        std::function<bool(const Die& die)> f) const;
+
+    // Get abbreviation corresponding to a code, in the chunk starting at
+    // offset in the .debug_abbrev section
+    [[nodiscard]] DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
+
+    /**
+     * Iterates over all attributes of a debugging info entry, calling the given
+     * callable for each. If all attributes are visited, then return the offset of
+     * next DIE, or else iteration is stopped early and return size_t(-1) if any
+     * of the calls return false.
+     */
+    size_t forEachAttribute(const CompilationUnit& cu, const Die& die,
+                            std::function<bool(const Attribute& die)> f) const;
+
+    Attribute readAttribute(const CompilationUnit& cu, const Die& die, AttributeSpec spec,
+                            std::string_view& info) const;
+
+    // Read one attribute <name, form> pair, remove_prefix sp; returns <0, 0> at end.
+    static AttributeSpec readAttributeSpec(std::string_view& sp);
+
+    // Read one attribute value, remove_prefix sp
+    using AttributeValue = std::variant<uint64_t, std::string_view>;
+    AttributeValue readAttributeValue(std::string_view& sp, uint64_t form, bool is64_bit) const;
+
+    // Get an ELF section by name, return true if found
+    std::string_view getSection(const char* name) const;
+
+    [[nodiscard]] CompilationUnit getCompilationUnit(uint64_t offset) const;
+    // Finds the Compilation Unit starting at offset.
+    [[nodiscard]] CompilationUnit findCompilationUnit(uint64_t targetOffset) const;
+
+    template <class T>
+    std::optional<T> getAttribute(const CompilationUnit& cu, const Die& die,
+                                  uint64_t attr_name) const {
+        std::optional<T> result;
+        forEachAttribute(cu, die, [&](const Attribute& attr) {
+            if (attr.spec.name == attr_name) {
+                result = std::get<T>(attr.attr_value);
+                return false;
+            }
+            return true;
+        });
+        return result;
+    }
+
+    // Check if the given address is in the range list at the given offset in .debug_ranges.
+    [[nodiscard]] bool isAddrInRangeList(const CompilationUnit& cu, uint64_t address,
+                                         std::optional<uint64_t> base_addr, size_t offset,
+                                         uint8_t addr_size) const;
+
+    std::string_view abbrev_;      // .debug_abbrev                     /// NOLINT
+    std::string_view addr_;        // .debug_addr (DWARF 5)               /// NOLINT
+    std::string_view aranges_;     // .debug_aranges                   /// NOLINT
+    std::string_view info_;        // .debug_info                         /// NOLINT
+    std::string_view line_;        // .debug_line                         /// NOLINT
+    std::string_view line_str_;    // .debug_line_str (DWARF 5)       /// NOLINT
+    std::string_view loclists_;    // .debug_loclists (DWARF 5)       /// NOLINT
+    std::string_view ranges_;      // .debug_ranges                     /// NOLINT
+    std::string_view rnglists_;    // .debug_rnglists (DWARF 5)       /// NOLINT
+    std::string_view str_;         // .debug_str                           /// NOLINT
+    std::string_view str_offsets_; // .debug_str_offsets (DWARF 5) /// NOLINT
+};
+
+} // namespace doris
+
+#endif
diff --git a/be/src/common/elf.cpp b/be/src/common/elf.cpp
new file mode 100644
index 0000000000..ae5051754d
--- /dev/null
+++ b/be/src/common/elf.cpp
@@ -0,0 +1,243 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/Elf.cpp
+// and modified by Doris
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+#include <common/elf.h>
+#include <fcntl.h>
+#include <fmt/format.h>
+#include <gutil/macros.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <vec/common/unaligned.h>
+
+#include <cstring>
+#include <system_error>
+
+#include "common/logging.h"
+
+namespace doris {
+
+Elf::Elf(const std::string& path) {
+    _file = path;
+    std::error_code ec;
+    elf_size = std::filesystem::file_size(_file, ec);
+    if (ec) {
+        LOG(FATAL) << fmt::format("failed to get file size {}: ({}), {}", _file.native(),
+                                  ec.value(), ec.message());
+    }
+    /// Check if it's an elf.
+    if (elf_size < sizeof(ElfEhdr)) {
+        LOG(FATAL) << fmt::format("The size of supposedly ELF file '{}' is too small", path);
+    }
+    RETRY_ON_EINTR(_fd, open(_file.c_str(), O_RDONLY));
+    if (_fd < 0) {
+        LOG(FATAL) << fmt::format("failed to open {}", _file.native());
+    }
+    mapped = static_cast<char*>(mmap(nullptr, elf_size, PROT_READ, MAP_SHARED, _fd, 0));
+    if (MAP_FAILED == mapped) {
+        LOG(FATAL) << fmt::format("MMappedFileDescriptor: Cannot mmap {}, read from {}.", elf_size,
+                                  path);
+    }
+
+    header = reinterpret_cast<const ElfEhdr*>(mapped);
+
+    if (memcmp(header->e_ident,
+               "\x7F"
+               "ELF",
+               4) != 0) {
+        LOG(FATAL) << fmt::format("The file '{}' is not ELF according to magic", path);
+    }
+
+    /// Get section header.
+    ElfOff section_header_offset = header->e_shoff;
+    uint16_t section_header_num_entries = header->e_shnum;
+
+    if (!section_header_offset || !section_header_num_entries ||
+        section_header_offset + section_header_num_entries * sizeof(ElfShdr) > elf_size) {
+        LOG(FATAL) << fmt::format(
+                "The ELF '{}' is truncated (section header points after end of file)", path);
+    }
+
+    section_headers = reinterpret_cast<const ElfShdr*>(mapped + section_header_offset);
+
+    /// The string table with section names.
+    auto section_names_strtab = findSection([&](const Section& section, size_t idx) {
+        return section.header.sh_type == SHT_STRTAB && header->e_shstrndx == idx;
+    });
+
+    if (!section_names_strtab) {
+        LOG(FATAL) << fmt::format("The ELF '{}' doesn't have string table with section names",
+                                  path);
+    }
+
+    ElfOff section_names_offset = section_names_strtab->header.sh_offset;
+    if (section_names_offset >= elf_size) {
+        LOG(FATAL) << fmt::format(
+                "The ELF '{}' is truncated (section names string table points after end of file)",
+                path);
+    }
+    section_names = reinterpret_cast<const char*>(mapped + section_names_offset);
+
+    /// Get program headers
+
+    ElfOff program_header_offset = header->e_phoff;
+    uint16_t program_header_num_entries = header->e_phnum;
+
+    if (!program_header_offset || !program_header_num_entries ||
+        program_header_offset + program_header_num_entries * sizeof(ElfPhdr) > elf_size) {
+        LOG(FATAL) << fmt::format(
+                "The ELF '{}' is truncated (program header points after end of file)", path);
+    }
+    program_headers = reinterpret_cast<const ElfPhdr*>(mapped + program_header_offset);
+}
+
+Elf::~Elf() {
+    if (mapped) {
+        munmap(static_cast<void*>(mapped), elf_size);
+    }
+    if (_fd > 0) {
+        int res = ::close(_fd);
+        if (-1 == res) {
+            LOG(WARNING) << fmt::format("failed to close {}", _file.native());
+        }
+        _fd = -1;
+    }
+}
+
+Elf::Section::Section(const ElfShdr& header_, const Elf& elf_) : header(header_), elf(elf_) {}
+
+bool Elf::iterateSections(std::function<bool(const Section& section, size_t idx)>&& pred) const {
+    for (size_t idx = 0; idx < header->e_shnum; ++idx) {
+        Section section(section_headers[idx], *this);
+
+        /// Sections spans after end of file.
+        if (section.header.sh_offset + section.header.sh_size > elf_size) {
+            continue;
+        }
+
+        if (pred(section, idx)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+std::optional<Elf::Section> Elf::findSection(
+        std::function<bool(const Section& section, size_t idx)>&& pred) const {
+    std::optional<Elf::Section> result;
+
+    iterateSections([&](const Section& section, size_t idx) {
+        if (pred(section, idx)) {
+            result.emplace(section);
+            return true;
+        }
+        return false;
+    });
+
+    return result;
+}
+
+std::optional<Elf::Section> Elf::findSectionByName(const char* name) const {
+    return findSection(
+            [&](const Section& section, size_t) { return 0 == strcmp(name, section.name()); });
+}
+
+std::string Elf::getBuildID() const {
+    /// Section headers are the first choice for a debuginfo file
+    if (std::string build_id; iterateSections([&build_id](const Section& section, size_t) {
+            if (section.header.sh_type == SHT_NOTE) {
+                build_id = Elf::getBuildID(section.begin(), section.size());
+                if (!build_id.empty()) {
+                    return true;
+                }
+            }
+            return false;
+        })) {
+        return build_id;
+    }
+
+    /// fallback to PHDR
+    for (size_t idx = 0; idx < header->e_phnum; ++idx) {
+        const ElfPhdr& phdr = program_headers[idx];
+
+        if (phdr.p_type == PT_NOTE) {
+            return getBuildID(mapped + phdr.p_offset, phdr.p_filesz);
+        }
+    }
+
+    return {};
+}
+
+#if defined(OS_SUNOS)
+std::string Elf::getBuildID(const char* nhdr_pos, size_t size) {
+    return {};
+}
+#else
+std::string Elf::getBuildID(const char* nhdr_pos, size_t size) {
+    const char* nhdr_end = nhdr_pos + size;
+
+    while (nhdr_pos < nhdr_end) {
+        ElfNhdr nhdr = unaligned_load<ElfNhdr>(nhdr_pos);
+
+        nhdr_pos += sizeof(ElfNhdr) + nhdr.n_namesz;
+        if (nhdr.n_type == NT_GNU_BUILD_ID) {
+            const char* build_id = nhdr_pos;
+            return {build_id, nhdr.n_descsz};
+        }
+        nhdr_pos += nhdr.n_descsz;
+    }
+
+    return {};
+}
+#endif // OS_SUNOS
+
+std::string Elf::getStoredBinaryHash() const {
+    if (auto section = findSectionByName(".clickhouse.hash")) {
+        return {section->begin(), section->end()};
+    } else {
+        return {};
+    }
+}
+
+const char* Elf::Section::name() const {
+    if (!elf.section_names) {
+        LOG(FATAL) << fmt::format("Section names are not initialized");
+    }
+
+    /// TODO buffer overflow is possible, we may need to check strlen.
+    return elf.section_names + header.sh_name;
+}
+
+const char* Elf::Section::begin() const {
+    return elf.mapped + header.sh_offset;
+}
+
+const char* Elf::Section::end() const {
+    return begin() + size();
+}
+
+size_t Elf::Section::size() const {
+    return header.sh_size;
+}
+
+} // namespace doris
+
+#endif
diff --git a/be/src/common/elf.h b/be/src/common/elf.h
new file mode 100644
index 0000000000..5bb5d93cb7
--- /dev/null
+++ b/be/src/common/elf.h
@@ -0,0 +1,95 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/Elf.h
+// and modified by Doris
+
+#pragma once
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+#include <elf.h>
+#include <link.h>
+
+#include <filesystem>
+#include <functional>
+#include <optional>
+#include <string>
+
+using ElfAddr = ElfW(Addr);
+using ElfEhdr = ElfW(Ehdr);
+using ElfOff = ElfW(Off);
+using ElfPhdr = ElfW(Phdr);
+using ElfShdr = ElfW(Shdr);
+using ElfNhdr = ElfW(Nhdr);
+using ElfSym = ElfW(Sym);
+
+namespace doris {
+
+/** Allow to navigate sections in ELF.
+  */
+class Elf final {
+public:
+    struct Section {
+        const ElfShdr& header;
+        [[nodiscard]] const char* name() const;
+
+        [[nodiscard]] const char* begin() const;
+        [[nodiscard]] const char* end() const;
+        [[nodiscard]] size_t size() const;
+
+        Section(const ElfShdr& header_, const Elf& elf_);
+
+    private:
+        const Elf& elf;
+    };
+
+    explicit Elf(const std::string& path);
+    ~Elf();
+
+    bool iterateSections(std::function<bool(const Section& section, size_t idx)>&& pred) const;
+    std::optional<Section> findSection(
+            std::function<bool(const Section& section, size_t idx)>&& pred) const;
+    std::optional<Section> findSectionByName(const char* name) const;
+
+    [[nodiscard]] const char* begin() const { return mapped; }
+    [[nodiscard]] const char* end() const { return mapped + elf_size; }
+    [[nodiscard]] size_t size() const { return elf_size; }
+
+    /// Obtain build id from SHT_NOTE of section headers (fallback to PT_NOTES section of program headers).
+    /// Return empty string if does not exist.
+    /// The string is returned in binary. Note that "readelf -n ./clickhouse-server" prints it in hex.
+    [[nodiscard]] std::string getBuildID() const;
+    static std::string getBuildID(const char* nhdr_pos, size_t size);
+
+    /// Hash of the binary for integrity checks.
+    [[nodiscard]] std::string getStoredBinaryHash() const;
+
+private:
+    int _fd = -1;
+    std::filesystem::path _file;
+    size_t elf_size;
+    char* mapped;
+    const ElfEhdr* header;
+    const ElfShdr* section_headers;
+    const ElfPhdr* program_headers;
+    const char* section_names = nullptr;
+};
+
+} // namespace doris
+
+#endif
diff --git a/be/src/common/memory_sanitizer.h b/be/src/common/memory_sanitizer.h
new file mode 100644
index 0000000000..4405e7dfc9
--- /dev/null
+++ b/be/src/common/memory_sanitizer.h
@@ -0,0 +1,50 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/MemorySanitizer.h
+// and modified by Doris
+
+#pragma once
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+
+#undef __msan_unpoison
+#undef __msan_test_shadow
+#undef __msan_print_shadow
+#undef __msan_unpoison_string
+
+#define __msan_unpoison(X, Y)            /// NOLINT
+#define __msan_test_shadow(X, Y) (false) /// NOLINT
+#define __msan_print_shadow(X, Y)        /// NOLINT
+#define __msan_unpoison_string(X)        /// NOLINT
+
+#if defined(__clang__) && defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+#undef __msan_unpoison
+#undef __msan_test_shadow
+#undef __msan_print_shadow
+#undef __msan_unpoison_string
+#include <sanitizer/msan_interface.h>
+#endif
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
diff --git a/be/src/common/multi_version.h b/be/src/common/multi_version.h
new file mode 100644
index 0000000000..ce4d91775d
--- /dev/null
+++ b/be/src/common/multi_version.h
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/MultiVersion.h
+// and modified by Doris
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+/** Allow to store and read-only usage of an object in several threads,
+  *  and to atomically replace an object in another thread.
+  * The replacement is atomic and reading threads can work with different versions of an object.
+  *
+  * Usage:
+  *  MultiVersion<T> x;
+  * - on data update:
+  *  x.set(new value);
+  * - on read-only usage:
+  * {
+  *     MultiVersion<T>::Version current_version = x.get();
+  *     // use *current_version
+  * }   // now we finish own current version; if the version is outdated and no one else is using it - it will be destroyed.
+  *
+  * All methods are thread-safe.
+  */
+template <typename T>
+class MultiVersion {
+public:
+    /// Version of object for usage. shared_ptr manage lifetime of version.
+    using Version = std::shared_ptr<const T>;
+
+    /// Default initialization - by nullptr.
+    MultiVersion() = default;
+
+    explicit MultiVersion(std::unique_ptr<const T>&& value) : current_version(std::move(value)) {}
+
+    /// Obtain current version for read-only usage. Returns shared_ptr, that manages lifetime of version.
+    Version get() const { return std::atomic_load(&current_version); }
+
+    /// TODO: replace atomic_load/store() on shared_ptr (which is deprecated as of C++20) by C++20 std::atomic<std::shared_ptr>.
+    /// Clang 15 currently does not support it.
+
+    /// Update an object with new version.
+    void set(std::unique_ptr<const T>&& value) {
+        std::atomic_store(&current_version, Version {std::move(value)});
+    }
+
+private:
+    Version current_version;
+};
diff --git a/be/src/common/phdr_cache.cpp b/be/src/common/phdr_cache.cpp
new file mode 100644
index 0000000000..656e40ff45
--- /dev/null
+++ b/be/src/common/phdr_cache.cpp
@@ -0,0 +1,147 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/base/phdr_cache.cpp
+// and modified by Doris
+
+#if defined(__clang__)
+#pragma clang diagnostic ignored "-Wreserved-identifier"
+#endif
+
+/// This code was based on the code by Fedor Korotkiy https://www.linkedin.com/in/fedor-korotkiy-659a1838/
+
+#if defined(__linux__) && !defined(THREAD_SANITIZER) && !defined(USE_MUSL)
+#define USE_PHDR_CACHE 1
+#endif
+
+/// Thread Sanitizer uses dl_iterate_phdr function on initialization and fails if we provide our own.
+#ifdef USE_PHDR_CACHE
+
+#if defined(__clang__)
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wunused-macros"
+#endif
+
+#define __msan_unpoison(X, Y) // NOLINT
+#if defined(__clang__) && defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+#undef __msan_unpoison
+#include <sanitizer/msan_interface.h>
+#endif
+#endif
+
+#include <dlfcn.h>
+#include <link.h>
+
+#include <atomic>
+#include <cstddef>
+#include <stdexcept>
+#include <vector>
+
+namespace {
+
+// This is adapted from
+// https://github.com/scylladb/seastar/blob/master/core/exception_hacks.hh
+// https://github.com/scylladb/seastar/blob/master/core/exception_hacks.cc
+
+using DLIterateFunction = int (*)(int (*callback)(dl_phdr_info* info, size_t size, void* data),
+                                  void* data);
+
+DLIterateFunction getOriginalDLIteratePHDR() {
+    void* func = dlsym(RTLD_NEXT, "dl_iterate_phdr");
+    if (!func) {
+        throw std::runtime_error("Cannot find dl_iterate_phdr function with dlsym");
+    }
+    return reinterpret_cast<DLIterateFunction>(func);
+}
+
+using PHDRCache = std::vector<dl_phdr_info>;
+std::atomic<PHDRCache*> phdr_cache {};
+
+} // namespace
+
+extern "C"
+#ifndef __clang__
+        [[gnu::visibility("default")]] [[gnu::externally_visible]]
+#endif
+        int
+        dl_iterate_phdr(int (*callback)(dl_phdr_info* info, size_t size, void* data), void* data) {
+    auto* current_phdr_cache = phdr_cache.load();
+    if (!current_phdr_cache) {
+        // Cache is not yet populated, pass through to the original function.
+        return getOriginalDLIteratePHDR()(callback, data);
+    }
+
+    int result = 0;
+    for (auto& entry : *current_phdr_cache) {
+        result = callback(&entry, offsetof(dl_phdr_info, dlpi_adds), data);
+        if (result != 0) {
+            break;
+        }
+    }
+    return result;
+}
+
+extern "C" {
+#ifdef ADDRESS_SANITIZER
+void __lsan_ignore_object(const void*);
+#else
+void __lsan_ignore_object(const void*) {} // NOLINT
+#endif
+}
+
+void updatePHDRCache() {
+    // Fill out ELF header cache for access without locking.
+    // This assumes no dynamic object loading/unloading after this point
+
+    PHDRCache* new_phdr_cache = new PHDRCache;
+    getOriginalDLIteratePHDR()(
+            [](dl_phdr_info* info, size_t /*size*/, void* data) {
+                // `info` is created by dl_iterate_phdr, which is a non-instrumented
+                // libc function, so we have to unpoison it manually.
+                __msan_unpoison(info, sizeof(*info));
+
+                reinterpret_cast<PHDRCache*>(data)->push_back(*info);
+                return 0;
+            },
+            new_phdr_cache);
+    phdr_cache.store(new_phdr_cache);
+
+    /// Memory is intentionally leaked.
+    __lsan_ignore_object(new_phdr_cache);
+}
+
+bool hasPHDRCache() {
+    return phdr_cache.load() != nullptr;
+}
+
+#else
+
+void updatePHDRCache() {}
+
+#if defined(USE_MUSL)
+/// With statically linked with musl, dl_iterate_phdr is immutable.
+bool hasPHDRCache() {
+    return true;
+}
+#else
+bool hasPHDRCache() {
+    return false;
+}
+#endif
+
+#endif
diff --git a/be/src/common/phdr_cache.h b/be/src/common/phdr_cache.h
new file mode 100644
index 0000000000..30a212c94c
--- /dev/null
+++ b/be/src/common/phdr_cache.h
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/base/phdr_cache.h
+// and modified by Doris
+
+#pragma once
+
+/// This code was based on the code by Fedor Korotkiy https://www.linkedin.com/in/fedor-korotkiy-659a1838/
+
+/** Collects all dl_phdr_info items and caches them in a static array.
+  * Also rewrites dl_iterate_phdr with a lock-free version which consults the above cache
+  * thus eliminating scalability bottleneck in C++ exception unwinding.
+  * As a drawback, this only works if no dynamic object unloading happens after this point.
+  * This function is thread-safe. You should call it to update cache after loading new shared libraries.
+  * Otherwise exception handling from dlopened libraries won't work (will call std::terminate immediately).
+  * NOTE: dlopen is forbidden in our code.
+  *
+  * NOTE: It is disabled with Thread Sanitizer because TSan can only use original "dl_iterate_phdr" function.
+  */
+void updatePHDRCache();
+
+/** Check if "dl_iterate_phdr" will be lock-free
+  * to determine if some features like Query Profiler can be used.
+  */
+bool hasPHDRCache();
diff --git a/be/src/common/stack_trace.cpp b/be/src/common/stack_trace.cpp
new file mode 100644
index 0000000000..9e951b6532
--- /dev/null
+++ b/be/src/common/stack_trace.cpp
@@ -0,0 +1,455 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/StackTrace.cpp
+// and modified by Doris
+
+#include "common/stack_trace.h"
+
+#include <common/dwarf.h>
+#include <common/elf.h>
+#include <common/memory_sanitizer.h>
+#include <common/symbol_index.h>
+#include <fmt/format.h>
+
+#include <atomic>
+#include <filesystem>
+#include <map>
+#include <mutex>
+#include <sstream>
+#include <unordered_map>
+
+#include "config.h"
+#include "vec/common/demangle.h"
+#include "vec/common/hex.h"
+
+#if USE_UNWIND
+#include <libunwind.h>
+#endif
+
+namespace {
+/// Currently this variable is set up once on server startup.
+/// But we use atomic just in case, so it is possible to be modified at runtime.
+std::atomic<bool> show_addresses = true;
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+void writePointerHex(const void* ptr, std::stringstream& buf) {
+    buf.write("0x", 2);
+    char hex_str[2 * sizeof(ptr)];
+    doris::vectorized::write_hex_uint_lowercase(reinterpret_cast<uintptr_t>(ptr), hex_str);
+    buf.write(hex_str, 2 * sizeof(ptr));
+}
+#endif
+
+bool shouldShowAddress(const void* addr) {
+    /// If the address is less than 4096, most likely it is a nullptr dereference with offset,
+    /// and showing this offset is secure nevertheless.
+    /// NOTE: 4096 is the page size on x86 and it can be different on other systems,
+    /// but for the purpose of this branch, it does not matter.
+    if (reinterpret_cast<uintptr_t>(addr) < 4096) {
+        return true;
+    }
+
+    return show_addresses.load(std::memory_order_relaxed);
+}
+} // namespace
+
+void StackTrace::setShowAddresses(bool show) {
+    show_addresses.store(show, std::memory_order_relaxed);
+}
+
+std::string SigsegvErrorString(const siginfo_t& info, [[maybe_unused]] const ucontext_t& context) {
+    using namespace std::string_literals;
+    std::string address =
+            info.si_addr == nullptr
+                    ? "NULL pointer"s
+                    : (shouldShowAddress(info.si_addr) ? fmt::format("{}", info.si_addr) : ""s);
+
+    const std::string_view access =
+#if defined(__x86_64__) && !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(__arm__) && \
+        !defined(__powerpc__)
+            (context.uc_mcontext.gregs[REG_ERR] & 0x02) ? "write" : "read";
+#else
+            "";
+#endif
+
+    std::string_view message;
+
+    switch (info.si_code) {
+    case SEGV_ACCERR:
+        message = "Attempted access has violated the permissions assigned to the memory area";
+        break;
+    case SEGV_MAPERR:
+        message = "Address not mapped to object";
+        break;
+    default:
+        message = "Unknown si_code";
+        break;
+    }
+
+    return fmt::format("Address: {}. Access: {}. {}.", std::move(address), access, message);
+}
+
+constexpr std::string_view SigbusErrorString(int si_code) {
+    switch (si_code) {
+    case BUS_ADRALN:
+        return "Invalid address alignment.";
+    case BUS_ADRERR:
+        return "Non-existent physical address.";
+    case BUS_OBJERR:
+        return "Object specific hardware error.";
+
+        // Linux specific
+#if defined(BUS_MCEERR_AR)
+    case BUS_MCEERR_AR:
+        return "Hardware memory error: action required.";
+#endif
+#if defined(BUS_MCEERR_AO)
+    case BUS_MCEERR_AO:
+        return "Hardware memory error: action optional.";
+#endif
+    default:
+        return "Unknown si_code.";
+    }
+}
+
+constexpr std::string_view SigfpeErrorString(int si_code) {
+    switch (si_code) {
+    case FPE_INTDIV:
+        return "Integer divide by zero.";
+    case FPE_INTOVF:
+        return "Integer overflow.";
+    case FPE_FLTDIV:
+        return "Floating point divide by zero.";
+    case FPE_FLTOVF:
+        return "Floating point overflow.";
+    case FPE_FLTUND:
+        return "Floating point underflow.";
+    case FPE_FLTRES:
+        return "Floating point inexact result.";
+    case FPE_FLTINV:
+        return "Floating point invalid operation.";
+    case FPE_FLTSUB:
+        return "Subscript out of range.";
+    default:
+        return "Unknown si_code.";
+    }
+}
+
+constexpr std::string_view SigillErrorString(int si_code) {
+    switch (si_code) {
+    case ILL_ILLOPC:
+        return "Illegal opcode.";
+    case ILL_ILLOPN:
+        return "Illegal operand.";
+    case ILL_ILLADR:
+        return "Illegal addressing mode.";
+    case ILL_ILLTRP:
+        return "Illegal trap.";
+    case ILL_PRVOPC:
+        return "Privileged opcode.";
+    case ILL_PRVREG:
+        return "Privileged register.";
+    case ILL_COPROC:
+        return "Coprocessor error.";
+    case ILL_BADSTK:
+        return "Internal stack error.";
+    default:
+        return "Unknown si_code.";
+    }
+}
+
+std::string signalToErrorMessage(int sig, const siginfo_t& info,
+                                 [[maybe_unused]] const ucontext_t& context) {
+    switch (sig) {
+    case SIGSEGV:
+        return SigsegvErrorString(info, context);
+    case SIGBUS:
+        return std::string {SigbusErrorString(info.si_code)};
+    case SIGILL:
+        return std::string {SigillErrorString(info.si_code)};
+    case SIGFPE:
+        return std::string {SigfpeErrorString(info.si_code)};
+    case SIGTSTP:
+        return "This is a signal used for debugging purposes by the user.";
+    default:
+        return "";
+    }
+}
+
+static void* getCallerAddress(const ucontext_t& context) {
+#if defined(__x86_64__)
+    /// Get the address at the time the signal was raised from the RIP (x86-64)
+#if defined(__FreeBSD__)
+    return reinterpret_cast<void*>(context.uc_mcontext.mc_rip);
+#elif defined(__APPLE__)
+    return reinterpret_cast<void*>(context.uc_mcontext->__ss.__rip);
+#else
+    return reinterpret_cast<void*>(context.uc_mcontext.gregs[REG_RIP]);
+#endif
+#elif defined(__APPLE__) && defined(__aarch64__)
+    return reinterpret_cast<void*>(context.uc_mcontext->__ss.__pc);
+#elif defined(__FreeBSD__) && defined(__aarch64__)
+    return reinterpret_cast<void*>(context.uc_mcontext.mc_gpregs.gp_elr);
+#elif defined(__aarch64__)
+    return reinterpret_cast<void*>(context.uc_mcontext.pc);
+#elif defined(__powerpc64__) && defined(__linux__)
+    return reinterpret_cast<void*>(context.uc_mcontext.gp_regs[PT_NIP]);
+#elif defined(__powerpc64__) && defined(__FreeBSD__)
+    return reinterpret_cast<void*>(context.uc_mcontext.mc_srr0);
+#elif defined(__riscv)
+    return reinterpret_cast<void*>(context.uc_mcontext.__gregs[REG_PC]);
+#elif defined(__s390x__)
+    return reinterpret_cast<void*>(context.uc_mcontext.psw.addr);
+#else
+    return nullptr;
+#endif
+}
+
+// FIXME: looks like this is used only for Sentry but duplicates the whole algo, maybe replace?
+void StackTrace::symbolize(const StackTrace::FramePointers& frame_pointers,
+                           [[maybe_unused]] size_t offset, size_t size,
+                           StackTrace::Frames& frames) {
+#if defined(__ELF__) && !defined(__FreeBSD__)
+    auto symbol_index_ptr = doris::SymbolIndex::instance();
+    const doris::SymbolIndex& symbol_index = *symbol_index_ptr;
+    std::unordered_map<std::string, doris::Dwarf> dwarfs;
+
+    for (size_t i = 0; i < offset; ++i) {
+        frames[i].virtual_addr = frame_pointers[i];
+    }
+
+    for (size_t i = offset; i < size; ++i) {
+        StackTrace::Frame& current_frame = frames[i];
+        current_frame.virtual_addr = frame_pointers[i];
+        const auto* object = symbol_index.findObject(current_frame.virtual_addr);
+        uintptr_t virtual_offset = object ? uintptr_t(object->address_begin) : 0;
+        current_frame.physical_addr =
+                reinterpret_cast<void*>(uintptr_t(current_frame.virtual_addr) - virtual_offset);
+
+        if (object) {
+            current_frame.object = object->name;
+            if (std::error_code ec;
+                std::filesystem::exists(current_frame.object.value(), ec) && !ec) {
+                auto dwarf_it = dwarfs.try_emplace(object->name, object->elf).first;
+
+                doris::Dwarf::LocationInfo location;
+                std::vector<doris::Dwarf::SymbolizedFrame> inline_frames;
+                if (dwarf_it->second.findAddress(uintptr_t(current_frame.physical_addr), location,
+                                                 doris::Dwarf::LocationInfoMode::FAST,
+                                                 inline_frames)) {
+                    current_frame.file = location.file.toString();
+                    current_frame.line = location.line;
+                }
+            }
+        } else {
+            current_frame.object = "?";
+        }
+
+        if (const auto* symbol = symbol_index.findSymbol(current_frame.virtual_addr)) {
+            current_frame.symbol = demangle(symbol->name);
+        } else {
+            current_frame.symbol = "?";
+        }
+    }
+#else
+    for (size_t i = 0; i < size; ++i) frames[i].virtual_addr = frame_pointers[i];
+#endif
+}
+
+StackTrace::StackTrace(const ucontext_t& signal_context) {
+    tryCapture();
+
+    /// This variable from signal handler is not instrumented by Memory Sanitizer.
+    __msan_unpoison(&signal_context, sizeof(signal_context));
+
+    void* caller_address = getCallerAddress(signal_context);
+
+    if (size == 0 && caller_address) {
+        frame_pointers[0] = caller_address;
+        size = 1;
+    } else {
+        /// Skip excessive stack frames that we have created while finding stack trace.
+        for (size_t i = 0; i < size; ++i) {
+            if (frame_pointers[i] == caller_address) {
+                offset = i;
+                break;
+            }
+        }
+    }
+}
+
+void StackTrace::tryCapture() {
+#if USE_UNWIND
+    size = unw_backtrace(frame_pointers.data(), capacity);
+    __msan_unpoison(frame_pointers.data(), size * sizeof(frame_pointers[0]));
+#else
+    size = 0;
+#endif
+}
+
+/// ClickHouse uses bundled libc++ so type names will be the same on every system thus it's safe to hardcode them
+constexpr std::pair<std::string_view, std::string_view> replacements[] = {
+        {"::__1", ""},
+        {"std::basic_string<char, std::char_traits<char>, std::allocator<char>>", "std::string"}};
+
+std::string collapseNames(std::string&& haystack) {
+    // TODO: surely there is a written version already for better in place search&replace
+    for (auto [needle, to] : replacements) {
+        size_t pos = 0;
+        while ((pos = haystack.find(needle, pos)) != std::string::npos) {
+            haystack.replace(pos, needle.length(), to);
+            pos += to.length();
+        }
+    }
+
+    return haystack;
+}
+
+struct StackTraceRefTriple {
+    const StackTrace::FramePointers& pointers;
+    size_t offset;
+    size_t size;
+};
+
+struct StackTraceTriple {
+    StackTrace::FramePointers pointers;
+    size_t offset;
+    size_t size;
+};
+
+template <class T>
+concept MaybeRef = std::is_same_v<T, StackTraceTriple> || std::is_same_v<T, StackTraceRefTriple>;
+
+constexpr bool operator<(const MaybeRef auto& left, const MaybeRef auto& right) {
+    return std::tuple {left.pointers, left.size, left.offset} <
+           std::tuple {right.pointers, right.size, right.offset};
+}
+
+static void toStringEveryLineImpl([[maybe_unused]] bool fatal,
+                                  const StackTraceRefTriple& stack_trace,
+                                  std::function<void(std::string_view)> callback) {
+    if (stack_trace.size == 0) {
+        return callback("<Empty trace>");
+    }
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+    using enum doris::Dwarf::LocationInfoMode;
+    const auto mode = fatal ? FULL_WITH_INLINE : FAST;
+    auto symbol_index_ptr = doris::SymbolIndex::instance();
+    const doris::SymbolIndex& symbol_index = *symbol_index_ptr;
+    std::unordered_map<std::string, doris::Dwarf> dwarfs;
+    for (size_t i = stack_trace.offset; i < stack_trace.size; ++i) {
+        std::vector<doris::Dwarf::SymbolizedFrame> inline_frames;
+        const void* virtual_addr = stack_trace.pointers[i];
+        const auto* object = symbol_index.findObject(virtual_addr);
+        uintptr_t virtual_offset = object ? uintptr_t(object->address_begin) : 0;
+        const void* physical_addr =
+                reinterpret_cast<const void*>(uintptr_t(virtual_addr) - virtual_offset);
+
+        std::stringstream out;
+        out << i << ". ";
+
+        if (std::error_code ec; object && std::filesystem::exists(object->name, ec) && !ec) {
+            auto dwarf_it = dwarfs.try_emplace(object->name, object->elf).first;
+
+            doris::Dwarf::LocationInfo location;
+
+            if (dwarf_it->second.findAddress(uintptr_t(physical_addr), location, mode,
+                                             inline_frames)) {
+                out << location.file.toString() << ":" << location.line << ": ";
+            }
+        }
+
+        if (const auto* const symbol = symbol_index.findSymbol(virtual_addr)) {
+            out << collapseNames(demangle(symbol->name));
+        } else {
+            out << "?";
+        }
+
+        if (shouldShowAddress(physical_addr)) {
+            out << " @ ";
+            writePointerHex(physical_addr, out);
+        }
+
+        out << " in " << (object ? object->name : "?");
+
+        for (size_t j = 0; j < inline_frames.size(); ++j) {
+            const auto& frame = inline_frames[j];
+            callback(fmt::format("{}.{}. inlined from {}:{}: {}", i, j + 1,
+                                 frame.location.file.toString(), frame.location.line,
+                                 collapseNames(demangle(frame.name))));
+        }
+
+        callback(out.str());
+    }
+#else
+    for (size_t i = stack_trace.offset; i < stack_trace.size; ++i)
+        if (const void* const addr = stack_trace.pointers[i]; shouldShowAddress(addr))
+            callback(fmt::format("{}. {}", i, addr));
+#endif
+}
+
+void StackTrace::toStringEveryLine(std::function<void(std::string_view)> callback) const {
+    toStringEveryLineImpl(true, {frame_pointers, offset, size}, std::move(callback));
+}
+
+using StackTraceCache = std::map<StackTraceTriple, std::string, std::less<>>;
+
+static StackTraceCache& cacheInstance() {
+    static StackTraceCache cache;
+    return cache;
+}
+
+static std::mutex stacktrace_cache_mutex;
+
+std::string toStringCached(const StackTrace::FramePointers& pointers, size_t offset, size_t size) {
+    /// Calculation of stack trace text is extremely slow.
+    /// We use simple cache because otherwise the server could be overloaded by trash queries.
+    /// Note that this cache can grow unconditionally, but practically it should be small.
+    std::lock_guard lock {stacktrace_cache_mutex};
+
+    StackTraceCache& cache = cacheInstance();
+    const StackTraceRefTriple key {pointers, offset, size};
+
+    if (auto it = cache.find(key); it != cache.end()) {
+        return it->second;
+    } else {
+        std::stringstream out;
+        toStringEveryLineImpl(false, key, [&](std::string_view str) { out << str << '\n'; });
+
+        return cache.emplace(StackTraceTriple {pointers, offset, size}, out.str()).first->second;
+    }
+}
+
+std::string StackTrace::toString() const {
+    return toStringCached(frame_pointers, offset, size);
+}
+
+std::string StackTrace::toString(void** frame_pointers_raw, size_t offset, size_t size) {
+    __msan_unpoison(frame_pointers_raw, size * sizeof(*frame_pointers_raw));
+
+    StackTrace::FramePointers frame_pointers {};
+    std::copy_n(frame_pointers_raw, size, frame_pointers.begin());
+
+    return toStringCached(frame_pointers, offset, size);
+}
+
+void StackTrace::dropCache() {
+    std::lock_guard lock {stacktrace_cache_mutex};
+    cacheInstance().clear();
+}
diff --git a/be/src/common/stack_trace.h b/be/src/common/stack_trace.h
new file mode 100644
index 0000000000..1bd60c2870
--- /dev/null
+++ b/be/src/common/stack_trace.h
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/StackTrace.h
+// and modified by Doris
+
+#pragma once
+
+#include <array>
+#include <csignal>
+#include <functional>
+#include <optional>
+#include <string>
+#include <vector>
+
+#ifdef __APPLE__
+// ucontext is not available without _XOPEN_SOURCE
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+#define _XOPEN_SOURCE 700
+#endif
+#include <ucontext.h>
+
+struct NoCapture {};
+
+/// Tries to capture current stack trace using libunwind or signal context
+/// NOTE: StackTrace calculation is signal safe only if updatePHDRCache() was called beforehand.
+class StackTrace {
+public:
+    struct Frame {
+        const void* virtual_addr = nullptr;
+        void* physical_addr = nullptr;
+        std::optional<std::string> symbol;
+        std::optional<std::string> object;
+        std::optional<std::string> file;
+        std::optional<uint64_t> line;
+    };
+
+    /* NOTE: It cannot be larger right now, since otherwise it
+     * will not fit into minimal PIPE_BUF (512) in TraceCollector.
+     */
+    static constexpr size_t capacity = 45;
+
+    using FramePointers = std::array<void*, capacity>;
+    using Frames = std::array<Frame, capacity>;
+
+    /// Tries to capture stack trace
+    inline StackTrace() { tryCapture(); }
+
+    /// Tries to capture stack trace. Fallbacks on parsing caller address from
+    /// signal context if no stack trace could be captured
+    explicit StackTrace(const ucontext_t& signal_context);
+
+    /// Creates empty object for deferred initialization
+    explicit inline StackTrace(NoCapture) {}
+
+    [[nodiscard]] constexpr size_t getSize() const { return size; }
+    [[nodiscard]] constexpr size_t getOffset() const { return offset; }
+    [[nodiscard]] const FramePointers& getFramePointers() const { return frame_pointers; }
+    [[nodiscard]] std::string toString() const;
+
+    static std::string toString(void** frame_pointers, size_t offset, size_t size);
+    static void dropCache();
+    static void symbolize(const FramePointers& frame_pointers, size_t offset, size_t size,
+                          StackTrace::Frames& frames);
+
+    void toStringEveryLine(std::function<void(std::string_view)> callback) const;
+
+    /// Displaying the addresses can be disabled for security reasons.
+    /// If you turn off addresses, it will be more secure, but we will be unable to help you with debugging.
+    /// Please note: addresses are also available in the system.stack_trace and system.trace_log tables.
+    static void setShowAddresses(bool show);
+
+protected:
+    void tryCapture();
+
+    size_t size = 0;
+    size_t offset = 0; /// How many frames to skip while displaying.
+    FramePointers frame_pointers {};
+};
+
+std::string signalToErrorMessage(int sig, const siginfo_t& info, const ucontext_t& context);
diff --git a/be/src/common/status.h b/be/src/common/status.h
index d64b70615c..1ccf5bec5f 100644
--- a/be/src/common/status.h
+++ b/be/src/common/status.h
@@ -361,7 +361,7 @@ public:
 #ifdef ENABLE_STACKTRACE
         if (stacktrace && capture_stacktrace(code)) {
             status._err_msg->_stack = get_stack_trace();
-            LOG(WARNING) << "meet error status: " << status;
+            LOG(WARNING) << "meet error status: " << status; // may print too many stacks.
         }
 #endif
         return status;
diff --git a/be/src/common/symbol_index.cpp b/be/src/common/symbol_index.cpp
new file mode 100644
index 0000000000..fc60e2186e
--- /dev/null
+++ b/be/src/common/symbol_index.cpp
@@ -0,0 +1,559 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/SymbolIndex.cpp
+// and modified by Doris
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+#include <common/symbol_index.h>
+#include <link.h>
+#include <pdqsort.h>
+
+#include <algorithm>
+#include <cassert>
+#include <filesystem>
+#include <optional>
+
+#include "common/stack_trace.h"
+#include "vec/common/hex.h"
+
+/**
+
+ELF object can contain three different places with symbol names and addresses:
+
+1. Symbol table in section headers. It is used for static linking and usually left in executable.
+It is not loaded in memory and they are not necessary for program to run.
+It does not relate to debug info and present regardless to -g flag.
+You can use strip to get rid of this symbol table.
+If you have this symbol table in your binary, you can manually read it and get symbol names, even for symbols from anonymous namespaces.
+
+2. Hashes in program headers such as DT_HASH and DT_GNU_HASH.
+It is necessary for dynamic object (.so libraries and any dynamically linked executable that depend on .so libraries)
+because it is used for dynamic linking that happens in runtime and performed by dynamic loader.
+Only exported symbols will be presented in that hash tables. Symbols from anonymous namespaces are not.
+This part of executable binary is loaded in memory and accessible via 'dl_iterate_phdr', 'dladdr' and 'backtrace_symbols' functions from libc.
+ClickHouse versions prior to 19.13 has used just these symbol names to symbolize stack traces
+and stack traces may be incomplete due to lack of symbols with internal linkage.
+But because ClickHouse is linked with most of the symbols exported (-rdynamic flag) it can still provide good enough stack traces.
+
+3. DWARF debug info. It contains the most detailed information about symbols and everything else.
+It allows to get source file names and line numbers from addresses. Only available if you use -g option for compiler.
+It is also used by default for ClickHouse builds, but because of its weight (about two gigabytes)
+it is split to separate binary and provided in clickhouse-common-static-dbg package.
+This separate binary is placed in /usr/lib/debug/usr/bin/clickhouse.debug and is loaded automatically by tools like gdb, addr2line.
+When you build ClickHouse by yourself, debug info is not split and present in a single huge binary.
+
+What ClickHouse is using to provide good stack traces?
+
+In versions prior to 19.13, only "program headers" (2) was used.
+
+In version 19.13, ClickHouse will read program headers (2) and cache them,
+also it will read itself as ELF binary and extract symbol tables from section headers (1)
+to also symbolize functions that are not exported for dynamic linking.
+And finally, it will read DWARF info (3) if available to display file names and line numbers.
+
+What detail can you obtain depending on your binary?
+
+If you have debug info (you build ClickHouse by yourself or install clickhouse-common-static-dbg package), you will get source file names and line numbers.
+Otherwise you will get only symbol names. If your binary contains symbol table in section headers (the default, unless stripped), you will get all symbol names.
+Otherwise you will get only exported symbols from program headers.
+
+*/
+
+#if defined(__clang__)
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wunused-macros"
+#endif
+
+#define __msan_unpoison_string(X) // NOLINT
+#if defined(__clang__) && defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+#undef __msan_unpoison_string
+#include <sanitizer/msan_interface.h>
+#endif
+#endif
+
+namespace doris {
+
+namespace {
+
+/// Notes: "PHDR" is "Program Headers".
+/// To look at program headers, run:
+///  readelf -l ./clickhouse-server
+/// To look at section headers, run:
+///  readelf -S ./clickhouse-server
+/// Also look at: https://wiki.osdev.org/ELF
+/// Also look at: man elf
+/// http://www.linker-aliens.org/blogs/ali/entry/inside_elf_symbol_tables/
+/// https://stackoverflow.com/questions/32088140/multiple-string-tables-in-elf-object
+
+void updateResources(ElfW(Addr) base_address, std::string_view object_name, std::string_view name,
+                     const void* address, SymbolIndex::Resources& resources) {
+    const char* char_address = static_cast<const char*>(address);
+
+    if (name.starts_with("_binary_") || name.starts_with("binary_")) {
+        if (name.ends_with("_start")) {
+            name = name.substr((name[0] == '_') + strlen("binary_"));
+            name = name.substr(0, name.size() - strlen("_start"));
+
+            auto& resource = resources[name];
+            if (!resource.base_address || resource.base_address == base_address) {
+                resource.base_address = base_address;
+                resource.start =
+                        std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor)
+                resource.object_name = object_name;
+            }
+        }
+        if (name.ends_with("_end")) {
+            name = name.substr((name[0] == '_') + strlen("binary_"));
+            name = name.substr(0, name.size() - strlen("_end"));
+
+            auto& resource = resources[name];
+            if (!resource.base_address || resource.base_address == base_address) {
+                resource.base_address = base_address;
+                resource.end =
+                        std::string_view {char_address, 0}; // NOLINT(bugprone-string-constructor)
+                resource.object_name = object_name;
+            }
+        }
+    }
+}
+
+/// Based on the code of musl-libc and the answer of Kanalpiroge on
+/// https://stackoverflow.com/questions/15779185/list-all-the-functions-symbols-on-the-fly-in-c-code-on-a-linux-architecture
+/// It does not extract all the symbols (but only public - exported and used for dynamic linking),
+/// but will work if we cannot find or parse ELF files.
+void collectSymbolsFromProgramHeaders(dl_phdr_info* info, std::vector<SymbolIndex::Symbol>& symbols,
+                                      SymbolIndex::Resources& resources) {
+    /* Iterate over all headers of the current shared lib
+     * (first call is for the executable itself)
+     */
+    for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) {
+        /* Further processing is only needed if the dynamic section is reached
+         */
+        if (info->dlpi_phdr[header_index].p_type != PT_DYNAMIC) {
+            continue;
+        }
+
+        /* Get a pointer to the first entry of the dynamic section.
+         * It's address is the shared lib's address + the virtual address
+         */
+        const ElfW(Dyn)* dyn_begin = reinterpret_cast<const ElfW(Dyn)*>(
+                info->dlpi_addr + info->dlpi_phdr[header_index].p_vaddr);
+
+        /// For unknown reason, addresses are sometimes relative sometimes absolute.
+        auto correct_address = [](ElfW(Addr) base, ElfW(Addr) ptr) {
+            return ptr > base ? ptr : base + ptr;
+        };
+
+        /* Iterate over all entries of the dynamic section until the
+         * end of the symbol table is reached. This is indicated by
+         * an entry with d_tag == DT_NULL.
+         */
+
+        size_t sym_cnt = 0;
+        for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) {
+            ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr);
+
+            // TODO: this branch leads to invalid address of the hash table. Need further investigation.
+            // if (it->d_tag == DT_HASH)
+            // {
+            //     const ElfW(Word) * hash = reinterpret_cast<const ElfW(Word) *>(base_address);
+            //     sym_cnt = hash[1];
+            //     break;
+            // }
+            if (it->d_tag == DT_GNU_HASH) {
+                /// This code based on Musl-libc.
+
+                const uint32_t* buckets = nullptr;
+                const uint32_t* hashval = nullptr;
+
+                const ElfW(Word)* hash = reinterpret_cast<const ElfW(Word)*>(base_address);
+
+                buckets = hash + 4 + (hash[2] * sizeof(size_t) / 4);
+
+                for (ElfW(Word) i = 0; i < hash[0]; ++i) {
+                    if (buckets[i] > sym_cnt) {
+                        sym_cnt = buckets[i];
+                    }
+                }
+
+                if (sym_cnt) {
+                    sym_cnt -= hash[1];
+                    hashval = buckets + hash[0] + sym_cnt;
+                    do {
+                        ++sym_cnt;
+                    } while (!(*hashval++ & 1));
+                }
+
+                break;
+            }
+        }
+
+        if (!sym_cnt) {
+            continue;
+        }
+
+        const char* strtab = nullptr;
+        for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) {
+            ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr);
+
+            if (it->d_tag == DT_STRTAB) {
+                strtab = reinterpret_cast<const char*>(base_address);
+                break;
+            }
+        }
+
+        if (!strtab) {
+            continue;
+        }
+
+        for (const auto* it = dyn_begin; it->d_tag != DT_NULL; ++it) {
+            ElfW(Addr) base_address = correct_address(info->dlpi_addr, it->d_un.d_ptr);
+
+            if (it->d_tag == DT_SYMTAB) {
+                /* Get the pointer to the first entry of the symbol table */
+                const ElfW(Sym)* elf_sym = reinterpret_cast<const ElfW(Sym)*>(base_address);
+
+                /* Iterate over the symbol table */
+                for (ElfW(Word) sym_index = 0; sym_index < ElfW(Word)(sym_cnt); ++sym_index) {
+                    /* Get the name of the sym_index-th symbol.
+                     * This is located at the address of st_name relative to the beginning of the string table.
+                     */
+                    const char* sym_name = &strtab[elf_sym[sym_index].st_name];
+
+                    if (!sym_name) {
+                        continue;
+                    }
+
+                    SymbolIndex::Symbol symbol;
+                    symbol.address_begin = reinterpret_cast<const void*>(
+                            info->dlpi_addr + elf_sym[sym_index].st_value);
+                    symbol.address_end = reinterpret_cast<const void*>(info->dlpi_addr +
+                                                                       elf_sym[sym_index].st_value +
+                                                                       elf_sym[sym_index].st_size);
+                    symbol.name = sym_name;
+
+                    /// We are not interested in empty symbols.
+                    if (elf_sym[sym_index].st_size) {
+                        symbols.push_back(symbol);
+                    }
+
+                    /// But resources can be represented by a pair of empty symbols (indicating their boundaries).
+                    updateResources(base_address, info->dlpi_name, symbol.name,
+                                    symbol.address_begin, resources);
+                }
+
+                break;
+            }
+        }
+    }
+}
+
+#if !defined USE_MUSL
+std::string getBuildIDFromProgramHeaders(dl_phdr_info* info) {
+    for (size_t header_index = 0; header_index < info->dlpi_phnum; ++header_index) {
+        const ElfPhdr& phdr = info->dlpi_phdr[header_index];
+        if (phdr.p_type != PT_NOTE) {
+            continue;
+        }
+
+        return Elf::getBuildID(reinterpret_cast<const char*>(info->dlpi_addr + phdr.p_vaddr),
+                               phdr.p_memsz);
+    }
+    return {};
+}
+#endif
+
+void collectSymbolsFromELFSymbolTable(dl_phdr_info* info, const Elf& elf,
+                                      const Elf::Section& symbol_table,
+                                      const Elf::Section& string_table,
+                                      std::vector<SymbolIndex::Symbol>& symbols,
+                                      SymbolIndex::Resources& resources) {
+    /// Iterate symbol table.
+    const ElfSym* symbol_table_entry = reinterpret_cast<const ElfSym*>(symbol_table.begin());
+    const ElfSym* symbol_table_end = reinterpret_cast<const ElfSym*>(symbol_table.end());
+
+    const char* strings = string_table.begin();
+
+    for (; symbol_table_entry < symbol_table_end; ++symbol_table_entry) {
+        if (!symbol_table_entry->st_name || !symbol_table_entry->st_value ||
+            strings + symbol_table_entry->st_name >= elf.end()) {
+            continue;
+        }
+
+        /// Find the name in strings table.
+        const char* symbol_name = strings + symbol_table_entry->st_name;
+
+        if (!symbol_name) {
+            continue;
+        }
+
+        SymbolIndex::Symbol symbol;
+        symbol.address_begin =
+                reinterpret_cast<const void*>(info->dlpi_addr + symbol_table_entry->st_value);
+        symbol.address_end = reinterpret_cast<const void*>(
+                info->dlpi_addr + symbol_table_entry->st_value + symbol_table_entry->st_size);
+        symbol.name = symbol_name;
+
+        if (symbol_table_entry->st_size) {
+            symbols.push_back(symbol);
+        }
+
+        updateResources(info->dlpi_addr, info->dlpi_name, symbol.name, symbol.address_begin,
+                        resources);
+    }
+}
+
+bool searchAndCollectSymbolsFromELFSymbolTable(dl_phdr_info* info, const Elf& elf,
+                                               unsigned section_header_type,
+                                               const char* string_table_name,
+                                               std::vector<SymbolIndex::Symbol>& symbols,
+                                               SymbolIndex::Resources& resources) {
+    std::optional<Elf::Section> symbol_table;
+    std::optional<Elf::Section> string_table;
+
+    if (!elf.iterateSections([&](const Elf::Section& section, size_t) {
+            if (section.header.sh_type == section_header_type) {
+                symbol_table.emplace(section);
+            } else if (section.header.sh_type == SHT_STRTAB &&
+                       0 == strcmp(section.name(), string_table_name)) {
+                string_table.emplace(section);
+            }
+
+            return (symbol_table && string_table);
+        })) {
+        return false;
+    }
+
+    collectSymbolsFromELFSymbolTable(info, elf, *symbol_table, *string_table, symbols, resources);
+    return true;
+}
+
+void collectSymbolsFromELF(dl_phdr_info* info, std::vector<SymbolIndex::Symbol>& symbols,
+                           std::vector<SymbolIndex::Object>& objects,
+                           SymbolIndex::Resources& resources, std::string& build_id) {
+    std::string object_name;
+    std::string our_build_id;
+#if defined(USE_MUSL)
+    object_name = "/proc/self/exe";
+    our_build_id = Elf(object_name).getBuildID();
+    build_id = our_build_id;
+#else
+    /// MSan does not know that the program segments in memory are initialized.
+    __msan_unpoison_string(info->dlpi_name);
+
+    object_name = info->dlpi_name;
+    our_build_id = getBuildIDFromProgramHeaders(info);
+
+    /// If the name is empty and there is a non-empty build-id - it's main executable.
+    /// Find a elf file for the main executable and set the build-id.
+    if (object_name.empty()) {
+        object_name = "/proc/self/exe";
+
+        if (our_build_id.empty()) {
+            our_build_id = Elf(object_name).getBuildID();
+        }
+
+        if (build_id.empty()) {
+            build_id = our_build_id;
+        }
+    }
+#endif
+
+    std::error_code ec;
+    std::filesystem::path canonical_path = std::filesystem::canonical(object_name, ec);
+    if (ec) {
+        return;
+    }
+
+    /// Debug info and symbol table sections may be split to separate binary.
+    std::filesystem::path local_debug_info_path =
+            canonical_path.parent_path() / canonical_path.stem();
+    local_debug_info_path += ".debug";
+    std::filesystem::path debug_info_path =
+            std::filesystem::path("/usr/lib/debug") / canonical_path.relative_path();
+    debug_info_path += ".debug";
+
+    /// NOTE: This is a workaround for current package system.
+    ///
+    /// Since nfpm cannot copy file only if it exists,
+    /// and so in cmake empty .debug file is created instead,
+    /// but if we will try to load empty Elf file, then the CANNOT_PARSE_ELF
+    /// exception will be thrown from the Elf::Elf.
+    auto exists_not_empty = [](const std::filesystem::path& path) {
+        return std::filesystem::exists(path) && !std::filesystem::is_empty(path);
+    };
+
+    if (exists_not_empty(local_debug_info_path)) {
+        object_name = local_debug_info_path;
+    } else if (exists_not_empty(debug_info_path)) {
+        object_name = debug_info_path;
+    } else if (build_id.size() >= 2) {
+        // Check if there is a .debug file in .build-id folder. For example:
+        // /usr/lib/debug/.build-id/e4/0526a12e9a8f3819a18694f6b798f10c624d5c.debug
+        std::string build_id_hex;
+        build_id_hex.resize(build_id.size() * 2);
+
+        char* pos = build_id_hex.data();
+        for (auto c : build_id) {
+            vectorized::write_hex_byte_lowercase(c, pos);
+            pos += 2;
+        }
+
+        std::filesystem::path build_id_debug_info_path(
+                fmt::format("/usr/lib/debug/.build-id/{}/{}.debug", build_id_hex.substr(0, 2),
+                            build_id_hex.substr(2)));
+        if (exists_not_empty(build_id_debug_info_path)) {
+            object_name = build_id_debug_info_path;
+        } else {
+            object_name = canonical_path;
+        }
+    } else {
+        object_name = canonical_path;
+    }
+    /// But we have to compare Build ID to check that debug info corresponds to the same executable.
+
+    SymbolIndex::Object object;
+    object.elf = std::make_unique<Elf>(object_name);
+
+    std::string file_build_id = object.elf->getBuildID();
+
+    if (our_build_id != file_build_id) {
+        /// If debug info doesn't correspond to our binary, fallback to the info in our binary.
+        if (object_name != canonical_path) {
+            object_name = canonical_path;
+            object.elf = std::make_unique<Elf>(object_name);
+
+            /// But it can still be outdated, for example, if executable file was deleted from filesystem and replaced by another file.
+            file_build_id = object.elf->getBuildID();
+            if (our_build_id != file_build_id) {
+                return;
+            }
+        } else {
+            return;
+        }
+    }
+
+    object.address_begin = reinterpret_cast<const void*>(info->dlpi_addr);
+    object.address_end = reinterpret_cast<const void*>(info->dlpi_addr + object.elf->size());
+    object.name = object_name;
+    objects.push_back(std::move(object));
+
+    searchAndCollectSymbolsFromELFSymbolTable(info, *objects.back().elf, SHT_SYMTAB, ".strtab",
+                                              symbols, resources);
+
+    /// Unneeded if they were parsed from "program headers" of loaded objects.
+#if defined USE_MUSL
+    searchAndCollectSymbolsFromELFSymbolTable(info, *objects.back().elf, SHT_DYNSYM, ".dynstr",
+                                              symbols, resources);
+#endif
+}
+
+/* Callback for dl_iterate_phdr.
+ * Is called by dl_iterate_phdr for every loaded shared lib until something
+ * else than 0 is returned by one call of this function.
+ */
+int collectSymbols(dl_phdr_info* info, size_t, void* data_ptr) {
+    SymbolIndex::Data& data = *reinterpret_cast<SymbolIndex::Data*>(data_ptr);
+
+    collectSymbolsFromProgramHeaders(info, data.symbols, data.resources);
+    collectSymbolsFromELF(info, data.symbols, data.objects, data.resources, data.build_id);
+
+    /* Continue iterations */
+    return 0;
+}
+
+template <typename T>
+const T* find(const void* address, const std::vector<T>& vec) {
+    /// First range that has left boundary greater than address.
+
+    auto it = std::lower_bound(
+            vec.begin(), vec.end(), address,
+            [](const T& symbol, const void* addr) { return symbol.address_begin <= addr; });
+
+    if (it == vec.begin()) {
+        return nullptr;
+    } else {
+        --it; /// Last range that has left boundary less or equals than address.
+    }
+
+    if (address >= it->address_begin && address < it->address_end) {
+        return &*it;
+    } else {
+        return nullptr;
+    }
+}
+
+} // namespace
+
+void SymbolIndex::update() {
+    dl_iterate_phdr(collectSymbols, &data);
+    ::pdqsort(data.objects.begin(), data.objects.end(),
+              [](const Object& a, const Object& b) { return a.address_begin < b.address_begin; });
+    ::pdqsort(data.symbols.begin(), data.symbols.end(),
+              [](const Symbol& a, const Symbol& b) { return a.address_begin < b.address_begin; });
+    /// We found symbols both from loaded program headers and from ELF symbol tables.
+    data.symbols.erase(std::unique(data.symbols.begin(), data.symbols.end(),
+                                   [](const Symbol& a, const Symbol& b) {
+                                       return a.address_begin == b.address_begin &&
+                                              a.address_end == b.address_end;
+                                   }),
+                       data.symbols.end());
+}
+
+const SymbolIndex::Symbol* SymbolIndex::findSymbol(const void* address) const {
+    return find(address, data.symbols);
+}
+
+const SymbolIndex::Object* SymbolIndex::findObject(const void* address) const {
+    return find(address, data.objects);
+}
+
+std::string SymbolIndex::getBuildIDHex() const {
+    std::string build_id_binary = getBuildID();
+    std::string build_id_hex;
+    build_id_hex.resize(build_id_binary.size() * 2);
+
+    char* pos = build_id_hex.data();
+    for (auto c : build_id_binary) {
+        vectorized::write_hex_byte_uppercase(c, pos);
+        pos += 2;
+    }
+
+    return build_id_hex;
+}
+
+MultiVersion<SymbolIndex>& SymbolIndex::instanceImpl() {
+    static MultiVersion<SymbolIndex> instance(std::unique_ptr<SymbolIndex>(new SymbolIndex));
+    return instance;
+}
+
+MultiVersion<SymbolIndex>::Version SymbolIndex::instance() {
+    return instanceImpl().get();
+}
+
+void SymbolIndex::reload() {
+    instanceImpl().set(std::unique_ptr<SymbolIndex>(new SymbolIndex));
+    /// Also drop stacktrace cache.
+    StackTrace::dropCache();
+}
+
+} // namespace doris
+
+#endif
diff --git a/be/src/common/symbol_index.h b/be/src/common/symbol_index.h
new file mode 100644
index 0000000000..0a9187c1f4
--- /dev/null
+++ b/be/src/common/symbol_index.h
@@ -0,0 +1,114 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// This file is copied from
+// https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/SymbolIndex.h
+// and modified by Doris
+
+#pragma once
+
+#if defined(__ELF__) && !defined(__FreeBSD__)
+
+#include <assert.h>
+#include <common/elf.h>
+#include <common/multi_version.h>
+
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace doris {
+
+/** Allow to quickly find symbol name from address.
+  * Used as a replacement for "dladdr" function which is extremely slow.
+  * It works better than "dladdr" because it also allows to search private symbols, that are not participated in shared linking.
+  */
+class SymbolIndex : private boost::noncopyable {
+protected:
+    SymbolIndex() { update(); }
+
+public:
+    static MultiVersion<SymbolIndex>::Version instance();
+    static void reload();
+
+    struct Symbol {
+        const void* address_begin;
+        const void* address_end;
+        const char* name;
+    };
+
+    struct Object {
+        const void* address_begin;
+        const void* address_end;
+        std::string name;
+        std::shared_ptr<Elf> elf;
+    };
+
+    /// Address in virtual memory should be passed. These addresses include offset where the object is loaded in memory.
+    const Symbol* findSymbol(const void* address) const;
+    const Object* findObject(const void* address) const;
+
+    const std::vector<Symbol>& symbols() const { return data.symbols; }
+    const std::vector<Object>& objects() const { return data.objects; }
+
+    std::string_view getResource(std::string name) const {
+        if (auto it = data.resources.find(name); it != data.resources.end()) {
+            return it->second.data();
+        }
+        return {};
+    }
+
+    /// The BuildID that is generated by compiler.
+    std::string getBuildID() const { return data.build_id; }
+    std::string getBuildIDHex() const;
+
+    struct ResourcesBlob {
+        /// Symbol can be presented in multiple shared objects,
+        /// base_address will be used to compare only symbols from the same SO.
+        ElfW(Addr) base_address = 0;
+        /// Just a human name of the SO.
+        std::string_view object_name;
+        /// Data blob.
+        std::string_view start;
+        std::string_view end;
+
+        [[nodiscard]] std::string_view data() const {
+            assert(end.data() >= start.data());
+            return std::string_view {start.data(), static_cast<size_t>(end.data() - start.data())};
+        }
+    };
+    using Resources = std::unordered_map<std::string_view /* symbol name */, ResourcesBlob>;
+
+    struct Data {
+        std::vector<Symbol> symbols;
+        std::vector<Object> objects;
+        std::string build_id;
+
+        /// Resources (embedded binary data) are located by symbols in form of _binary_name_start and _binary_name_end.
+        Resources resources;
+    };
+
+private:
+    Data data;
+
+    void update();
+    static MultiVersion<SymbolIndex>& instanceImpl();
+};
+
+} // namespace doris
+
+#endif
diff --git a/be/src/service/doris_main.cpp b/be/src/service/doris_main.cpp
index 8f65f19c18..57dec22820 100644
--- a/be/src/service/doris_main.cpp
+++ b/be/src/service/doris_main.cpp
@@ -50,6 +50,7 @@
 #include "common/config.h"
 #include "common/daemon.h"
 #include "common/logging.h"
+#include "common/phdr_cache.h"
 #include "common/resource_tls.h"
 #include "common/signal_handler.h"
 #include "common/status.h"
@@ -414,6 +415,10 @@ int main(int argc, char** argv) {
         }
     }
 
+    // PHDR speed up exception handling, but exceptions from dynamically loaded libraries (dlopen)
+    // will work only after additional call of this function.
+    updatePHDRCache();
+
     // Load file cache before starting up daemon threads to make sure StorageEngine is read.
     doris::Daemon daemon;
     daemon.init(argc, argv, paths);
diff --git a/be/src/util/defer_op.h b/be/src/util/defer_op.h
index cf86af5781..cf404c5920 100644
--- a/be/src/util/defer_op.h
+++ b/be/src/util/defer_op.h
@@ -41,7 +41,7 @@ private:
 };
 
 // Nested use Defer, variable name concat line number
-#define DEFER_CONCAT(n, ...) const auto defer##n = Defer([&]() { __VA_ARGS__; })
+#define DEFER_CONCAT(n, ...) const auto defer##n = doris::Defer([&]() { __VA_ARGS__; })
 #define DEFER_FWD(n, ...) DEFER_CONCAT(n, __VA_ARGS__)
 #define DEFER(...) DEFER_FWD(__LINE__, __VA_ARGS__)
 
diff --git a/be/src/util/stack_util.cpp b/be/src/util/stack_util.cpp
index ccb3785c4c..dd143ead3f 100644
--- a/be/src/util/stack_util.cpp
+++ b/be/src/util/stack_util.cpp
@@ -17,6 +17,16 @@
 
 #include "util/stack_util.h"
 
+#include <execinfo.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include <boost/stacktrace.hpp>
+
+#include "common/stack_trace.h"
+#include "util/mem_info.h"
+#include "util/pretty_printer.h"
+
 namespace google {
 namespace glog_internal_namespace_ {
 void DumpStackTraceToString(std::string* stacktrace);
@@ -25,15 +35,54 @@ void DumpStackTraceToString(std::string* stacktrace);
 
 namespace doris {
 
-// `boost::stacktrace::stacktrace()` has memory leak, so use the glog internal func to print stacktrace.
-// The reason for the boost::stacktrace memory leak is that a state is saved in the thread local of each
-// thread but is not actively released. Refer to:
-// https://github.com/boostorg/stacktrace/issues/118
-// https://github.com/boostorg/stacktrace/issues/111
 std::string get_stack_trace() {
+#ifdef ENABLE_STACKTRACE
+    auto tool = config::get_stack_trace_tool;
+    if (tool == "glog") {
+        return get_stack_trace_by_glog();
+    } else if (tool == "boost") {
+        return get_stack_trace_by_boost();
+    } else if (tool == "glibc") {
+        return get_stack_trace_by_glibc();
+    } else if (tool == "libunwind") {
+#if USE_UNWIND
+        return get_stack_trace_by_libunwind();
+#else
+        return get_stack_trace_by_glog();
+#endif
+    } else {
+        return "no stack";
+    }
+#endif
+    return "no enable stack";
+}
+
+std::string get_stack_trace_by_glog() {
     std::string s;
     google::glog_internal_namespace_::DumpStackTraceToString(&s);
     return s;
 }
 
+std::string get_stack_trace_by_boost() {
+    return boost::stacktrace::to_string(boost::stacktrace::stacktrace());
+}
+
+std::string get_stack_trace_by_glibc() {
+    void* trace[16];
+    char** messages = (char**)nullptr;
+    int i, trace_size = 0;
+
+    trace_size = backtrace(trace, 16);
+    messages = backtrace_symbols(trace, trace_size);
+    std::stringstream out;
+    for (i = 1; i < trace_size; ++i) {
+        out << messages[i] << "\n";
+    }
+    return out.str();
+}
+
+std::string get_stack_trace_by_libunwind() {
+    return StackTrace().toString();
+}
+
 } // namespace doris
diff --git a/be/src/util/stack_util.h b/be/src/util/stack_util.h
index 27e97326e2..e33e6f02f5 100644
--- a/be/src/util/stack_util.h
+++ b/be/src/util/stack_util.h
@@ -21,9 +21,38 @@
 
 namespace doris {
 
-// Returns the stack trace as a string from the current location.
+/** Returns the stack trace as a string from the current location.
+  */
+
+// Select a stack trace tool according to config::get_stack_trace_tool
+// glog: 1000 times cost 8min, no line numbers.
+// boost: 1000 times cost 1min, has line numbers, but has memory leak.
+// glibc: 1000 times cost 1min, no line numbers, unresolved backtrace symbol.
+// libunwind: cost is negligible, has line numbers.
+std::string get_stack_trace();
+
 // Note: there is a libc bug that causes this not to work on 64 bit machines
 // for recursive calls.
-std::string get_stack_trace();
+std::string get_stack_trace_by_glog();
+
+// `boost::stacktrace::stacktrace()` has memory leak, reason for the boost::stacktrace memory leak
+// is that a state is saved in the thread local of each thread but is not actively released. Refer to:
+// https://github.com/boostorg/stacktrace/issues/118
+// https://github.com/boostorg/stacktrace/issues/111
+std::string get_stack_trace_by_boost();
+
+// backtrace symbol no parsing with Addr2Line, this is slower and often make mistakes, requiring manual parsing
+// https://stackoverflow.com/questions/3151779/best-way-to-invoke-gdb-from-inside-program-to-print-its-stacktrace/4611112#4611112
+// https://stackoverflow.com/questions/55450932/how-ro-resolve-cpp-symbols-from-backtrace-symbols-in-the-offset-during-runtime
+std::string get_stack_trace_by_glibc();
+
+// use StackTraceCache, PHDRCache speed up, is customizable and has some optimizations.
+// TODO:
+//  1. currently support linux __x86_64__, __arm__, __powerpc__, not supported __FreeBSD__, APPLE
+//     Note: __arm__, __powerpc__ not been verified
+//  2. Support signal handle
+//  3. libunwid support unw_backtrace for jemalloc
+//  4. Use of undefined compile option USE_MUSL for later
+std::string get_stack_trace_by_libunwind();
 
 } // namespace doris
diff --git a/build.sh b/build.sh
index 7a316b4d77..0bcf14ef36 100755
--- a/build.sh
+++ b/build.sh
@@ -335,6 +335,14 @@ if [[ -z "${USE_DWARF}" ]]; then
     USE_DWARF='OFF'
 fi
 
+if [[ -z "${USE_UNWIND}" ]]; then
+    if [[ "$(uname -s)" != 'Darwin' ]]; then
+        USE_UNWIND='ON'
+    else
+        USE_UNWIND='OFF'
+    fi
+fi
+
 if [[ -z "${DISPLAY_BUILD_TIME}" ]]; then
     DISPLAY_BUILD_TIME='OFF'
 fi
@@ -395,6 +403,7 @@ echo "Get params:
     USE_AVX2                    -- ${USE_AVX2}
     USE_LIBCPP                  -- ${USE_LIBCPP}
     USE_DWARF                   -- ${USE_DWARF}
+    USE_UNWIND                  -- ${USE_UNWIND}
     STRIP_DEBUG_INFO            -- ${STRIP_DEBUG_INFO}
     USE_MEM_TRACKER             -- ${USE_MEM_TRACKER}
     USE_JEMALLOC                -- ${USE_JEMALLOC}
@@ -480,6 +489,7 @@ if [[ "${BUILD_BE}" -eq 1 ]]; then
         -DBUILD_META_TOOL="${BUILD_META_TOOL}" \
         -DSTRIP_DEBUG_INFO="${STRIP_DEBUG_INFO}" \
         -DUSE_DWARF="${USE_DWARF}" \
+        -DUSE_UNWIND="${USE_UNWIND}" \
         -DDISPLAY_BUILD_TIME="${DISPLAY_BUILD_TIME}" \
         -DENABLE_PCH="${ENABLE_PCH}" \
         -DUSE_MEM_TRACKER="${USE_MEM_TRACKER}" \
diff --git a/run-be-ut.sh b/run-be-ut.sh
index aa45946f7b..ad3be370c5 100755
--- a/run-be-ut.sh
+++ b/run-be-ut.sh
@@ -179,6 +179,14 @@ if [[ -z "${USE_DWARF}" ]]; then
     USE_DWARF='OFF'
 fi
 
+if [[ -z "${USE_UNWIND}" ]]; then
+    if [[ "$(uname -s)" != 'Darwin' ]]; then
+        USE_UNWIND='ON'
+    else
+        USE_UNWIND='OFF'
+    fi
+fi
+
 MAKE_PROGRAM="$(command -v "${BUILD_SYSTEM}")"
 echo "-- Make program: ${MAKE_PROGRAM}"
 echo "-- Use ccache: ${CMAKE_USE_CCACHE}"
@@ -195,6 +203,7 @@ cd "${CMAKE_BUILD_DIR}"
     -DBUILD_BENCHMARK_TOOL="${BUILD_BENCHMARK_TOOL}" \
     -DWITH_MYSQL=OFF \
     -DUSE_DWARF="${USE_DWARF}" \
+    -DUSE_UNWIND="${USE_UNWIND}" \
     -DUSE_MEM_TRACKER="${USE_MEM_TRACKER}" \
     -DUSE_JEMALLOC=OFF \
     -DEXTRA_CXX_FLAGS="${EXTRA_CXX_FLAGS}" \
diff --git a/thirdparty/build-thirdparty.sh b/thirdparty/build-thirdparty.sh
index 1fd8f0f7f7..2c7f75d6a1 100755
--- a/thirdparty/build-thirdparty.sh
+++ b/thirdparty/build-thirdparty.sh
@@ -1432,6 +1432,32 @@ build_jemalloc() {
     mv "${TP_INSTALL_DIR}"/lib/libjemalloc.a "${TP_INSTALL_DIR}"/lib/libjemalloc_doris.a
 }
 
+# libunwind
+build_libunwind() {
+    # https://github.com/libunwind/libunwind
+    # https://github.com/libunwind/libunwind/issues/189
+    # https://stackoverflow.com/questions/27842377/building-libunwind-for-mac
+    if [[ "${KERNEL}" != 'Darwin' ]]; then
+        check_if_source_exist "${LIBUNWIND_SOURCE}"
+        cd "${TP_SOURCE_DIR}/${LIBUNWIND_SOURCE}"
+
+        mkdir -p "${BUILD_DIR}"
+        cd "${BUILD_DIR}"
+
+        # We should enable optimizations (otherwise it will be too slow in debug)
+        # and disable sanitizers (otherwise infinite loop may happen)
+        # close exceptions and rtti can improve the operating efficiency of the program
+        # LIBUNWIND_NO_HEAP: https://reviews.llvm.org/D11897
+        # LIBUNWIND_IS_NATIVE_ONLY: https://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20160523/159802.html
+        # -nostdinc++ only required for gcc compilation
+        cflags='-std=c99 -D_LIBUNWIND_NO_HEAP=1 -D_DEBUG -D_LIBUNWIND_IS_NATIVE_ONLY -O3 -fno-exceptions -funwind-tables -fno-sanitize=all -nostdinc++ -fno-rtti'
+        CFLAGS="${cflags}" ../configure --prefix="${TP_INSTALL_DIR}"
+
+        make -j "${PARALLEL}"
+        make install
+    fi
+}
+
 # benchmark
 build_benchmark() {
     check_if_source_exist "${BENCHMARK_SOURCE}"
@@ -1590,6 +1616,7 @@ build_hadoop_libs() {
 
 if [[ "${#packages[@]}" -eq 0 ]]; then
     packages=(
+        libunwind
         libunixodbc
         openssl
         libevent
diff --git a/thirdparty/vars.sh b/thirdparty/vars.sh
index bc7a53a37e..f10c56fa9e 100644
--- a/thirdparty/vars.sh
+++ b/thirdparty/vars.sh
@@ -298,6 +298,12 @@ JEMALLOC_DORIS_NAME="jemalloc-5.3.0.tar.bz2"
 JEMALLOC_DORIS_SOURCE="jemalloc-5.3.0"
 JEMALLOC_DORIS_MD5SUM="09a8328574dab22a7df848eae6dbbf53"
 
+# libunwind
+LIBUNWIND_DOWNLOAD="http://download.savannah.nongnu.org/releases/libunwind/libunwind-1.6.2.tar.gz"
+LIBUNWIND_NAME="libunwind-1.6.2.tar.gz"
+LIBUNWIND_SOURCE="libunwind-1.6.2"
+LIBUNWIND_MD5SUM="f625b6a98ac1976116c71708a73dc44a"
+
 # cctz
 CCTZ_DOWNLOAD="https://github.com/google/cctz/archive/v2.3.tar.gz"
 CCTZ_NAME="cctz-2.3.tar.gz"
@@ -499,6 +505,7 @@ export TP_ARCHIVES=(
     'ORC'
     'JEMALLOC_ARROW'
     'JEMALLOC_DORIS'
+    'LIBUNWIND'
     'CCTZ'
     'DATATABLES'
     'BOOTSTRAP_TABLE_JS'


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org