| //===- LineTable.cpp --------------------------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/DebugInfo/GSYM/LineTable.h" |
| #include "llvm/DebugInfo/GSYM/FileWriter.h" |
| #include "llvm/Support/DataExtractor.h" |
| |
| using namespace llvm; |
| using namespace gsym; |
| |
| enum LineTableOpCode { |
| EndSequence = 0x00, ///< End of the line table. |
| SetFile = 0x01, ///< Set LineTableRow.file_idx, don't push a row. |
| AdvancePC = 0x02, ///< Increment LineTableRow.address, and push a row. |
| AdvanceLine = 0x03, ///< Set LineTableRow.file_line, don't push a row. |
| FirstSpecial = 0x04, ///< All special opcodes push a row. |
| }; |
| |
| struct DeltaInfo { |
| int64_t Delta; |
| uint32_t Count; |
| DeltaInfo(int64_t D, uint32_t C) : Delta(D), Count(C) {} |
| }; |
| |
| inline bool operator<(const DeltaInfo &LHS, int64_t Delta) { |
| return LHS.Delta < Delta; |
| } |
| |
| static bool encodeSpecial(int64_t MinLineDelta, int64_t MaxLineDelta, |
| int64_t LineDelta, uint64_t AddrDelta, |
| uint8_t &SpecialOp) { |
| if (LineDelta < MinLineDelta) |
| return false; |
| if (LineDelta > MaxLineDelta) |
| return false; |
| int64_t LineRange = MaxLineDelta - MinLineDelta + 1; |
| int64_t AdjustedOp = ((LineDelta - MinLineDelta) + AddrDelta * LineRange); |
| int64_t Op = AdjustedOp + FirstSpecial; |
| if (Op < 0) |
| return false; |
| if (Op > 255) |
| return false; |
| SpecialOp = (uint8_t)Op; |
| return true; |
| } |
| |
| typedef std::function<bool(const LineEntry &Row)> LineEntryCallback; |
| |
| static llvm::Error parse(DataExtractor &Data, uint64_t BaseAddr, |
| LineEntryCallback const &Callback) { |
| uint64_t Offset = 0; |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": missing LineTable MinDelta", Offset); |
| int64_t MinDelta = Data.getSLEB128(&Offset); |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": missing LineTable MaxDelta", Offset); |
| int64_t MaxDelta = Data.getSLEB128(&Offset); |
| int64_t LineRange = MaxDelta - MinDelta + 1; |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": missing LineTable FirstLine", Offset); |
| const uint32_t FirstLine = (uint32_t)Data.getULEB128(&Offset); |
| LineEntry Row(BaseAddr, 1, FirstLine); |
| bool Done = false; |
| while (!Done) { |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": EOF found before EndSequence", Offset); |
| uint8_t Op = Data.getU8(&Offset); |
| switch (Op) { |
| case EndSequence: |
| Done = true; |
| break; |
| case SetFile: |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": EOF found before SetFile value", |
| Offset); |
| Row.File = (uint32_t)Data.getULEB128(&Offset); |
| break; |
| case AdvancePC: |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": EOF found before AdvancePC value", |
| Offset); |
| Row.Addr += Data.getULEB128(&Offset); |
| // If the function callback returns false, we stop parsing. |
| if (Callback(Row) == false) |
| return Error::success(); |
| break; |
| case AdvanceLine: |
| if (!Data.isValidOffset(Offset)) |
| return createStringError(std::errc::io_error, |
| "0x%8.8" PRIx64 ": EOF found before AdvanceLine value", |
| Offset); |
| Row.Line += Data.getSLEB128(&Offset); |
| break; |
| default: { |
| // A byte that contains both address and line increment. |
| uint8_t AdjustedOp = Op - FirstSpecial; |
| int64_t LineDelta = MinDelta + (AdjustedOp % LineRange); |
| uint64_t AddrDelta = (AdjustedOp / LineRange); |
| Row.Line += LineDelta; |
| Row.Addr += AddrDelta; |
| // If the function callback returns false, we stop parsing. |
| if (Callback(Row) == false) |
| return Error::success(); |
| break; |
| } |
| } |
| } |
| return Error::success(); |
| } |
| |
| llvm::Error LineTable::encode(FileWriter &Out, uint64_t BaseAddr) const { |
| // Users must verify the LineTable is valid prior to calling this funtion. |
| // We don't want to emit any LineTable objects if they are not valid since |
| // it will waste space in the GSYM file. |
| if (!isValid()) |
| return createStringError(std::errc::invalid_argument, |
| "attempted to encode invalid LineTable object"); |
| |
| int64_t MinLineDelta = INT64_MAX; |
| int64_t MaxLineDelta = INT64_MIN; |
| std::vector<DeltaInfo> DeltaInfos; |
| if (Lines.size() == 1) { |
| MinLineDelta = 0; |
| MaxLineDelta = 0; |
| } else { |
| int64_t PrevLine = 1; |
| bool First = true; |
| for (const auto &line_entry : Lines) { |
| if (First) |
| First = false; |
| else { |
| int64_t LineDelta = (int64_t)line_entry.Line - PrevLine; |
| auto End = DeltaInfos.end(); |
| auto Pos = std::lower_bound(DeltaInfos.begin(), End, LineDelta); |
| if (Pos != End && Pos->Delta == LineDelta) |
| ++Pos->Count; |
| else |
| DeltaInfos.insert(Pos, DeltaInfo(LineDelta, 1)); |
| if (LineDelta < MinLineDelta) |
| MinLineDelta = LineDelta; |
| if (LineDelta > MaxLineDelta) |
| MaxLineDelta = LineDelta; |
| } |
| PrevLine = (int64_t)line_entry.Line; |
| } |
| assert(MinLineDelta <= MaxLineDelta); |
| } |
| // Set the min and max line delta intelligently based on the counts of |
| // the line deltas. if our range is too large. |
| const int64_t MaxLineRange = 14; |
| if (MaxLineDelta - MinLineDelta > MaxLineRange) { |
| uint32_t BestIndex = 0; |
| uint32_t BestEndIndex = 0; |
| uint32_t BestCount = 0; |
| const size_t NumDeltaInfos = DeltaInfos.size(); |
| for (uint32_t I = 0; I < NumDeltaInfos; ++I) { |
| const int64_t FirstDelta = DeltaInfos[I].Delta; |
| uint32_t CurrCount = 0; |
| uint32_t J; |
| for (J = I; J < NumDeltaInfos; ++J) { |
| auto LineRange = DeltaInfos[J].Delta - FirstDelta; |
| if (LineRange > MaxLineRange) |
| break; |
| CurrCount += DeltaInfos[J].Count; |
| } |
| if (CurrCount > BestCount) { |
| BestIndex = I; |
| BestEndIndex = J - 1; |
| BestCount = CurrCount; |
| } |
| } |
| MinLineDelta = DeltaInfos[BestIndex].Delta; |
| MaxLineDelta = DeltaInfos[BestEndIndex].Delta; |
| } |
| if (MinLineDelta == MaxLineDelta && MinLineDelta > 0 && |
| MinLineDelta < MaxLineRange) |
| MinLineDelta = 0; |
| assert(MinLineDelta <= MaxLineDelta); |
| |
| // Initialize the line entry state as a starting point. All line entries |
| // will be deltas from this. |
| LineEntry Prev(BaseAddr, 1, Lines.front().Line); |
| |
| // Write out the min and max line delta as signed LEB128. |
| Out.writeSLEB(MinLineDelta); |
| Out.writeSLEB(MaxLineDelta); |
| // Write out the starting line number as a unsigned LEB128. |
| Out.writeULEB(Prev.Line); |
| |
| for (const auto &Curr : Lines) { |
| if (Curr.Addr < BaseAddr) |
| return createStringError(std::errc::invalid_argument, |
| "LineEntry has address 0x%" PRIx64 " which is " |
| "less than the function start address 0x%" |
| PRIx64, Curr.Addr, BaseAddr); |
| if (Curr.Addr < Prev.Addr) |
| return createStringError(std::errc::invalid_argument, |
| "LineEntry in LineTable not in ascending order"); |
| const uint64_t AddrDelta = Curr.Addr - Prev.Addr; |
| int64_t LineDelta = 0; |
| if (Curr.Line > Prev.Line) |
| LineDelta = Curr.Line - Prev.Line; |
| else if (Prev.Line > Curr.Line) |
| LineDelta = -((int32_t)(Prev.Line - Curr.Line)); |
| |
| // Set the file if it doesn't match the current one. |
| if (Curr.File != Prev.File) { |
| Out.writeU8(SetFile); |
| Out.writeULEB(Curr.File); |
| } |
| |
| uint8_t SpecialOp; |
| if (encodeSpecial(MinLineDelta, MaxLineDelta, LineDelta, AddrDelta, |
| SpecialOp)) { |
| // Advance the PC and line and push a row. |
| Out.writeU8(SpecialOp); |
| } else { |
| // We can't encode the address delta and line delta into |
| // a single special opcode, we must do them separately. |
| |
| // Advance the line. |
| if (LineDelta != 0) { |
| Out.writeU8(AdvanceLine); |
| Out.writeSLEB(LineDelta); |
| } |
| |
| // Advance the PC and push a row. |
| Out.writeU8(AdvancePC); |
| Out.writeULEB(AddrDelta); |
| } |
| Prev = Curr; |
| } |
| Out.writeU8(EndSequence); |
| return Error::success(); |
| } |
| |
| // Parse all line table entries into the "LineTable" vector. We can |
| // cache the results of this if needed, or we can call LineTable::lookup() |
| // below. |
| llvm::Expected<LineTable> LineTable::decode(DataExtractor &Data, |
| uint64_t BaseAddr) { |
| LineTable LT; |
| llvm::Error Err = parse(Data, BaseAddr, [&](const LineEntry &Row) -> bool { |
| LT.Lines.push_back(Row); |
| return true; // Keep parsing by returning true. |
| }); |
| if (Err) |
| return std::move(Err); |
| return LT; |
| } |
| // Parse the line table on the fly and find the row we are looking for. |
| // We will need to determine if we need to cache the line table by calling |
| // LineTable::parseAllEntries(...) or just call this function each time. |
| // There is a CPU vs memory tradeoff we will need to determined. |
| Expected<LineEntry> LineTable::lookup(DataExtractor &Data, uint64_t BaseAddr, uint64_t Addr) { |
| LineEntry Result; |
| llvm::Error Err = parse(Data, BaseAddr, |
| [Addr, &Result](const LineEntry &Row) -> bool { |
| if (Addr < Row.Addr) |
| return false; // Stop parsing, result contains the line table row! |
| Result = Row; |
| if (Addr == Row.Addr) { |
| // Stop parsing, this is the row we are looking for since the address |
| // matches. |
| return false; |
| } |
| return true; // Keep parsing till we find the right row. |
| }); |
| if (Err) |
| return std::move(Err); |
| if (Result.isValid()) |
| return Result; |
| return createStringError(std::errc::invalid_argument, |
| "address 0x%" PRIx64 " is not in the line table", |
| Addr); |
| } |
| |
| raw_ostream &llvm::gsym::operator<<(raw_ostream &OS, const LineTable <) { |
| for (const auto &LineEntry : LT) |
| OS << LineEntry << '\n'; |
| return OS; |
| } |