294 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
//===- 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;
 | 
						|
}
 |