From 7654c3d3609b56149153ce05594564bc9ebd0a92 Mon Sep 17 00:00:00 2001 From: Nicholas Ormrod Date: Thu, 11 Jul 2024 19:00:06 -0700 Subject: [PATCH] Move symbolizer to folly/debugging/symbolizer Summary: The following targets were moved to folly/debugging/symbolizer: ``` //folly/experimental/symbolizer:dwarf //folly/experimental/symbolizer:elf //folly/experimental/symbolizer:symbolized_frame //folly/experimental/symbolizer:line_reader //folly/experimental/symbolizer:stack_trace //folly/experimental/symbolizer:elf_cache //folly/experimental/symbolizer:symbolize_printer //folly/experimental/symbolizer:symbolizer //folly/experimental/symbolizer:signal_handler ``` `arc f` was applied This is a codemod. It was automatically generated and will be landed once it is approved and tests are passing in sandcastle. You have been added as a reviewer by Sentinel or Butterfly. p:mesymk.folly Differential Revision: D59254804 fbshipit-source-id: e73a726a48269e17276b3e5031537d1f64d989ba --- folly/debugging/symbolizer/BUCK | 209 ++++++++ .../symbolizer/Dwarf.cpp | 6 +- folly/debugging/symbolizer/Dwarf.h | 87 ++++ .../symbolizer/DwarfImpl.cpp | 4 +- folly/debugging/symbolizer/DwarfImpl.h | 131 +++++ .../symbolizer/DwarfLineNumberVM.cpp | 4 +- .../debugging/symbolizer/DwarfLineNumberVM.h | 133 +++++ .../symbolizer/DwarfSection.cpp | 4 +- folly/debugging/symbolizer/DwarfSection.h | 56 +++ .../symbolizer/DwarfUtil.cpp | 2 +- folly/debugging/symbolizer/DwarfUtil.h | 251 ++++++++++ folly/debugging/symbolizer/Elf-inl.h | 145 ++++++ .../symbolizer/Elf.cpp | 2 +- folly/debugging/symbolizer/Elf.h | 456 ++++++++++++++++++ .../symbolizer/ElfCache.cpp | 2 +- folly/debugging/symbolizer/ElfCache.h | 139 ++++++ .../symbolizer/LineReader.cpp | 2 +- folly/debugging/symbolizer/LineReader.h | 89 ++++ .../symbolizer/SignalHandler.cpp | 2 +- folly/debugging/symbolizer/SignalHandler.h | 65 +++ .../symbolizer/StackTrace.cpp | 2 +- folly/debugging/symbolizer/StackTrace.h | 84 ++++ .../symbolizer/SymbolizePrinter.cpp | 2 +- folly/debugging/symbolizer/SymbolizePrinter.h | 201 ++++++++ .../symbolizer/SymbolizedFrame.cpp | 2 +- folly/debugging/symbolizer/SymbolizedFrame.h | 125 +++++ .../symbolizer/Symbolizer.cpp | 2 +- folly/debugging/symbolizer/Symbolizer.h | 310 ++++++++++++ folly/experimental/symbolizer/BUCK | 145 +----- folly/experimental/symbolizer/Dwarf.h | 72 +-- folly/experimental/symbolizer/DwarfImpl.h | 116 +---- .../symbolizer/DwarfLineNumberVM.h | 118 +---- folly/experimental/symbolizer/DwarfSection.h | 41 +- folly/experimental/symbolizer/DwarfUtil.h | 236 +-------- folly/experimental/symbolizer/Elf-inl.h | 130 +---- folly/experimental/symbolizer/Elf.h | 441 +---------------- folly/experimental/symbolizer/ElfCache.h | 124 +---- folly/experimental/symbolizer/LineReader.h | 74 +-- folly/experimental/symbolizer/SignalHandler.h | 50 +- folly/experimental/symbolizer/StackTrace.h | 69 +-- .../symbolizer/SymbolizePrinter.h | 186 +------ .../experimental/symbolizer/SymbolizedFrame.h | 110 +---- folly/experimental/symbolizer/Symbolizer.h | 295 +---------- 43 files changed, 2530 insertions(+), 2194 deletions(-) create mode 100644 folly/debugging/symbolizer/BUCK rename folly/{experimental => debugging}/symbolizer/Dwarf.cpp (97%) create mode 100644 folly/debugging/symbolizer/Dwarf.h rename folly/{experimental => debugging}/symbolizer/DwarfImpl.cpp (99%) create mode 100644 folly/debugging/symbolizer/DwarfImpl.h rename folly/{experimental => debugging}/symbolizer/DwarfLineNumberVM.cpp (99%) create mode 100644 folly/debugging/symbolizer/DwarfLineNumberVM.h rename folly/{experimental => debugging}/symbolizer/DwarfSection.cpp (93%) create mode 100644 folly/debugging/symbolizer/DwarfSection.h rename folly/{experimental => debugging}/symbolizer/DwarfUtil.cpp (99%) create mode 100644 folly/debugging/symbolizer/DwarfUtil.h create mode 100644 folly/debugging/symbolizer/Elf-inl.h rename folly/{experimental => debugging}/symbolizer/Elf.cpp (99%) create mode 100644 folly/debugging/symbolizer/Elf.h rename folly/{experimental => debugging}/symbolizer/ElfCache.cpp (98%) create mode 100644 folly/debugging/symbolizer/ElfCache.h rename folly/{experimental => debugging}/symbolizer/LineReader.cpp (97%) create mode 100644 folly/debugging/symbolizer/LineReader.h rename folly/{experimental => debugging}/symbolizer/SignalHandler.cpp (99%) create mode 100644 folly/debugging/symbolizer/SignalHandler.h rename folly/{experimental => debugging}/symbolizer/StackTrace.cpp (99%) create mode 100644 folly/debugging/symbolizer/StackTrace.h rename folly/{experimental => debugging}/symbolizer/SymbolizePrinter.cpp (99%) create mode 100644 folly/debugging/symbolizer/SymbolizePrinter.h rename folly/{experimental => debugging}/symbolizer/SymbolizedFrame.cpp (98%) create mode 100644 folly/debugging/symbolizer/SymbolizedFrame.h rename folly/{experimental => debugging}/symbolizer/Symbolizer.cpp (99%) create mode 100644 folly/debugging/symbolizer/Symbolizer.h diff --git a/folly/debugging/symbolizer/BUCK b/folly/debugging/symbolizer/BUCK new file mode 100644 index 00000000000..ad96d03d87b --- /dev/null +++ b/folly/debugging/symbolizer/BUCK @@ -0,0 +1,209 @@ +load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library") + +oncall("fbcode_entropy_wardens_folly") + +cpp_library( + # @autodeps-skip + name = "dwarf", + srcs = [ + "Dwarf.cpp", + "DwarfImpl.cpp", + "DwarfLineNumberVM.cpp", + "DwarfSection.cpp", + "DwarfUtil.cpp", + ], + headers = [ + "Dwarf.h", + "DwarfImpl.h", + "DwarfLineNumberVM.h", + "DwarfSection.h", + "DwarfUtil.h", + ], + deps = [ + "//folly:optional", + "//folly/lang:safe_assert", + "//folly/portability:config", + "//folly/portability:unistd", + ], + exported_deps = [ + "fbsource//third-party/libdwarf:dwarf", + "//folly:function", + "//folly:range", + "//folly/experimental/symbolizer:elf", + "//folly/experimental/symbolizer:elf_cache", + "//folly/experimental/symbolizer:symbolized_frame", + ], + external_deps = [ + "glog", + ], +) + +cpp_library( + name = "elf", + srcs = [ + "Elf.cpp", + ], + headers = [ + "Elf.h", + "Elf-inl.h", + ], + deps = [ + "//folly:exception", + "//folly:scope_guard", + "//folly/lang:c_string", + "//folly/portability:sys_mman", + ], + exported_deps = [ + "//folly:conv", + "//folly:likely", + "//folly:range", + "//folly/lang:safe_assert", + "//folly/portability:config", + ], + external_deps = [ + "glog", + ], +) + +cpp_library( + name = "symbolized_frame", + srcs = ["SymbolizedFrame.cpp"], + headers = ["SymbolizedFrame.h"], + exported_deps = [ + "//folly:range", + ], +) + +cpp_library( + name = "line_reader", + srcs = ["LineReader.cpp"], + headers = ["LineReader.h"], + deps = [ + "//folly:file_util", + ], + exported_deps = [ + "//folly:range", + ], +) + +cpp_library( + name = "stack_trace", + srcs = ["StackTrace.cpp"], + headers = ["StackTrace.h"], + deps = [ + "//folly:cpp_attributes", + "//folly:portability", + "//folly/portability:config", + "//folly/portability:libunwind", + "//folly/tracing:async_stack", + ], + exported_deps = [ + "//folly/portability:sys_types", + ], +) + +cpp_library( + name = "elf_cache", + srcs = [ + "ElfCache.cpp", + ], + headers = [ + "ElfCache.h", + ], + deps = [ + "//folly:scope_guard", + "//folly/portability:sys_mman", + ], + exported_deps = [ + "//folly:optional", + "//folly:range", + "//folly/experimental/symbolizer:elf", + "//folly/hash:hash", + "//folly/memory:reentrant_allocator", + "//folly/portability:config", + ], + exported_external_deps = [ + "boost", + ], +) + +cpp_library( + name = "symbolize_printer", + srcs = [ + "SymbolizePrinter.cpp", + ], + headers = [ + "SymbolizePrinter.h", + ], + deps = [ + "//folly:demangle", + "//folly:file_util", + "//folly:scope_guard", + "//folly/io:iobuf", + "//folly/lang:to_ascii", + ], + exported_deps = [ + "//folly:fbstring", + "//folly:range", + "//folly/experimental/symbolizer:symbolized_frame", + ], +) + +cpp_library( + name = "symbolizer", + srcs = [ + "Symbolizer.cpp", + ], + headers = [ + "Symbolizer.h", + ], + deps = [ + "//folly:file_util", + "//folly:memory", + "//folly:scope_guard", + "//folly:synchronized", + "//folly/container:evicting_cache_map", + "//folly/experimental/symbolizer:elf", + "//folly/experimental/symbolizer:line_reader", + "//folly/experimental/symbolizer/detail:debug", + "//folly/lang:safe_assert", + "//folly/lang:to_ascii", + "//folly/memory:sanitize_address", + "//folly/portability:sys_mman", + "//folly/tracing:async_stack", + ], + exported_deps = [ + "//folly:fbstring", + "//folly:optional", + "//folly:range", + "//folly:string", + "//folly/experimental/symbolizer:dwarf", + "//folly/experimental/symbolizer:elf_cache", + "//folly/experimental/symbolizer:stack_trace", + "//folly/experimental/symbolizer:symbolize_printer", + "//folly/experimental/symbolizer:symbolized_frame", + "//folly/io:iobuf", + "//folly/portability:config", + "//folly/portability:unistd", + ], +) + +cpp_library( + name = "signal_handler", + srcs = [ + "SignalHandler.cpp", + ], + headers = [ + "SignalHandler.h", + ], + deps = [ + "//folly:scope_guard", + "//folly/experimental/symbolizer:symbolizer", + "//folly/lang:to_ascii", + "//folly/portability:sys_syscall", + "//folly/portability:unistd", + ], + external_deps = [ + "glog", + ], +) diff --git a/folly/experimental/symbolizer/Dwarf.cpp b/folly/debugging/symbolizer/Dwarf.cpp similarity index 97% rename from folly/experimental/symbolizer/Dwarf.cpp rename to folly/debugging/symbolizer/Dwarf.cpp index 2fa17c2497a..91dd910bb07 100644 --- a/folly/experimental/symbolizer/Dwarf.cpp +++ b/folly/debugging/symbolizer/Dwarf.cpp @@ -14,14 +14,14 @@ * limitations under the License. */ -#include +#include #include #include #include -#include -#include +#include +#include #include #include diff --git a/folly/debugging/symbolizer/Dwarf.h b/folly/debugging/symbolizer/Dwarf.h new file mode 100644 index 00000000000..a9f2c8d8ab1 --- /dev/null +++ b/folly/debugging/symbolizer/Dwarf.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +// DWARF record parser + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace folly { +namespace symbolizer { + +#if FOLLY_HAVE_DWARF && FOLLY_HAVE_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 { + /** + * Note that Dwarf uses (and returns) StringPiece a lot. + * The StringPieces point within sections in the ELF file, and so will + * be live for as long as the passed-in ElfFile is live. + */ + public: + /** Create a DWARF parser around an ELF file. */ + Dwarf(ElfCacheBase* elfCache, const ElfFile* elf); + + /** + * Find the file and line number information corresponding to address. + * If `eachParameterName` is provided, the callback will be invoked once + * for each parameter of the function. + */ + bool findAddress( + uintptr_t address, + LocationInfoMode mode, + SymbolizedFrame& frame, + folly::Range inlineFrames = {}, + folly::FunctionRef + eachParameterName = {}) const; + + private: + ElfCacheBase* elfCache_; + DebugSections defaultDebugSections_; +}; + +#endif + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/DwarfImpl.cpp b/folly/debugging/symbolizer/DwarfImpl.cpp similarity index 99% rename from folly/experimental/symbolizer/DwarfImpl.cpp rename to folly/debugging/symbolizer/DwarfImpl.cpp index 82817f690f4..c22db72ea66 100644 --- a/folly/experimental/symbolizer/DwarfImpl.cpp +++ b/folly/debugging/symbolizer/DwarfImpl.cpp @@ -15,13 +15,13 @@ */ #include -#include +#include #include #include #include -#include +#include #include #include diff --git a/folly/debugging/symbolizer/DwarfImpl.h b/folly/debugging/symbolizer/DwarfImpl.h new file mode 100644 index 00000000000..2a087ec9e06 --- /dev/null +++ b/folly/debugging/symbolizer/DwarfImpl.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +// DWARF record parser + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace folly { +namespace symbolizer { + +#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF + +struct CallLocation; + +class DwarfImpl { + public: + explicit DwarfImpl( + ElfCacheBase* elfCache, CompilationUnits& cu, LocationInfoMode mode); + + /** + * Find the @locationInfo for @address in the compilation unit @cu. + * + * Best effort: + * - fills @inlineFrames if mode == FULL_WITH_INLINE, + * - calls @eachParameterName on the function parameters. + * + * if @checkAddress is true, we verify that the address is mapped to + * a range in this CU before running the line number VM + */ + bool findLocation( + uintptr_t address, + SymbolizedFrame& frame, + folly::Range inlineFrames, + folly::FunctionRef eachParameterName, + bool checkAddress = true) const; + + private: + using AttributeValue = std::variant; + + /** + * Finds a subprogram debugging info entry that contains a given address among + * children of given die. Depth first search. + */ + bool findSubProgramDieForAddress( + const CompilationUnit& cu, + const Die& die, + uint64_t address, + folly::Optional baseAddrCU, + Die& subprogram) const; + + /** + * Finds inlined subroutine DIEs and their caller lines that contains a given + * address among children of given die. Depth first search. + */ + void findInlinedSubroutineDieForAddress( + const CompilationUnit& cu, + const Die& die, + const DwarfLineNumberVM& lineVM, + uint64_t address, + folly::Optional baseAddrCU, + folly::Range locations, + size_t& numFound) const; + + CompilationUnit findCompilationUnit( + const CompilationUnit& cu, uint64_t targetOffset) const; + + /** + * Find the actual definition DIE instead of declaration for the given die. + */ + Die findDefinitionDie(const CompilationUnit& cu, const Die& die) const; + + /** + * Iterates over all children of a debugging info entry, calling the given + * callable for each. Iteration is stopped early if any of the calls return + * false. Returns the offset of next DIE after iterations. + */ + size_t forEachChild( + const CompilationUnit& cu, + const Die& die, + folly::FunctionRef f) const; + + /** + * Check if the given address is in the range list at the given offset in + * .debug_ranges. + */ + bool isAddrInRangeList( + const CompilationUnit& cu, + uint64_t address, + folly::Optional baseAddr, + size_t offset, + uint8_t addrSize) const; + + void fillInlineFrames( + uintptr_t address, + SymbolizedFrame& frame, + folly::Range inlineLocations, + folly::Range inlineFrames) const; + + ElfCacheBase* elfCache_; + CompilationUnits& cu_; + const LocationInfoMode mode_; +}; + +#endif + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/DwarfLineNumberVM.cpp b/folly/debugging/symbolizer/DwarfLineNumberVM.cpp similarity index 99% rename from folly/experimental/symbolizer/DwarfLineNumberVM.cpp rename to folly/debugging/symbolizer/DwarfLineNumberVM.cpp index 6e40377d5d7..e2db33ecf00 100644 --- a/folly/experimental/symbolizer/DwarfLineNumberVM.cpp +++ b/folly/debugging/symbolizer/DwarfLineNumberVM.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#include +#include #include -#include +#include #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF diff --git a/folly/debugging/symbolizer/DwarfLineNumberVM.h b/folly/debugging/symbolizer/DwarfLineNumberVM.h new file mode 100644 index 00000000000..88bab798911 --- /dev/null +++ b/folly/debugging/symbolizer/DwarfLineNumberVM.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace folly { +namespace symbolizer { + +#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF + +class DwarfLineNumberVM { + public: + DwarfLineNumberVM( + folly::StringPiece data, + folly::StringPiece compilationDirectory, + const DebugSections& debugSections); + + bool findAddress(uintptr_t target, Path& file, uint64_t& line); + + /** Gets full file name at given index including directory. */ + Path getFullFileName(uint64_t index) const; + + private: + bool init(); + void reset(); + + /** Execute until we commit one new row to the line number matrix */ + bool next(folly::StringPiece& program); + enum StepResult { + CONTINUE, // Continue feeding opcodes + COMMIT, // Commit new tuple + END, // End of sequence + }; + /** Execute one opcode */ + StepResult step(folly::StringPiece& program); + + struct FileName { + folly::StringPiece relativeName; + // 0 = current compilation directory + // otherwise, 1-based index in the list of include directories + uint64_t directoryIndex; + }; + + /** Read one FileName object, advance sp */ + static bool readFileName(folly::StringPiece& program, FileName& fn); + + /** + * Get file name at given index; may be in the initial table + * (fileNames_) or defined using DW_LNE_define_file (and we reexecute + * enough of the program to find it, if so) + */ + FileName getFileName(uint64_t index) const; + + /** Get include directory at given index */ + folly::StringPiece getIncludeDirectory(uint64_t index) const; + + /** + * Execute opcodes until finding a DW_LNE_define_file and return true; + * return file at the end. + */ + bool nextDefineFile(folly::StringPiece& program, FileName& fn) const; + + // Initialization + bool initializationSuccess_ = false; + bool is64Bit_; + folly::StringPiece data_; + folly::StringPiece compilationDirectory_; + const DebugSections& debugSections_; + + // Header + uint16_t version_; + uint8_t minLength_; + bool defaultIsStmt_; + int8_t lineBase_; + uint8_t lineRange_; + uint8_t opcodeBase_; + const uint8_t* standardOpcodeLengths_; + + // 6.2.4 The Line Number Program Header. + struct { + size_t includeDirectoryCount; + folly::StringPiece includeDirectories; + size_t fileNameCount; + folly::StringPiece fileNames; + } v4_; + + struct { + uint8_t directoryEntryFormatCount; + folly::StringPiece directoryEntryFormat; + uint64_t directoriesCount; + folly::StringPiece directories; + + uint8_t fileNameEntryFormatCount; + folly::StringPiece fileNameEntryFormat; + uint64_t fileNamesCount; + folly::StringPiece fileNames; + } v5_; + + // 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_; +}; + +#endif + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/DwarfSection.cpp b/folly/debugging/symbolizer/DwarfSection.cpp similarity index 93% rename from folly/experimental/symbolizer/DwarfSection.cpp rename to folly/debugging/symbolizer/DwarfSection.cpp index 68b5635671c..e47350626fd 100644 --- a/folly/experimental/symbolizer/DwarfSection.cpp +++ b/folly/debugging/symbolizer/DwarfSection.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ -#include +#include -#include +#include #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF diff --git a/folly/debugging/symbolizer/DwarfSection.h b/folly/debugging/symbolizer/DwarfSection.h new file mode 100644 index 00000000000..e4eb6edc3e9 --- /dev/null +++ b/folly/debugging/symbolizer/DwarfSection.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include + +#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF + +namespace folly { +namespace symbolizer { + +/** + * 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 DwarfSection { + public: + DwarfSection() : is64Bit_(false) {} + + explicit DwarfSection(folly::StringPiece d); + + /** + * Return next chunk, if any; the 4- or 12-byte length was already + * parsed and isn't part of the chunk. + */ + bool next(folly::StringPiece& chunk); + + /** Is the current chunk 64 bit? */ + bool is64Bit() const { return is64Bit_; } + + private: + // Yes, 32- and 64- bit sections may coexist. Yikes! + bool is64Bit_; + folly::StringPiece data_; +}; + +} // namespace symbolizer +} // namespace folly + +#endif diff --git a/folly/experimental/symbolizer/DwarfUtil.cpp b/folly/debugging/symbolizer/DwarfUtil.cpp similarity index 99% rename from folly/experimental/symbolizer/DwarfUtil.cpp rename to folly/debugging/symbolizer/DwarfUtil.cpp index 567267faa06..1aedc180b04 100644 --- a/folly/experimental/symbolizer/DwarfUtil.cpp +++ b/folly/debugging/symbolizer/DwarfUtil.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include diff --git a/folly/debugging/symbolizer/DwarfUtil.h b/folly/debugging/symbolizer/DwarfUtil.h new file mode 100644 index 00000000000..4c5f02471c4 --- /dev/null +++ b/folly/debugging/symbolizer/DwarfUtil.h @@ -0,0 +1,251 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +// DWARF record parser + +#pragma once + +#include + +#include +#include +#include +#include + +#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF +#include + +namespace folly { +namespace symbolizer { + +/** + * More than one location info may exist if current frame is an inline + * function call. + */ +const uint32_t kMaxInlineLocationInfoPerFrame = 20; + +// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to +// speed up inline function lookup. +const uint32_t kMaxAbbreviationEntries = 1000; + +// A struct contains an Elf object and different debug sections. +struct DebugSections { + const ElfFile* elf; + folly::StringPiece debugCuIndex; // .debug_cu_index + folly::StringPiece debugAbbrev; // .debug_abbrev + folly::StringPiece debugAddr; // .debug_addr (DWARF 5) + folly::StringPiece debugAranges; // .debug_aranges + folly::StringPiece debugInfo; // .debug_info + folly::StringPiece debugLine; // .debug_line + folly::StringPiece debugLineStr; // .debug_line_str (DWARF 5) + folly::StringPiece debugLoclists; // .debug_loclists (DWARF 5) + folly::StringPiece debugRanges; // .debug_ranges + folly::StringPiece debugRnglists; // .debug_rnglists (DWARF 5) + folly::StringPiece debugStr; // .debug_str + folly::StringPiece debugStrOffsets; // .debug_str_offsets (DWARF 5) +}; + +// Abbreviation for a Debugging Information Entry. +struct DIEAbbreviation { + uint64_t code = 0; + uint64_t tag = 0; + bool hasChildren = false; + folly::StringPiece attributes; +}; + +// A struct that contains the metadata of a compilation unit. +struct CompilationUnit { + DebugSections debugSections; + + bool is64Bit = false; + uint8_t version = 0; + uint8_t unitType = DW_UT_compile; // DW_UT_compile or DW_UT_skeleton + uint8_t addrSize = 0; + // Offset in .debug_info of this compilation unit. + uint32_t offset = 0; + uint32_t size = 0; + // Offset in .debug_info for the first DIE in this compilation unit. + uint32_t firstDie = 0; + folly::Optional abbrevOffset; + + // Compilation directory. + folly::StringPiece compDir = "."; + // The beginning of the CU's contribution to .debug_addr + // DW_AT_addr_base (DWARF 5, DebugFission) + folly::Optional addrBase; + // The beginning of the CU's contribution to .debug_ranges + // DW_AT_ranges_base (DebugFission) + folly::Optional rangesBase; + // The beginning of the offsets table (immediately following the + // header) of the CU's contribution to .debug_loclists + folly::Optional loclistsBase; // DW_AT_loclists_base (DWARF 5) + // The beginning of the offsets table (immediately following the + // header) of the CU's contribution to .debug_rnglists + folly::Optional rnglistsBase; // DW_AT_rnglists_base (DWARF 5) + // Points to the first string offset of the compilation unit’s + // contribution to the .debug_str_offsets (or .debug_str_offsets.dwo) section. + folly::Optional strOffsetsBase; // DW_AT_str_offsets_base (DWARF 5) + + // The actual dwo file and id contains the debug sections for this + // compilation unit. + folly::Optional dwoName; + folly::Optional dwoId; + + // Only the CompilationUnit that contains the caller functions needs this. + // Indexed by (abbr.code - 1) if (abbr.code - 1) < abbrCache.size(); + folly::Range abbrCache; +}; + +// Contains the main compilation unit in the binary file and an optional +// compilation unit in dwo/dwp file if the main one is a skeleton. +struct CompilationUnits { + CompilationUnit mainCompilationUnit; + folly::Optional splitCU; + + CompilationUnit& defaultCompilationUnit() { + if (splitCU.hasValue()) { + return *splitCU; + } + return mainCompilationUnit; + } +}; + +// Debugging information entry to define a low-level representation of a +// source program. Each debugging information entry consists of an identifying +// tag and a series of attributes. An entry, or group of entries together, +// provide a description of a corresponding entity in the source program. +struct Die { + bool is64Bit = false; + // Offset from start to first attribute + uint8_t attrOffset = 0; + // Offset within debug info. + uint32_t offset = 0; + uint64_t code = 0; + DIEAbbreviation abbr; +}; + +struct AttributeSpec { + uint64_t name = 0; + uint64_t form = 0; + int64_t implicitConst = 0; // only set when form=DW_FORM_implicit_const + + explicit operator bool() const { return name != 0 || form != 0; } +}; + +struct Attribute { + AttributeSpec spec; + const Die& die; + std::variant attrValue; +}; + +// Get an ELF section by name. +folly::StringPiece getElfSection(const ElfFile* elf, const char* name); + +// All following read* functions read from a StringPiece, advancing the +// StringPiece, and aborting if there's not enough room. + +// Read (bitwise) one object of type T +template +typename std::enable_if< + std::is_standard_layout::value && std::is_trivial::value, + T>::type +read(folly::StringPiece& sp) { + FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow"); + T x; + memcpy(&x, sp.data(), sizeof(T)); + sp.advance(sizeof(T)); + return x; +} + +// Read (bitwise) an unsigned number of N bytes (N in 1, 2, 3, 4). +template +uint64_t readU64(folly::StringPiece& sp); + +// Read ULEB (unsigned) varint value; algorithm from the DWARF spec +uint64_t readULEB(folly::StringPiece& sp, uint8_t& shift, uint8_t& val); +uint64_t readULEB(folly::StringPiece& sp); + +// Read SLEB (signed) varint value; algorithm from the DWARF spec +int64_t readSLEB(folly::StringPiece& sp); + +// Read a value of "section offset" type, which may be 4 or 8 bytes +uint64_t readOffset(folly::StringPiece& sp, bool is64Bit); + +// Read "len" bytes +folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len); + +AttributeSpec readAttributeSpec(folly::StringPiece& sp); + +// Reads an abbreviation from a StringPiece, return true if at end; advance sp +bool readAbbreviation(folly::StringPiece& section, DIEAbbreviation& abbr); + +// Read a null-terminated string +folly::StringPiece readNullTerminated(folly::StringPiece& sp); + +folly::StringPiece getStringFromStringSection( + folly::StringPiece str, uint64_t offset); + +CompilationUnits getCompilationUnits( + ElfCacheBase* elfCache, + const DebugSections& debugSections, + uint64_t offset, + bool requireSplitDwarf = false); + +/** cu must exist during the life cycle of created Die. */ +Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset); + +// Read attribute value. +Attribute readAttribute( + const CompilationUnit& cu, + const Die& die, + AttributeSpec spec, + folly::StringPiece& info); + +/* + * Iterate over all attributes of the given DIE, calling the given callable + * for each. Iteration is stopped early if any of the calls return false. + */ +size_t forEachAttribute( + const CompilationUnit& cu, + const Die& die, + folly::FunctionRef f); + +template +folly::Optional getAttribute( + const CompilationUnit& cu, const Die& die, uint64_t attrName) { + folly::Optional result; + forEachAttribute(cu, die, [&](const Attribute& attr) { + if (attr.spec.name == attrName) { + result = std::get(attr.attrValue); + return false; + } + return true; + }); + return result; +} + +folly::StringPiece getFunctionNameFromDie( + const CompilationUnit& srcu, const Die& die); + +folly::StringPiece getFunctionName( + const CompilationUnit& srcu, uint64_t dieOffset); + +Die findDefinitionDie(const CompilationUnit& cu, const Die& die); + +} // namespace symbolizer +} // namespace folly + +#endif diff --git a/folly/debugging/symbolizer/Elf-inl.h b/folly/debugging/symbolizer/Elf-inl.h new file mode 100644 index 00000000000..6535bdd01de --- /dev/null +++ b/folly/debugging/symbolizer/Elf-inl.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#ifndef FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_ +#error This file must be included from Elf.h +#endif + +namespace folly { +namespace symbolizer { + +template +const ElfPhdr* ElfFile::iterateProgramHeaders(Fn fn) const + noexcept(is_nothrow_invocable_v) { + // there exist ELF binaries which execute correctly, but have invalid internal + // offset(s) to program/section headers; most probably due to invalid + // stripping of symbols + if (elfHeader().e_phoff + sizeof(ElfPhdr) >= length_) { + return nullptr; + } + + const ElfPhdr* ptr = &at(elfHeader().e_phoff); + for (size_t i = 0; i < elfHeader().e_phnum; i++, ptr++) { + if (fn(*ptr)) { + return ptr; + } + } + return nullptr; +} + +template +const ElfShdr* ElfFile::iterateSections(Fn fn) const + noexcept(is_nothrow_invocable_v) { + // there exist ELF binaries which execute correctly, but have invalid internal + // offset(s) to program/section headers; most probably due to invalid + // stripping of symbols + if (elfHeader().e_shoff + sizeof(ElfShdr) >= length_) { + return nullptr; + } + + const ElfShdr* ptr = &at(elfHeader().e_shoff); + for (size_t i = 0; i < elfHeader().e_shnum; i++, ptr++) { + if (fn(*ptr)) { + return ptr; + } + } + return nullptr; +} + +template +const ElfShdr* ElfFile::iterateSectionsWithType(uint32_t type, Fn fn) const + noexcept(is_nothrow_invocable_v) { + return iterateSections( + [&](const ElfShdr& sh) { return sh.sh_type == type && fn(sh); }); +} + +template +const ElfShdr* ElfFile::iterateSectionsWithTypes( + std::initializer_list types, Fn fn) const + noexcept(is_nothrow_invocable_v) { + return iterateSections([&](const ElfShdr& sh) { + auto const it = std::find(types.begin(), types.end(), sh.sh_type); + return it != types.end() && fn(sh); + }); +} + +template +const char* ElfFile::iterateStrings(const ElfShdr& stringTable, Fn fn) const + noexcept(is_nothrow_invocable_v) { + validateStringTable(stringTable); + + const char* start = file_ + stringTable.sh_offset; + const char* end = start + stringTable.sh_size; + + const char* ptr = start; + while (ptr != end && !fn(ptr)) { + ptr += strlen(ptr) + 1; + } + + return ptr != end ? ptr : nullptr; +} + +template +const E* ElfFile::iterateSectionEntries(const ElfShdr& section, Fn&& fn) const + + noexcept(is_nothrow_invocable_v) { + FOLLY_SAFE_CHECK( + section.sh_entsize == sizeof(E), "invalid entry size in table"); + + const E* ent = &at(section.sh_offset); + const E* end = ent + (section.sh_size / section.sh_entsize); + + while (ent < end) { + if (fn(*ent)) { + return ent; + } + + ++ent; + } + + return nullptr; +} + +template +const ElfSym* ElfFile::iterateSymbols(const ElfShdr& section, Fn fn) const + noexcept(is_nothrow_invocable_v) { + return iterateSectionEntries(section, fn); +} + +template +const ElfSym* ElfFile::iterateSymbolsWithType( + const ElfShdr& section, uint32_t type, Fn fn) const + noexcept(is_nothrow_invocable_v) { + // N.B. st_info has the same representation on 32- and 64-bit platforms + return iterateSymbols(section, [&](const ElfSym& sym) -> bool { + return ELF32_ST_TYPE(sym.st_info) == type && fn(sym); + }); +} + +template +const ElfSym* ElfFile::iterateSymbolsWithTypes( + const ElfShdr& section, std::initializer_list types, Fn fn) const + noexcept(is_nothrow_invocable_v) { + // N.B. st_info has the same representation on 32- and 64-bit platforms + return iterateSymbols(section, [&](const ElfSym& sym) -> bool { + auto const elfType = ELF32_ST_TYPE(sym.st_info); + auto const it = std::find(types.begin(), types.end(), elfType); + return it != types.end() && fn(sym); + }); +} + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/Elf.cpp b/folly/debugging/symbolizer/Elf.cpp similarity index 99% rename from folly/experimental/symbolizer/Elf.cpp rename to folly/debugging/symbolizer/Elf.cpp index 6a7f74fa470..cf9d1f73804 100644 --- a/folly/experimental/symbolizer/Elf.cpp +++ b/folly/debugging/symbolizer/Elf.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include diff --git a/folly/debugging/symbolizer/Elf.h b/folly/debugging/symbolizer/Elf.h new file mode 100644 index 00000000000..c89ac23b842 --- /dev/null +++ b/folly/debugging/symbolizer/Elf.h @@ -0,0 +1,456 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +// ELF file parser + +#pragma once +#define FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if FOLLY_HAVE_ELF + +#include +#include // For ElfW() + +namespace folly { +namespace symbolizer { + +#if defined(ElfW) +#define FOLLY_ELF_ELFW(name) ElfW(name) +#elif defined(__FreeBSD__) +#define FOLLY_ELF_ELFW(name) Elf_##name +#endif + +using ElfAddr = FOLLY_ELF_ELFW(Addr); +using ElfEhdr = FOLLY_ELF_ELFW(Ehdr); +using ElfOff = FOLLY_ELF_ELFW(Off); +using ElfPhdr = FOLLY_ELF_ELFW(Phdr); +using ElfShdr = FOLLY_ELF_ELFW(Shdr); +using ElfSym = FOLLY_ELF_ELFW(Sym); +using ElfRel = FOLLY_ELF_ELFW(Rel); +using ElfRela = FOLLY_ELF_ELFW(Rela); + +// ElfFileId is supposed to uniquely identify any instance of an ELF binary. +// It does that by using the file's inode, dev ID, size and modification time +// (ns): Just using dev, inode is not +// unique enough, because the file can be overwritten with new contents, but +// will keep same dev and inode, so we take into account modification time and +// file size to minimize risk. +struct ElfFileId { + dev_t dev; + ino_t inode; + off_t size; + uint64_t mtime; +}; + +inline bool operator==(const ElfFileId& lhs, const ElfFileId& rhs) { + return lhs.dev == rhs.dev && lhs.inode == rhs.inode && lhs.size == rhs.size && + lhs.mtime == rhs.mtime; +} + +/** + * ELF file parser. + * + * We handle native files only (32-bit files on a 32-bit platform, 64-bit files + * on a 64-bit platform), and only executables (ET_EXEC) shared objects + * (ET_DYN), core files (ET_CORE) and relocatable file (ET_REL). + */ +class ElfFile { + public: + class Options { + public: + constexpr Options() noexcept {} + + constexpr bool writable() const noexcept { return writable_; } + + constexpr Options& writable(bool const value) noexcept { + writable_ = value; + return *this; + } + + private: + bool writable_ = false; + }; + + ElfFile() noexcept; + + // Note: may throw, call openNoThrow() explicitly if you don't want to throw + explicit ElfFile(const char* name, Options const& options = Options()); + + // Open the ELF file. + // Returns 0 on success, kSystemError (guaranteed to be -1) (and sets errno) + // on IO error, kInvalidElfFile (and sets errno to EINVAL) for an invalid + // Elf file. On error, if msg is not nullptr, sets *msg to a static string + // indicating what failed. + enum OpenResultCode : int { + kSuccess = 0, + kSystemError = -1, + kInvalidElfFile = -2, + }; + struct OpenResult { + OpenResultCode code{}; + char const* msg{}; + + /* implicit */ constexpr operator OpenResultCode() const noexcept { + return code; + } + }; + // Open the ELF file. Does not throw on error. + OpenResult openNoThrow( + const char* name, Options const& options = Options()) noexcept; + + // Like openNoThrow, but follow .gnu_debuglink if present + OpenResult openAndFollow( + const char* name, Options const& options = Options()) noexcept; + + // Open the ELF file. Throws on error. + void open(const char* name, Options const& options = Options()); + + ~ElfFile(); + + ElfFile(ElfFile&& other) noexcept; + ElfFile& operator=(ElfFile&& other) noexcept; + + /** Retrieve the ELF header */ + const ElfEhdr& elfHeader() const noexcept { return at(0); } + + /** + * Get the base address, the address where the file should be loaded if + * no relocations happened. + */ + uintptr_t getBaseAddress() const noexcept { return baseAddress_; } + + /** Find a section given its name */ + const ElfShdr* getSectionByName(const char* name) const noexcept; + + /** Find a section given its index in the section header table */ + const ElfShdr* getSectionByIndex(size_t idx) const noexcept; + + /** Retrieve the name of a section */ + const char* getSectionName(const ElfShdr& section) const noexcept; + + /** Get the actual section body */ + folly::StringPiece getSectionBody(const ElfShdr& section) const noexcept; + + /** Retrieve a string from a string table section */ + const char* getString( + const ElfShdr& stringTable, size_t offset) const noexcept; + + /** + * Iterate over all strings in a string table section for as long as + * fn(str) returns false. + * Returns the current ("found") string when fn returned true, or nullptr + * if fn returned false for all strings in the table. + */ + template + const char* iterateStrings(const ElfShdr& stringTable, Fn fn) const + noexcept(is_nothrow_invocable_v); + + /** + * Iterate over program headers as long as fn(section) returns false. + * Returns a pointer to the current ("found") section when fn returned + * true, or nullptr if fn returned false for all sections. + */ + template + const ElfPhdr* iterateProgramHeaders(Fn fn) const + noexcept(is_nothrow_invocable_v); + + /** + * Iterate over all sections for as long as fn(section) returns false. + * Returns a pointer to the current ("found") section when fn returned + * true, or nullptr if fn returned false for all sections. + */ + template + const ElfShdr* iterateSections(Fn fn) const + noexcept(is_nothrow_invocable_v); + + /** + * Iterate over all sections with a given type. Similar to + * iterateSections(), but filtered only for sections with the given type. + */ + template + const ElfShdr* iterateSectionsWithType(uint32_t type, Fn fn) const + noexcept(is_nothrow_invocable_v); + + /** + * Iterate over all sections with a given types. Similar to + * iterateSectionWithTypes(), but filtered on multiple types. + */ + template + const ElfShdr* iterateSectionsWithTypes( + std::initializer_list types, Fn fn) const + noexcept(is_nothrow_invocable_v); + + /** + * Iterate over all symbols within a given section. + * + * Returns a pointer to the current ("found") symbol when fn returned true, + * or nullptr if fn returned false for all symbols. + */ + template + const ElfSym* iterateSymbols(const ElfShdr& section, Fn fn) const + noexcept(is_nothrow_invocable_v); + template + const ElfSym* iterateSymbolsWithType( + const ElfShdr& section, uint32_t type, Fn fn) const + noexcept(is_nothrow_invocable_v); + template + const ElfSym* iterateSymbolsWithTypes( + const ElfShdr& section, + std::initializer_list types, + Fn fn) const noexcept(is_nothrow_invocable_v); + + /** + * Iterate over entries within a given section. + * + * Returns a pointer to the current ("found") entry when fn returned + * true, or nullptr if fn returned false for all entries. + */ + template + const E* iterateSectionEntries(const ElfShdr& section, Fn&& fn) const + + noexcept(is_nothrow_invocable_v); + + /** + * Find symbol definition by address. + * Note that this is the file virtual address, so you need to undo + * any relocation that might have happened. + * + * Returns {nullptr, nullptr} if not found. + */ + typedef std::pair Symbol; + Symbol getDefinitionByAddress(uintptr_t address) const noexcept; + + /** + * Find symbol definition by name. Optionally specify the symbol types to + * consider. + * + * If a symbol with this name cannot be found, a Symbol + * will be returned. This is O(N) in the number of symbols in the file. + * + * Returns {nullptr, nullptr} if not found. + */ + Symbol getSymbolByName( + const char* name, + std::initializer_list types = { + STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept; + + /** + * Find multiple symbol definitions by name. Because searching for a symbol is + * O(N) this method enables searching for multiple symbols in a single pass. + * + * Returns a map containing a key for each unique symbol name in the provided + * names container. The corresponding value is either Symbol or if the symbol was not found. + */ + template + std::unordered_map getSymbolsByName( + const C& names, + std::initializer_list types = { + STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept { + std::unordered_map result(names.size()); + if (names.empty()) { + return result; + } + + for (const std::string& name : names) { + result[name] = {nullptr, nullptr}; + } + size_t seenCount = 0; + + auto findSymbol = [&](const folly::symbolizer::ElfShdr& section, + const folly::symbolizer::ElfSym& sym) -> bool { + auto symbol = folly::symbolizer::ElfFile::Symbol(§ion, &sym); + auto name = getSymbolName(symbol); + if (name == nullptr) { + return false; + } + auto itr = result.find(name); + if (itr != result.end() && itr->second.first == nullptr && + itr->second.second == nullptr) { + itr->second = symbol; + ++seenCount; + } + return seenCount == result.size(); + }; + + auto iterSection = [&](const folly::symbolizer::ElfShdr& section) -> bool { + iterateSymbolsWithTypes(section, types, [&](const auto& sym) -> bool { + return findSymbol(section, sym); + }); + return false; + }; + // Try the .dynsym section first if it exists, it's smaller. + iterateSectionsWithType(SHT_DYNSYM, iterSection) || + iterateSectionsWithType(SHT_SYMTAB, iterSection); + + return result; + } + + /** + * Get the value of a symbol. + */ + template + const T* getSymbolValue(const ElfSym* symbol) const noexcept { + const ElfShdr* section = getSectionByIndex(symbol->st_shndx); + if (section == nullptr) { + return nullptr; + } + + return valueAt(*section, symbol->st_value); + } + + /** + * Get the value of the object stored at the given address. + * + * This is the function that you want to use in conjunction with + * getSymbolValue() to follow pointers. For example, to get the value of + * a char* symbol, you'd do something like this: + * + * auto sym = getSymbolByName("someGlobalValue"); + * auto addrPtr = getSymbolValue(sym.second); + * const char* str = getAddressValue(*addrPtr); + */ + template + const T* getAddressValue(const ElfAddr addr) const noexcept { + const ElfShdr* section = getSectionContainingAddress(addr); + if (section == nullptr) { + return nullptr; + } + + return valueAt(*section, addr); + } + + /** + * Retrieve symbol name. + */ + const char* getSymbolName(const Symbol& symbol) const noexcept; + + /** Find the section containing the given address */ + const ElfShdr* getSectionContainingAddress(ElfAddr addr) const noexcept; + + const char* filepath() const { return filepath_; } + + const ElfFileId& getFileId() const { return fileId_; } + + /** + * Announce an intention to access file data in a specific pattern in the + * future. https://man7.org/linux/man-pages/man2/posix_fadvise.2.html + */ + std::pair posixFadvise( + off_t offset, off_t len, int const advice) const noexcept; + std::pair posixFadvise( + int const advice) const noexcept; + + private: + OpenResult init() noexcept; + void reset() noexcept; + ElfFile(const ElfFile&) = delete; + ElfFile& operator=(const ElfFile&) = delete; + + void validateStringTable(const ElfShdr& stringTable) const noexcept; + + template + const T& at(ElfOff offset) const noexcept { + static_assert( + std::is_standard_layout::value && std::is_trivial::value, + "non-pod"); + FOLLY_SAFE_CHECK( + offset + sizeof(T) <= length_, + "Offset (", + static_cast(offset), + " + ", + sizeof(T), + ") is not contained within our mapped file (", + filepath_, + ") of length ", + length_); + return *reinterpret_cast(file_ + offset); + } + + template + const T* valueAt(const ElfShdr& section, const ElfAddr addr) const noexcept { + // For exectuables and shared objects, st_value holds a virtual address + // that refers to the memory owned by sections. Since we didn't map the + // sections into the addresses that they're expecting (sh_addr), but + // instead just mmapped the entire file directly, we need to translate + // between addresses and offsets into the file. + // + // TODO: For other file types, st_value holds a file offset directly. Since + // I don't have a use-case for that right now, just assert that + // nobody wants this. We can always add it later. + if (!(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN || + elfHeader().e_type == ET_CORE)) { + return nullptr; + } + if (!(addr >= section.sh_addr && + (addr + sizeof(T)) <= (section.sh_addr + section.sh_size))) { + return nullptr; + } + + // SHT_NOBITS: a section that occupies no space in the file but otherwise + // resembles SHT_PROGBITS. Although this section contains no bytes, the + // sh_offset member contains the conceptual file offset. Typically used + // for zero-initialized data sections like .bss. + if (section.sh_type == SHT_NOBITS) { + static T t = {}; + return &t; + } + + ElfOff offset = section.sh_offset + (addr - section.sh_addr); + + return (offset + sizeof(T) <= length_) ? &at(offset) : nullptr; + } + + static constexpr size_t kFilepathMaxLen = 512; + char filepath_[kFilepathMaxLen] = {}; + int fd_; + char* file_; // mmap() location + size_t length_; // mmap() length + ElfFileId fileId_; + + uintptr_t baseAddress_; +}; + +} // namespace symbolizer +} // namespace folly + +namespace std { +template <> +struct hash { + size_t operator()(const folly::symbolizer::ElfFileId fileId) const { + return folly::hash::hash_combine( + fileId.dev, fileId.inode, fileId.size, fileId.mtime); + } +}; +} // namespace std + +#include + +#endif // FOLLY_HAVE_ELF diff --git a/folly/experimental/symbolizer/ElfCache.cpp b/folly/debugging/symbolizer/ElfCache.cpp similarity index 98% rename from folly/experimental/symbolizer/ElfCache.cpp rename to folly/debugging/symbolizer/ElfCache.cpp index 06fd9cc1892..def4a6ef21f 100644 --- a/folly/experimental/symbolizer/ElfCache.cpp +++ b/folly/debugging/symbolizer/ElfCache.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include diff --git a/folly/debugging/symbolizer/ElfCache.h b/folly/debugging/symbolizer/ElfCache.h new file mode 100644 index 00000000000..bc0f053a3d7 --- /dev/null +++ b/folly/debugging/symbolizer/ElfCache.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace folly { +namespace symbolizer { + +#if FOLLY_HAVE_ELF + +class ElfCacheBase { + public: + virtual std::shared_ptr getFile(StringPiece path) = 0; + virtual ~ElfCacheBase() {} +}; + +/** + * Cache ELF files. Async-signal-safe: does memory allocation via mmap. + * + * Not MT-safe. May not be used concurrently from multiple threads. + */ +class SignalSafeElfCache : public ElfCacheBase { + public: + std::shared_ptr getFile(StringPiece path) override; + + // Path + // + // A minimal implementation of the subset of std::string used below, as if: + // + // using Path = std::basic_string< + // char, std::char_traits, reentrant_allocator>; + // + // Since some library implementations of std::basic_string, as on CentOS 7, + // do not build when instantiated with a non-default-constructible allocator, + // and since other library replacements, such as folly::basic_fbstring, just + // ignore the allocator parameter. + class Path { + public: + Path( + char const* data, + std::size_t size, + reentrant_allocator const& alloc) noexcept; + Path() = delete; + Path(Path const&) = delete; + void operator=(Path const&) = delete; + + /* implicit */ operator StringPiece() const noexcept { return data_; } + + char const* c_str() const noexcept { return data_.data(); } + + friend bool operator<(Path const& a, Path const& b) noexcept { + return a.data_ < b.data_; + } + + private: + std::vector> data_; + }; + + struct Entry : boost::intrusive::avl_set_base_hook<> { + Path path; + std::shared_ptr file; + bool init = false; + + explicit Entry(StringPiece p, reentrant_allocator alloc) noexcept + : path{p.data(), p.size(), alloc}, + file{std::allocate_shared(alloc)} {} + Entry(Entry const&) = delete; + Entry& operator=(Entry const& that) = delete; + + friend bool operator<(Entry const& a, Entry const& b) noexcept { + return a.path < b.path; + } + }; + + struct State { + reentrant_allocator alloc{ + reentrant_allocator_options().block_size_lg(16).large_size_lg(12)}; + std::forward_list> list{alloc}; + // note: map entry dtors check that they have already been unlinked + boost::intrusive::avl_set map; // must follow list + }; + Optional state_; +}; + +/** + * General-purpose ELF file cache. + * + * LRU of given capacity. MT-safe (uses locking). Not async-signal-safe. + */ +class ElfCache : public ElfCacheBase { + public: + std::shared_ptr getFile(StringPiece path) override; + + private: + std::mutex mutex_; + + struct Entry { + std::string path; + ElfFile file; + }; + + static std::shared_ptr filePtr(const std::shared_ptr& e); + + std::unordered_map, Hash> files_; +}; + +#endif // FOLLY_HAVE_ELF + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/LineReader.cpp b/folly/debugging/symbolizer/LineReader.cpp similarity index 97% rename from folly/experimental/symbolizer/LineReader.cpp rename to folly/debugging/symbolizer/LineReader.cpp index a2bbb7aa7d5..306ffe8e398 100644 --- a/folly/experimental/symbolizer/LineReader.cpp +++ b/folly/debugging/symbolizer/LineReader.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include diff --git a/folly/debugging/symbolizer/LineReader.h b/folly/debugging/symbolizer/LineReader.h new file mode 100644 index 00000000000..a504730c4be --- /dev/null +++ b/folly/debugging/symbolizer/LineReader.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include + +#include + +namespace folly { +namespace symbolizer { + +/** + * Async-signal-safe line reader. + */ +class LineReader { + public: + /** + * Create a line reader that reads into a user-provided buffer (of size + * bufSize). + */ + LineReader(int fd, char* buf, size_t bufSize); + + LineReader(const LineReader&) = delete; + LineReader& operator=(const LineReader&) = delete; + + enum State { + kReading, + kEof, + kError, + }; + /** + * Read the next line from the file. + * + * If the line is at most bufSize characters long, including the trailing + * newline, it will be returned (including the trailing newline). + * + * If the line is longer than bufSize, we return the first bufSize bytes + * (which won't include a trailing newline) and then continue from that + * point onwards. + * + * The lines returned are not null-terminated. + * + * Returns kReading with a valid line, kEof if at end of file, or kError + * if a read error was encountered. + * + * Example: + * bufSize = 10 + * input has "hello world\n" + * The first call returns "hello worl" + * The second call returns "d\n" + */ + State readLine(StringPiece& line); + + private: + int const fd_; + char* const buf_; + char* const bufEnd_; + + // buf_ <= bol_ <= eol_ <= end_ <= bufEnd_ + // + // [buf_, end_): current buffer contents (read from file) + // + // [buf_, bol_): free (already processed, can be discarded) + // [bol_, eol_): current line, including \n if it exists, eol_ points + // 1 character past the \n + // [eol_, end_): read, unprocessed + // [end_, bufEnd_): free + + char* bol_; + char* eol_; + char* end_; + State state_; +}; +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/SignalHandler.cpp b/folly/debugging/symbolizer/SignalHandler.cpp similarity index 99% rename from folly/experimental/symbolizer/SignalHandler.cpp rename to folly/debugging/symbolizer/SignalHandler.cpp index 28d1c634bad..29c26d7386b 100644 --- a/folly/experimental/symbolizer/SignalHandler.cpp +++ b/folly/debugging/symbolizer/SignalHandler.cpp @@ -16,7 +16,7 @@ // This is heavily inspired by the signal handler from google-glog -#include +#include #include #include diff --git a/folly/debugging/symbolizer/SignalHandler.h b/folly/debugging/symbolizer/SignalHandler.h new file mode 100644 index 00000000000..1806ab34fec --- /dev/null +++ b/folly/debugging/symbolizer/SignalHandler.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include + +namespace folly { +namespace symbolizer { + +extern const unsigned long kAllFatalSignals; + +/** + * Install handler for fatal signals. The list of signals being handled is in + * SignalHandler.cpp. + * + * The handler will dump signal and time information followed by a stack trace + * to stderr, and then call the callbacks registered below. + * + * The signals parameter can be used to specify only specific fatal signals for + * which the handler should be installed. Only signals from kAllFatalSignals + * are honored in this list, other signals are ignored. + */ +void installFatalSignalHandler( + std::bitset<64> signals = std::bitset<64>(kAllFatalSignals)); + +/** + * Add a callback to be run when receiving a fatal signal. They will also + * be called by LOG(FATAL) and abort() (as those raise SIGABRT internally). + * + * These callbacks must be async-signal-safe, so don't even think of using + * LOG(...) or printf or malloc / new or doing anything even remotely fun. + * + * All these fatal callback must be added before calling + * installFatalSignalCallbacks(), below. + */ +typedef void (*SignalCallback)(); +void addFatalSignalCallback(SignalCallback cb); + +/** + * Install the fatal signal callbacks; fatal signals will call these + * callbacks in the order in which they were added. + */ +void installFatalSignalCallbacks(); + +/** + * True if a fatal signal was received (i.e. the process is crashing). + */ +bool fatalSignalReceived(); + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/StackTrace.cpp b/folly/debugging/symbolizer/StackTrace.cpp similarity index 99% rename from folly/experimental/symbolizer/StackTrace.cpp rename to folly/debugging/symbolizer/StackTrace.cpp index 9d6456051b7..625499f9f79 100644 --- a/folly/experimental/symbolizer/StackTrace.cpp +++ b/folly/debugging/symbolizer/StackTrace.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include diff --git a/folly/debugging/symbolizer/StackTrace.h b/folly/debugging/symbolizer/StackTrace.h new file mode 100644 index 00000000000..212979014e8 --- /dev/null +++ b/folly/debugging/symbolizer/StackTrace.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace folly { +namespace symbolizer { + +/** + * Get the current stack trace into addresses, which has room for at least + * maxAddresses frames. + * + * Returns the number of frames written in the array. + * Returns -1 on failure. + * + * NOT async-signal-safe, but fast. + */ +ssize_t getStackTrace(uintptr_t* addresses, size_t maxAddresses); + +/** + * Get the current stack trace into addresses, which has room for at least + * maxAddresses frames. + * + * Returns the number of frames written in the array. + * Returns -1 on failure. + * + * Async-signal-safe, but likely slower. + */ +ssize_t getStackTraceSafe(uintptr_t* addresses, size_t maxAddresses); + +/** + * Get the current stack trace into addresses, which has room for at least + * maxAddresses frames. + * + * Returns the number of frames written in the array. + * Returns -1 on failure. + * + * Heap allocates its context. Likely slower than getStackTrace but + * avoids large stack allocations. + */ +ssize_t getStackTraceHeap(uintptr_t* addresses, size_t maxAddresses); + +/** + * Get the current async stack trace into addresses, which has room for at least + * maxAddresses frames. If no async operation is progress, then this will + * write 0 frames. + * + * This will include both async and non-async frames. For example, the stack + * trace could look something like this: + * + * funcD <-- non-async, current top of stack + * funcC <-- non-async + * co_funcB <-- async + * co_funcA <-- async + * main <-- non-async, root of async stack + * + * Returns the number of frames written in the array. + * Returns -1 on failure. + * + * Async-signal-safe, but likely slower. + */ +ssize_t getAsyncStackTraceSafe(uintptr_t* addresses, size_t maxAddresses); + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/SymbolizePrinter.cpp b/folly/debugging/symbolizer/SymbolizePrinter.cpp similarity index 99% rename from folly/experimental/symbolizer/SymbolizePrinter.cpp rename to folly/debugging/symbolizer/SymbolizePrinter.cpp index 692bffc3743..95b34678c5e 100644 --- a/folly/experimental/symbolizer/SymbolizePrinter.cpp +++ b/folly/debugging/symbolizer/SymbolizePrinter.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include diff --git a/folly/debugging/symbolizer/SymbolizePrinter.h b/folly/debugging/symbolizer/SymbolizePrinter.h new file mode 100644 index 00000000000..f88b05b3e49 --- /dev/null +++ b/folly/debugging/symbolizer/SymbolizePrinter.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace folly { +class IOBuf; + +namespace symbolizer { + +/** + * Format one address in the way it's usually printed by SymbolizePrinter. + * Async-signal-safe. + */ +class AddressFormatter { + public: + AddressFormatter(); + + /** + * Format the address. Returns an internal buffer. + */ + StringPiece format(uintptr_t address); + + private: + static constexpr char bufTemplate[] = " @ 0000000000000000"; + char buf_[sizeof(bufTemplate)]; +}; + +/** + * Print a list of symbolized addresses. Base class. + */ +class SymbolizePrinter { + public: + /** + * Print one frame, no ending newline. + */ + void print(const SymbolizedFrame& frame); + + /** + * Print one frame with ending newline. + */ + void println(const SymbolizedFrame& frame); + + /** + * Print multiple frames on separate lines. + */ + void println(const SymbolizedFrame* frames, size_t frameCount); + + /** + * Print a string, no endling newline. + */ + void print(StringPiece sp) { doPrint(sp); } + + /** + * Print multiple frames on separate lines, skipping the first + * skip addresses. + */ + template + void println(const FrameArray& fa, size_t skip = 0) { + if (skip < fa.frameCount) { + println(fa.frames + skip, fa.frameCount - skip); + } + } + + /** + * If output buffered inside this class, send it to the output stream, so that + * any output done in other ways appears after this. + */ + virtual void flush() {} + + virtual ~SymbolizePrinter() {} + + enum Options { + // Skip file and line information + NO_FILE_AND_LINE = 1 << 0, + + // As terse as it gets: function name if found, address otherwise + TERSE = 1 << 1, + + // Always colorize output (ANSI escape code) + COLOR = 1 << 2, + + // Colorize output only if output is printed to a TTY (ANSI escape code) + COLOR_IF_TTY = 1 << 3, + + // Skip frame address information + NO_FRAME_ADDRESS = 1 << 4, + + // Simple file and line output + TERSE_FILE_AND_LINE = 1 << 5, + }; + + // NOTE: enum values used as indexes in kColorMap. + enum Color { Default, Red, Green, Yellow, Blue, Cyan, White, Purple, Num }; + void color(Color c); + + protected: + explicit SymbolizePrinter(int options, bool isTty = false) + : options_(options), isTty_(isTty) {} + + const int options_; + const bool isTty_; + + private: + void printTerse(const SymbolizedFrame& frame); + virtual void doPrint(StringPiece sp) = 0; + + static constexpr std::array kColorMap = {{ + "\x1B[0m", + "\x1B[31m", + "\x1B[32m", + "\x1B[33m", + "\x1B[34m", + "\x1B[36m", + "\x1B[37m", + "\x1B[35m", + }}; +}; + +/** + * Print a list of symbolized addresses to a stream. + * Not reentrant. Do not use from signal handling code. + */ +class OStreamSymbolizePrinter : public SymbolizePrinter { + public: + explicit OStreamSymbolizePrinter(std::ostream& out, int options = 0); + + private: + void doPrint(StringPiece sp) override; + std::ostream& out_; +}; + +/** + * Print a list of symbolized addresses to a file descriptor. + * Ignores errors. Async-signal-safe. + */ +class FDSymbolizePrinter : public SymbolizePrinter { + public: + explicit FDSymbolizePrinter(int fd, int options = 0, size_t bufferSize = 0); + ~FDSymbolizePrinter() override; + virtual void flush() override; + + private: + void doPrint(StringPiece sp) override; + + const int fd_; + std::unique_ptr buffer_; +}; + +/** + * Print a list of symbolized addresses to a FILE*. + * Ignores errors. Not reentrant. Do not use from signal handling code. + */ +class FILESymbolizePrinter : public SymbolizePrinter { + public: + explicit FILESymbolizePrinter(FILE* file, int options = 0); + + private: + void doPrint(StringPiece sp) override; + FILE* const file_ = nullptr; +}; + +/** + * Print a list of symbolized addresses to a std::string. + * Not reentrant. Do not use from signal handling code. + */ +class StringSymbolizePrinter : public SymbolizePrinter { + public: + explicit StringSymbolizePrinter(int options = 0) + : SymbolizePrinter(options) {} + + std::string str() const { return buf_.toStdString(); } + const fbstring& fbstr() const { return buf_; } + fbstring moveFbString() { return std::move(buf_); } + + private: + void doPrint(StringPiece sp) override; + fbstring buf_; +}; + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/SymbolizedFrame.cpp b/folly/debugging/symbolizer/SymbolizedFrame.cpp similarity index 98% rename from folly/experimental/symbolizer/SymbolizedFrame.cpp rename to folly/debugging/symbolizer/SymbolizedFrame.cpp index ca7fb3f37eb..a5a42d68ff7 100644 --- a/folly/experimental/symbolizer/SymbolizedFrame.cpp +++ b/folly/debugging/symbolizer/SymbolizedFrame.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include namespace folly { namespace symbolizer { diff --git a/folly/debugging/symbolizer/SymbolizedFrame.h b/folly/debugging/symbolizer/SymbolizedFrame.h new file mode 100644 index 00000000000..2a1d0d2c64b --- /dev/null +++ b/folly/debugging/symbolizer/SymbolizedFrame.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace folly { +namespace symbolizer { + +class ElfFile; + +/** + * Represent a file path as a collection of three parts (base directory, + * subdirectory, and file). + */ +class Path { + public: + Path() = default; + + Path( + folly::StringPiece baseDir, + folly::StringPiece subDir, + folly::StringPiece file); + + folly::StringPiece baseDir() const { return baseDir_; } + folly::StringPiece subDir() const { return subDir_; } + folly::StringPiece 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; + } + + private: + folly::StringPiece baseDir_; + folly::StringPiece subDir_; + folly::StringPiece file_; +}; + +inline std::ostream& operator<<(std::ostream& out, const Path& path) { + return out << path.toString(); +} + +enum class LocationInfoMode { + // Don't resolve location info. + DISABLED, + // Perform CU lookup using .debug_aranges (might be incomplete). + FAST, + // Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure. + FULL, + // Scan .debug_info (super slower, use with caution) for inline functions in + // addition to FULL. + FULL_WITH_INLINE, +}; + +/** + * Contains location info like file name, line number, etc. + */ +struct LocationInfo { + bool hasFileAndLine = false; + bool hasMainFile = false; + Path mainFile; + Path file; + uint64_t line = 0; +}; + +/** + * Frame information: symbol name and location. + */ +struct SymbolizedFrame { + bool found = false; + uintptr_t addr = 0; + // Mangled symbol name. Use `folly::demangle()` to demangle it. + const char* name = nullptr; + LocationInfo location; + std::shared_ptr file; + + void clear() { *this = SymbolizedFrame(); } +}; + +template +struct FrameArray { + FrameArray() = default; + + size_t frameCount = 0; + uintptr_t addresses[N]; + SymbolizedFrame frames[N]; +}; + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/Symbolizer.cpp b/folly/debugging/symbolizer/Symbolizer.cpp similarity index 99% rename from folly/experimental/symbolizer/Symbolizer.cpp rename to folly/debugging/symbolizer/Symbolizer.cpp index 145441618d9..19345bcd2c5 100644 --- a/folly/experimental/symbolizer/Symbolizer.cpp +++ b/folly/debugging/symbolizer/Symbolizer.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include diff --git a/folly/debugging/symbolizer/Symbolizer.h b/folly/debugging/symbolizer/Symbolizer.h new file mode 100644 index 00000000000..3db4d5da8d1 --- /dev/null +++ b/folly/debugging/symbolizer/Symbolizer.h @@ -0,0 +1,310 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace folly { +namespace symbolizer { + +/** + * Get stack trace into a given FrameArray, return true on success (and + * set frameCount to the actual frame count, which may be > N) and false + * on failure. + */ +namespace detail { +template +bool fixFrameArray(FrameArray& fa, ssize_t n) { + if (n != -1) { + fa.frameCount = n; + for (size_t i = 0; i < fa.frameCount; ++i) { + fa.frames[i].found = false; + } + return true; + } else { + fa.frameCount = 0; + return false; + } +} +} // namespace detail + +// Always inline these functions; they don't do much, and unittests rely +// on them never showing up in a stack trace. +template +FOLLY_ALWAYS_INLINE bool getStackTrace(FrameArray& fa); + +template +inline bool getStackTrace(FrameArray& fa) { + return detail::fixFrameArray(fa, getStackTrace(fa.addresses, N)); +} +template +FOLLY_ALWAYS_INLINE bool getStackTraceSafe(FrameArray& fa); + +template +inline bool getStackTraceSafe(FrameArray& fa) { + return detail::fixFrameArray(fa, getStackTraceSafe(fa.addresses, N)); +} + +template +FOLLY_ALWAYS_INLINE bool getStackTraceHeap(FrameArray& fa); + +template +inline bool getStackTraceHeap(FrameArray& fa) { + return detail::fixFrameArray(fa, getStackTraceHeap(fa.addresses, N)); +} + +template +FOLLY_ALWAYS_INLINE bool getAsyncStackTraceSafe(FrameArray& fa); + +template +inline bool getAsyncStackTraceSafe(FrameArray& fa) { + return detail::fixFrameArray(fa, getAsyncStackTraceSafe(fa.addresses, N)); +} + +#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF + +class Symbolizer { + public: + static constexpr auto kDefaultLocationInfoMode = LocationInfoMode::FAST; + + static bool isAvailable(); + + explicit Symbolizer(LocationInfoMode mode = kDefaultLocationInfoMode) + : Symbolizer(nullptr, mode) {} + + explicit Symbolizer( + ElfCacheBase* cache, + LocationInfoMode mode = kDefaultLocationInfoMode, + size_t symbolCacheSize = 0, + std::string exePath = "/proc/self/exe"); + + ~Symbolizer(); + + /** + * Symbolize given addresses and return the number of @frames filled: + * + * - all entries in @addrs will be symbolized (if possible, e.g. if they're + * valid code addresses and if frames.size() >= addrs.size()) + * + * - if `mode_ == FULL_WITH_INLINE` and `frames.size() > addrs.size()` then at + * most `frames.size() - addrs.size()` additional inlined functions will + * also be symbolized (at most `kMaxInlineLocationInfoPerFrame` per @addr + * entry). + */ + size_t symbolize( + folly::Range addrs, + folly::Range frames); + + size_t symbolize( + const uintptr_t* addresses, SymbolizedFrame* frames, size_t frameCount) { + return symbolize( + folly::Range(addresses, frameCount), + folly::Range(frames, frameCount)); + } + + template + size_t symbolize(FrameArray& fa) { + return symbolize( + folly::Range(fa.addresses, fa.frameCount), + folly::Range(fa.frames, N)); + } + + /** + * Shortcut to symbolize one address. + */ + bool symbolize(uintptr_t address, SymbolizedFrame& frame) { + symbolize( + folly::Range(&address, 1), + folly::Range(&frame, 1)); + return frame.found; + } + + private: + ElfCacheBase* const cache_; + const LocationInfoMode mode_; + const std::string exePath_; + + // Details in cpp file to minimize header dependencies + struct SymbolCache; + std::unique_ptr symbolCache_; +}; + +/** + * Use this class to print a stack trace from normal code. It will malloc and + * won't flush or sync. + * + * These methods are thread safe, through locking. However, they are not signal + * safe. + */ +class FastStackTracePrinter { + public: + static constexpr size_t kDefaultSymbolCacheSize = 10000; + + explicit FastStackTracePrinter( + std::unique_ptr printer, + size_t symbolCacheSize = kDefaultSymbolCacheSize); + + ~FastStackTracePrinter(); + + /** + * This is NOINLINE to make sure it shows up in the stack we grab, which makes + * it easy to skip printing it. + */ + FOLLY_NOINLINE void printStackTrace(bool symbolize); + + void flush(); + + private: + static constexpr size_t kMaxStackTraceDepth = 100; + + const std::unique_ptr printer_; + Symbolizer symbolizer_; +}; + +#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF + +/** + * Use this class to print a stack trace from a signal handler, or other place + * where you shouldn't allocate memory on the heap, and fsync()ing your file + * descriptor is more important than performance. + * + * Make sure to create one of these on startup, not in the signal handler, as + * the constructor allocates on the heap, whereas the other methods don't. Best + * practice is to just leak this object, rather than worry about destruction + * order. + * + * These methods aren't thread safe, so if you could have signals on multiple + * threads at the same time, you need to do your own locking to ensure you don't + * call these methods from multiple threads. They are signal safe, however. + */ +class SafeStackTracePrinter { + public: + explicit SafeStackTracePrinter(int fd = STDERR_FILENO); + + virtual ~SafeStackTracePrinter() {} + + /** + * Only allocates on the stack and is signal-safe but not thread-safe. Don't + * call printStackTrace() on the same StackTracePrinter object from multiple + * threads at the same time. + * + * This is NOINLINE to make sure it shows up in the stack we grab, which makes + * it easy to skip printing it. + */ + FOLLY_NOINLINE void printStackTrace(bool symbolize); + + void print(StringPiece sp) { printer_.print(sp); } + + // Flush printer_, also fsync, in case we're about to crash again... + void flush(); + + protected: + virtual void printSymbolizedStackTrace(); + void printUnsymbolizedStackTrace(); + + private: + static constexpr size_t kMaxStackTraceDepth = 100; + + int fd_; + FDSymbolizePrinter printer_; + std::unique_ptr> addresses_; +}; + +#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF + +/** + * Gets the stack trace for the current thread and returns a string + * representation. Convenience function meant for debugging and logging. + * Empty string indicates stack trace functionality is not available. + * + * NOT async-signal-safe. + */ +std::string getStackTraceStr(); + +/** + * Gets the async stack trace for the current thread and returns a string + * representation. Convenience function meant for debugging and logging. + * Empty string indicates stack trace functionality is not available. + * + * NOT async-signal-safe. + */ +std::string getAsyncStackTraceStr(); + +/** + * Get the async stack traces (string representation) for suspended coroutines. + * Convenience function meant for debugging and logging, works only in some + * DEBUG builds + * + * Note: The returned traces will only have async frames (no normal frames). + */ +std::vector getSuspendedStackTraces(); + +#else +// Define these in the header, as headers are always available, but not all +// platforms can link against the symbolizer library cpp sources. + +inline std::string getStackTraceStr() { + return ""; +} + +inline std::string getAsyncStackTraceStr() { + return ""; +} + +inline std::vector getSuspendedStackTraces() { + return {}; +} +#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF + +#if FOLLY_HAVE_SWAPCONTEXT + +/** + * Use this class in rare situations where signal handlers are running in a + * tiny stack specified by sigaltstack. + * + * This is neither thread-safe nor signal-safe. However, it can usually print + * something useful while SafeStackTracePrinter would stack overflow. + * + * Signal handlers would need to block other signals to make this safer. + * Note it's still unsafe even with that. + */ +class UnsafeSelfAllocateStackTracePrinter : public SafeStackTracePrinter { + protected: + void printSymbolizedStackTrace() override; + const long pageSizeUnchecked_ = sysconf(_SC_PAGESIZE); +}; + +#endif // FOLLY_HAVE_SWAPCONTEXT + +} // namespace symbolizer +} // namespace folly diff --git a/folly/experimental/symbolizer/BUCK b/folly/experimental/symbolizer/BUCK index c275858b9bf..2685b1dd990 100644 --- a/folly/experimental/symbolizer/BUCK +++ b/folly/experimental/symbolizer/BUCK @@ -3,15 +3,7 @@ load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library") oncall("fbcode_entropy_wardens_folly") cpp_library( - # @autodeps-skip name = "dwarf", - srcs = [ - "Dwarf.cpp", - "DwarfImpl.cpp", - "DwarfLineNumberVM.cpp", - "DwarfSection.cpp", - "DwarfUtil.cpp", - ], headers = [ "Dwarf.h", "DwarfImpl.h", @@ -19,191 +11,88 @@ cpp_library( "DwarfSection.h", "DwarfUtil.h", ], - deps = [ - "//folly:optional", - "//folly/lang:safe_assert", - "//folly/portability:config", - "//folly/portability:unistd", - ], exported_deps = [ - "fbsource//third-party/libdwarf:dwarf", - ":elf", - ":elf_cache", - ":symbolized_frame", - "//folly:function", - "//folly:range", - ], - external_deps = [ - "glog", + "//folly/debugging/symbolizer:dwarf", ], ) cpp_library( name = "elf", - srcs = [ - "Elf.cpp", - ], headers = [ "Elf.h", "Elf-inl.h", ], - deps = [ - "//folly:exception", - "//folly:scope_guard", - "//folly/lang:c_string", - "//folly/portability:sys_mman", - ], exported_deps = [ - "//folly:conv", - "//folly:likely", - "//folly:range", - "//folly/lang:safe_assert", - "//folly/portability:config", - ], - external_deps = [ - "glog", + "//folly/debugging/symbolizer:elf", ], ) cpp_library( name = "symbolized_frame", - srcs = ["SymbolizedFrame.cpp"], - headers = ["SymbolizedFrame.h"], + headers = [ + "SymbolizedFrame.h", + ], exported_deps = [ - "//folly:range", + "//folly/debugging/symbolizer:symbolized_frame", ], ) cpp_library( name = "line_reader", - srcs = ["LineReader.cpp"], - headers = ["LineReader.h"], - deps = [ - "//folly:file_util", + headers = [ + "LineReader.h", ], exported_deps = [ - "//folly:range", + "//folly/debugging/symbolizer:line_reader", ], ) cpp_library( name = "stack_trace", - srcs = ["StackTrace.cpp"], - headers = ["StackTrace.h"], - deps = [ - "//folly:cpp_attributes", - "//folly:portability", - "//folly/portability:config", - "//folly/portability:libunwind", - "//folly/tracing:async_stack", + headers = [ + "StackTrace.h", ], exported_deps = [ - "//folly/portability:sys_types", + "//folly/debugging/symbolizer:stack_trace", ], ) cpp_library( name = "elf_cache", - srcs = [ - "ElfCache.cpp", - ], headers = [ "ElfCache.h", ], - deps = [ - "//folly:scope_guard", - "//folly/portability:sys_mman", - ], exported_deps = [ - ":elf", - "//folly:optional", - "//folly:range", - "//folly/hash:hash", - "//folly/memory:reentrant_allocator", - "//folly/portability:config", - ], - exported_external_deps = [ - "boost", + "//folly/debugging/symbolizer:elf_cache", ], ) cpp_library( name = "symbolize_printer", - srcs = [ - "SymbolizePrinter.cpp", - ], headers = [ "SymbolizePrinter.h", ], - deps = [ - "//folly:demangle", - "//folly:file_util", - "//folly:scope_guard", - "//folly/io:iobuf", - "//folly/lang:to_ascii", - ], exported_deps = [ - ":symbolized_frame", - "//folly:fbstring", - "//folly:range", + "//folly/debugging/symbolizer:symbolize_printer", ], ) cpp_library( name = "symbolizer", - srcs = [ - "Symbolizer.cpp", - ], headers = [ "Symbolizer.h", ], - deps = [ - ":elf", - ":line_reader", - "//folly:file_util", - "//folly:memory", - "//folly:scope_guard", - "//folly:synchronized", - "//folly/container:evicting_cache_map", - "//folly/experimental/symbolizer/detail:debug", - "//folly/lang:safe_assert", - "//folly/lang:to_ascii", - "//folly/memory:sanitize_address", - "//folly/portability:sys_mman", - "//folly/tracing:async_stack", - ], exported_deps = [ - ":dwarf", - ":elf_cache", - ":stack_trace", - ":symbolize_printer", - ":symbolized_frame", - "//folly:fbstring", - "//folly:optional", - "//folly:range", - "//folly:string", - "//folly/io:iobuf", - "//folly/portability:config", - "//folly/portability:unistd", + "//folly/debugging/symbolizer:symbolizer", ], ) cpp_library( name = "signal_handler", - srcs = [ - "SignalHandler.cpp", - ], headers = [ "SignalHandler.h", ], - deps = [ - ":symbolizer", - "//folly:scope_guard", - "//folly/lang:to_ascii", - "//folly/portability:sys_syscall", - "//folly/portability:unistd", - ], - external_deps = [ - "glog", + exported_deps = [ + "//folly/debugging/symbolizer:signal_handler", ], ) diff --git a/folly/experimental/symbolizer/Dwarf.h b/folly/experimental/symbolizer/Dwarf.h index 17d452f44ad..794d051dbc4 100644 --- a/folly/experimental/symbolizer/Dwarf.h +++ b/folly/experimental/symbolizer/Dwarf.h @@ -14,74 +14,4 @@ * limitations under the License. */ -// DWARF record parser - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace folly { -namespace symbolizer { - -#if FOLLY_HAVE_DWARF && FOLLY_HAVE_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 { - /** - * Note that Dwarf uses (and returns) StringPiece a lot. - * The StringPieces point within sections in the ELF file, and so will - * be live for as long as the passed-in ElfFile is live. - */ - public: - /** Create a DWARF parser around an ELF file. */ - Dwarf(ElfCacheBase* elfCache, const ElfFile* elf); - - /** - * Find the file and line number information corresponding to address. - * If `eachParameterName` is provided, the callback will be invoked once - * for each parameter of the function. - */ - bool findAddress( - uintptr_t address, - LocationInfoMode mode, - SymbolizedFrame& frame, - folly::Range inlineFrames = {}, - folly::FunctionRef - eachParameterName = {}) const; - - private: - ElfCacheBase* elfCache_; - DebugSections defaultDebugSections_; -}; - -#endif - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/DwarfImpl.h b/folly/experimental/symbolizer/DwarfImpl.h index 1be0b32a820..4c82a1de0f4 100644 --- a/folly/experimental/symbolizer/DwarfImpl.h +++ b/folly/experimental/symbolizer/DwarfImpl.h @@ -14,118 +14,4 @@ * limitations under the License. */ -// DWARF record parser - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace folly { -namespace symbolizer { - -#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF - -struct CallLocation; - -class DwarfImpl { - public: - explicit DwarfImpl( - ElfCacheBase* elfCache, CompilationUnits& cu, LocationInfoMode mode); - - /** - * Find the @locationInfo for @address in the compilation unit @cu. - * - * Best effort: - * - fills @inlineFrames if mode == FULL_WITH_INLINE, - * - calls @eachParameterName on the function parameters. - * - * if @checkAddress is true, we verify that the address is mapped to - * a range in this CU before running the line number VM - */ - bool findLocation( - uintptr_t address, - SymbolizedFrame& frame, - folly::Range inlineFrames, - folly::FunctionRef eachParameterName, - bool checkAddress = true) const; - - private: - using AttributeValue = std::variant; - - /** - * Finds a subprogram debugging info entry that contains a given address among - * children of given die. Depth first search. - */ - bool findSubProgramDieForAddress( - const CompilationUnit& cu, - const Die& die, - uint64_t address, - folly::Optional baseAddrCU, - Die& subprogram) const; - - /** - * Finds inlined subroutine DIEs and their caller lines that contains a given - * address among children of given die. Depth first search. - */ - void findInlinedSubroutineDieForAddress( - const CompilationUnit& cu, - const Die& die, - const DwarfLineNumberVM& lineVM, - uint64_t address, - folly::Optional baseAddrCU, - folly::Range locations, - size_t& numFound) const; - - CompilationUnit findCompilationUnit( - const CompilationUnit& cu, uint64_t targetOffset) const; - - /** - * Find the actual definition DIE instead of declaration for the given die. - */ - Die findDefinitionDie(const CompilationUnit& cu, const Die& die) const; - - /** - * Iterates over all children of a debugging info entry, calling the given - * callable for each. Iteration is stopped early if any of the calls return - * false. Returns the offset of next DIE after iterations. - */ - size_t forEachChild( - const CompilationUnit& cu, - const Die& die, - folly::FunctionRef f) const; - - /** - * Check if the given address is in the range list at the given offset in - * .debug_ranges. - */ - bool isAddrInRangeList( - const CompilationUnit& cu, - uint64_t address, - folly::Optional baseAddr, - size_t offset, - uint8_t addrSize) const; - - void fillInlineFrames( - uintptr_t address, - SymbolizedFrame& frame, - folly::Range inlineLocations, - folly::Range inlineFrames) const; - - ElfCacheBase* elfCache_; - CompilationUnits& cu_; - const LocationInfoMode mode_; -}; - -#endif - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/DwarfLineNumberVM.h b/folly/experimental/symbolizer/DwarfLineNumberVM.h index 05aef77f5cd..71fd419cdbc 100644 --- a/folly/experimental/symbolizer/DwarfLineNumberVM.h +++ b/folly/experimental/symbolizer/DwarfLineNumberVM.h @@ -14,120 +14,4 @@ * limitations under the License. */ -#pragma once - -#include -#include -#include - -namespace folly { -namespace symbolizer { - -#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF - -class DwarfLineNumberVM { - public: - DwarfLineNumberVM( - folly::StringPiece data, - folly::StringPiece compilationDirectory, - const DebugSections& debugSections); - - bool findAddress(uintptr_t target, Path& file, uint64_t& line); - - /** Gets full file name at given index including directory. */ - Path getFullFileName(uint64_t index) const; - - private: - bool init(); - void reset(); - - /** Execute until we commit one new row to the line number matrix */ - bool next(folly::StringPiece& program); - enum StepResult { - CONTINUE, // Continue feeding opcodes - COMMIT, // Commit new tuple - END, // End of sequence - }; - /** Execute one opcode */ - StepResult step(folly::StringPiece& program); - - struct FileName { - folly::StringPiece relativeName; - // 0 = current compilation directory - // otherwise, 1-based index in the list of include directories - uint64_t directoryIndex; - }; - - /** Read one FileName object, advance sp */ - static bool readFileName(folly::StringPiece& program, FileName& fn); - - /** - * Get file name at given index; may be in the initial table - * (fileNames_) or defined using DW_LNE_define_file (and we reexecute - * enough of the program to find it, if so) - */ - FileName getFileName(uint64_t index) const; - - /** Get include directory at given index */ - folly::StringPiece getIncludeDirectory(uint64_t index) const; - - /** - * Execute opcodes until finding a DW_LNE_define_file and return true; - * return file at the end. - */ - bool nextDefineFile(folly::StringPiece& program, FileName& fn) const; - - // Initialization - bool initializationSuccess_ = false; - bool is64Bit_; - folly::StringPiece data_; - folly::StringPiece compilationDirectory_; - const DebugSections& debugSections_; - - // Header - uint16_t version_; - uint8_t minLength_; - bool defaultIsStmt_; - int8_t lineBase_; - uint8_t lineRange_; - uint8_t opcodeBase_; - const uint8_t* standardOpcodeLengths_; - - // 6.2.4 The Line Number Program Header. - struct { - size_t includeDirectoryCount; - folly::StringPiece includeDirectories; - size_t fileNameCount; - folly::StringPiece fileNames; - } v4_; - - struct { - uint8_t directoryEntryFormatCount; - folly::StringPiece directoryEntryFormat; - uint64_t directoriesCount; - folly::StringPiece directories; - - uint8_t fileNameEntryFormatCount; - folly::StringPiece fileNameEntryFormat; - uint64_t fileNamesCount; - folly::StringPiece fileNames; - } v5_; - - // 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_; -}; - -#endif - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/DwarfSection.h b/folly/experimental/symbolizer/DwarfSection.h index e4eb6edc3e9..a6510f027e9 100644 --- a/folly/experimental/symbolizer/DwarfSection.h +++ b/folly/experimental/symbolizer/DwarfSection.h @@ -14,43 +14,4 @@ * limitations under the License. */ -#pragma once - -#include - -#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF - -namespace folly { -namespace symbolizer { - -/** - * 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 DwarfSection { - public: - DwarfSection() : is64Bit_(false) {} - - explicit DwarfSection(folly::StringPiece d); - - /** - * Return next chunk, if any; the 4- or 12-byte length was already - * parsed and isn't part of the chunk. - */ - bool next(folly::StringPiece& chunk); - - /** Is the current chunk 64 bit? */ - bool is64Bit() const { return is64Bit_; } - - private: - // Yes, 32- and 64- bit sections may coexist. Yikes! - bool is64Bit_; - folly::StringPiece data_; -}; - -} // namespace symbolizer -} // namespace folly - -#endif +#include diff --git a/folly/experimental/symbolizer/DwarfUtil.h b/folly/experimental/symbolizer/DwarfUtil.h index 4c5f02471c4..a1ebc20b133 100644 --- a/folly/experimental/symbolizer/DwarfUtil.h +++ b/folly/experimental/symbolizer/DwarfUtil.h @@ -14,238 +14,4 @@ * limitations under the License. */ -// DWARF record parser - -#pragma once - -#include - -#include -#include -#include -#include - -#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF -#include - -namespace folly { -namespace symbolizer { - -/** - * More than one location info may exist if current frame is an inline - * function call. - */ -const uint32_t kMaxInlineLocationInfoPerFrame = 20; - -// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to -// speed up inline function lookup. -const uint32_t kMaxAbbreviationEntries = 1000; - -// A struct contains an Elf object and different debug sections. -struct DebugSections { - const ElfFile* elf; - folly::StringPiece debugCuIndex; // .debug_cu_index - folly::StringPiece debugAbbrev; // .debug_abbrev - folly::StringPiece debugAddr; // .debug_addr (DWARF 5) - folly::StringPiece debugAranges; // .debug_aranges - folly::StringPiece debugInfo; // .debug_info - folly::StringPiece debugLine; // .debug_line - folly::StringPiece debugLineStr; // .debug_line_str (DWARF 5) - folly::StringPiece debugLoclists; // .debug_loclists (DWARF 5) - folly::StringPiece debugRanges; // .debug_ranges - folly::StringPiece debugRnglists; // .debug_rnglists (DWARF 5) - folly::StringPiece debugStr; // .debug_str - folly::StringPiece debugStrOffsets; // .debug_str_offsets (DWARF 5) -}; - -// Abbreviation for a Debugging Information Entry. -struct DIEAbbreviation { - uint64_t code = 0; - uint64_t tag = 0; - bool hasChildren = false; - folly::StringPiece attributes; -}; - -// A struct that contains the metadata of a compilation unit. -struct CompilationUnit { - DebugSections debugSections; - - bool is64Bit = false; - uint8_t version = 0; - uint8_t unitType = DW_UT_compile; // DW_UT_compile or DW_UT_skeleton - uint8_t addrSize = 0; - // Offset in .debug_info of this compilation unit. - uint32_t offset = 0; - uint32_t size = 0; - // Offset in .debug_info for the first DIE in this compilation unit. - uint32_t firstDie = 0; - folly::Optional abbrevOffset; - - // Compilation directory. - folly::StringPiece compDir = "."; - // The beginning of the CU's contribution to .debug_addr - // DW_AT_addr_base (DWARF 5, DebugFission) - folly::Optional addrBase; - // The beginning of the CU's contribution to .debug_ranges - // DW_AT_ranges_base (DebugFission) - folly::Optional rangesBase; - // The beginning of the offsets table (immediately following the - // header) of the CU's contribution to .debug_loclists - folly::Optional loclistsBase; // DW_AT_loclists_base (DWARF 5) - // The beginning of the offsets table (immediately following the - // header) of the CU's contribution to .debug_rnglists - folly::Optional rnglistsBase; // DW_AT_rnglists_base (DWARF 5) - // Points to the first string offset of the compilation unit’s - // contribution to the .debug_str_offsets (or .debug_str_offsets.dwo) section. - folly::Optional strOffsetsBase; // DW_AT_str_offsets_base (DWARF 5) - - // The actual dwo file and id contains the debug sections for this - // compilation unit. - folly::Optional dwoName; - folly::Optional dwoId; - - // Only the CompilationUnit that contains the caller functions needs this. - // Indexed by (abbr.code - 1) if (abbr.code - 1) < abbrCache.size(); - folly::Range abbrCache; -}; - -// Contains the main compilation unit in the binary file and an optional -// compilation unit in dwo/dwp file if the main one is a skeleton. -struct CompilationUnits { - CompilationUnit mainCompilationUnit; - folly::Optional splitCU; - - CompilationUnit& defaultCompilationUnit() { - if (splitCU.hasValue()) { - return *splitCU; - } - return mainCompilationUnit; - } -}; - -// Debugging information entry to define a low-level representation of a -// source program. Each debugging information entry consists of an identifying -// tag and a series of attributes. An entry, or group of entries together, -// provide a description of a corresponding entity in the source program. -struct Die { - bool is64Bit = false; - // Offset from start to first attribute - uint8_t attrOffset = 0; - // Offset within debug info. - uint32_t offset = 0; - uint64_t code = 0; - DIEAbbreviation abbr; -}; - -struct AttributeSpec { - uint64_t name = 0; - uint64_t form = 0; - int64_t implicitConst = 0; // only set when form=DW_FORM_implicit_const - - explicit operator bool() const { return name != 0 || form != 0; } -}; - -struct Attribute { - AttributeSpec spec; - const Die& die; - std::variant attrValue; -}; - -// Get an ELF section by name. -folly::StringPiece getElfSection(const ElfFile* elf, const char* name); - -// All following read* functions read from a StringPiece, advancing the -// StringPiece, and aborting if there's not enough room. - -// Read (bitwise) one object of type T -template -typename std::enable_if< - std::is_standard_layout::value && std::is_trivial::value, - T>::type -read(folly::StringPiece& sp) { - FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow"); - T x; - memcpy(&x, sp.data(), sizeof(T)); - sp.advance(sizeof(T)); - return x; -} - -// Read (bitwise) an unsigned number of N bytes (N in 1, 2, 3, 4). -template -uint64_t readU64(folly::StringPiece& sp); - -// Read ULEB (unsigned) varint value; algorithm from the DWARF spec -uint64_t readULEB(folly::StringPiece& sp, uint8_t& shift, uint8_t& val); -uint64_t readULEB(folly::StringPiece& sp); - -// Read SLEB (signed) varint value; algorithm from the DWARF spec -int64_t readSLEB(folly::StringPiece& sp); - -// Read a value of "section offset" type, which may be 4 or 8 bytes -uint64_t readOffset(folly::StringPiece& sp, bool is64Bit); - -// Read "len" bytes -folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len); - -AttributeSpec readAttributeSpec(folly::StringPiece& sp); - -// Reads an abbreviation from a StringPiece, return true if at end; advance sp -bool readAbbreviation(folly::StringPiece& section, DIEAbbreviation& abbr); - -// Read a null-terminated string -folly::StringPiece readNullTerminated(folly::StringPiece& sp); - -folly::StringPiece getStringFromStringSection( - folly::StringPiece str, uint64_t offset); - -CompilationUnits getCompilationUnits( - ElfCacheBase* elfCache, - const DebugSections& debugSections, - uint64_t offset, - bool requireSplitDwarf = false); - -/** cu must exist during the life cycle of created Die. */ -Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset); - -// Read attribute value. -Attribute readAttribute( - const CompilationUnit& cu, - const Die& die, - AttributeSpec spec, - folly::StringPiece& info); - -/* - * Iterate over all attributes of the given DIE, calling the given callable - * for each. Iteration is stopped early if any of the calls return false. - */ -size_t forEachAttribute( - const CompilationUnit& cu, - const Die& die, - folly::FunctionRef f); - -template -folly::Optional getAttribute( - const CompilationUnit& cu, const Die& die, uint64_t attrName) { - folly::Optional result; - forEachAttribute(cu, die, [&](const Attribute& attr) { - if (attr.spec.name == attrName) { - result = std::get(attr.attrValue); - return false; - } - return true; - }); - return result; -} - -folly::StringPiece getFunctionNameFromDie( - const CompilationUnit& srcu, const Die& die); - -folly::StringPiece getFunctionName( - const CompilationUnit& srcu, uint64_t dieOffset); - -Die findDefinitionDie(const CompilationUnit& cu, const Die& die); - -} // namespace symbolizer -} // namespace folly - -#endif +#include diff --git a/folly/experimental/symbolizer/Elf-inl.h b/folly/experimental/symbolizer/Elf-inl.h index 6535bdd01de..c0f69e9aded 100644 --- a/folly/experimental/symbolizer/Elf-inl.h +++ b/folly/experimental/symbolizer/Elf-inl.h @@ -14,132 +14,4 @@ * limitations under the License. */ -#ifndef FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_ -#error This file must be included from Elf.h -#endif - -namespace folly { -namespace symbolizer { - -template -const ElfPhdr* ElfFile::iterateProgramHeaders(Fn fn) const - noexcept(is_nothrow_invocable_v) { - // there exist ELF binaries which execute correctly, but have invalid internal - // offset(s) to program/section headers; most probably due to invalid - // stripping of symbols - if (elfHeader().e_phoff + sizeof(ElfPhdr) >= length_) { - return nullptr; - } - - const ElfPhdr* ptr = &at(elfHeader().e_phoff); - for (size_t i = 0; i < elfHeader().e_phnum; i++, ptr++) { - if (fn(*ptr)) { - return ptr; - } - } - return nullptr; -} - -template -const ElfShdr* ElfFile::iterateSections(Fn fn) const - noexcept(is_nothrow_invocable_v) { - // there exist ELF binaries which execute correctly, but have invalid internal - // offset(s) to program/section headers; most probably due to invalid - // stripping of symbols - if (elfHeader().e_shoff + sizeof(ElfShdr) >= length_) { - return nullptr; - } - - const ElfShdr* ptr = &at(elfHeader().e_shoff); - for (size_t i = 0; i < elfHeader().e_shnum; i++, ptr++) { - if (fn(*ptr)) { - return ptr; - } - } - return nullptr; -} - -template -const ElfShdr* ElfFile::iterateSectionsWithType(uint32_t type, Fn fn) const - noexcept(is_nothrow_invocable_v) { - return iterateSections( - [&](const ElfShdr& sh) { return sh.sh_type == type && fn(sh); }); -} - -template -const ElfShdr* ElfFile::iterateSectionsWithTypes( - std::initializer_list types, Fn fn) const - noexcept(is_nothrow_invocable_v) { - return iterateSections([&](const ElfShdr& sh) { - auto const it = std::find(types.begin(), types.end(), sh.sh_type); - return it != types.end() && fn(sh); - }); -} - -template -const char* ElfFile::iterateStrings(const ElfShdr& stringTable, Fn fn) const - noexcept(is_nothrow_invocable_v) { - validateStringTable(stringTable); - - const char* start = file_ + stringTable.sh_offset; - const char* end = start + stringTable.sh_size; - - const char* ptr = start; - while (ptr != end && !fn(ptr)) { - ptr += strlen(ptr) + 1; - } - - return ptr != end ? ptr : nullptr; -} - -template -const E* ElfFile::iterateSectionEntries(const ElfShdr& section, Fn&& fn) const - - noexcept(is_nothrow_invocable_v) { - FOLLY_SAFE_CHECK( - section.sh_entsize == sizeof(E), "invalid entry size in table"); - - const E* ent = &at(section.sh_offset); - const E* end = ent + (section.sh_size / section.sh_entsize); - - while (ent < end) { - if (fn(*ent)) { - return ent; - } - - ++ent; - } - - return nullptr; -} - -template -const ElfSym* ElfFile::iterateSymbols(const ElfShdr& section, Fn fn) const - noexcept(is_nothrow_invocable_v) { - return iterateSectionEntries(section, fn); -} - -template -const ElfSym* ElfFile::iterateSymbolsWithType( - const ElfShdr& section, uint32_t type, Fn fn) const - noexcept(is_nothrow_invocable_v) { - // N.B. st_info has the same representation on 32- and 64-bit platforms - return iterateSymbols(section, [&](const ElfSym& sym) -> bool { - return ELF32_ST_TYPE(sym.st_info) == type && fn(sym); - }); -} - -template -const ElfSym* ElfFile::iterateSymbolsWithTypes( - const ElfShdr& section, std::initializer_list types, Fn fn) const - noexcept(is_nothrow_invocable_v) { - // N.B. st_info has the same representation on 32- and 64-bit platforms - return iterateSymbols(section, [&](const ElfSym& sym) -> bool { - auto const elfType = ELF32_ST_TYPE(sym.st_info); - auto const it = std::find(types.begin(), types.end(), elfType); - return it != types.end() && fn(sym); - }); -} - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/Elf.h b/folly/experimental/symbolizer/Elf.h index e737540f352..c2fa92ac3c0 100644 --- a/folly/experimental/symbolizer/Elf.h +++ b/folly/experimental/symbolizer/Elf.h @@ -14,443 +14,4 @@ * limitations under the License. */ -// ELF file parser - -#pragma once -#define FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#if FOLLY_HAVE_ELF - -#include -#include // For ElfW() - -namespace folly { -namespace symbolizer { - -#if defined(ElfW) -#define FOLLY_ELF_ELFW(name) ElfW(name) -#elif defined(__FreeBSD__) -#define FOLLY_ELF_ELFW(name) Elf_##name -#endif - -using ElfAddr = FOLLY_ELF_ELFW(Addr); -using ElfEhdr = FOLLY_ELF_ELFW(Ehdr); -using ElfOff = FOLLY_ELF_ELFW(Off); -using ElfPhdr = FOLLY_ELF_ELFW(Phdr); -using ElfShdr = FOLLY_ELF_ELFW(Shdr); -using ElfSym = FOLLY_ELF_ELFW(Sym); -using ElfRel = FOLLY_ELF_ELFW(Rel); -using ElfRela = FOLLY_ELF_ELFW(Rela); - -// ElfFileId is supposed to uniquely identify any instance of an ELF binary. -// It does that by using the file's inode, dev ID, size and modification time -// (ns): Just using dev, inode is not -// unique enough, because the file can be overwritten with new contents, but -// will keep same dev and inode, so we take into account modification time and -// file size to minimize risk. -struct ElfFileId { - dev_t dev; - ino_t inode; - off_t size; - uint64_t mtime; -}; - -inline bool operator==(const ElfFileId& lhs, const ElfFileId& rhs) { - return lhs.dev == rhs.dev && lhs.inode == rhs.inode && lhs.size == rhs.size && - lhs.mtime == rhs.mtime; -} - -/** - * ELF file parser. - * - * We handle native files only (32-bit files on a 32-bit platform, 64-bit files - * on a 64-bit platform), and only executables (ET_EXEC) shared objects - * (ET_DYN), core files (ET_CORE) and relocatable file (ET_REL). - */ -class ElfFile { - public: - class Options { - public: - constexpr Options() noexcept {} - - constexpr bool writable() const noexcept { return writable_; } - - constexpr Options& writable(bool const value) noexcept { - writable_ = value; - return *this; - } - - private: - bool writable_ = false; - }; - - ElfFile() noexcept; - - // Note: may throw, call openNoThrow() explicitly if you don't want to throw - explicit ElfFile(const char* name, Options const& options = Options()); - - // Open the ELF file. - // Returns 0 on success, kSystemError (guaranteed to be -1) (and sets errno) - // on IO error, kInvalidElfFile (and sets errno to EINVAL) for an invalid - // Elf file. On error, if msg is not nullptr, sets *msg to a static string - // indicating what failed. - enum OpenResultCode : int { - kSuccess = 0, - kSystemError = -1, - kInvalidElfFile = -2, - }; - struct OpenResult { - OpenResultCode code{}; - char const* msg{}; - - /* implicit */ constexpr operator OpenResultCode() const noexcept { - return code; - } - }; - // Open the ELF file. Does not throw on error. - OpenResult openNoThrow( - const char* name, Options const& options = Options()) noexcept; - - // Like openNoThrow, but follow .gnu_debuglink if present - OpenResult openAndFollow( - const char* name, Options const& options = Options()) noexcept; - - // Open the ELF file. Throws on error. - void open(const char* name, Options const& options = Options()); - - ~ElfFile(); - - ElfFile(ElfFile&& other) noexcept; - ElfFile& operator=(ElfFile&& other) noexcept; - - /** Retrieve the ELF header */ - const ElfEhdr& elfHeader() const noexcept { return at(0); } - - /** - * Get the base address, the address where the file should be loaded if - * no relocations happened. - */ - uintptr_t getBaseAddress() const noexcept { return baseAddress_; } - - /** Find a section given its name */ - const ElfShdr* getSectionByName(const char* name) const noexcept; - - /** Find a section given its index in the section header table */ - const ElfShdr* getSectionByIndex(size_t idx) const noexcept; - - /** Retrieve the name of a section */ - const char* getSectionName(const ElfShdr& section) const noexcept; - - /** Get the actual section body */ - folly::StringPiece getSectionBody(const ElfShdr& section) const noexcept; - - /** Retrieve a string from a string table section */ - const char* getString( - const ElfShdr& stringTable, size_t offset) const noexcept; - - /** - * Iterate over all strings in a string table section for as long as - * fn(str) returns false. - * Returns the current ("found") string when fn returned true, or nullptr - * if fn returned false for all strings in the table. - */ - template - const char* iterateStrings(const ElfShdr& stringTable, Fn fn) const - noexcept(is_nothrow_invocable_v); - - /** - * Iterate over program headers as long as fn(section) returns false. - * Returns a pointer to the current ("found") section when fn returned - * true, or nullptr if fn returned false for all sections. - */ - template - const ElfPhdr* iterateProgramHeaders(Fn fn) const - noexcept(is_nothrow_invocable_v); - - /** - * Iterate over all sections for as long as fn(section) returns false. - * Returns a pointer to the current ("found") section when fn returned - * true, or nullptr if fn returned false for all sections. - */ - template - const ElfShdr* iterateSections(Fn fn) const - noexcept(is_nothrow_invocable_v); - - /** - * Iterate over all sections with a given type. Similar to - * iterateSections(), but filtered only for sections with the given type. - */ - template - const ElfShdr* iterateSectionsWithType(uint32_t type, Fn fn) const - noexcept(is_nothrow_invocable_v); - - /** - * Iterate over all sections with a given types. Similar to - * iterateSectionWithTypes(), but filtered on multiple types. - */ - template - const ElfShdr* iterateSectionsWithTypes( - std::initializer_list types, Fn fn) const - noexcept(is_nothrow_invocable_v); - - /** - * Iterate over all symbols within a given section. - * - * Returns a pointer to the current ("found") symbol when fn returned true, - * or nullptr if fn returned false for all symbols. - */ - template - const ElfSym* iterateSymbols(const ElfShdr& section, Fn fn) const - noexcept(is_nothrow_invocable_v); - template - const ElfSym* iterateSymbolsWithType( - const ElfShdr& section, uint32_t type, Fn fn) const - noexcept(is_nothrow_invocable_v); - template - const ElfSym* iterateSymbolsWithTypes( - const ElfShdr& section, - std::initializer_list types, - Fn fn) const noexcept(is_nothrow_invocable_v); - - /** - * Iterate over entries within a given section. - * - * Returns a pointer to the current ("found") entry when fn returned - * true, or nullptr if fn returned false for all entries. - */ - template - const E* iterateSectionEntries(const ElfShdr& section, Fn&& fn) const - - noexcept(is_nothrow_invocable_v); - - /** - * Find symbol definition by address. - * Note that this is the file virtual address, so you need to undo - * any relocation that might have happened. - * - * Returns {nullptr, nullptr} if not found. - */ - typedef std::pair Symbol; - Symbol getDefinitionByAddress(uintptr_t address) const noexcept; - - /** - * Find symbol definition by name. Optionally specify the symbol types to - * consider. - * - * If a symbol with this name cannot be found, a Symbol - * will be returned. This is O(N) in the number of symbols in the file. - * - * Returns {nullptr, nullptr} if not found. - */ - Symbol getSymbolByName( - const char* name, - std::initializer_list types = { - STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept; - - /** - * Find multiple symbol definitions by name. Because searching for a symbol is - * O(N) this method enables searching for multiple symbols in a single pass. - * - * Returns a map containing a key for each unique symbol name in the provided - * names container. The corresponding value is either Symbol or if the symbol was not found. - */ - template - std::unordered_map getSymbolsByName( - const C& names, - std::initializer_list types = { - STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept { - std::unordered_map result(names.size()); - if (names.empty()) { - return result; - } - - for (const std::string& name : names) { - result[name] = {nullptr, nullptr}; - } - size_t seenCount = 0; - - auto findSymbol = [&](const folly::symbolizer::ElfShdr& section, - const folly::symbolizer::ElfSym& sym) -> bool { - auto symbol = folly::symbolizer::ElfFile::Symbol(§ion, &sym); - auto name = getSymbolName(symbol); - if (name == nullptr) { - return false; - } - auto itr = result.find(name); - if (itr != result.end() && itr->second.first == nullptr && - itr->second.second == nullptr) { - itr->second = symbol; - ++seenCount; - } - return seenCount == result.size(); - }; - - auto iterSection = [&](const folly::symbolizer::ElfShdr& section) -> bool { - iterateSymbolsWithTypes(section, types, [&](const auto& sym) -> bool { - return findSymbol(section, sym); - }); - return false; - }; - // Try the .dynsym section first if it exists, it's smaller. - iterateSectionsWithType(SHT_DYNSYM, iterSection) || - iterateSectionsWithType(SHT_SYMTAB, iterSection); - - return result; - } - - /** - * Get the value of a symbol. - */ - template - const T* getSymbolValue(const ElfSym* symbol) const noexcept { - const ElfShdr* section = getSectionByIndex(symbol->st_shndx); - if (section == nullptr) { - return nullptr; - } - - return valueAt(*section, symbol->st_value); - } - - /** - * Get the value of the object stored at the given address. - * - * This is the function that you want to use in conjunction with - * getSymbolValue() to follow pointers. For example, to get the value of - * a char* symbol, you'd do something like this: - * - * auto sym = getSymbolByName("someGlobalValue"); - * auto addrPtr = getSymbolValue(sym.second); - * const char* str = getAddressValue(*addrPtr); - */ - template - const T* getAddressValue(const ElfAddr addr) const noexcept { - const ElfShdr* section = getSectionContainingAddress(addr); - if (section == nullptr) { - return nullptr; - } - - return valueAt(*section, addr); - } - - /** - * Retrieve symbol name. - */ - const char* getSymbolName(const Symbol& symbol) const noexcept; - - /** Find the section containing the given address */ - const ElfShdr* getSectionContainingAddress(ElfAddr addr) const noexcept; - - const char* filepath() const { return filepath_; } - - const ElfFileId& getFileId() const { return fileId_; } - - /** - * Announce an intention to access file data in a specific pattern in the - * future. https://man7.org/linux/man-pages/man2/posix_fadvise.2.html - */ - std::pair posixFadvise( - off_t offset, off_t len, int const advice) const noexcept; - std::pair posixFadvise( - int const advice) const noexcept; - - private: - OpenResult init() noexcept; - void reset() noexcept; - ElfFile(const ElfFile&) = delete; - ElfFile& operator=(const ElfFile&) = delete; - - void validateStringTable(const ElfShdr& stringTable) const noexcept; - - template - const T& at(ElfOff offset) const noexcept { - static_assert( - std::is_standard_layout::value && std::is_trivial::value, - "non-pod"); - FOLLY_SAFE_CHECK( - offset + sizeof(T) <= length_, - "Offset (", - static_cast(offset), - " + ", - sizeof(T), - ") is not contained within our mapped file (", - filepath_, - ") of length ", - length_); - return *reinterpret_cast(file_ + offset); - } - - template - const T* valueAt(const ElfShdr& section, const ElfAddr addr) const noexcept { - // For exectuables and shared objects, st_value holds a virtual address - // that refers to the memory owned by sections. Since we didn't map the - // sections into the addresses that they're expecting (sh_addr), but - // instead just mmapped the entire file directly, we need to translate - // between addresses and offsets into the file. - // - // TODO: For other file types, st_value holds a file offset directly. Since - // I don't have a use-case for that right now, just assert that - // nobody wants this. We can always add it later. - if (!(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN || - elfHeader().e_type == ET_CORE)) { - return nullptr; - } - if (!(addr >= section.sh_addr && - (addr + sizeof(T)) <= (section.sh_addr + section.sh_size))) { - return nullptr; - } - - // SHT_NOBITS: a section that occupies no space in the file but otherwise - // resembles SHT_PROGBITS. Although this section contains no bytes, the - // sh_offset member contains the conceptual file offset. Typically used - // for zero-initialized data sections like .bss. - if (section.sh_type == SHT_NOBITS) { - static T t = {}; - return &t; - } - - ElfOff offset = section.sh_offset + (addr - section.sh_addr); - - return (offset + sizeof(T) <= length_) ? &at(offset) : nullptr; - } - - static constexpr size_t kFilepathMaxLen = 512; - char filepath_[kFilepathMaxLen] = {}; - int fd_; - char* file_; // mmap() location - size_t length_; // mmap() length - ElfFileId fileId_; - - uintptr_t baseAddress_; -}; - -} // namespace symbolizer -} // namespace folly - -namespace std { -template <> -struct hash { - size_t operator()(const folly::symbolizer::ElfFileId fileId) const { - return folly::hash::hash_combine( - fileId.dev, fileId.inode, fileId.size, fileId.mtime); - } -}; -} // namespace std - -#include - -#endif // FOLLY_HAVE_ELF +#include diff --git a/folly/experimental/symbolizer/ElfCache.h b/folly/experimental/symbolizer/ElfCache.h index bc0f053a3d7..fd15bf53433 100644 --- a/folly/experimental/symbolizer/ElfCache.h +++ b/folly/experimental/symbolizer/ElfCache.h @@ -14,126 +14,4 @@ * limitations under the License. */ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace folly { -namespace symbolizer { - -#if FOLLY_HAVE_ELF - -class ElfCacheBase { - public: - virtual std::shared_ptr getFile(StringPiece path) = 0; - virtual ~ElfCacheBase() {} -}; - -/** - * Cache ELF files. Async-signal-safe: does memory allocation via mmap. - * - * Not MT-safe. May not be used concurrently from multiple threads. - */ -class SignalSafeElfCache : public ElfCacheBase { - public: - std::shared_ptr getFile(StringPiece path) override; - - // Path - // - // A minimal implementation of the subset of std::string used below, as if: - // - // using Path = std::basic_string< - // char, std::char_traits, reentrant_allocator>; - // - // Since some library implementations of std::basic_string, as on CentOS 7, - // do not build when instantiated with a non-default-constructible allocator, - // and since other library replacements, such as folly::basic_fbstring, just - // ignore the allocator parameter. - class Path { - public: - Path( - char const* data, - std::size_t size, - reentrant_allocator const& alloc) noexcept; - Path() = delete; - Path(Path const&) = delete; - void operator=(Path const&) = delete; - - /* implicit */ operator StringPiece() const noexcept { return data_; } - - char const* c_str() const noexcept { return data_.data(); } - - friend bool operator<(Path const& a, Path const& b) noexcept { - return a.data_ < b.data_; - } - - private: - std::vector> data_; - }; - - struct Entry : boost::intrusive::avl_set_base_hook<> { - Path path; - std::shared_ptr file; - bool init = false; - - explicit Entry(StringPiece p, reentrant_allocator alloc) noexcept - : path{p.data(), p.size(), alloc}, - file{std::allocate_shared(alloc)} {} - Entry(Entry const&) = delete; - Entry& operator=(Entry const& that) = delete; - - friend bool operator<(Entry const& a, Entry const& b) noexcept { - return a.path < b.path; - } - }; - - struct State { - reentrant_allocator alloc{ - reentrant_allocator_options().block_size_lg(16).large_size_lg(12)}; - std::forward_list> list{alloc}; - // note: map entry dtors check that they have already been unlinked - boost::intrusive::avl_set map; // must follow list - }; - Optional state_; -}; - -/** - * General-purpose ELF file cache. - * - * LRU of given capacity. MT-safe (uses locking). Not async-signal-safe. - */ -class ElfCache : public ElfCacheBase { - public: - std::shared_ptr getFile(StringPiece path) override; - - private: - std::mutex mutex_; - - struct Entry { - std::string path; - ElfFile file; - }; - - static std::shared_ptr filePtr(const std::shared_ptr& e); - - std::unordered_map, Hash> files_; -}; - -#endif // FOLLY_HAVE_ELF - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/LineReader.h b/folly/experimental/symbolizer/LineReader.h index a504730c4be..3fd83b1016a 100644 --- a/folly/experimental/symbolizer/LineReader.h +++ b/folly/experimental/symbolizer/LineReader.h @@ -14,76 +14,4 @@ * limitations under the License. */ -#pragma once - -#include - -#include - -namespace folly { -namespace symbolizer { - -/** - * Async-signal-safe line reader. - */ -class LineReader { - public: - /** - * Create a line reader that reads into a user-provided buffer (of size - * bufSize). - */ - LineReader(int fd, char* buf, size_t bufSize); - - LineReader(const LineReader&) = delete; - LineReader& operator=(const LineReader&) = delete; - - enum State { - kReading, - kEof, - kError, - }; - /** - * Read the next line from the file. - * - * If the line is at most bufSize characters long, including the trailing - * newline, it will be returned (including the trailing newline). - * - * If the line is longer than bufSize, we return the first bufSize bytes - * (which won't include a trailing newline) and then continue from that - * point onwards. - * - * The lines returned are not null-terminated. - * - * Returns kReading with a valid line, kEof if at end of file, or kError - * if a read error was encountered. - * - * Example: - * bufSize = 10 - * input has "hello world\n" - * The first call returns "hello worl" - * The second call returns "d\n" - */ - State readLine(StringPiece& line); - - private: - int const fd_; - char* const buf_; - char* const bufEnd_; - - // buf_ <= bol_ <= eol_ <= end_ <= bufEnd_ - // - // [buf_, end_): current buffer contents (read from file) - // - // [buf_, bol_): free (already processed, can be discarded) - // [bol_, eol_): current line, including \n if it exists, eol_ points - // 1 character past the \n - // [eol_, end_): read, unprocessed - // [end_, bufEnd_): free - - char* bol_; - char* eol_; - char* end_; - State state_; -}; -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/SignalHandler.h b/folly/experimental/symbolizer/SignalHandler.h index 1806ab34fec..baa66679f35 100644 --- a/folly/experimental/symbolizer/SignalHandler.h +++ b/folly/experimental/symbolizer/SignalHandler.h @@ -14,52 +14,4 @@ * limitations under the License. */ -#pragma once - -#include - -namespace folly { -namespace symbolizer { - -extern const unsigned long kAllFatalSignals; - -/** - * Install handler for fatal signals. The list of signals being handled is in - * SignalHandler.cpp. - * - * The handler will dump signal and time information followed by a stack trace - * to stderr, and then call the callbacks registered below. - * - * The signals parameter can be used to specify only specific fatal signals for - * which the handler should be installed. Only signals from kAllFatalSignals - * are honored in this list, other signals are ignored. - */ -void installFatalSignalHandler( - std::bitset<64> signals = std::bitset<64>(kAllFatalSignals)); - -/** - * Add a callback to be run when receiving a fatal signal. They will also - * be called by LOG(FATAL) and abort() (as those raise SIGABRT internally). - * - * These callbacks must be async-signal-safe, so don't even think of using - * LOG(...) or printf or malloc / new or doing anything even remotely fun. - * - * All these fatal callback must be added before calling - * installFatalSignalCallbacks(), below. - */ -typedef void (*SignalCallback)(); -void addFatalSignalCallback(SignalCallback cb); - -/** - * Install the fatal signal callbacks; fatal signals will call these - * callbacks in the order in which they were added. - */ -void installFatalSignalCallbacks(); - -/** - * True if a fatal signal was received (i.e. the process is crashing). - */ -bool fatalSignalReceived(); - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/StackTrace.h b/folly/experimental/symbolizer/StackTrace.h index 212979014e8..e6f5b15c440 100644 --- a/folly/experimental/symbolizer/StackTrace.h +++ b/folly/experimental/symbolizer/StackTrace.h @@ -14,71 +14,4 @@ * limitations under the License. */ -#pragma once - -#include -#include -#include - -#include - -namespace folly { -namespace symbolizer { - -/** - * Get the current stack trace into addresses, which has room for at least - * maxAddresses frames. - * - * Returns the number of frames written in the array. - * Returns -1 on failure. - * - * NOT async-signal-safe, but fast. - */ -ssize_t getStackTrace(uintptr_t* addresses, size_t maxAddresses); - -/** - * Get the current stack trace into addresses, which has room for at least - * maxAddresses frames. - * - * Returns the number of frames written in the array. - * Returns -1 on failure. - * - * Async-signal-safe, but likely slower. - */ -ssize_t getStackTraceSafe(uintptr_t* addresses, size_t maxAddresses); - -/** - * Get the current stack trace into addresses, which has room for at least - * maxAddresses frames. - * - * Returns the number of frames written in the array. - * Returns -1 on failure. - * - * Heap allocates its context. Likely slower than getStackTrace but - * avoids large stack allocations. - */ -ssize_t getStackTraceHeap(uintptr_t* addresses, size_t maxAddresses); - -/** - * Get the current async stack trace into addresses, which has room for at least - * maxAddresses frames. If no async operation is progress, then this will - * write 0 frames. - * - * This will include both async and non-async frames. For example, the stack - * trace could look something like this: - * - * funcD <-- non-async, current top of stack - * funcC <-- non-async - * co_funcB <-- async - * co_funcA <-- async - * main <-- non-async, root of async stack - * - * Returns the number of frames written in the array. - * Returns -1 on failure. - * - * Async-signal-safe, but likely slower. - */ -ssize_t getAsyncStackTraceSafe(uintptr_t* addresses, size_t maxAddresses); - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/SymbolizePrinter.h b/folly/experimental/symbolizer/SymbolizePrinter.h index f88b05b3e49..015f2abd4e7 100644 --- a/folly/experimental/symbolizer/SymbolizePrinter.h +++ b/folly/experimental/symbolizer/SymbolizePrinter.h @@ -14,188 +14,4 @@ * limitations under the License. */ -#pragma once - -#include - -#include -#include -#include - -namespace folly { -class IOBuf; - -namespace symbolizer { - -/** - * Format one address in the way it's usually printed by SymbolizePrinter. - * Async-signal-safe. - */ -class AddressFormatter { - public: - AddressFormatter(); - - /** - * Format the address. Returns an internal buffer. - */ - StringPiece format(uintptr_t address); - - private: - static constexpr char bufTemplate[] = " @ 0000000000000000"; - char buf_[sizeof(bufTemplate)]; -}; - -/** - * Print a list of symbolized addresses. Base class. - */ -class SymbolizePrinter { - public: - /** - * Print one frame, no ending newline. - */ - void print(const SymbolizedFrame& frame); - - /** - * Print one frame with ending newline. - */ - void println(const SymbolizedFrame& frame); - - /** - * Print multiple frames on separate lines. - */ - void println(const SymbolizedFrame* frames, size_t frameCount); - - /** - * Print a string, no endling newline. - */ - void print(StringPiece sp) { doPrint(sp); } - - /** - * Print multiple frames on separate lines, skipping the first - * skip addresses. - */ - template - void println(const FrameArray& fa, size_t skip = 0) { - if (skip < fa.frameCount) { - println(fa.frames + skip, fa.frameCount - skip); - } - } - - /** - * If output buffered inside this class, send it to the output stream, so that - * any output done in other ways appears after this. - */ - virtual void flush() {} - - virtual ~SymbolizePrinter() {} - - enum Options { - // Skip file and line information - NO_FILE_AND_LINE = 1 << 0, - - // As terse as it gets: function name if found, address otherwise - TERSE = 1 << 1, - - // Always colorize output (ANSI escape code) - COLOR = 1 << 2, - - // Colorize output only if output is printed to a TTY (ANSI escape code) - COLOR_IF_TTY = 1 << 3, - - // Skip frame address information - NO_FRAME_ADDRESS = 1 << 4, - - // Simple file and line output - TERSE_FILE_AND_LINE = 1 << 5, - }; - - // NOTE: enum values used as indexes in kColorMap. - enum Color { Default, Red, Green, Yellow, Blue, Cyan, White, Purple, Num }; - void color(Color c); - - protected: - explicit SymbolizePrinter(int options, bool isTty = false) - : options_(options), isTty_(isTty) {} - - const int options_; - const bool isTty_; - - private: - void printTerse(const SymbolizedFrame& frame); - virtual void doPrint(StringPiece sp) = 0; - - static constexpr std::array kColorMap = {{ - "\x1B[0m", - "\x1B[31m", - "\x1B[32m", - "\x1B[33m", - "\x1B[34m", - "\x1B[36m", - "\x1B[37m", - "\x1B[35m", - }}; -}; - -/** - * Print a list of symbolized addresses to a stream. - * Not reentrant. Do not use from signal handling code. - */ -class OStreamSymbolizePrinter : public SymbolizePrinter { - public: - explicit OStreamSymbolizePrinter(std::ostream& out, int options = 0); - - private: - void doPrint(StringPiece sp) override; - std::ostream& out_; -}; - -/** - * Print a list of symbolized addresses to a file descriptor. - * Ignores errors. Async-signal-safe. - */ -class FDSymbolizePrinter : public SymbolizePrinter { - public: - explicit FDSymbolizePrinter(int fd, int options = 0, size_t bufferSize = 0); - ~FDSymbolizePrinter() override; - virtual void flush() override; - - private: - void doPrint(StringPiece sp) override; - - const int fd_; - std::unique_ptr buffer_; -}; - -/** - * Print a list of symbolized addresses to a FILE*. - * Ignores errors. Not reentrant. Do not use from signal handling code. - */ -class FILESymbolizePrinter : public SymbolizePrinter { - public: - explicit FILESymbolizePrinter(FILE* file, int options = 0); - - private: - void doPrint(StringPiece sp) override; - FILE* const file_ = nullptr; -}; - -/** - * Print a list of symbolized addresses to a std::string. - * Not reentrant. Do not use from signal handling code. - */ -class StringSymbolizePrinter : public SymbolizePrinter { - public: - explicit StringSymbolizePrinter(int options = 0) - : SymbolizePrinter(options) {} - - std::string str() const { return buf_.toStdString(); } - const fbstring& fbstr() const { return buf_; } - fbstring moveFbString() { return std::move(buf_); } - - private: - void doPrint(StringPiece sp) override; - fbstring buf_; -}; - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/SymbolizedFrame.h b/folly/experimental/symbolizer/SymbolizedFrame.h index 2a1d0d2c64b..6c0c0c40ba4 100644 --- a/folly/experimental/symbolizer/SymbolizedFrame.h +++ b/folly/experimental/symbolizer/SymbolizedFrame.h @@ -14,112 +14,4 @@ * limitations under the License. */ -#pragma once - -#include -#include -#include -#include - -#include - -namespace folly { -namespace symbolizer { - -class ElfFile; - -/** - * Represent a file path as a collection of three parts (base directory, - * subdirectory, and file). - */ -class Path { - public: - Path() = default; - - Path( - folly::StringPiece baseDir, - folly::StringPiece subDir, - folly::StringPiece file); - - folly::StringPiece baseDir() const { return baseDir_; } - folly::StringPiece subDir() const { return subDir_; } - folly::StringPiece 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; - } - - private: - folly::StringPiece baseDir_; - folly::StringPiece subDir_; - folly::StringPiece file_; -}; - -inline std::ostream& operator<<(std::ostream& out, const Path& path) { - return out << path.toString(); -} - -enum class LocationInfoMode { - // Don't resolve location info. - DISABLED, - // Perform CU lookup using .debug_aranges (might be incomplete). - FAST, - // Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure. - FULL, - // Scan .debug_info (super slower, use with caution) for inline functions in - // addition to FULL. - FULL_WITH_INLINE, -}; - -/** - * Contains location info like file name, line number, etc. - */ -struct LocationInfo { - bool hasFileAndLine = false; - bool hasMainFile = false; - Path mainFile; - Path file; - uint64_t line = 0; -}; - -/** - * Frame information: symbol name and location. - */ -struct SymbolizedFrame { - bool found = false; - uintptr_t addr = 0; - // Mangled symbol name. Use `folly::demangle()` to demangle it. - const char* name = nullptr; - LocationInfo location; - std::shared_ptr file; - - void clear() { *this = SymbolizedFrame(); } -}; - -template -struct FrameArray { - FrameArray() = default; - - size_t frameCount = 0; - uintptr_t addresses[N]; - SymbolizedFrame frames[N]; -}; - -} // namespace symbolizer -} // namespace folly +#include diff --git a/folly/experimental/symbolizer/Symbolizer.h b/folly/experimental/symbolizer/Symbolizer.h index 3db4d5da8d1..20d2c4d72fd 100644 --- a/folly/experimental/symbolizer/Symbolizer.h +++ b/folly/experimental/symbolizer/Symbolizer.h @@ -14,297 +14,4 @@ * limitations under the License. */ -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace folly { -namespace symbolizer { - -/** - * Get stack trace into a given FrameArray, return true on success (and - * set frameCount to the actual frame count, which may be > N) and false - * on failure. - */ -namespace detail { -template -bool fixFrameArray(FrameArray& fa, ssize_t n) { - if (n != -1) { - fa.frameCount = n; - for (size_t i = 0; i < fa.frameCount; ++i) { - fa.frames[i].found = false; - } - return true; - } else { - fa.frameCount = 0; - return false; - } -} -} // namespace detail - -// Always inline these functions; they don't do much, and unittests rely -// on them never showing up in a stack trace. -template -FOLLY_ALWAYS_INLINE bool getStackTrace(FrameArray& fa); - -template -inline bool getStackTrace(FrameArray& fa) { - return detail::fixFrameArray(fa, getStackTrace(fa.addresses, N)); -} -template -FOLLY_ALWAYS_INLINE bool getStackTraceSafe(FrameArray& fa); - -template -inline bool getStackTraceSafe(FrameArray& fa) { - return detail::fixFrameArray(fa, getStackTraceSafe(fa.addresses, N)); -} - -template -FOLLY_ALWAYS_INLINE bool getStackTraceHeap(FrameArray& fa); - -template -inline bool getStackTraceHeap(FrameArray& fa) { - return detail::fixFrameArray(fa, getStackTraceHeap(fa.addresses, N)); -} - -template -FOLLY_ALWAYS_INLINE bool getAsyncStackTraceSafe(FrameArray& fa); - -template -inline bool getAsyncStackTraceSafe(FrameArray& fa) { - return detail::fixFrameArray(fa, getAsyncStackTraceSafe(fa.addresses, N)); -} - -#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF - -class Symbolizer { - public: - static constexpr auto kDefaultLocationInfoMode = LocationInfoMode::FAST; - - static bool isAvailable(); - - explicit Symbolizer(LocationInfoMode mode = kDefaultLocationInfoMode) - : Symbolizer(nullptr, mode) {} - - explicit Symbolizer( - ElfCacheBase* cache, - LocationInfoMode mode = kDefaultLocationInfoMode, - size_t symbolCacheSize = 0, - std::string exePath = "/proc/self/exe"); - - ~Symbolizer(); - - /** - * Symbolize given addresses and return the number of @frames filled: - * - * - all entries in @addrs will be symbolized (if possible, e.g. if they're - * valid code addresses and if frames.size() >= addrs.size()) - * - * - if `mode_ == FULL_WITH_INLINE` and `frames.size() > addrs.size()` then at - * most `frames.size() - addrs.size()` additional inlined functions will - * also be symbolized (at most `kMaxInlineLocationInfoPerFrame` per @addr - * entry). - */ - size_t symbolize( - folly::Range addrs, - folly::Range frames); - - size_t symbolize( - const uintptr_t* addresses, SymbolizedFrame* frames, size_t frameCount) { - return symbolize( - folly::Range(addresses, frameCount), - folly::Range(frames, frameCount)); - } - - template - size_t symbolize(FrameArray& fa) { - return symbolize( - folly::Range(fa.addresses, fa.frameCount), - folly::Range(fa.frames, N)); - } - - /** - * Shortcut to symbolize one address. - */ - bool symbolize(uintptr_t address, SymbolizedFrame& frame) { - symbolize( - folly::Range(&address, 1), - folly::Range(&frame, 1)); - return frame.found; - } - - private: - ElfCacheBase* const cache_; - const LocationInfoMode mode_; - const std::string exePath_; - - // Details in cpp file to minimize header dependencies - struct SymbolCache; - std::unique_ptr symbolCache_; -}; - -/** - * Use this class to print a stack trace from normal code. It will malloc and - * won't flush or sync. - * - * These methods are thread safe, through locking. However, they are not signal - * safe. - */ -class FastStackTracePrinter { - public: - static constexpr size_t kDefaultSymbolCacheSize = 10000; - - explicit FastStackTracePrinter( - std::unique_ptr printer, - size_t symbolCacheSize = kDefaultSymbolCacheSize); - - ~FastStackTracePrinter(); - - /** - * This is NOINLINE to make sure it shows up in the stack we grab, which makes - * it easy to skip printing it. - */ - FOLLY_NOINLINE void printStackTrace(bool symbolize); - - void flush(); - - private: - static constexpr size_t kMaxStackTraceDepth = 100; - - const std::unique_ptr printer_; - Symbolizer symbolizer_; -}; - -#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF - -/** - * Use this class to print a stack trace from a signal handler, or other place - * where you shouldn't allocate memory on the heap, and fsync()ing your file - * descriptor is more important than performance. - * - * Make sure to create one of these on startup, not in the signal handler, as - * the constructor allocates on the heap, whereas the other methods don't. Best - * practice is to just leak this object, rather than worry about destruction - * order. - * - * These methods aren't thread safe, so if you could have signals on multiple - * threads at the same time, you need to do your own locking to ensure you don't - * call these methods from multiple threads. They are signal safe, however. - */ -class SafeStackTracePrinter { - public: - explicit SafeStackTracePrinter(int fd = STDERR_FILENO); - - virtual ~SafeStackTracePrinter() {} - - /** - * Only allocates on the stack and is signal-safe but not thread-safe. Don't - * call printStackTrace() on the same StackTracePrinter object from multiple - * threads at the same time. - * - * This is NOINLINE to make sure it shows up in the stack we grab, which makes - * it easy to skip printing it. - */ - FOLLY_NOINLINE void printStackTrace(bool symbolize); - - void print(StringPiece sp) { printer_.print(sp); } - - // Flush printer_, also fsync, in case we're about to crash again... - void flush(); - - protected: - virtual void printSymbolizedStackTrace(); - void printUnsymbolizedStackTrace(); - - private: - static constexpr size_t kMaxStackTraceDepth = 100; - - int fd_; - FDSymbolizePrinter printer_; - std::unique_ptr> addresses_; -}; - -#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF - -/** - * Gets the stack trace for the current thread and returns a string - * representation. Convenience function meant for debugging and logging. - * Empty string indicates stack trace functionality is not available. - * - * NOT async-signal-safe. - */ -std::string getStackTraceStr(); - -/** - * Gets the async stack trace for the current thread and returns a string - * representation. Convenience function meant for debugging and logging. - * Empty string indicates stack trace functionality is not available. - * - * NOT async-signal-safe. - */ -std::string getAsyncStackTraceStr(); - -/** - * Get the async stack traces (string representation) for suspended coroutines. - * Convenience function meant for debugging and logging, works only in some - * DEBUG builds - * - * Note: The returned traces will only have async frames (no normal frames). - */ -std::vector getSuspendedStackTraces(); - -#else -// Define these in the header, as headers are always available, but not all -// platforms can link against the symbolizer library cpp sources. - -inline std::string getStackTraceStr() { - return ""; -} - -inline std::string getAsyncStackTraceStr() { - return ""; -} - -inline std::vector getSuspendedStackTraces() { - return {}; -} -#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF - -#if FOLLY_HAVE_SWAPCONTEXT - -/** - * Use this class in rare situations where signal handlers are running in a - * tiny stack specified by sigaltstack. - * - * This is neither thread-safe nor signal-safe. However, it can usually print - * something useful while SafeStackTracePrinter would stack overflow. - * - * Signal handlers would need to block other signals to make this safer. - * Note it's still unsafe even with that. - */ -class UnsafeSelfAllocateStackTracePrinter : public SafeStackTracePrinter { - protected: - void printSymbolizedStackTrace() override; - const long pageSizeUnchecked_ = sysconf(_SC_PAGESIZE); -}; - -#endif // FOLLY_HAVE_SWAPCONTEXT - -} // namespace symbolizer -} // namespace folly +#include