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