/* * Copyright (c) Facebook, Inc. and its 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 namespace folly { namespace symbolizer { #if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF namespace detail { // A top level chunk in the .debug_info that contains a compilation unit. struct CompilationUnit; // 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; // Abbreviation for a Debugging Information Entry. struct DIEAbbreviation; struct AttributeSpec; struct Attribute; struct CallLocation; } // namespace detail /** * 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. */ explicit Dwarf(const ElfFile* elf); /** * More than one location info may exist if current frame is an inline * function call. */ static const uint32_t kMaxInlineLocationInfoPerFrame = 10; /** * 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, LocationInfo& info, folly::Range inlineFrames = {}, folly::FunctionRef eachParameterName = {}) const; private: using AttributeValue = boost::variant; /** * DWARF section made up of chunks, each prefixed with a length header. The * length indicates whether the chunk is DWARF-32 or DWARF-64, which guides * interpretation of "section offset" records. (yes, DWARF-32 and DWARF-64 * sections may coexist in the same file). */ class Section; /** Interpreter for the line number bytecode VM */ class LineNumberVM; /** * 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. */ bool findLocation( uintptr_t address, const LocationInfoMode mode, detail::CompilationUnit& cu, LocationInfo& info, folly::Range inlineFrames, folly::FunctionRef eachParameterName) const; /** * Finds a subprogram debugging info entry that contains a given address among * children of given die. Depth first search. */ bool findSubProgramDieForAddress( const detail::CompilationUnit& cu, const detail::Die& die, uint64_t address, folly::Optional baseAddrCU, detail::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 detail::CompilationUnit& cu, const detail::Die& die, const LineNumberVM& lineVM, uint64_t address, folly::Optional baseAddrCU, folly::Range locations, size_t& numFound) const; static bool findDebugInfoOffset( uintptr_t address, StringPiece aranges, uint64_t& offset); /** Get an ELF section by name. */ folly::StringPiece getSection(const char* name) const; /** cu must exist during the life cycle of created detail::Die. */ detail::Die getDieAtOffset( const detail::CompilationUnit& cu, uint64_t offset) const; /** * Find the actual definition DIE instead of declaration for the given die. */ detail::Die findDefinitionDie( const detail::CompilationUnit& cu, const detail::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 detail::CompilationUnit& cu, const detail::Die& die, folly::FunctionRef f) const; /** * Gets abbreviation corresponding to a code, in the chunk starting at * offset in the .debug_abbrev section */ detail::DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const; /** * Iterates over all attributes of a debugging info entry, calling the given * callable for each. If all attributes are visited, then return the offset of * next DIE, or else iteration is stopped early and return size_t(-1) if any * of the calls return false. */ size_t forEachAttribute( const detail::CompilationUnit& cu, const detail::Die& die, folly::FunctionRef f) const; template folly::Optional getAttribute( const detail::CompilationUnit& cu, const detail::Die& die, uint64_t attrName) const; /** * Check if the given address is in the range list at the given offset in * .debug_ranges. */ bool isAddrInRangeList( uint64_t address, folly::Optional baseAddr, size_t offset, uint8_t addrSize) const; const ElfFile* elf_; const folly::StringPiece debugInfo_; // .debug_info const folly::StringPiece debugAbbrev_; // .debug_abbrev const folly::StringPiece debugLine_; // .debug_line const folly::StringPiece debugStr_; // .debug_str const folly::StringPiece debugAranges_; // .debug_aranges const folly::StringPiece debugRanges_; // .debug_ranges }; class Dwarf::Section { public: Section() : is64Bit_(false) {} explicit Section(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_; }; class Dwarf::LineNumberVM { public: LineNumberVM( folly::StringPiece data, folly::StringPiece compilationDirectory); 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 { auto fn = getFileName(index); return Path({}, getIncludeDirectory(fn.directoryIndex), fn.relativeName); } private: void 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 is64Bit_; folly::StringPiece data_; folly::StringPiece compilationDirectory_; // Header uint16_t version_; uint8_t minLength_; bool defaultIsStmt_; int8_t lineBase_; uint8_t lineRange_; uint8_t opcodeBase_; const uint8_t* standardOpcodeLengths_; folly::StringPiece includeDirectories_; size_t includeDirectoryCount_; folly::StringPiece fileNames_; size_t fileNameCount_; // State machine registers uint64_t address_; uint64_t file_; uint64_t line_; uint64_t column_; bool isStmt_; bool basicBlock_; bool endSequence_; bool prologueEnd_; bool epilogueBegin_; uint64_t isa_; uint64_t discriminator_; }; #endif } // namespace symbolizer } // namespace folly