Merge pull request #6201 from yandex/speed-up-dladdr

Introspection functions + better stack traces.
This commit is contained in:
alexey-milovidov 2019-07-31 00:49:16 +03:00 committed by GitHub
commit cb37c79c41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2342 additions and 107 deletions

View File

@ -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)

View File

@ -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)

1033
dbms/src/Common/Dwarf.cpp Normal file

File diff suppressed because it is too large Load Diff

287
dbms/src/Common/Dwarf.h Normal file
View File

@ -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
};
}

130
dbms/src/Common/Elf.cpp Normal file
View File

@ -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;
}
}

63
dbms/src/Common/Elf.h Normal file
View File

@ -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;
};
}

View File

@ -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;

View File

@ -6,7 +6,7 @@
#include <Poco/Exception.h>
#include <common/StackTrace.h>
#include <Common/StackTrace.h>
namespace Poco { class Logger; }

View File

@ -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>

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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();
};
}

View File

@ -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>

View File

@ -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)

View File

@ -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;
}

View File

@ -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") \

View File

@ -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>();
}
}

View File

@ -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>();
}
}

View File

@ -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>();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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%';

View File

@ -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)

View File

@ -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>

View File

@ -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);
}