Global data is emitted into parts, which are modelled as a MCSection. A label (symbol of type LD) is not allowed in a part, which requires special handling. The approach is to not emit the label at all, and using the part symbol in relocations.
797 lines
27 KiB
C++
797 lines
27 KiB
C++
//===- lib/MC/GOFFObjectWriter.cpp - GOFF File Writer ---------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements GOFF object file writer information.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/BinaryFormat/GOFF.h"
|
|
#include "llvm/MC/MCAsmBackend.h"
|
|
#include "llvm/MC/MCAssembler.h"
|
|
#include "llvm/MC/MCGOFFAttributes.h"
|
|
#include "llvm/MC/MCGOFFObjectWriter.h"
|
|
#include "llvm/MC/MCSectionGOFF.h"
|
|
#include "llvm/MC/MCSymbolGOFF.h"
|
|
#include "llvm/MC/MCValue.h"
|
|
#include "llvm/Support/ConvertEBCDIC.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "goff-writer"
|
|
|
|
namespace {
|
|
// Common flag values on records.
|
|
|
|
// Flag: This record is continued.
|
|
constexpr uint8_t RecContinued = GOFF::Flags(7, 1, 1);
|
|
|
|
// Flag: This record is a continuation.
|
|
constexpr uint8_t RecContinuation = GOFF::Flags(6, 1, 1);
|
|
|
|
// The GOFFOstream is responsible to write the data into the fixed physical
|
|
// records of the format. A user of this class announces the begin of a new
|
|
// logical record. While writing the payload, the physical records are created
|
|
// for the data. Possible fill bytes at the end of a physical record are written
|
|
// automatically. In principle, the GOFFOstream is agnostic of the endianness of
|
|
// the payload. However, it also supports writing data in big endian byte order.
|
|
//
|
|
// The physical records use the flag field to indicate if the there is a
|
|
// successor and predecessor record. To be able to set these flags while
|
|
// writing, the basic implementation idea is to always buffer the last seen
|
|
// physical record.
|
|
class GOFFOstream {
|
|
/// The underlying raw_pwrite_stream.
|
|
raw_pwrite_stream &OS;
|
|
|
|
/// The number of logical records emitted so far.
|
|
uint32_t LogicalRecords = 0;
|
|
|
|
/// The number of physical records emitted so far.
|
|
uint32_t PhysicalRecords = 0;
|
|
|
|
/// The size of the buffer. Same as the payload size of a physical record.
|
|
static constexpr uint8_t BufferSize = GOFF::PayloadLength;
|
|
|
|
/// Current position in buffer.
|
|
char *BufferPtr = Buffer;
|
|
|
|
/// Static allocated buffer for the stream.
|
|
char Buffer[BufferSize];
|
|
|
|
/// The type of the current logical record, and the flags (aka continued and
|
|
/// continuation indicators) for the previous (physical) record.
|
|
uint8_t TypeAndFlags = 0;
|
|
|
|
public:
|
|
GOFFOstream(raw_pwrite_stream &OS);
|
|
~GOFFOstream();
|
|
|
|
raw_pwrite_stream &getOS() { return OS; }
|
|
size_t getWrittenSize() const { return PhysicalRecords * GOFF::RecordLength; }
|
|
uint32_t getNumLogicalRecords() { return LogicalRecords; }
|
|
|
|
/// Write the specified bytes.
|
|
void write(const char *Ptr, size_t Size);
|
|
|
|
/// Write zeroes, up to a maximum of 16 bytes.
|
|
void write_zeros(unsigned NumZeros);
|
|
|
|
/// Support for endian-specific data.
|
|
template <typename value_type> void writebe(value_type Value) {
|
|
Value =
|
|
support::endian::byte_swap<value_type>(Value, llvm::endianness::big);
|
|
write((const char *)&Value, sizeof(value_type));
|
|
}
|
|
|
|
/// Begin a new logical record. Implies finalizing the previous record.
|
|
void newRecord(GOFF::RecordType Type);
|
|
|
|
/// Ends a logical record.
|
|
void finalizeRecord();
|
|
|
|
private:
|
|
/// Updates the continued/continuation flags, and writes the record prefix of
|
|
/// a physical record.
|
|
void updateFlagsAndWritePrefix(bool IsContinued);
|
|
|
|
/// Returns the remaining size in the buffer.
|
|
size_t getRemainingSize();
|
|
};
|
|
} // namespace
|
|
|
|
GOFFOstream::GOFFOstream(raw_pwrite_stream &OS) : OS(OS) {}
|
|
|
|
GOFFOstream::~GOFFOstream() { finalizeRecord(); }
|
|
|
|
void GOFFOstream::updateFlagsAndWritePrefix(bool IsContinued) {
|
|
// Update the flags based on the previous state and the flag IsContinued.
|
|
if (TypeAndFlags & RecContinued)
|
|
TypeAndFlags |= RecContinuation;
|
|
if (IsContinued)
|
|
TypeAndFlags |= RecContinued;
|
|
else
|
|
TypeAndFlags &= ~RecContinued;
|
|
|
|
OS << static_cast<unsigned char>(GOFF::PTVPrefix) // Record Type
|
|
<< static_cast<unsigned char>(TypeAndFlags) // Continuation
|
|
<< static_cast<unsigned char>(0); // Version
|
|
|
|
++PhysicalRecords;
|
|
}
|
|
|
|
size_t GOFFOstream::getRemainingSize() {
|
|
return size_t(&Buffer[BufferSize] - BufferPtr);
|
|
}
|
|
|
|
void GOFFOstream::write(const char *Ptr, size_t Size) {
|
|
size_t RemainingSize = getRemainingSize();
|
|
|
|
// Data fits into the buffer.
|
|
if (LLVM_LIKELY(Size <= RemainingSize)) {
|
|
memcpy(BufferPtr, Ptr, Size);
|
|
BufferPtr += Size;
|
|
return;
|
|
}
|
|
|
|
// Otherwise the buffer is partially filled or full, and data does not fit
|
|
// into it.
|
|
updateFlagsAndWritePrefix(/*IsContinued=*/true);
|
|
OS.write(Buffer, size_t(BufferPtr - Buffer));
|
|
if (RemainingSize > 0) {
|
|
OS.write(Ptr, RemainingSize);
|
|
Ptr += RemainingSize;
|
|
Size -= RemainingSize;
|
|
}
|
|
|
|
while (Size > BufferSize) {
|
|
updateFlagsAndWritePrefix(/*IsContinued=*/true);
|
|
OS.write(Ptr, BufferSize);
|
|
Ptr += BufferSize;
|
|
Size -= BufferSize;
|
|
}
|
|
|
|
// The remaining bytes fit into the buffer.
|
|
memcpy(Buffer, Ptr, Size);
|
|
BufferPtr = &Buffer[Size];
|
|
}
|
|
|
|
void GOFFOstream::write_zeros(unsigned NumZeros) {
|
|
assert(NumZeros <= 16 && "Range for zeros too large");
|
|
|
|
// Handle the common case first: all fits in the buffer.
|
|
size_t RemainingSize = getRemainingSize();
|
|
if (LLVM_LIKELY(RemainingSize >= NumZeros)) {
|
|
memset(BufferPtr, 0, NumZeros);
|
|
BufferPtr += NumZeros;
|
|
return;
|
|
}
|
|
|
|
// Otherwise some field value is cleared.
|
|
static char Zeros[16] = {
|
|
0,
|
|
};
|
|
write(Zeros, NumZeros);
|
|
}
|
|
|
|
void GOFFOstream::newRecord(GOFF::RecordType Type) {
|
|
finalizeRecord();
|
|
TypeAndFlags = Type << 4;
|
|
++LogicalRecords;
|
|
}
|
|
|
|
void GOFFOstream::finalizeRecord() {
|
|
if (Buffer == BufferPtr)
|
|
return;
|
|
updateFlagsAndWritePrefix(/*IsContinued=*/false);
|
|
OS.write(Buffer, size_t(BufferPtr - Buffer));
|
|
OS.write_zeros(getRemainingSize());
|
|
BufferPtr = Buffer;
|
|
}
|
|
|
|
namespace {
|
|
// A GOFFSymbol holds all the data required for writing an ESD record.
|
|
class GOFFSymbol {
|
|
public:
|
|
std::string Name;
|
|
uint32_t EsdId;
|
|
uint32_t ParentEsdId;
|
|
uint64_t Offset = 0; // Offset of the symbol into the section. LD only.
|
|
// Offset is only 32 bit, the larger type is used to
|
|
// enable error checking.
|
|
GOFF::ESDSymbolType SymbolType;
|
|
GOFF::ESDNameSpaceId NameSpace = GOFF::ESD_NS_ProgramManagementBinder;
|
|
|
|
GOFF::BehavioralAttributes BehavAttrs;
|
|
GOFF::SymbolFlags SymbolFlags;
|
|
uint32_t SortKey = 0;
|
|
uint32_t SectionLength = 0;
|
|
uint32_t ADAEsdId = 0;
|
|
uint32_t EASectionEDEsdId = 0;
|
|
uint32_t EASectionOffset = 0;
|
|
uint8_t FillByteValue = 0;
|
|
|
|
GOFFSymbol() : EsdId(0), ParentEsdId(0) {}
|
|
|
|
GOFFSymbol(StringRef Name, uint32_t EsdID, const GOFF::SDAttr &Attr)
|
|
: Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(0),
|
|
SymbolType(GOFF::ESD_ST_SectionDefinition) {
|
|
BehavAttrs.setTaskingBehavior(Attr.TaskingBehavior);
|
|
BehavAttrs.setBindingScope(Attr.BindingScope);
|
|
}
|
|
|
|
GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID,
|
|
const GOFF::EDAttr &Attr)
|
|
: Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID),
|
|
SymbolType(GOFF::ESD_ST_ElementDefinition) {
|
|
this->NameSpace = Attr.NameSpace;
|
|
// We always set a fill byte value.
|
|
this->FillByteValue = Attr.FillByteValue;
|
|
SymbolFlags.setFillBytePresence(1);
|
|
SymbolFlags.setReservedQwords(Attr.ReservedQwords);
|
|
// TODO Do we need/should set the "mangled" flag?
|
|
BehavAttrs.setReadOnly(Attr.IsReadOnly);
|
|
BehavAttrs.setRmode(Attr.Rmode);
|
|
BehavAttrs.setTextStyle(Attr.TextStyle);
|
|
BehavAttrs.setBindingAlgorithm(Attr.BindAlgorithm);
|
|
BehavAttrs.setLoadingBehavior(Attr.LoadBehavior);
|
|
BehavAttrs.setAlignment(Attr.Alignment);
|
|
}
|
|
|
|
GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID,
|
|
GOFF::ESDNameSpaceId NameSpace, const GOFF::LDAttr &Attr)
|
|
: Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID),
|
|
SymbolType(GOFF::ESD_ST_LabelDefinition), NameSpace(NameSpace) {
|
|
SymbolFlags.setRenameable(Attr.IsRenamable);
|
|
BehavAttrs.setExecutable(Attr.Executable);
|
|
BehavAttrs.setBindingStrength(Attr.BindingStrength);
|
|
BehavAttrs.setLinkageType(Attr.Linkage);
|
|
BehavAttrs.setAmode(Attr.Amode);
|
|
BehavAttrs.setBindingScope(Attr.BindingScope);
|
|
}
|
|
|
|
GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID,
|
|
const GOFF::EDAttr &EDAttr, const GOFF::PRAttr &Attr)
|
|
: Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID),
|
|
SymbolType(GOFF::ESD_ST_PartReference), NameSpace(EDAttr.NameSpace) {
|
|
SymbolFlags.setRenameable(Attr.IsRenamable);
|
|
BehavAttrs.setExecutable(Attr.Executable);
|
|
BehavAttrs.setLinkageType(Attr.Linkage);
|
|
BehavAttrs.setBindingScope(Attr.BindingScope);
|
|
BehavAttrs.setAlignment(EDAttr.Alignment);
|
|
}
|
|
|
|
GOFFSymbol(StringRef Name, uint32_t EsdID, uint32_t ParentEsdID,
|
|
const GOFF::ERAttr &Attr)
|
|
: Name(Name.data(), Name.size()), EsdId(EsdID), ParentEsdId(ParentEsdID),
|
|
SymbolType(GOFF::ESD_ST_ExternalReference),
|
|
NameSpace(GOFF::ESD_NS_NormalName) {
|
|
BehavAttrs.setExecutable(Attr.Executable);
|
|
BehavAttrs.setBindingStrength(Attr.BindingStrength);
|
|
BehavAttrs.setLinkageType(Attr.Linkage);
|
|
BehavAttrs.setAmode(Attr.Amode);
|
|
BehavAttrs.setBindingScope(Attr.BindingScope);
|
|
}
|
|
};
|
|
|
|
class GOFFWriter {
|
|
GOFFOstream OS;
|
|
MCAssembler &Asm;
|
|
MCSectionGOFF *RootSD;
|
|
|
|
/// Saved relocation data collected in recordRelocations().
|
|
std::vector<GOFFRelocationEntry> &Relocations;
|
|
|
|
void writeHeader();
|
|
void writeSymbol(const GOFFSymbol &Symbol);
|
|
void writeText(const MCSectionGOFF *MC);
|
|
void writeRelocations();
|
|
void writeEnd();
|
|
|
|
void defineSectionSymbols(const MCSectionGOFF &Section);
|
|
void defineLabel(const MCSymbolGOFF &Symbol);
|
|
void defineExtern(const MCSymbolGOFF &Symbol);
|
|
void defineSymbols();
|
|
|
|
public:
|
|
GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm, MCSectionGOFF *RootSD,
|
|
std::vector<GOFFRelocationEntry> &Relocations);
|
|
uint64_t writeObject();
|
|
};
|
|
} // namespace
|
|
|
|
GOFFWriter::GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm,
|
|
MCSectionGOFF *RootSD,
|
|
std::vector<GOFFRelocationEntry> &Relocations)
|
|
: OS(OS), Asm(Asm), RootSD(RootSD), Relocations(Relocations) {}
|
|
|
|
void GOFFWriter::defineSectionSymbols(const MCSectionGOFF &Section) {
|
|
if (Section.isSD()) {
|
|
GOFFSymbol SD(Section.getName(), Section.getOrdinal(),
|
|
Section.getSDAttributes());
|
|
writeSymbol(SD);
|
|
}
|
|
|
|
if (Section.isED()) {
|
|
GOFFSymbol ED(Section.getName(), Section.getOrdinal(),
|
|
Section.getParent()->getOrdinal(), Section.getEDAttributes());
|
|
ED.SectionLength = Asm.getSectionAddressSize(Section);
|
|
writeSymbol(ED);
|
|
}
|
|
|
|
if (Section.isPR()) {
|
|
MCSectionGOFF *Parent = Section.getParent();
|
|
GOFFSymbol PR(Section.getName(), Section.getOrdinal(), Parent->getOrdinal(),
|
|
Parent->getEDAttributes(), Section.getPRAttributes());
|
|
PR.SectionLength = Asm.getSectionAddressSize(Section);
|
|
if (Section.requiresNonZeroLength()) {
|
|
// We cannot have a zero-length section for data. If we do,
|
|
// artificially inflate it. Use 2 bytes to avoid odd alignments. Note:
|
|
// if this is ever changed, you will need to update the code in
|
|
// SystemZAsmPrinter::emitCEEMAIN and SystemZAsmPrinter::emitCELQMAIN to
|
|
// generate -1 if there is no ADA
|
|
if (!PR.SectionLength)
|
|
PR.SectionLength = 2;
|
|
}
|
|
writeSymbol(PR);
|
|
}
|
|
}
|
|
|
|
void GOFFWriter::defineLabel(const MCSymbolGOFF &Symbol) {
|
|
MCSectionGOFF &Section = static_cast<MCSectionGOFF &>(Symbol.getSection());
|
|
GOFFSymbol LD(Symbol.getName(), Symbol.getIndex(), Section.getOrdinal(),
|
|
Section.getEDAttributes().NameSpace,
|
|
GOFF::LDAttr{false, Symbol.getCodeData(),
|
|
Symbol.getBindingStrength(), Symbol.getLinkage(),
|
|
GOFF::ESD_AMODE_64, Symbol.getBindingScope()});
|
|
if (Symbol.getADA())
|
|
LD.ADAEsdId = Symbol.getADA()->getOrdinal();
|
|
LD.Offset = Asm.getSymbolOffset(Symbol);
|
|
writeSymbol(LD);
|
|
}
|
|
|
|
void GOFFWriter::defineExtern(const MCSymbolGOFF &Symbol) {
|
|
GOFFSymbol ER(Symbol.getName(), Symbol.getIndex(), RootSD->getOrdinal(),
|
|
GOFF::ERAttr{Symbol.getCodeData(), Symbol.getBindingStrength(),
|
|
Symbol.getLinkage(), GOFF::ESD_AMODE_64,
|
|
Symbol.getBindingScope()});
|
|
writeSymbol(ER);
|
|
}
|
|
|
|
void GOFFWriter::defineSymbols() {
|
|
unsigned Ordinal = 0;
|
|
// Process all sections.
|
|
for (MCSection &S : Asm) {
|
|
auto &Section = static_cast<MCSectionGOFF &>(S);
|
|
Section.setOrdinal(++Ordinal);
|
|
defineSectionSymbols(Section);
|
|
}
|
|
|
|
// Process all symbols
|
|
for (const MCSymbol &Sym : Asm.symbols()) {
|
|
if (Sym.isTemporary())
|
|
continue;
|
|
auto &Symbol = static_cast<const MCSymbolGOFF &>(Sym);
|
|
if (!Symbol.isDefined()) {
|
|
Symbol.setIndex(++Ordinal);
|
|
defineExtern(Symbol);
|
|
} else if (Symbol.isInEDSection()) {
|
|
Symbol.setIndex(++Ordinal);
|
|
defineLabel(Symbol);
|
|
} else {
|
|
// Symbol is in PR section, the symbol refers to the section.
|
|
Symbol.setIndex(Symbol.getSection().getOrdinal());
|
|
}
|
|
}
|
|
}
|
|
|
|
void GOFFWriter::writeHeader() {
|
|
OS.newRecord(GOFF::RT_HDR);
|
|
OS.write_zeros(1); // Reserved
|
|
OS.writebe<uint32_t>(0); // Target Hardware Environment
|
|
OS.writebe<uint32_t>(0); // Target Operating System Environment
|
|
OS.write_zeros(2); // Reserved
|
|
OS.writebe<uint16_t>(0); // CCSID
|
|
OS.write_zeros(16); // Character Set name
|
|
OS.write_zeros(16); // Language Product Identifier
|
|
OS.writebe<uint32_t>(1); // Architecture Level
|
|
OS.writebe<uint16_t>(0); // Module Properties Length
|
|
OS.write_zeros(6); // Reserved
|
|
}
|
|
|
|
void GOFFWriter::writeSymbol(const GOFFSymbol &Symbol) {
|
|
if (Symbol.Offset >= (((uint64_t)1) << 31))
|
|
report_fatal_error("ESD offset out of range");
|
|
|
|
// All symbol names are in EBCDIC.
|
|
SmallString<256> Name;
|
|
ConverterEBCDIC::convertToEBCDIC(Symbol.Name, Name);
|
|
|
|
// Check length here since this number is technically signed but we need uint
|
|
// for writing to records.
|
|
if (Name.size() >= GOFF::MaxDataLength)
|
|
report_fatal_error("Symbol max name length exceeded");
|
|
uint16_t NameLength = Name.size();
|
|
|
|
OS.newRecord(GOFF::RT_ESD);
|
|
OS.writebe<uint8_t>(Symbol.SymbolType); // Symbol Type
|
|
OS.writebe<uint32_t>(Symbol.EsdId); // ESDID
|
|
OS.writebe<uint32_t>(Symbol.ParentEsdId); // Parent or Owning ESDID
|
|
OS.writebe<uint32_t>(0); // Reserved
|
|
OS.writebe<uint32_t>(
|
|
static_cast<uint32_t>(Symbol.Offset)); // Offset or Address
|
|
OS.writebe<uint32_t>(0); // Reserved
|
|
OS.writebe<uint32_t>(Symbol.SectionLength); // Length
|
|
OS.writebe<uint32_t>(Symbol.EASectionEDEsdId); // Extended Attribute ESDID
|
|
OS.writebe<uint32_t>(Symbol.EASectionOffset); // Extended Attribute Offset
|
|
OS.writebe<uint32_t>(0); // Reserved
|
|
OS.writebe<uint8_t>(Symbol.NameSpace); // Name Space ID
|
|
OS.writebe<uint8_t>(Symbol.SymbolFlags); // Flags
|
|
OS.writebe<uint8_t>(Symbol.FillByteValue); // Fill-Byte Value
|
|
OS.writebe<uint8_t>(0); // Reserved
|
|
OS.writebe<uint32_t>(Symbol.ADAEsdId); // ADA ESDID
|
|
OS.writebe<uint32_t>(Symbol.SortKey); // Sort Priority
|
|
OS.writebe<uint64_t>(0); // Reserved
|
|
for (auto F : Symbol.BehavAttrs.Attr)
|
|
OS.writebe<uint8_t>(F); // Behavioral Attributes
|
|
OS.writebe<uint16_t>(NameLength); // Name Length
|
|
OS.write(Name.data(), NameLength); // Name
|
|
}
|
|
|
|
namespace {
|
|
/// Adapter stream to write a text section.
|
|
class TextStream : public raw_ostream {
|
|
/// The underlying GOFFOstream.
|
|
GOFFOstream &OS;
|
|
|
|
/// The buffer size is the maximum number of bytes in a TXT section.
|
|
static constexpr size_t BufferSize = GOFF::MaxDataLength;
|
|
|
|
/// Static allocated buffer for the stream, used by the raw_ostream class. The
|
|
/// buffer is sized to hold the payload of a logical TXT record.
|
|
char Buffer[BufferSize];
|
|
|
|
/// The offset for the next TXT record. This is equal to the number of bytes
|
|
/// written.
|
|
size_t Offset;
|
|
|
|
/// The Esdid of the GOFF section.
|
|
const uint32_t EsdId;
|
|
|
|
/// The record style.
|
|
const GOFF::ESDTextStyle RecordStyle;
|
|
|
|
/// See raw_ostream::write_impl.
|
|
void write_impl(const char *Ptr, size_t Size) override;
|
|
|
|
uint64_t current_pos() const override { return Offset; }
|
|
|
|
public:
|
|
explicit TextStream(GOFFOstream &OS, uint32_t EsdId,
|
|
GOFF::ESDTextStyle RecordStyle)
|
|
: OS(OS), Offset(0), EsdId(EsdId), RecordStyle(RecordStyle) {
|
|
SetBuffer(Buffer, sizeof(Buffer));
|
|
}
|
|
|
|
~TextStream() override { flush(); }
|
|
};
|
|
} // namespace
|
|
|
|
void TextStream::write_impl(const char *Ptr, size_t Size) {
|
|
size_t WrittenLength = 0;
|
|
|
|
// We only have signed 32bits of offset.
|
|
if (Offset + Size > std::numeric_limits<int32_t>::max())
|
|
report_fatal_error("TXT section too large");
|
|
|
|
while (WrittenLength < Size) {
|
|
size_t ToWriteLength =
|
|
std::min(Size - WrittenLength, size_t(GOFF::MaxDataLength));
|
|
|
|
OS.newRecord(GOFF::RT_TXT);
|
|
OS.writebe<uint8_t>(GOFF::Flags(4, 4, RecordStyle)); // Text Record Style
|
|
OS.writebe<uint32_t>(EsdId); // Element ESDID
|
|
OS.writebe<uint32_t>(0); // Reserved
|
|
OS.writebe<uint32_t>(static_cast<uint32_t>(Offset)); // Offset
|
|
OS.writebe<uint32_t>(0); // Text Field True Length
|
|
OS.writebe<uint16_t>(0); // Text Encoding
|
|
OS.writebe<uint16_t>(ToWriteLength); // Data Length
|
|
OS.write(Ptr + WrittenLength, ToWriteLength); // Data
|
|
|
|
WrittenLength += ToWriteLength;
|
|
Offset += ToWriteLength;
|
|
}
|
|
}
|
|
|
|
void GOFFWriter::writeText(const MCSectionGOFF *Section) {
|
|
// A BSS section contains only zeros, no need to write this.
|
|
if (Section->isBSS())
|
|
return;
|
|
|
|
TextStream S(OS, Section->getOrdinal(), Section->getTextStyle());
|
|
Asm.writeSectionData(S, Section);
|
|
}
|
|
|
|
namespace {
|
|
// RelocDataItemBuffer provides a static buffer for relocation data items.
|
|
class RelocDataItemBuffer {
|
|
char Buffer[GOFF::MaxDataLength];
|
|
char *Ptr;
|
|
|
|
public:
|
|
RelocDataItemBuffer() : Ptr(Buffer) {}
|
|
const char *data() { return Buffer; }
|
|
size_t size() { return Ptr - Buffer; }
|
|
void reset() { Ptr = Buffer; }
|
|
bool fits(size_t S) { return size() + S < GOFF::MaxDataLength; }
|
|
template <typename T> void writebe(T Val) {
|
|
assert(fits(sizeof(T)) && "Out-of-bounds write");
|
|
support::endian::write<T, llvm::endianness::big>(Ptr, Val);
|
|
Ptr += sizeof(T);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
void GOFFWriter::writeRelocations() {
|
|
// Set the IDs in the relocation entries.
|
|
for (auto &RelocEntry : Relocations) {
|
|
auto GetRptr = [](const MCSymbolGOFF *Sym) -> uint32_t {
|
|
if (Sym->isTemporary())
|
|
return static_cast<MCSectionGOFF &>(Sym->getSection())
|
|
.getBeginSymbol()
|
|
->getIndex();
|
|
return Sym->getIndex();
|
|
};
|
|
|
|
RelocEntry.PEsdId = RelocEntry.Pptr->getOrdinal();
|
|
RelocEntry.REsdId = GetRptr(RelocEntry.Rptr);
|
|
}
|
|
|
|
// Sort relocation data items by the P pointer to save space.
|
|
std::sort(
|
|
Relocations.begin(), Relocations.end(),
|
|
[](const GOFFRelocationEntry &Left, const GOFFRelocationEntry &Right) {
|
|
return std::tie(Left.PEsdId, Left.REsdId, Left.POffset) <
|
|
std::tie(Right.PEsdId, Right.REsdId, Right.POffset);
|
|
});
|
|
|
|
// Construct the compressed relocation data items, and write them out.
|
|
RelocDataItemBuffer Buffer;
|
|
for (auto I = Relocations.begin(), E = Relocations.end(); I != E;) {
|
|
Buffer.reset();
|
|
|
|
uint32_t PrevResdId = -1;
|
|
uint32_t PrevPesdId = -1;
|
|
uint64_t PrevPOffset = -1;
|
|
for (; I != E; ++I) {
|
|
const GOFFRelocationEntry &Rel = *I;
|
|
|
|
bool SameREsdId = (Rel.REsdId == PrevResdId);
|
|
bool SamePEsdId = (Rel.PEsdId == PrevPesdId);
|
|
bool SamePOffset = (Rel.POffset == PrevPOffset);
|
|
bool EightByteOffset = ((Rel.POffset >> 32) & 0xffffffff);
|
|
|
|
// Calculate size of relocation data item, and check if it still fits into
|
|
// the record.
|
|
size_t ItemSize = 8; // Smallest size of a relocation data item.
|
|
if (!SameREsdId)
|
|
ItemSize += 4;
|
|
if (!SamePEsdId)
|
|
ItemSize += 4;
|
|
if (!SamePOffset)
|
|
ItemSize += (EightByteOffset ? 8 : 4);
|
|
if (!Buffer.fits(ItemSize))
|
|
break;
|
|
|
|
GOFF::Flags RelocFlags[6];
|
|
RelocFlags[0].set(0, 1, SameREsdId);
|
|
RelocFlags[0].set(1, 1, SamePEsdId);
|
|
RelocFlags[0].set(2, 1, SamePOffset);
|
|
RelocFlags[0].set(6, 1, EightByteOffset);
|
|
|
|
RelocFlags[1].set(0, 4, Rel.ReferenceType);
|
|
RelocFlags[1].set(4, 4, Rel.ReferentType);
|
|
|
|
RelocFlags[2].set(0, 7, Rel.Action);
|
|
RelocFlags[2].set(7, 1, Rel.FetchStore);
|
|
|
|
RelocFlags[4].set(0, 8, Rel.TargetLength);
|
|
|
|
for (auto F : RelocFlags)
|
|
Buffer.writebe<uint8_t>(F);
|
|
Buffer.writebe<uint16_t>(0); // Reserved.
|
|
if (!SameREsdId)
|
|
Buffer.writebe<uint32_t>(Rel.REsdId);
|
|
if (!SamePEsdId)
|
|
Buffer.writebe<uint32_t>(Rel.PEsdId);
|
|
if (!SamePOffset) {
|
|
if (EightByteOffset)
|
|
Buffer.writebe<uint64_t>(Rel.POffset);
|
|
else
|
|
Buffer.writebe<uint32_t>(Rel.POffset);
|
|
}
|
|
|
|
PrevResdId = Rel.REsdId;
|
|
PrevPesdId = Rel.PEsdId;
|
|
PrevPOffset = Rel.POffset;
|
|
}
|
|
|
|
OS.newRecord(GOFF::RT_RLD);
|
|
OS.writebe<uint8_t>(0); // Reserved.
|
|
OS.writebe<uint16_t>(Buffer.size()); // Length (of the relocation data).
|
|
OS.write(Buffer.data(), Buffer.size()); // Relocation Directory Data Items.
|
|
}
|
|
}
|
|
|
|
void GOFFWriter::writeEnd() {
|
|
uint8_t F = GOFF::END_EPR_None;
|
|
uint8_t AMODE = 0;
|
|
uint32_t ESDID = 0;
|
|
|
|
// TODO Set Flags/AMODE/ESDID for entry point.
|
|
|
|
OS.newRecord(GOFF::RT_END);
|
|
OS.writebe<uint8_t>(GOFF::Flags(6, 2, F)); // Indicator flags
|
|
OS.writebe<uint8_t>(AMODE); // AMODE
|
|
OS.write_zeros(3); // Reserved
|
|
// The record count is the number of logical records. In principle, this value
|
|
// is available as OS.logicalRecords(). However, some tools rely on this field
|
|
// being zero.
|
|
OS.writebe<uint32_t>(0); // Record Count
|
|
OS.writebe<uint32_t>(ESDID); // ESDID (of entry point)
|
|
}
|
|
|
|
uint64_t GOFFWriter::writeObject() {
|
|
writeHeader();
|
|
|
|
defineSymbols();
|
|
|
|
for (const MCSection &Section : Asm)
|
|
writeText(static_cast<const MCSectionGOFF *>(&Section));
|
|
|
|
writeRelocations();
|
|
|
|
writeEnd();
|
|
|
|
// Make sure all records are written.
|
|
OS.finalizeRecord();
|
|
|
|
LLVM_DEBUG(dbgs() << "Wrote " << OS.getNumLogicalRecords()
|
|
<< " logical records.");
|
|
|
|
return OS.getWrittenSize();
|
|
}
|
|
|
|
GOFFObjectWriter::GOFFObjectWriter(
|
|
std::unique_ptr<MCGOFFObjectTargetWriter> MOTW, raw_pwrite_stream &OS)
|
|
: TargetObjectWriter(std::move(MOTW)), OS(OS) {}
|
|
|
|
GOFFObjectWriter::~GOFFObjectWriter() = default;
|
|
|
|
void GOFFObjectWriter::recordRelocation(const MCFragment &F,
|
|
const MCFixup &Fixup, MCValue Target,
|
|
uint64_t &FixedValue) {
|
|
const MCFixupKindInfo &FKI =
|
|
Asm->getBackend().getFixupKindInfo(Fixup.getKind());
|
|
const uint32_t Length = FKI.TargetSize / 8;
|
|
assert(FKI.TargetSize % 8 == 0 && "Target Size not multiple of 8");
|
|
const uint64_t FixupOffset = Asm->getFragmentOffset(F) + Fixup.getOffset();
|
|
|
|
unsigned RelocType = TargetObjectWriter->getRelocType(Target, Fixup);
|
|
|
|
const MCSectionGOFF *PSection = static_cast<MCSectionGOFF *>(F.getParent());
|
|
const auto &A = *static_cast<const MCSymbolGOFF *>(Target.getAddSym());
|
|
const MCSymbolGOFF *B = static_cast<const MCSymbolGOFF *>(Target.getSubSym());
|
|
if (RelocType == MCGOFFObjectTargetWriter::Reloc_Type_RICon) {
|
|
if (A.isUndefined()) {
|
|
Asm->reportError(
|
|
Fixup.getLoc(),
|
|
Twine("symbol ")
|
|
.concat(A.getName())
|
|
.concat(" must be defined for a relative immediate relocation"));
|
|
return;
|
|
}
|
|
if (&A.getSection() != PSection) {
|
|
Asm->reportError(Fixup.getLoc(),
|
|
Twine("relative immediate relocation section mismatch: ")
|
|
.concat(A.getSection().getName())
|
|
.concat(" of symbol ")
|
|
.concat(A.getName())
|
|
.concat(" <-> ")
|
|
.concat(PSection->getName()));
|
|
return;
|
|
}
|
|
if (B) {
|
|
Asm->reportError(
|
|
Fixup.getLoc(),
|
|
Twine("subtractive symbol ")
|
|
.concat(B->getName())
|
|
.concat(" not supported for a relative immediate relocation"));
|
|
return;
|
|
}
|
|
FixedValue = Asm->getSymbolOffset(A) - FixupOffset + Target.getConstant();
|
|
return;
|
|
}
|
|
FixedValue = Target.getConstant();
|
|
|
|
// The symbol only has a section-relative offset if it is a temporary symbol.
|
|
FixedValue += A.isTemporary() ? Asm->getSymbolOffset(A) : 0;
|
|
A.setUsedInReloc();
|
|
if (B) {
|
|
FixedValue -= B->isTemporary() ? Asm->getSymbolOffset(*B) : 0;
|
|
B->setUsedInReloc();
|
|
}
|
|
|
|
// UseQCon causes class offsets versus absolute addresses to be used. This
|
|
// is analogous to using QCONs in older OBJ object file format.
|
|
bool UseQCon = RelocType == MCGOFFObjectTargetWriter::Reloc_Type_QCon;
|
|
|
|
GOFF::RLDFetchStore FetchStore =
|
|
(RelocType == MCGOFFObjectTargetWriter::Reloc_Type_RCon ||
|
|
RelocType == MCGOFFObjectTargetWriter::Reloc_Type_VCon)
|
|
? GOFF::RLDFetchStore::RLD_FS_Store
|
|
: GOFF::RLDFetchStore::RLD_FS_Fetch;
|
|
assert((FetchStore == GOFF::RLDFetchStore::RLD_FS_Fetch || B == nullptr) &&
|
|
"No dependent relocations expected");
|
|
|
|
enum GOFF::RLDReferenceType ReferenceType = GOFF::RLD_RT_RAddress;
|
|
enum GOFF::RLDReferentType ReferentType = GOFF::RLD_RO_Label;
|
|
if (UseQCon) {
|
|
ReferenceType = GOFF::RLD_RT_ROffset;
|
|
ReferentType = GOFF::RLD_RO_Class;
|
|
}
|
|
if (RelocType == MCGOFFObjectTargetWriter::Reloc_Type_RCon)
|
|
ReferenceType = GOFF::RLD_RT_RTypeConstant;
|
|
|
|
auto DumpReloc = [&PSection, &ReferenceType, &FixupOffset,
|
|
&FixedValue](const char *N, const MCSymbolGOFF *Sym) {
|
|
const char *Con;
|
|
switch (ReferenceType) {
|
|
case GOFF::RLDReferenceType::RLD_RT_RAddress:
|
|
Con = "ACon";
|
|
break;
|
|
case GOFF::RLDReferenceType::RLD_RT_ROffset:
|
|
Con = "QCon";
|
|
break;
|
|
case GOFF::RLDReferenceType::RLD_RT_RTypeConstant:
|
|
Con = "VCon";
|
|
break;
|
|
default:
|
|
Con = "(unknown)";
|
|
}
|
|
dbgs() << "Reloc " << N << ": " << Con << " Rptr: " << Sym->getName()
|
|
<< " Pptr: " << PSection->getName() << " Offset: " << FixupOffset
|
|
<< " Fixed Imm: " << FixedValue << "\n";
|
|
};
|
|
(void)DumpReloc;
|
|
|
|
// Save relocation data for later writing.
|
|
LLVM_DEBUG(DumpReloc("A", &A));
|
|
Relocations.emplace_back(PSection, &A, ReferenceType, ReferentType,
|
|
GOFF::RLD_ACT_Add, FetchStore, FixupOffset, Length);
|
|
if (B) {
|
|
LLVM_DEBUG(DumpReloc("B", B));
|
|
Relocations.emplace_back(
|
|
PSection, B, ReferenceType, ReferentType, GOFF::RLD_ACT_Subtract,
|
|
GOFF::RLDFetchStore::RLD_FS_Fetch, FixupOffset, Length);
|
|
}
|
|
}
|
|
|
|
uint64_t GOFFObjectWriter::writeObject() {
|
|
uint64_t Size = GOFFWriter(OS, *Asm, RootSD, Relocations).writeObject();
|
|
return Size;
|
|
}
|
|
|
|
std::unique_ptr<MCObjectWriter>
|
|
llvm::createGOFFObjectWriter(std::unique_ptr<MCGOFFObjectTargetWriter> MOTW,
|
|
raw_pwrite_stream &OS) {
|
|
return std::make_unique<GOFFObjectWriter>(std::move(MOTW), OS);
|
|
}
|