1 //===- FunctionInfo.cpp ---------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "llvm/DebugInfo/GSYM/FunctionInfo.h"
10 #include "llvm/DebugInfo/GSYM/FileWriter.h"
11 #include "llvm/DebugInfo/GSYM/GsymReader.h"
12 #include "llvm/DebugInfo/GSYM/LineTable.h"
13 #include "llvm/DebugInfo/GSYM/InlineInfo.h"
14 #include "llvm/Support/DataExtractor.h"
15 #include <optional>
16 
17 using namespace llvm;
18 using namespace gsym;
19 
20 /// FunctionInfo information type that is used to encode the optional data
21 /// that is associated with a FunctionInfo object.
22 enum InfoType : uint32_t {
23   EndOfList = 0u,
24   LineTableInfo = 1u,
25   InlineInfo = 2u
26 };
27 
28 raw_ostream &llvm::gsym::operator<<(raw_ostream &OS, const FunctionInfo &FI) {
29   OS << FI.Range << ": " << "Name=" << HEX32(FI.Name) << '\n';
30   if (FI.OptLineTable)
31     OS << FI.OptLineTable << '\n';
32   if (FI.Inline)
33     OS << FI.Inline << '\n';
34   return OS;
35 }
36 
37 llvm::Expected<FunctionInfo> FunctionInfo::decode(DataExtractor &Data,
38                                                   uint64_t BaseAddr) {
39   FunctionInfo FI;
40   uint64_t Offset = 0;
41   if (!Data.isValidOffsetForDataOfSize(Offset, 4))
42     return createStringError(std::errc::io_error,
43         "0x%8.8" PRIx64 ": missing FunctionInfo Size", Offset);
44   FI.Range = {BaseAddr, BaseAddr + Data.getU32(&Offset)};
45   if (!Data.isValidOffsetForDataOfSize(Offset, 4))
46     return createStringError(std::errc::io_error,
47         "0x%8.8" PRIx64 ": missing FunctionInfo Name", Offset);
48   FI.Name = Data.getU32(&Offset);
49   if (FI.Name == 0)
50     return createStringError(std::errc::io_error,
51         "0x%8.8" PRIx64 ": invalid FunctionInfo Name value 0x%8.8x",
52         Offset - 4, FI.Name);
53   bool Done = false;
54   while (!Done) {
55     if (!Data.isValidOffsetForDataOfSize(Offset, 4))
56       return createStringError(std::errc::io_error,
57           "0x%8.8" PRIx64 ": missing FunctionInfo InfoType value", Offset);
58     const uint32_t IT = Data.getU32(&Offset);
59     if (!Data.isValidOffsetForDataOfSize(Offset, 4))
60       return createStringError(std::errc::io_error,
61           "0x%8.8" PRIx64 ": missing FunctionInfo InfoType length", Offset);
62     const uint32_t InfoLength = Data.getU32(&Offset);
63     if (!Data.isValidOffsetForDataOfSize(Offset, InfoLength))
64       return createStringError(std::errc::io_error,
65           "0x%8.8" PRIx64 ": missing FunctionInfo data for InfoType %u",
66           Offset, IT);
67     DataExtractor InfoData(Data.getData().substr(Offset, InfoLength),
68                            Data.isLittleEndian(),
69                            Data.getAddressSize());
70     switch (IT) {
71       case InfoType::EndOfList:
72         Done = true;
73         break;
74 
75       case InfoType::LineTableInfo:
76         if (Expected<LineTable> LT = LineTable::decode(InfoData, BaseAddr))
77           FI.OptLineTable = std::move(LT.get());
78         else
79           return LT.takeError();
80         break;
81 
82       case InfoType::InlineInfo:
83         if (Expected<InlineInfo> II = InlineInfo::decode(InfoData, BaseAddr))
84           FI.Inline = std::move(II.get());
85         else
86           return II.takeError();
87         break;
88 
89       default:
90         return createStringError(std::errc::io_error,
91                                  "0x%8.8" PRIx64 ": unsupported InfoType %u",
92                                  Offset-8, IT);
93     }
94     Offset += InfoLength;
95   }
96   return std::move(FI);
97 }
98 
99 uint64_t FunctionInfo::cacheEncoding() {
100   EncodingCache.clear();
101   if (!isValid())
102     return 0;
103   raw_svector_ostream OutStrm(EncodingCache);
104   FileWriter FW(OutStrm, support::endian::system_endianness());
105   llvm::Expected<uint64_t> Result = encode(FW);
106   if (!Result) {
107     EncodingCache.clear();
108     consumeError(Result.takeError());
109     return 0;
110   }
111   return EncodingCache.size();
112 }
113 
114 llvm::Expected<uint64_t> FunctionInfo::encode(FileWriter &Out) const {
115   if (!isValid())
116     return createStringError(std::errc::invalid_argument,
117         "attempted to encode invalid FunctionInfo object");
118   // Align FunctionInfo data to a 4 byte alignment.
119   Out.alignTo(4);
120   const uint64_t FuncInfoOffset = Out.tell();
121   // Check if we have already encoded this function info into EncodingCache.
122   // This will be non empty when creating segmented GSYM files as we need to
123   // precompute exactly how big FunctionInfo objects encode into so we can
124   // accurately make segments of a specific size.
125   if (!EncodingCache.empty() &&
126       support::endian::system_endianness() == Out.getByteOrder()) {
127     // We already encoded this object, just write out the bytes.
128     Out.writeData(llvm::ArrayRef<uint8_t>((const uint8_t *)EncodingCache.data(),
129                                           EncodingCache.size()));
130     return FuncInfoOffset;
131   }
132   // Write the size in bytes of this function as a uint32_t. This can be zero
133   // if we just have a symbol from a symbol table and that symbol has no size.
134   Out.writeU32(size());
135   // Write the name of this function as a uint32_t string table offset.
136   Out.writeU32(Name);
137 
138   if (OptLineTable) {
139     Out.writeU32(InfoType::LineTableInfo);
140     // Write a uint32_t length as zero for now, we will fix this up after
141     // writing the LineTable out with the number of bytes that were written.
142     Out.writeU32(0);
143     const auto StartOffset = Out.tell();
144     llvm::Error err = OptLineTable->encode(Out, Range.start());
145     if (err)
146       return std::move(err);
147     const auto Length = Out.tell() - StartOffset;
148     if (Length > UINT32_MAX)
149         return createStringError(std::errc::invalid_argument,
150             "LineTable length is greater than UINT32_MAX");
151     // Fixup the size of the LineTable data with the correct size.
152     Out.fixup32(static_cast<uint32_t>(Length), StartOffset - 4);
153   }
154 
155   // Write out the inline function info if we have any and if it is valid.
156   if (Inline) {
157     Out.writeU32(InfoType::InlineInfo);
158     // Write a uint32_t length as zero for now, we will fix this up after
159     // writing the LineTable out with the number of bytes that were written.
160     Out.writeU32(0);
161     const auto StartOffset = Out.tell();
162     llvm::Error err = Inline->encode(Out, Range.start());
163     if (err)
164       return std::move(err);
165     const auto Length = Out.tell() - StartOffset;
166     if (Length > UINT32_MAX)
167         return createStringError(std::errc::invalid_argument,
168             "InlineInfo length is greater than UINT32_MAX");
169     // Fixup the size of the InlineInfo data with the correct size.
170     Out.fixup32(static_cast<uint32_t>(Length), StartOffset - 4);
171   }
172 
173   // Terminate the data chunks with and end of list with zero size
174   Out.writeU32(InfoType::EndOfList);
175   Out.writeU32(0);
176   return FuncInfoOffset;
177 }
178 
179 
180 llvm::Expected<LookupResult> FunctionInfo::lookup(DataExtractor &Data,
181                                                   const GsymReader &GR,
182                                                   uint64_t FuncAddr,
183                                                   uint64_t Addr) {
184   LookupResult LR;
185   LR.LookupAddr = Addr;
186   uint64_t Offset = 0;
187   LR.FuncRange = {FuncAddr, FuncAddr + Data.getU32(&Offset)};
188   uint32_t NameOffset = Data.getU32(&Offset);
189   // The "lookup" functions doesn't report errors as accurately as the "decode"
190   // function as it is meant to be fast. For more accurage errors we could call
191   // "decode".
192   if (!Data.isValidOffset(Offset))
193     return createStringError(std::errc::io_error,
194                               "FunctionInfo data is truncated");
195   // This function will be called with the result of a binary search of the
196   // address table, we must still make sure the address does not fall into a
197   // gap between functions are after the last function.
198   if (LR.FuncRange.size() > 0 && !LR.FuncRange.contains(Addr))
199     return createStringError(std::errc::io_error,
200         "address 0x%" PRIx64 " is not in GSYM", Addr);
201 
202   if (NameOffset == 0)
203     return createStringError(std::errc::io_error,
204         "0x%8.8" PRIx64 ": invalid FunctionInfo Name value 0x00000000",
205         Offset - 4);
206   LR.FuncName = GR.getString(NameOffset);
207   bool Done = false;
208   std::optional<LineEntry> LineEntry;
209   std::optional<DataExtractor> InlineInfoData;
210   while (!Done) {
211     if (!Data.isValidOffsetForDataOfSize(Offset, 8))
212       return createStringError(std::errc::io_error,
213                                "FunctionInfo data is truncated");
214     const uint32_t IT = Data.getU32(&Offset);
215     const uint32_t InfoLength = Data.getU32(&Offset);
216     const StringRef InfoBytes = Data.getData().substr(Offset, InfoLength);
217     if (InfoLength != InfoBytes.size())
218       return createStringError(std::errc::io_error,
219                                "FunctionInfo data is truncated");
220     DataExtractor InfoData(InfoBytes, Data.isLittleEndian(),
221                            Data.getAddressSize());
222     switch (IT) {
223       case InfoType::EndOfList:
224         Done = true;
225         break;
226 
227       case InfoType::LineTableInfo:
228         if (auto ExpectedLE = LineTable::lookup(InfoData, FuncAddr, Addr))
229           LineEntry = ExpectedLE.get();
230         else
231           return ExpectedLE.takeError();
232         break;
233 
234       case InfoType::InlineInfo:
235         // We will parse the inline info after our line table, but only if
236         // we have a line entry.
237         InlineInfoData = InfoData;
238         break;
239 
240       default:
241         break;
242     }
243     Offset += InfoLength;
244   }
245 
246   if (!LineEntry) {
247     // We don't have a valid line entry for our address, fill in our source
248     // location as best we can and return.
249     SourceLocation SrcLoc;
250     SrcLoc.Name = LR.FuncName;
251     SrcLoc.Offset = Addr - FuncAddr;
252     LR.Locations.push_back(SrcLoc);
253     return LR;
254   }
255 
256   std::optional<FileEntry> LineEntryFile = GR.getFile(LineEntry->File);
257   if (!LineEntryFile)
258     return createStringError(std::errc::invalid_argument,
259                               "failed to extract file[%" PRIu32 "]",
260                               LineEntry->File);
261 
262   SourceLocation SrcLoc;
263   SrcLoc.Name = LR.FuncName;
264   SrcLoc.Offset = Addr - FuncAddr;
265   SrcLoc.Dir = GR.getString(LineEntryFile->Dir);
266   SrcLoc.Base = GR.getString(LineEntryFile->Base);
267   SrcLoc.Line = LineEntry->Line;
268   LR.Locations.push_back(SrcLoc);
269   // If we don't have inline information, we are done.
270   if (!InlineInfoData)
271     return LR;
272   // We have inline information. Try to augment the lookup result with this
273   // data.
274   llvm::Error Err = InlineInfo::lookup(GR, *InlineInfoData, FuncAddr, Addr,
275                                        LR.Locations);
276   if (Err)
277     return std::move(Err);
278   return LR;
279 }
280