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(¤t_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(¤t_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