| //===- 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; | |
| } |