mirror of https://github.com/ByConity/ByConity
Merge pull request #6201 from yandex/speed-up-dladdr
Introspection functions + better stack traces.
This commit is contained in:
commit
cb37c79c41
|
@ -106,6 +106,10 @@ endif ()
|
|||
if (COMPILER_CLANG)
|
||||
# clang: warning: argument unused during compilation: '-specs=/usr/share/dpkg/no-pie-compile.specs' [-Wunused-command-line-argument]
|
||||
set (COMMON_WARNING_FLAGS "${COMMON_WARNING_FLAGS} -Wno-unused-command-line-argument")
|
||||
# generate ranges for fast "addr2line" search
|
||||
if (NOT CMAKE_BUILD_TYPE_UC STREQUAL "RELEASE")
|
||||
set(COMPILER_FLAGS "${COMPILER_FLAGS} -gdwarf-aranges")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
option (ENABLE_TESTS "Enables tests" ON)
|
||||
|
|
|
@ -159,6 +159,15 @@ if (OS_FREEBSD)
|
|||
target_compile_definitions (clickhouse_common_io PUBLIC CLOCK_MONOTONIC_COARSE=CLOCK_MONOTONIC_FAST)
|
||||
endif ()
|
||||
|
||||
if (USE_UNWIND)
|
||||
target_compile_definitions (clickhouse_common_io PRIVATE USE_UNWIND=1)
|
||||
target_include_directories (clickhouse_common_io SYSTEM BEFORE PRIVATE ${UNWIND_INCLUDE_DIR})
|
||||
|
||||
if (NOT USE_INTERNAL_UNWIND_LIBRARY_FOR_EXCEPTION_HANDLING)
|
||||
target_link_libraries (clickhouse_common_io PRIVATE ${UNWIND_LIBRARY})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
add_subdirectory(src/Common/ZooKeeper)
|
||||
add_subdirectory(src/Common/Config)
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,287 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* 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 <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
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 Elf & elf);
|
||||
|
||||
/**
|
||||
* Represent a file path a s collection of three parts (base directory,
|
||||
* subdirectory, and file).
|
||||
*/
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
Path() {}
|
||||
|
||||
Path(std::string_view baseDir, std::string_view subDir, std::string_view file);
|
||||
|
||||
std::string_view baseDir() const { return baseDir_; }
|
||||
std::string_view subDir() const { return subDir_; }
|
||||
std::string_view file() const { return file_; }
|
||||
|
||||
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;
|
||||
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_;
|
||||
std::string_view subDir_;
|
||||
std::string_view file_;
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
struct LocationInfo
|
||||
{
|
||||
bool hasMainFile = false;
|
||||
Path mainFile;
|
||||
|
||||
bool hasFileAndLine = false;
|
||||
Path file;
|
||||
uint64_t line = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the file and line number information corresponding to address.
|
||||
*/
|
||||
bool findAddress(uintptr_t address, LocationInfo & info, LocationInfoMode mode) const;
|
||||
|
||||
private:
|
||||
static bool findDebugInfoOffset(uintptr_t address, std::string_view aranges, uint64_t & offset);
|
||||
|
||||
void init();
|
||||
bool findLocation(uintptr_t address, std::string_view & infoEntry, LocationInfo & info) const;
|
||||
|
||||
const Elf * elf_;
|
||||
|
||||
// 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() : is64Bit_(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?
|
||||
bool is64Bit() const { return is64Bit_; }
|
||||
|
||||
private:
|
||||
// Yes, 32- and 64- bit sections may coexist. Yikes!
|
||||
bool is64Bit_;
|
||||
std::string_view data_;
|
||||
};
|
||||
|
||||
// Abbreviation for a Debugging Information Entry.
|
||||
struct DIEAbbreviation
|
||||
{
|
||||
uint64_t code;
|
||||
uint64_t tag;
|
||||
bool hasChildren;
|
||||
|
||||
struct Attribute
|
||||
{
|
||||
uint64_t name;
|
||||
uint64_t form;
|
||||
};
|
||||
|
||||
std::string_view attributes;
|
||||
};
|
||||
|
||||
// Interpreter for the line number bytecode VM
|
||||
class LineNumberVM
|
||||
{
|
||||
public:
|
||||
LineNumberVM(std::string_view data, std::string_view compilationDirectory);
|
||||
|
||||
bool findAddress(uintptr_t address, Path & file, uint64_t & line);
|
||||
|
||||
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;
|
||||
// 0 = current compilation directory
|
||||
// otherwise, 1-based index in the list of include directories
|
||||
uint64_t directoryIndex;
|
||||
};
|
||||
// Read one FileName object, remove_prefix sp
|
||||
static bool readFileName(std::string_view & sp, 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)
|
||||
FileName getFileName(uint64_t index) const;
|
||||
|
||||
// Get include directory at given index
|
||||
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_;
|
||||
std::string_view data_;
|
||||
std::string_view compilationDirectory_;
|
||||
|
||||
// Header
|
||||
uint16_t version_;
|
||||
uint8_t minLength_;
|
||||
bool defaultIsStmt_;
|
||||
int8_t lineBase_;
|
||||
uint8_t lineRange_;
|
||||
uint8_t opcodeBase_;
|
||||
const uint8_t * standardOpcodeLengths_;
|
||||
|
||||
std::string_view includeDirectories_;
|
||||
size_t includeDirectoryCount_;
|
||||
|
||||
std::string_view fileNames_;
|
||||
size_t fileNameCount_;
|
||||
|
||||
// State machine registers
|
||||
uint64_t address_;
|
||||
uint64_t file_;
|
||||
uint64_t line_;
|
||||
uint64_t column_;
|
||||
bool isStmt_;
|
||||
bool basicBlock_;
|
||||
bool endSequence_;
|
||||
bool prologueEnd_;
|
||||
bool epilogueBegin_;
|
||||
uint64_t isa_;
|
||||
uint64_t discriminator_;
|
||||
};
|
||||
|
||||
// Read an abbreviation from a std::string_view, return true if at end; remove_prefix sp
|
||||
static bool readAbbreviation(std::string_view & sp, DIEAbbreviation & abbr);
|
||||
|
||||
// Get abbreviation corresponding to a code, in the chunk starting at
|
||||
// offset in the .debug_abbrev section
|
||||
DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
|
||||
|
||||
// Read one attribute <name, form> pair, remove_prefix sp; returns <0, 0> at end.
|
||||
static DIEAbbreviation::Attribute readAttribute(std::string_view & sp);
|
||||
|
||||
// Read one attribute value, remove_prefix sp
|
||||
typedef std::variant<uint64_t, std::string_view> AttributeValue;
|
||||
AttributeValue readAttributeValue(std::string_view & sp, uint64_t form, bool is64Bit) const;
|
||||
|
||||
// Get an ELF section by name, return true if found
|
||||
bool getSection(const char * name, std::string_view * section) const;
|
||||
|
||||
// Get a string from the .debug_str section
|
||||
std::string_view getStringFromStringSection(uint64_t offset) const;
|
||||
|
||||
std::string_view info_; // .debug_info
|
||||
std::string_view abbrev_; // .debug_abbrev
|
||||
std::string_view aranges_; // .debug_aranges
|
||||
std::string_view line_; // .debug_line
|
||||
std::string_view strings_; // .debug_str
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
#include <Common/Elf.h>
|
||||
#include <Common/Exception.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int CANNOT_PARSE_ELF;
|
||||
}
|
||||
|
||||
|
||||
Elf::Elf(const std::string & path)
|
||||
: in(path, 0)
|
||||
{
|
||||
/// Check if it's an elf.
|
||||
elf_size = in.buffer().size();
|
||||
if (elf_size < sizeof(ElfEhdr))
|
||||
throw Exception("The size of supposedly ELF file is too small", ErrorCodes::CANNOT_PARSE_ELF);
|
||||
|
||||
mapped = in.buffer().begin();
|
||||
header = reinterpret_cast<const ElfEhdr *>(mapped);
|
||||
|
||||
if (memcmp(header->e_ident, "\x7F""ELF", 4) != 0)
|
||||
throw Exception("The file is not ELF according to magic", ErrorCodes::CANNOT_PARSE_ELF);
|
||||
|
||||
/// 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)
|
||||
throw Exception("The ELF is truncated (section header points after end of file)", ErrorCodes::CANNOT_PARSE_ELF);
|
||||
|
||||
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)
|
||||
throw Exception("The ELF doesn't have string table with section names", ErrorCodes::CANNOT_PARSE_ELF);
|
||||
|
||||
ElfOff section_names_offset = section_names_strtab->header.sh_offset;
|
||||
if (section_names_offset >= elf_size)
|
||||
throw Exception("The ELF is truncated (section names string table points after end of file)", ErrorCodes::CANNOT_PARSE_ELF);
|
||||
|
||||
section_names = reinterpret_cast<const char *>(mapped + section_names_offset);
|
||||
}
|
||||
|
||||
|
||||
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()); });
|
||||
}
|
||||
|
||||
|
||||
const char * Elf::Section::name() const
|
||||
{
|
||||
if (!elf.section_names)
|
||||
throw Exception("Section names are not initialized", ErrorCodes::CANNOT_PARSE_ELF);
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <IO/MMapReadBufferFromFile.h>
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
|
||||
|
||||
using ElfAddr = ElfW(Addr);
|
||||
using ElfEhdr = ElfW(Ehdr);
|
||||
using ElfOff = ElfW(Off);
|
||||
using ElfPhdr = ElfW(Phdr);
|
||||
using ElfShdr = ElfW(Shdr);
|
||||
using ElfSym = ElfW(Sym);
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** Allow to navigate sections in ELF.
|
||||
*/
|
||||
class Elf final
|
||||
{
|
||||
public:
|
||||
struct Section
|
||||
{
|
||||
const ElfShdr & header;
|
||||
const char * name() const;
|
||||
|
||||
const char * begin() const;
|
||||
const char * end() const;
|
||||
size_t size() const;
|
||||
|
||||
Section(const ElfShdr & header, const Elf & elf);
|
||||
|
||||
private:
|
||||
const Elf & elf;
|
||||
};
|
||||
|
||||
explicit Elf(const std::string & path);
|
||||
|
||||
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;
|
||||
|
||||
const char * begin() const { return mapped; }
|
||||
const char * end() const { return mapped + elf_size; }
|
||||
size_t size() const { return elf_size; }
|
||||
|
||||
private:
|
||||
MMapReadBufferFromFile in;
|
||||
size_t elf_size;
|
||||
const char * mapped;
|
||||
const ElfEhdr * header;
|
||||
const ElfShdr * section_headers;
|
||||
const char * section_names = nullptr;
|
||||
};
|
||||
|
||||
}
|
|
@ -438,6 +438,8 @@ namespace ErrorCodes
|
|||
extern const int CANNOT_SET_TIMER_PERIOD = 461;
|
||||
extern const int CANNOT_DELETE_TIMER = 462;
|
||||
extern const int CANNOT_FCNTL = 463;
|
||||
extern const int CANNOT_PARSE_ELF = 464;
|
||||
extern const int CANNOT_PARSE_DWARF = 465;
|
||||
|
||||
extern const int KEEPER_EXCEPTION = 999;
|
||||
extern const int POCO_EXCEPTION = 1000;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <Poco/Exception.h>
|
||||
|
||||
#include <common/StackTrace.h>
|
||||
#include <Common/StackTrace.h>
|
||||
|
||||
namespace Poco { class Logger; }
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include <common/Pipe.h>
|
||||
#include <common/phdr_cache.h>
|
||||
#include <common/config_common.h>
|
||||
#include <common/StackTrace.h>
|
||||
#include <Common/StackTrace.h>
|
||||
#include <common/StringRef.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <Common/CurrentThread.h>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#include <common/StackTrace.h>
|
||||
#include <common/SimpleCache.h>
|
||||
#include <common/demangle.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <cxxabi.h>
|
||||
#include <execinfo.h>
|
||||
#include <Common/StackTrace.h>
|
||||
#include <Common/SymbolIndex.h>
|
||||
#include <Common/Dwarf.h>
|
||||
#include <Common/Elf.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
|
||||
#if USE_UNWIND
|
||||
#define UNW_LOCAL_ONLY
|
||||
#include <libunwind.h>
|
||||
#endif
|
||||
|
||||
std::string signalToErrorMessage(int sig, const siginfo_t & info, const ucontext_t & context)
|
||||
{
|
||||
|
@ -168,9 +168,9 @@ void * getCallerAddress(const ucontext_t & context)
|
|||
#endif
|
||||
#elif defined(__aarch64__)
|
||||
return reinterpret_cast<void *>(context.uc_mcontext.pc);
|
||||
#endif
|
||||
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
StackTrace::StackTrace()
|
||||
|
@ -195,6 +195,12 @@ StackTrace::StackTrace(NoCapture)
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
#if USE_UNWIND
|
||||
extern "C" int unw_backtrace(void **, int);
|
||||
#endif
|
||||
|
||||
|
||||
void StackTrace::tryCapture()
|
||||
{
|
||||
size = 0;
|
||||
|
@ -227,50 +233,45 @@ std::string StackTrace::toStringImpl(const Frames & frames, size_t size)
|
|||
if (size == 0)
|
||||
return "<Empty trace>";
|
||||
|
||||
char ** symbols = backtrace_symbols(frames.data(), size);
|
||||
if (!symbols)
|
||||
return "<Invalid trace>";
|
||||
const DB::SymbolIndex & symbol_index = DB::SymbolIndex::instance();
|
||||
std::unordered_map<std::string, DB::Dwarf> dwarfs;
|
||||
|
||||
std::stringstream backtrace;
|
||||
try
|
||||
std::stringstream out;
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
const void * addr = frames[i];
|
||||
|
||||
out << "#" << i << " " << addr << " ";
|
||||
auto symbol = symbol_index.findSymbol(addr);
|
||||
if (symbol)
|
||||
{
|
||||
/// We do "demangling" of names. The name is in parenthesis, before the '+' character.
|
||||
|
||||
char * name_start = nullptr;
|
||||
char * name_end = nullptr;
|
||||
std::string demangled_name;
|
||||
int status = 0;
|
||||
|
||||
if (nullptr != (name_start = strchr(symbols[i], '('))
|
||||
&& nullptr != (name_end = strchr(name_start, '+')))
|
||||
{
|
||||
++name_start;
|
||||
*name_end = '\0';
|
||||
demangled_name = demangle(name_start, status);
|
||||
*name_end = '+';
|
||||
}
|
||||
|
||||
backtrace << i << ". ";
|
||||
|
||||
if (0 == status && name_start && name_end)
|
||||
{
|
||||
backtrace.write(symbols[i], name_start - symbols[i]);
|
||||
backtrace << demangled_name << name_end;
|
||||
}
|
||||
else
|
||||
backtrace << symbols[i];
|
||||
|
||||
backtrace << std::endl;
|
||||
out << demangle(symbol->name, status);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
free(symbols);
|
||||
throw;
|
||||
else
|
||||
out << "?";
|
||||
|
||||
out << " ";
|
||||
|
||||
if (auto object = symbol_index.findObject(addr))
|
||||
{
|
||||
if (std::filesystem::exists(object->name))
|
||||
{
|
||||
auto dwarf_it = dwarfs.try_emplace(object->name, *object->elf).first;
|
||||
|
||||
DB::Dwarf::LocationInfo location;
|
||||
if (dwarf_it->second.findAddress(uintptr_t(addr) - uintptr_t(object->address_begin), location, DB::Dwarf::LocationInfoMode::FAST))
|
||||
out << location.file.toString() << ":" << location.line;
|
||||
else
|
||||
out << object->name;
|
||||
}
|
||||
}
|
||||
else
|
||||
out << "?";
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
free(symbols);
|
||||
return backtrace.str();
|
||||
return out.str();
|
||||
}
|
|
@ -0,0 +1,318 @@
|
|||
#include <Common/SymbolIndex.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
||||
#include <link.h>
|
||||
|
||||
//#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
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
|
||||
|
||||
|
||||
/// 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)
|
||||
{
|
||||
/* 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 (auto it = dyn_begin; it->d_tag != DT_NULL; ++it)
|
||||
{
|
||||
if (it->d_tag == DT_HASH)
|
||||
{
|
||||
const ElfW(Word) * hash = reinterpret_cast<const ElfW(Word) *>(correct_address(info->dlpi_addr, it->d_un.d_ptr));
|
||||
sym_cnt = hash[1];
|
||||
break;
|
||||
}
|
||||
else 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) *>(correct_address(info->dlpi_addr, it->d_un.d_ptr));
|
||||
|
||||
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 (auto it = dyn_begin; it->d_tag != DT_NULL; ++it)
|
||||
{
|
||||
if (it->d_tag == DT_STRTAB)
|
||||
{
|
||||
strtab = reinterpret_cast<const char *>(correct_address(info->dlpi_addr, it->d_un.d_ptr));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strtab)
|
||||
continue;
|
||||
|
||||
for (auto it = dyn_begin; it->d_tag != DT_NULL; ++it)
|
||||
{
|
||||
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) *>(correct_address(info->dlpi_addr, it->d_un.d_ptr));
|
||||
|
||||
/* Iterate over the symbol table */
|
||||
for (ElfW(Word) sym_index = 0; sym_index < sym_cnt; ++sym_index)
|
||||
{
|
||||
/// We are not interested in empty symbols.
|
||||
if (!elf_sym[sym_index].st_size)
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
symbols.push_back(std::move(symbol));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void collectSymbolsFromELFSymbolTable(
|
||||
dl_phdr_info * info,
|
||||
const Elf & elf,
|
||||
const Elf::Section & symbol_table,
|
||||
const Elf::Section & string_table,
|
||||
std::vector<SymbolIndex::Symbol> & symbols)
|
||||
{
|
||||
/// 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
|
||||
|| !symbol_table_entry->st_size
|
||||
|| 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;
|
||||
symbols.push_back(std::move(symbol));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool searchAndCollectSymbolsFromELFSymbolTable(
|
||||
dl_phdr_info * info,
|
||||
const Elf & elf,
|
||||
unsigned section_header_type,
|
||||
const char * string_table_name,
|
||||
std::vector<SymbolIndex::Symbol> & symbols)
|
||||
{
|
||||
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);
|
||||
|
||||
if (symbol_table && string_table)
|
||||
return true;
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
collectSymbolsFromELFSymbolTable(info, elf, *symbol_table, *string_table, symbols);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void collectSymbolsFromELF(dl_phdr_info * info,
|
||||
std::vector<SymbolIndex::Symbol> & symbols,
|
||||
std::vector<SymbolIndex::Object> & objects)
|
||||
{
|
||||
std::string object_name = info->dlpi_name;
|
||||
|
||||
/// If the name is empty - it's main executable.
|
||||
/// Find a elf file for the main executable.
|
||||
|
||||
if (object_name.empty())
|
||||
object_name = "/proc/self/exe";
|
||||
|
||||
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 splitted to separate binary.
|
||||
std::filesystem::path debug_info_path = std::filesystem::path("/usr/lib/debug") / canonical_path;
|
||||
|
||||
object_name = std::filesystem::exists(debug_info_path) ? debug_info_path : canonical_path;
|
||||
|
||||
SymbolIndex::Object object;
|
||||
object.elf = std::make_unique<Elf>(object_name);
|
||||
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);
|
||||
|
||||
/// Unneeded because they were parsed from "program headers" of loaded objects.
|
||||
//searchAndCollectSymbolsFromELFSymbolTable(info, *objects.back().elf, SHT_DYNSYM, ".dynstr", symbols);
|
||||
}
|
||||
|
||||
|
||||
/* 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);
|
||||
collectSymbolsFromELF(info, data.symbols, data.objects);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SymbolIndex::update()
|
||||
{
|
||||
dl_iterate_phdr(collectSymbols, &data.symbols);
|
||||
|
||||
std::sort(data.objects.begin(), data.objects.end(), [](const Object & a, const Object & b) { return a.address_begin < b.address_begin; });
|
||||
std::sort(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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ext/singleton.h>
|
||||
#include <Common/Elf.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** 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 : public ext::singleton<SymbolIndex>
|
||||
{
|
||||
protected:
|
||||
friend class ext::singleton<SymbolIndex>;
|
||||
SymbolIndex() { update(); }
|
||||
|
||||
public:
|
||||
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::unique_ptr<Elf> elf;
|
||||
};
|
||||
|
||||
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; }
|
||||
|
||||
struct Data
|
||||
{
|
||||
std::vector<Symbol> symbols;
|
||||
std::vector<Object> objects;
|
||||
};
|
||||
private:
|
||||
Data data;
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
#include <Core/Field.h>
|
||||
#include <Poco/Logger.h>
|
||||
#include <common/Pipe.h>
|
||||
#include <common/StackTrace.h>
|
||||
#include <Common/StackTrace.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <IO/ReadHelpers.h>
|
||||
#include <IO/ReadBufferFromFileDescriptor.h>
|
||||
|
|
|
@ -78,3 +78,6 @@ target_link_libraries (stopwatch PRIVATE clickhouse_common_io)
|
|||
|
||||
add_executable (mi_malloc_test mi_malloc_test.cpp)
|
||||
target_link_libraries (mi_malloc_test PRIVATE clickhouse_common_io)
|
||||
|
||||
add_executable (symbol_index symbol_index.cpp)
|
||||
target_link_libraries (symbol_index PRIVATE clickhouse_common_io)
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#include <Common/SymbolIndex.h>
|
||||
#include <Common/Elf.h>
|
||||
#include <Common/Dwarf.h>
|
||||
#include <Core/Defines.h>
|
||||
#include <common/demangle.h>
|
||||
#include <iostream>
|
||||
#include <dlfcn.h>
|
||||
|
||||
|
||||
NO_INLINE const void * getAddress()
|
||||
{
|
||||
return __builtin_return_address(0);
|
||||
}
|
||||
|
||||
using namespace DB;
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::cerr << "Usage: ./symbol_index address\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const SymbolIndex & symbol_index = SymbolIndex::instance();
|
||||
|
||||
for (const auto & elem : symbol_index.symbols())
|
||||
std::cout << elem.name << ": " << elem.address_begin << " ... " << elem.address_end << "\n";
|
||||
std::cout << "\n";
|
||||
|
||||
const void * address = reinterpret_cast<void*>(std::stoull(argv[1], nullptr, 16));
|
||||
|
||||
auto symbol = symbol_index.findSymbol(address);
|
||||
if (symbol)
|
||||
std::cerr << symbol->name << ": " << symbol->address_begin << " ... " << symbol->address_end << "\n";
|
||||
else
|
||||
std::cerr << "SymbolIndex: Not found\n";
|
||||
|
||||
Dl_info info;
|
||||
if (dladdr(address, &info) && info.dli_sname)
|
||||
std::cerr << demangle(info.dli_sname) << ": " << info.dli_saddr << "\n";
|
||||
else
|
||||
std::cerr << "dladdr: Not found\n";
|
||||
|
||||
auto object = symbol_index.findObject(getAddress());
|
||||
Dwarf dwarf(*object->elf);
|
||||
|
||||
Dwarf::LocationInfo location;
|
||||
if (dwarf.findAddress(uintptr_t(address), location, Dwarf::LocationInfoMode::FAST))
|
||||
std::cerr << location.file.toString() << ":" << location.line << "\n";
|
||||
else
|
||||
std::cerr << "Dwarf: Not found\n";
|
||||
|
||||
std::cerr << "\n";
|
||||
std::cerr << StackTrace().toString() << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -334,6 +334,7 @@ struct Settings : public SettingsCollection<Settings>
|
|||
\
|
||||
M(SettingBool, allow_hyperscan, true, "Allow functions that use Hyperscan library. Disable to avoid potentially long compilation times and excessive resource usage.") \
|
||||
M(SettingBool, allow_simdjson, true, "Allow using simdjson library in 'JSON*' functions if AVX2 instructions are available. If disabled rapidjson will be used.") \
|
||||
M(SettingBool, allow_introspection_functions, false, "Allow functions for introspection of ELF and DWARF for query profiling. These functions are slow and may impose security considerations.") \
|
||||
\
|
||||
M(SettingUInt64, max_partitions_per_insert_block, 100, "Limit maximum number of partitions in single INSERTed block. Zero means unlimited. Throw exception if the block contains too many partitions. This setting is a safety threshold, because using large number of partitions is a common misconception.") \
|
||||
M(SettingBool, check_query_single_value_result, true, "Return check query result as single 1/0 value") \
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
#include <Common/Elf.h>
|
||||
#include <Common/Dwarf.h>
|
||||
#include <Common/SymbolIndex.h>
|
||||
#include <Common/HashTable/HashMap.h>
|
||||
#include <Common/Arena.h>
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Columns/ColumnsNumber.h>
|
||||
#include <DataTypes/DataTypeString.h>
|
||||
#include <Functions/IFunction.h>
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
#include <Functions/FunctionFactory.h>
|
||||
#include <IO/WriteBufferFromArena.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <Interpreters/Context.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int ILLEGAL_COLUMN;
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int FUNCTION_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
class FunctionAddressToLine : public IFunction
|
||||
{
|
||||
public:
|
||||
static constexpr auto name = "addressToLine";
|
||||
static FunctionPtr create(const Context & context)
|
||||
{
|
||||
if (!context.getSettingsRef().allow_introspection_functions)
|
||||
throw Exception("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
|
||||
|
||||
return std::make_shared<FunctionAddressToLine>();
|
||||
}
|
||||
|
||||
String getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
size_t getNumberOfArguments() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
|
||||
{
|
||||
if (arguments.size() != 1)
|
||||
throw Exception("Function " + getName() + " needs exactly one argument; passed "
|
||||
+ toString(arguments.size()) + ".", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
||||
|
||||
const auto & type = arguments[0].type;
|
||||
|
||||
if (!WhichDataType(type.get()).isUInt64())
|
||||
throw Exception("The only argument for function " + getName() + " must be UInt64. Found "
|
||||
+ type->getName() + " instead.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
||||
|
||||
return std::make_shared<DataTypeString>();
|
||||
}
|
||||
|
||||
bool useDefaultImplementationForConstants() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override
|
||||
{
|
||||
const ColumnPtr & column = block.getByPosition(arguments[0]).column;
|
||||
const ColumnUInt64 * column_concrete = checkAndGetColumn<ColumnUInt64>(column.get());
|
||||
|
||||
if (!column_concrete)
|
||||
throw Exception("Illegal column " + column->getName() + " of argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN);
|
||||
|
||||
const typename ColumnVector<UInt64>::Container & data = column_concrete->getData();
|
||||
auto result_column = ColumnString::create();
|
||||
|
||||
for (size_t i = 0; i < input_rows_count; ++i)
|
||||
{
|
||||
StringRef res_str = implCached(data[i]);
|
||||
result_column->insertData(res_str.data, res_str.size);
|
||||
}
|
||||
|
||||
block.getByPosition(result).column = std::move(result_column);
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
Arena arena;
|
||||
using Map = HashMap<uintptr_t, StringRef>;
|
||||
Map map;
|
||||
std::unordered_map<std::string, Dwarf> dwarfs;
|
||||
|
||||
StringRef impl(uintptr_t addr)
|
||||
{
|
||||
const SymbolIndex & symbol_index = SymbolIndex::instance();
|
||||
|
||||
if (auto object = symbol_index.findObject(reinterpret_cast<const void *>(addr)))
|
||||
{
|
||||
auto dwarf_it = dwarfs.try_emplace(object->name, *object->elf).first;
|
||||
if (!std::filesystem::exists(object->name))
|
||||
return {};
|
||||
|
||||
Dwarf::LocationInfo location;
|
||||
if (dwarf_it->second.findAddress(addr - uintptr_t(object->address_begin), location, Dwarf::LocationInfoMode::FAST))
|
||||
{
|
||||
const char * arena_begin = nullptr;
|
||||
WriteBufferFromArena out(arena, arena_begin);
|
||||
|
||||
writeString(location.file.toString(), out);
|
||||
writeChar(':', out);
|
||||
writeIntText(location.line, out);
|
||||
|
||||
StringRef out_str = out.finish();
|
||||
out_str.data = arena.insert(out_str.data, out_str.size);
|
||||
return out_str;
|
||||
}
|
||||
else
|
||||
{
|
||||
return object->name;
|
||||
}
|
||||
}
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
StringRef implCached(uintptr_t addr)
|
||||
{
|
||||
Map::iterator it;
|
||||
bool inserted;
|
||||
std::lock_guard lock(mutex);
|
||||
map.emplace(addr, it, inserted);
|
||||
if (inserted)
|
||||
it->getSecond() = impl(addr);
|
||||
return it->getSecond();
|
||||
}
|
||||
};
|
||||
|
||||
void registerFunctionAddressToLine(FunctionFactory & factory)
|
||||
{
|
||||
factory.registerFunction<FunctionAddressToLine>();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
#include <dlfcn.h>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <common/unaligned.h>
|
||||
#include <common/demangle.h>
|
||||
#include <common/SimpleCache.h>
|
||||
#include <Common/SymbolIndex.h>
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Columns/ColumnsNumber.h>
|
||||
#include <DataTypes/DataTypeString.h>
|
||||
#include <Functions/IFunction.h>
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
#include <Functions/FunctionFactory.h>
|
||||
#include <Interpreters/Context.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
|
||||
|
||||
|
@ -20,17 +16,19 @@ namespace ErrorCodes
|
|||
{
|
||||
extern const int ILLEGAL_COLUMN;
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int SIZES_OF_ARRAYS_DOESNT_MATCH;
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int FUNCTION_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
class FunctionSymbolizeAddress : public IFunction
|
||||
class FunctionAddressToSymbol : public IFunction
|
||||
{
|
||||
public:
|
||||
static constexpr auto name = "symbolizeAddress";
|
||||
static FunctionPtr create(const Context &)
|
||||
static constexpr auto name = "addressToSymbol";
|
||||
static FunctionPtr create(const Context & context)
|
||||
{
|
||||
return std::make_shared<FunctionSymbolizeAddress>();
|
||||
if (!context.getSettingsRef().allow_introspection_functions)
|
||||
throw Exception("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
|
||||
return std::make_shared<FunctionAddressToSymbol>();
|
||||
}
|
||||
|
||||
String getName() const override
|
||||
|
@ -63,25 +61,10 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::string addressToSymbol(UInt64 uint_address)
|
||||
{
|
||||
void * addr = unalignedLoad<void *>(&uint_address);
|
||||
|
||||
/// This is extremely slow.
|
||||
Dl_info info;
|
||||
if (dladdr(addr, &info) && info.dli_sname)
|
||||
{
|
||||
int demangling_status = 0;
|
||||
return demangle(info.dli_sname, demangling_status);
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override
|
||||
{
|
||||
const SymbolIndex & symbol_index = SymbolIndex::instance();
|
||||
|
||||
const ColumnPtr & column = block.getByPosition(arguments[0]).column;
|
||||
const ColumnUInt64 * column_concrete = checkAndGetColumn<ColumnUInt64>(column.get());
|
||||
|
||||
|
@ -91,25 +74,21 @@ public:
|
|||
const typename ColumnVector<UInt64>::Container & data = column_concrete->getData();
|
||||
auto result_column = ColumnString::create();
|
||||
|
||||
static SimpleCache<decltype(addressToSymbol), &addressToSymbol> func_cached;
|
||||
|
||||
for (size_t i = 0; i < input_rows_count; ++i)
|
||||
{
|
||||
std::string symbol = func_cached(data[i]);
|
||||
result_column->insertDataWithTerminatingZero(symbol.data(), symbol.size() + 1);
|
||||
if (const auto * symbol = symbol_index.findSymbol(reinterpret_cast<const void *>(data[i])))
|
||||
result_column->insertDataWithTerminatingZero(symbol->name, strlen(symbol->name) + 1);
|
||||
else
|
||||
result_column->insertDefault();
|
||||
}
|
||||
|
||||
block.getByPosition(result).column = std::move(result_column);
|
||||
|
||||
/// Do not let our cache to grow indefinitely (simply drop it)
|
||||
if (func_cached.size() > 1000000)
|
||||
func_cached.drop();
|
||||
}
|
||||
};
|
||||
|
||||
void registerFunctionSymbolizeAddress(FunctionFactory & factory)
|
||||
void registerFunctionAddressToSymbol(FunctionFactory & factory)
|
||||
{
|
||||
factory.registerFunction<FunctionSymbolizeAddress>();
|
||||
factory.registerFunction<FunctionAddressToSymbol>();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#include <common/demangle.h>
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Columns/ColumnsNumber.h>
|
||||
#include <DataTypes/DataTypeString.h>
|
||||
#include <Functions/IFunction.h>
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
#include <Functions/FunctionFactory.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <Interpreters/Context.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int ILLEGAL_COLUMN;
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int FUNCTION_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
class FunctionDemangle : public IFunction
|
||||
{
|
||||
public:
|
||||
static constexpr auto name = "demangle";
|
||||
static FunctionPtr create(const Context & context)
|
||||
{
|
||||
if (!context.getSettingsRef().allow_introspection_functions)
|
||||
throw Exception("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
|
||||
|
||||
return std::make_shared<FunctionDemangle>();
|
||||
}
|
||||
|
||||
String getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
size_t getNumberOfArguments() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
|
||||
{
|
||||
if (arguments.size() != 1)
|
||||
throw Exception("Function " + getName() + " needs exactly one argument; passed "
|
||||
+ toString(arguments.size()) + ".", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
||||
|
||||
const auto & type = arguments[0].type;
|
||||
|
||||
if (!WhichDataType(type.get()).isString())
|
||||
throw Exception("The only argument for function " + getName() + " must be String. Found "
|
||||
+ type->getName() + " instead.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
||||
|
||||
return std::make_shared<DataTypeString>();
|
||||
}
|
||||
|
||||
bool useDefaultImplementationForConstants() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override
|
||||
{
|
||||
const ColumnPtr & column = block.getByPosition(arguments[0]).column;
|
||||
const ColumnString * column_concrete = checkAndGetColumn<ColumnString>(column.get());
|
||||
|
||||
if (!column_concrete)
|
||||
throw Exception("Illegal column " + column->getName() + " of argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN);
|
||||
|
||||
auto result_column = ColumnString::create();
|
||||
|
||||
for (size_t i = 0; i < input_rows_count; ++i)
|
||||
{
|
||||
StringRef source = column_concrete->getDataAt(i);
|
||||
int status = 0;
|
||||
std::string demangled = demangle(source.data, status);
|
||||
result_column->insertDataWithTerminatingZero(demangled.data(), demangled.size() + 1);
|
||||
}
|
||||
|
||||
block.getByPosition(result).column = std::move(result_column);
|
||||
}
|
||||
};
|
||||
|
||||
void registerFunctionDemangle(FunctionFactory & factory)
|
||||
{
|
||||
factory.registerFunction<FunctionDemangle>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ void registerFunctionsIntrospection(FunctionFactory &);
|
|||
void registerFunctionsNull(FunctionFactory &);
|
||||
void registerFunctionsFindCluster(FunctionFactory &);
|
||||
void registerFunctionsJSON(FunctionFactory &);
|
||||
void registerFunctionSymbolizeAddress(FunctionFactory &);
|
||||
void registerFunctionsIntrospection(FunctionFactory &);
|
||||
|
||||
void registerFunctions()
|
||||
{
|
||||
|
@ -79,7 +79,7 @@ void registerFunctions()
|
|||
registerFunctionsNull(factory);
|
||||
registerFunctionsFindCluster(factory);
|
||||
registerFunctionsJSON(factory);
|
||||
registerFunctionSymbolizeAddress(factory);
|
||||
registerFunctionsIntrospection(factory);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
namespace DB
|
||||
{
|
||||
|
||||
class FunctionFactory;
|
||||
|
||||
void registerFunctionAddressToSymbol(FunctionFactory & factory);
|
||||
void registerFunctionDemangle(FunctionFactory & factory);
|
||||
void registerFunctionAddressToLine(FunctionFactory & factory);
|
||||
|
||||
void registerFunctionsIntrospection(FunctionFactory & factory)
|
||||
{
|
||||
registerFunctionAddressToSymbol(factory);
|
||||
registerFunctionDemangle(factory);
|
||||
registerFunctionAddressToLine(factory);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
#include <Parsers/ASTCreateQuery.h>
|
||||
#include <Parsers/ParserCreateQuery.h>
|
||||
#include <Parsers/parseQuery.h>
|
||||
#include <common/StackTrace.h>
|
||||
#include <Common/StackTrace.h>
|
||||
#include <Common/Config/ConfigProcessor.h>
|
||||
#include <Common/ZooKeeper/ZooKeeper.h>
|
||||
#include <Common/ShellCommand.h>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
SET allow_introspection_functions = 1;
|
||||
|
||||
SET query_profiler_real_time_period_ns = 100000000;
|
||||
SET log_queries = 1;
|
||||
SELECT sleep(0.5), ignore('test real time query profiler');
|
||||
SET log_queries = 0;
|
||||
SYSTEM FLUSH LOGS;
|
||||
WITH symbolizeAddress(arrayJoin(trace)) AS symbol SELECT count() > 0 FROM system.trace_log t WHERE event_date >= yesterday() AND query_id = (SELECT query_id FROM system.query_log WHERE event_date >= yesterday() AND query LIKE '%test real time query profiler%' ORDER BY event_time DESC LIMIT 1) AND symbol LIKE '%FunctionSleep%';
|
||||
WITH addressToSymbol(arrayJoin(trace)) AS symbol SELECT count() > 0 FROM system.trace_log t WHERE event_date >= yesterday() AND query_id = (SELECT query_id FROM system.query_log WHERE event_date >= yesterday() AND query LIKE '%test real time query profiler%' ORDER BY event_time DESC LIMIT 1) AND symbol LIKE '%FunctionSleep%';
|
||||
|
||||
SET query_profiler_real_time_period_ns = 0;
|
||||
SET query_profiler_cpu_time_period_ns = 100000000;
|
||||
|
@ -11,4 +13,4 @@ SET log_queries = 1;
|
|||
SELECT count(), ignore('test cpu time query profiler') FROM numbers(1000000000);
|
||||
SET log_queries = 0;
|
||||
SYSTEM FLUSH LOGS;
|
||||
WITH symbolizeAddress(arrayJoin(trace)) AS symbol SELECT count() > 0 FROM system.trace_log t WHERE event_date >= yesterday() AND query_id = (SELECT query_id FROM system.query_log WHERE event_date >= yesterday() AND query LIKE '%test cpu time query profiler%' ORDER BY event_time DESC LIMIT 1) AND symbol LIKE '%Numbers%';
|
||||
WITH addressToSymbol(arrayJoin(trace)) AS symbol SELECT count() > 0 FROM system.trace_log t WHERE event_date >= yesterday() AND query_id = (SELECT query_id FROM system.query_log WHERE event_date >= yesterday() AND query LIKE '%test cpu time query profiler%' ORDER BY event_time DESC LIMIT 1) AND symbol LIKE '%Numbers%';
|
||||
|
|
|
@ -23,12 +23,10 @@ add_library (common
|
|||
src/getThreadNumber.cpp
|
||||
src/sleep.cpp
|
||||
src/argsToConfig.cpp
|
||||
src/StackTrace.cpp
|
||||
src/Pipe.cpp
|
||||
src/phdr_cache.cpp
|
||||
|
||||
include/common/SimpleCache.h
|
||||
include/common/StackTrace.h
|
||||
include/common/Types.h
|
||||
include/common/DayNum.h
|
||||
include/common/DateLUT.h
|
||||
|
@ -68,14 +66,6 @@ add_library (common
|
|||
|
||||
${CONFIG_COMMON})
|
||||
|
||||
if (USE_UNWIND)
|
||||
target_compile_definitions (common PRIVATE USE_UNWIND=1)
|
||||
target_include_directories (common BEFORE PRIVATE ${UNWIND_INCLUDE_DIR})
|
||||
if (NOT USE_INTERNAL_UNWIND_LIBRARY_FOR_EXCEPTION_HANDLING)
|
||||
target_link_libraries (common PRIVATE ${UNWIND_LIBRARY})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# When testing for memory leaks with Valgrind, dont link tcmalloc or jemalloc.
|
||||
|
||||
if (USE_JEMALLOC)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <common/logger_useful.h>
|
||||
#include <common/ErrorHandlers.h>
|
||||
#include <common/Pipe.h>
|
||||
#include <common/StackTrace.h>
|
||||
#include <Common/StackTrace.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <iostream>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include "syscall.h"
|
||||
#include <syscall.h>
|
||||
|
||||
int utimensat(int fd, const char *path, const struct timespec times[2], int flags)
|
||||
{
|
||||
int r = __syscall(SYS_utimensat, fd, path, times, flags);
|
||||
#ifdef SYS_futimesat
|
||||
if (r != -ENOSYS || flags) return __syscall_ret(r);
|
||||
struct timeval *tv = 0, tmp[2];
|
||||
if (times) {
|
||||
int i;
|
||||
tv = tmp;
|
||||
for (i=0; i<2; i++) {
|
||||
if (times[i].tv_nsec >= 1000000000ULL) {
|
||||
if (times[i].tv_nsec == UTIME_NOW &&
|
||||
times[1-i].tv_nsec == UTIME_NOW) {
|
||||
tv = 0;
|
||||
break;
|
||||
}
|
||||
if (times[i].tv_nsec == UTIME_OMIT)
|
||||
return __syscall_ret(-ENOSYS);
|
||||
return __syscall_ret(-EINVAL);
|
||||
}
|
||||
tmp[i].tv_sec = times[i].tv_sec;
|
||||
tmp[i].tv_usec = times[i].tv_nsec / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
r = __syscall(SYS_futimesat, fd, path, tv);
|
||||
if (r != -ENOSYS || fd != AT_FDCWD) return __syscall_ret(r);
|
||||
r = __syscall(SYS_utimes, path, tv);
|
||||
#endif
|
||||
return __syscall_ret(r);
|
||||
}
|
Loading…
Reference in New Issue