//===- DWARFDebugAddr.cpp -------------------------------------------------===//
//
// 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/DWARF/DWARFDebugAddr.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"

using namespace llvm;

void DWARFDebugAddrTable::clear() {
  HeaderData = {};
  Addrs.clear();
  invalidateLength();
}

Error DWARFDebugAddrTable::extract(DWARFDataExtractor Data,
                                   uint32_t *OffsetPtr,
                                   uint16_t Version,
                                   uint8_t AddrSize,
                                   std::function<void(Error)> WarnCallback) {
  clear();
  HeaderOffset = *OffsetPtr;
  // Read and verify the length field.
  if (!Data.isValidOffsetForDataOfSize(*OffsetPtr, sizeof(uint32_t)))
    return createStringError(errc::invalid_argument,
                       "section is not large enough to contain a "
                       ".debug_addr table length at offset 0x%"
                       PRIx32, *OffsetPtr);
  uint16_t UnitVersion;
  if (Version == 0) {
    WarnCallback(createStringError(errc::invalid_argument,
                       "DWARF version is not defined in CU,"
                       " assuming version 5"));
    UnitVersion = 5;
  } else {
    UnitVersion = Version;
  }
  // TODO: Add support for DWARF64.
  Format = dwarf::DwarfFormat::DWARF32;
  if (UnitVersion >= 5) {
    HeaderData.Length = Data.getU32(OffsetPtr);
    if (HeaderData.Length == 0xffffffffu) {
      invalidateLength();
      return createStringError(errc::not_supported,
          "DWARF64 is not supported in .debug_addr at offset 0x%" PRIx32,
          HeaderOffset);
    }
    if (HeaderData.Length + sizeof(uint32_t) < sizeof(Header)) {
      uint32_t TmpLength = getLength();
      invalidateLength();
      return createStringError(errc::invalid_argument,
                         ".debug_addr table at offset 0x%" PRIx32
                         " has too small length (0x%" PRIx32
                         ") to contain a complete header",
                         HeaderOffset, TmpLength);
    }
    uint32_t End = HeaderOffset + getLength();
    if (!Data.isValidOffsetForDataOfSize(HeaderOffset, End - HeaderOffset)) {
      uint32_t TmpLength = getLength();
      invalidateLength();
      return createStringError(errc::invalid_argument,
          "section is not large enough to contain a .debug_addr table "
          "of length 0x%" PRIx32 " at offset 0x%" PRIx32,
          TmpLength, HeaderOffset);
    }

    HeaderData.Version = Data.getU16(OffsetPtr);
    HeaderData.AddrSize = Data.getU8(OffsetPtr);
    HeaderData.SegSize = Data.getU8(OffsetPtr);
    DataSize = getDataSize();
  } else {
    HeaderData.Version = UnitVersion;
    HeaderData.AddrSize = AddrSize;
    // TODO: Support for non-zero SegSize.
    HeaderData.SegSize = 0;
    DataSize = Data.size();
  }

  // Perform basic validation of the remaining header fields.

  // We support DWARF version 5 for now as well as pre-DWARF5
  // implementations of .debug_addr table, which doesn't contain a header
  // and consists only of a series of addresses.
  if (HeaderData.Version > 5) {
    return createStringError(errc::not_supported, "version %" PRIu16
        " of .debug_addr section at offset 0x%" PRIx32 " is not supported",
        HeaderData.Version, HeaderOffset);
  }
  // FIXME: For now we just treat version mismatch as an error,
  // however the correct way to associate a .debug_addr table
  // with a .debug_info table is to look at the DW_AT_addr_base
  // attribute in the info table.
  if (HeaderData.Version != UnitVersion)
    return createStringError(errc::invalid_argument,
                       ".debug_addr table at offset 0x%" PRIx32
                       " has version %" PRIu16
                       " which is different from the version suggested"
                       " by the DWARF unit header: %" PRIu16,
                       HeaderOffset, HeaderData.Version, UnitVersion);
  if (HeaderData.AddrSize != 4 && HeaderData.AddrSize != 8)
    return createStringError(errc::not_supported,
                       ".debug_addr table at offset 0x%" PRIx32
                       " has unsupported address size %" PRIu8,
                       HeaderOffset, HeaderData.AddrSize);
  if (HeaderData.AddrSize != AddrSize && AddrSize != 0)
    return createStringError(errc::invalid_argument,
                       ".debug_addr table at offset 0x%" PRIx32
                       " has address size %" PRIu8
                       " which is different from CU address size %" PRIu8,
                       HeaderOffset, HeaderData.AddrSize, AddrSize);

  // TODO: add support for non-zero segment selector size.
  if (HeaderData.SegSize != 0)
    return createStringError(errc::not_supported,
                       ".debug_addr table at offset 0x%" PRIx32
                       " has unsupported segment selector size %" PRIu8,
                       HeaderOffset, HeaderData.SegSize);
  if (DataSize % HeaderData.AddrSize != 0) {
    invalidateLength();
    return createStringError(errc::invalid_argument,
                       ".debug_addr table at offset 0x%" PRIx32
                       " contains data of size %" PRIu32
                       " which is not a multiple of addr size %" PRIu8,
                       HeaderOffset, DataSize, HeaderData.AddrSize);
  }
  Data.setAddressSize(HeaderData.AddrSize);
  uint32_t AddrCount = DataSize / HeaderData.AddrSize;
  for (uint32_t I = 0; I < AddrCount; ++I)
    if (HeaderData.AddrSize == 4)
      Addrs.push_back(Data.getU32(OffsetPtr));
    else
      Addrs.push_back(Data.getU64(OffsetPtr));
  return Error::success();
}

void DWARFDebugAddrTable::dump(raw_ostream &OS, DIDumpOptions DumpOpts) const {
  if (DumpOpts.Verbose)
    OS << format("0x%8.8" PRIx32 ": ", HeaderOffset);
  OS << format("Addr Section: length = 0x%8.8" PRIx32
               ", version = 0x%4.4" PRIx16 ", "
               "addr_size = 0x%2.2" PRIx8 ", seg_size = 0x%2.2" PRIx8 "\n",
               HeaderData.Length, HeaderData.Version, HeaderData.AddrSize,
               HeaderData.SegSize);

  if (Addrs.size() > 0) {
    const char *AddrFmt = (HeaderData.AddrSize == 4) ? "0x%8.8" PRIx64 "\n"
                                                     : "0x%16.16" PRIx64 "\n";
    OS << "Addrs: [\n";
    for (uint64_t Addr : Addrs)
      OS << format(AddrFmt, Addr);
    OS << "]\n";
  }
}

Expected<uint64_t> DWARFDebugAddrTable::getAddrEntry(uint32_t Index) const {
  if (Index < Addrs.size())
    return Addrs[Index];
  return createStringError(errc::invalid_argument,
                           "Index %" PRIu32 " is out of range of the "
                           ".debug_addr table at offset 0x%" PRIx32,
                           Index, HeaderOffset);
}

uint32_t DWARFDebugAddrTable::getLength() const {
  if (HeaderData.Length == 0)
    return 0;
  // TODO: DWARF64 support.
  return HeaderData.Length + sizeof(uint32_t);
}

uint32_t DWARFDebugAddrTable::getDataSize() const {
  if (DataSize != 0)
    return DataSize;
  if (getLength() == 0)
    return 0;
  return getLength() - getHeaderSize();
}
