aae1b3a5dd5e4857e266416c60d79d31493868d6
[folly.git] / folly / experimental / symbolizer / Dwarf.h
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 // DWARF record parser
18
19 #pragma once
20
21 #include <boost/variant.hpp>
22
23 #include <folly/Range.h>
24 #include <folly/experimental/symbolizer/Elf.h>
25
26 namespace folly {
27 namespace symbolizer {
28
29 /**
30  * DWARF record parser.
31  *
32  * We only implement enough DWARF functionality to convert from PC address
33  * to file and line number information.
34  *
35  * This means (although they're not part of the public API of this class), we
36  * can parse Debug Information Entries (DIEs), abbreviations, attributes (of
37  * all forms), and we can interpret bytecode for the line number VM.
38  *
39  * We can interpret DWARF records of version 2, 3, or 4, although we don't
40  * actually support many of the version 4 features (such as VLIW, multiple
41  * operations per instruction)
42  *
43  * Note that the DWARF record parser does not allocate heap memory at all.
44  * This is on purpose: you can use the parser from
45  * memory-constrained situations (such as an exception handler for
46  * std::out_of_memory)  If it weren't for this requirement, some things would
47  * be much simpler: the Path class would be unnecessary and would be replaced
48  * with a std::string; the list of file names in the line number VM would be
49  * kept as a vector of strings instead of re-executing the program to look for
50  * DW_LNE_define_file instructions, etc.
51  */
52 class Dwarf {
53   // Note that Dwarf uses (and returns) StringPiece a lot.
54   // The StringPieces point within sections in the ELF file, and so will
55   // be live for as long as the passed-in ElfFile is live.
56  public:
57   /** Create a DWARF parser around an ELF file. */
58   explicit Dwarf(const ElfFile* elf);
59
60   /**
61    * Represent a file path a s collection of three parts (base directory,
62    * subdirectory, and file).
63    */
64   class Path {
65    public:
66     Path() { }
67
68     Path(folly::StringPiece baseDir, folly::StringPiece subDir,
69          folly::StringPiece file);
70
71     folly::StringPiece baseDir() const { return baseDir_; }
72     folly::StringPiece subDir() const { return subDir_; }
73     folly::StringPiece file() const { return file_; }
74
75     size_t size() const;
76
77     /**
78      * Copy the Path to a buffer of size bufSize.
79      *
80      * toBuffer behaves like snprintf: It will always null-terminate the
81      * buffer (so it will copy at most bufSize-1 bytes), and it will return
82      * the number of bytes that would have been written if there had been
83      * enough room, so, if toBuffer returns a value >= bufSize, the output
84      * was truncated.
85      */
86     size_t toBuffer(char* buf, size_t bufSize) const;
87
88     void toString(std::string& dest) const;
89     std::string toString() const {
90       std::string s;
91       toString(s);
92       return s;
93     }
94
95     // TODO(tudorb): Implement operator==, operator!=; not as easy as it
96     // seems as the same path can be represented in multiple ways
97    private:
98     folly::StringPiece baseDir_;
99     folly::StringPiece subDir_;
100     folly::StringPiece file_;
101   };
102
103   enum class LocationInfoMode {
104     // Don't resolve location info.
105     DISABLED,
106     // Perform CU lookup using .debug_aranges (might be incomplete).
107     FAST,
108     // Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
109     FULL,
110   };
111
112   struct LocationInfo {
113     bool hasMainFile = false;
114     Path mainFile;
115
116     bool hasFileAndLine = false;
117     Path file;
118     uint64_t line = 0;
119   };
120
121   /**
122    * Find the file and line number information corresponding to address.
123    */
124   bool findAddress(uintptr_t address,
125                    LocationInfo& info,
126                    LocationInfoMode mode) const;
127
128  private:
129   static bool findDebugInfoOffset(uintptr_t address,
130                                   StringPiece aranges,
131                                   uint64_t& offset);
132
133   void init();
134   bool findLocation(uintptr_t address,
135                     StringPiece& infoEntry,
136                     LocationInfo& info) const;
137
138   const ElfFile* elf_;
139
140   // DWARF section made up of chunks, each prefixed with a length header.
141   // The length indicates whether the chunk is DWARF-32 or DWARF-64, which
142   // guides interpretation of "section offset" records.
143   // (yes, DWARF-32 and DWARF-64 sections may coexist in the same file)
144   class Section {
145    public:
146     Section() : is64Bit_(false) { }
147
148     explicit Section(folly::StringPiece d);
149
150     // Return next chunk, if any; the 4- or 12-byte length was already
151     // parsed and isn't part of the chunk.
152     bool next(folly::StringPiece& chunk);
153
154     // Is the current chunk 64 bit?
155     bool is64Bit() const { return is64Bit_; }
156
157    private:
158     // Yes, 32- and 64- bit sections may coexist.  Yikes!
159     bool is64Bit_;
160     folly::StringPiece data_;
161   };
162
163   // Abbreviation for a Debugging Information Entry.
164   struct DIEAbbreviation {
165     uint64_t code;
166     uint64_t tag;
167     bool hasChildren;
168
169     struct Attribute {
170       uint64_t name;
171       uint64_t form;
172     };
173
174     folly::StringPiece attributes;
175   };
176
177   // Interpreter for the line number bytecode VM
178   class LineNumberVM {
179    public:
180     LineNumberVM(folly::StringPiece data,
181                  folly::StringPiece compilationDirectory);
182
183     bool findAddress(uintptr_t address, Path& file, uint64_t& line);
184
185    private:
186     void init();
187     void reset();
188
189     // Execute until we commit one new row to the line number matrix
190     bool next(folly::StringPiece& program);
191     enum StepResult {
192       CONTINUE,  // Continue feeding opcodes
193       COMMIT,    // Commit new <address, file, line> tuple
194       END,       // End of sequence
195     };
196     // Execute one opcode
197     StepResult step(folly::StringPiece& program);
198
199     struct FileName {
200       folly::StringPiece relativeName;
201       // 0 = current compilation directory
202       // otherwise, 1-based index in the list of include directories
203       uint64_t directoryIndex;
204     };
205     // Read one FileName object, advance sp
206     static bool readFileName(folly::StringPiece& sp, FileName& fn);
207
208     // Get file name at given index; may be in the initial table
209     // (fileNames_) or defined using DW_LNE_define_file (and we reexecute
210     // enough of the program to find it, if so)
211     FileName getFileName(uint64_t index) const;
212
213     // Get include directory at given index
214     folly::StringPiece getIncludeDirectory(uint64_t index) const;
215
216     // Execute opcodes until finding a DW_LNE_define_file and return true;
217     // return file at the end.
218     bool nextDefineFile(folly::StringPiece& program, FileName& fn) const;
219
220     // Initialization
221     bool is64Bit_;
222     folly::StringPiece data_;
223     folly::StringPiece compilationDirectory_;
224
225     // Header
226     uint16_t version_;
227     uint8_t minLength_;
228     bool defaultIsStmt_;
229     int8_t lineBase_;
230     uint8_t lineRange_;
231     uint8_t opcodeBase_;
232     const uint8_t* standardOpcodeLengths_;
233
234     folly::StringPiece includeDirectories_;
235     size_t includeDirectoryCount_;
236
237     folly::StringPiece fileNames_;
238     size_t fileNameCount_;
239
240     // State machine registers
241     uint64_t address_;
242     uint64_t file_;
243     uint64_t line_;
244     uint64_t column_;
245     bool isStmt_;
246     bool basicBlock_;
247     bool endSequence_;
248     bool prologueEnd_;
249     bool epilogueBegin_;
250     uint64_t isa_;
251     uint64_t discriminator_;
252   };
253
254   // Read an abbreviation from a StringPiece, return true if at end; advance sp
255   static bool readAbbreviation(folly::StringPiece& sp, DIEAbbreviation& abbr);
256
257   // Get abbreviation corresponding to a code, in the chunk starting at
258   // offset in the .debug_abbrev section
259   DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
260
261   // Read one attribute <name, form> pair, advance sp; returns <0, 0> at end.
262   static DIEAbbreviation::Attribute readAttribute(folly::StringPiece& sp);
263
264   // Read one attribute value, advance sp
265   typedef boost::variant<uint64_t, folly::StringPiece> AttributeValue;
266   AttributeValue readAttributeValue(
267       folly::StringPiece& sp,
268       uint64_t form,
269       bool is64Bit) const;
270
271   // Get an ELF section by name, return true if found
272   bool getSection(const char* name, folly::StringPiece* section) const;
273
274   // Get a string from the .debug_str section
275   folly::StringPiece getStringFromStringSection(uint64_t offset) const;
276
277   folly::StringPiece info_;       // .debug_info
278   folly::StringPiece abbrev_;     // .debug_abbrev
279   folly::StringPiece aranges_;    // .debug_aranges
280   folly::StringPiece line_;       // .debug_line
281   folly::StringPiece strings_;    // .debug_str
282 };
283
284 inline std::ostream& operator<<(std::ostream& out, const Dwarf::Path& path) {
285   return out << path.toString();
286 }
287
288 }  // namespace symbolizer
289 }  // namespace folly