1 | //===- LineTable.cpp --------------------------------------------*- C++ -*-===// |
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/LineTable.h" |
10 | #include "llvm/DebugInfo/GSYM/FileWriter.h" |
11 | #include "llvm/Support/DataExtractor.h" |
12 | |
13 | using namespace llvm; |
14 | using namespace gsym; |
15 | |
16 | enum LineTableOpCode { |
17 | EndSequence = 0x00, ///< End of the line table. |
18 | SetFile = 0x01, ///< Set LineTableRow.file_idx, don't push a row. |
19 | AdvancePC = 0x02, ///< Increment LineTableRow.address, and push a row. |
20 | AdvanceLine = 0x03, ///< Set LineTableRow.file_line, don't push a row. |
21 | FirstSpecial = 0x04, ///< All special opcodes push a row. |
22 | }; |
23 | |
24 | struct DeltaInfo { |
25 | int64_t Delta; |
26 | uint32_t Count; |
27 | DeltaInfo(int64_t D, uint32_t C) : Delta(D), Count(C) {} |
28 | }; |
29 | |
30 | inline bool operator<(const DeltaInfo &LHS, int64_t Delta) { |
31 | return LHS.Delta < Delta; |
32 | } |
33 | |
34 | static bool encodeSpecial(int64_t MinLineDelta, int64_t MaxLineDelta, |
35 | int64_t LineDelta, uint64_t AddrDelta, |
36 | uint8_t &SpecialOp) { |
37 | if (LineDelta < MinLineDelta) |
38 | return false; |
39 | if (LineDelta > MaxLineDelta) |
40 | return false; |
41 | int64_t LineRange = MaxLineDelta - MinLineDelta + 1; |
42 | int64_t AdjustedOp = ((LineDelta - MinLineDelta) + AddrDelta * LineRange); |
43 | int64_t Op = AdjustedOp + FirstSpecial; |
44 | if (Op < 0) |
45 | return false; |
46 | if (Op > 255) |
47 | return false; |
48 | SpecialOp = (uint8_t)Op; |
49 | return true; |
50 | } |
51 | |
52 | typedef std::function<bool(const LineEntry &Row)> LineEntryCallback; |
53 | |
54 | static llvm::Error (DataExtractor &Data, uint64_t BaseAddr, |
55 | LineEntryCallback const &Callback) { |
56 | uint64_t Offset = 0; |
57 | if (!Data.isValidOffset(offset: Offset)) |
58 | return createStringError(EC: std::errc::io_error, |
59 | Fmt: "0x%8.8" PRIx64 ": missing LineTable MinDelta" , Vals: Offset); |
60 | int64_t MinDelta = Data.getSLEB128(OffsetPtr: &Offset); |
61 | if (!Data.isValidOffset(offset: Offset)) |
62 | return createStringError(EC: std::errc::io_error, |
63 | Fmt: "0x%8.8" PRIx64 ": missing LineTable MaxDelta" , Vals: Offset); |
64 | int64_t MaxDelta = Data.getSLEB128(OffsetPtr: &Offset); |
65 | int64_t LineRange = MaxDelta - MinDelta + 1; |
66 | if (!Data.isValidOffset(offset: Offset)) |
67 | return createStringError(EC: std::errc::io_error, |
68 | Fmt: "0x%8.8" PRIx64 ": missing LineTable FirstLine" , Vals: Offset); |
69 | const uint32_t FirstLine = (uint32_t)Data.getULEB128(offset_ptr: &Offset); |
70 | LineEntry Row(BaseAddr, 1, FirstLine); |
71 | bool Done = false; |
72 | while (!Done) { |
73 | if (!Data.isValidOffset(offset: Offset)) |
74 | return createStringError(EC: std::errc::io_error, |
75 | Fmt: "0x%8.8" PRIx64 ": EOF found before EndSequence" , Vals: Offset); |
76 | uint8_t Op = Data.getU8(offset_ptr: &Offset); |
77 | switch (Op) { |
78 | case EndSequence: |
79 | Done = true; |
80 | break; |
81 | case SetFile: |
82 | if (!Data.isValidOffset(offset: Offset)) |
83 | return createStringError(EC: std::errc::io_error, |
84 | Fmt: "0x%8.8" PRIx64 ": EOF found before SetFile value" , |
85 | Vals: Offset); |
86 | Row.File = (uint32_t)Data.getULEB128(offset_ptr: &Offset); |
87 | break; |
88 | case AdvancePC: |
89 | if (!Data.isValidOffset(offset: Offset)) |
90 | return createStringError(EC: std::errc::io_error, |
91 | Fmt: "0x%8.8" PRIx64 ": EOF found before AdvancePC value" , |
92 | Vals: Offset); |
93 | Row.Addr += Data.getULEB128(offset_ptr: &Offset); |
94 | // If the function callback returns false, we stop parsing. |
95 | if (Callback(Row) == false) |
96 | return Error::success(); |
97 | break; |
98 | case AdvanceLine: |
99 | if (!Data.isValidOffset(offset: Offset)) |
100 | return createStringError(EC: std::errc::io_error, |
101 | Fmt: "0x%8.8" PRIx64 ": EOF found before AdvanceLine value" , |
102 | Vals: Offset); |
103 | Row.Line += Data.getSLEB128(OffsetPtr: &Offset); |
104 | break; |
105 | default: { |
106 | // A byte that contains both address and line increment. |
107 | uint8_t AdjustedOp = Op - FirstSpecial; |
108 | int64_t LineDelta = MinDelta + (AdjustedOp % LineRange); |
109 | uint64_t AddrDelta = (AdjustedOp / LineRange); |
110 | Row.Line += LineDelta; |
111 | Row.Addr += AddrDelta; |
112 | // If the function callback returns false, we stop parsing. |
113 | if (Callback(Row) == false) |
114 | return Error::success(); |
115 | break; |
116 | } |
117 | } |
118 | } |
119 | return Error::success(); |
120 | } |
121 | |
122 | llvm::Error LineTable::encode(FileWriter &Out, uint64_t BaseAddr) const { |
123 | // Users must verify the LineTable is valid prior to calling this funtion. |
124 | // We don't want to emit any LineTable objects if they are not valid since |
125 | // it will waste space in the GSYM file. |
126 | if (!isValid()) |
127 | return createStringError(EC: std::errc::invalid_argument, |
128 | Fmt: "attempted to encode invalid LineTable object" ); |
129 | |
130 | int64_t MinLineDelta = INT64_MAX; |
131 | int64_t MaxLineDelta = INT64_MIN; |
132 | std::vector<DeltaInfo> DeltaInfos; |
133 | if (Lines.size() == 1) { |
134 | MinLineDelta = 0; |
135 | MaxLineDelta = 0; |
136 | } else { |
137 | int64_t PrevLine = 1; |
138 | bool First = true; |
139 | for (const auto &line_entry : Lines) { |
140 | if (First) |
141 | First = false; |
142 | else { |
143 | int64_t LineDelta = (int64_t)line_entry.Line - PrevLine; |
144 | auto End = DeltaInfos.end(); |
145 | auto Pos = std::lower_bound(DeltaInfos.begin(), End, LineDelta); |
146 | if (Pos != End && Pos->Delta == LineDelta) |
147 | ++Pos->Count; |
148 | else |
149 | DeltaInfos.insert(position: Pos, x: DeltaInfo(LineDelta, 1)); |
150 | if (LineDelta < MinLineDelta) |
151 | MinLineDelta = LineDelta; |
152 | if (LineDelta > MaxLineDelta) |
153 | MaxLineDelta = LineDelta; |
154 | } |
155 | PrevLine = (int64_t)line_entry.Line; |
156 | } |
157 | assert(MinLineDelta <= MaxLineDelta); |
158 | } |
159 | // Set the min and max line delta intelligently based on the counts of |
160 | // the line deltas. if our range is too large. |
161 | const int64_t MaxLineRange = 14; |
162 | if (MaxLineDelta - MinLineDelta > MaxLineRange) { |
163 | uint32_t BestIndex = 0; |
164 | uint32_t BestEndIndex = 0; |
165 | uint32_t BestCount = 0; |
166 | const size_t NumDeltaInfos = DeltaInfos.size(); |
167 | for (uint32_t I = 0; I < NumDeltaInfos; ++I) { |
168 | const int64_t FirstDelta = DeltaInfos[I].Delta; |
169 | uint32_t CurrCount = 0; |
170 | uint32_t J; |
171 | for (J = I; J < NumDeltaInfos; ++J) { |
172 | auto LineRange = DeltaInfos[J].Delta - FirstDelta; |
173 | if (LineRange > MaxLineRange) |
174 | break; |
175 | CurrCount += DeltaInfos[J].Count; |
176 | } |
177 | if (CurrCount > BestCount) { |
178 | BestIndex = I; |
179 | BestEndIndex = J - 1; |
180 | BestCount = CurrCount; |
181 | } |
182 | } |
183 | MinLineDelta = DeltaInfos[BestIndex].Delta; |
184 | MaxLineDelta = DeltaInfos[BestEndIndex].Delta; |
185 | } |
186 | if (MinLineDelta == MaxLineDelta && MinLineDelta > 0 && |
187 | MinLineDelta < MaxLineRange) |
188 | MinLineDelta = 0; |
189 | assert(MinLineDelta <= MaxLineDelta); |
190 | |
191 | // Initialize the line entry state as a starting point. All line entries |
192 | // will be deltas from this. |
193 | LineEntry Prev(BaseAddr, 1, Lines.front().Line); |
194 | |
195 | // Write out the min and max line delta as signed LEB128. |
196 | Out.writeSLEB(Value: MinLineDelta); |
197 | Out.writeSLEB(Value: MaxLineDelta); |
198 | // Write out the starting line number as a unsigned LEB128. |
199 | Out.writeULEB(Value: Prev.Line); |
200 | |
201 | for (const auto &Curr : Lines) { |
202 | if (Curr.Addr < BaseAddr) |
203 | return createStringError(EC: std::errc::invalid_argument, |
204 | Fmt: "LineEntry has address 0x%" PRIx64 " which is " |
205 | "less than the function start address 0x%" |
206 | PRIx64, Vals: Curr.Addr, Vals: BaseAddr); |
207 | if (Curr.Addr < Prev.Addr) |
208 | return createStringError(EC: std::errc::invalid_argument, |
209 | Fmt: "LineEntry in LineTable not in ascending order" ); |
210 | const uint64_t AddrDelta = Curr.Addr - Prev.Addr; |
211 | int64_t LineDelta = 0; |
212 | if (Curr.Line > Prev.Line) |
213 | LineDelta = Curr.Line - Prev.Line; |
214 | else if (Prev.Line > Curr.Line) |
215 | LineDelta = -((int32_t)(Prev.Line - Curr.Line)); |
216 | |
217 | // Set the file if it doesn't match the current one. |
218 | if (Curr.File != Prev.File) { |
219 | Out.writeU8(Value: SetFile); |
220 | Out.writeULEB(Value: Curr.File); |
221 | } |
222 | |
223 | uint8_t SpecialOp; |
224 | if (encodeSpecial(MinLineDelta, MaxLineDelta, LineDelta, AddrDelta, |
225 | SpecialOp)) { |
226 | // Advance the PC and line and push a row. |
227 | Out.writeU8(Value: SpecialOp); |
228 | } else { |
229 | // We can't encode the address delta and line delta into |
230 | // a single special opcode, we must do them separately. |
231 | |
232 | // Advance the line. |
233 | if (LineDelta != 0) { |
234 | Out.writeU8(Value: AdvanceLine); |
235 | Out.writeSLEB(Value: LineDelta); |
236 | } |
237 | |
238 | // Advance the PC and push a row. |
239 | Out.writeU8(Value: AdvancePC); |
240 | Out.writeULEB(Value: AddrDelta); |
241 | } |
242 | Prev = Curr; |
243 | } |
244 | Out.writeU8(Value: EndSequence); |
245 | return Error::success(); |
246 | } |
247 | |
248 | // Parse all line table entries into the "LineTable" vector. We can |
249 | // cache the results of this if needed, or we can call LineTable::lookup() |
250 | // below. |
251 | llvm::Expected<LineTable> LineTable::(DataExtractor &Data, |
252 | uint64_t BaseAddr) { |
253 | LineTable LT; |
254 | llvm::Error Err = parse(Data, BaseAddr, Callback: [&](const LineEntry &Row) -> bool { |
255 | LT.Lines.push_back(x: Row); |
256 | return true; // Keep parsing by returning true. |
257 | }); |
258 | if (Err) |
259 | return std::move(Err); |
260 | return LT; |
261 | } |
262 | // Parse the line table on the fly and find the row we are looking for. |
263 | // We will need to determine if we need to cache the line table by calling |
264 | // LineTable::parseAllEntries(...) or just call this function each time. |
265 | // There is a CPU vs memory tradeoff we will need to determined. |
266 | Expected<LineEntry> LineTable::(DataExtractor &Data, uint64_t BaseAddr, uint64_t Addr) { |
267 | LineEntry Result; |
268 | llvm::Error Err = parse(Data, BaseAddr, |
269 | Callback: [Addr, &Result](const LineEntry &Row) -> bool { |
270 | if (Addr < Row.Addr) |
271 | return false; // Stop parsing, result contains the line table row! |
272 | Result = Row; |
273 | return true; // Keep parsing till we find the right row. |
274 | }); |
275 | if (Err) |
276 | return std::move(Err); |
277 | if (Result.isValid()) |
278 | return Result; |
279 | return createStringError(EC: std::errc::invalid_argument, |
280 | Fmt: "address 0x%" PRIx64 " is not in the line table" , |
281 | Vals: Addr); |
282 | } |
283 | |
284 | raw_ostream &llvm::gsym::operator<<(raw_ostream &OS, const LineTable <) { |
285 | for (const auto &LineEntry : LT) |
286 | OS << LineEntry << '\n'; |
287 | return OS; |
288 | } |
289 | |