//===- 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/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/Casting.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 void writebe(value_type Value) { Value = support::endian::byte_swap(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(GOFF::PTVPrefix) // Record Type << static_cast(TypeAndFlags) // Continuation << static_cast(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); } }; class GOFFWriter { GOFFOstream OS; MCAssembler &Asm; void writeHeader(); void writeSymbol(const GOFFSymbol &Symbol); void writeText(const MCSectionGOFF *MC); void writeEnd(); void defineSectionSymbols(const MCSectionGOFF &Section); void defineLabel(const MCSymbolGOFF &Symbol); void defineSymbols(); public: GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm); uint64_t writeObject(); }; } // namespace GOFFWriter::GOFFWriter(raw_pwrite_stream &OS, MCAssembler &Asm) : OS(OS), Asm(Asm) {} 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(Symbol.getSection()); GOFFSymbol LD(Symbol.getName(), Symbol.getIndex(), Section.getOrdinal(), Section.getEDAttributes().NameSpace, Symbol.getLDAttributes()); if (Symbol.getADA()) LD.ADAEsdId = Symbol.getADA()->getOrdinal(); writeSymbol(LD); } void GOFFWriter::defineSymbols() { unsigned Ordinal = 0; // Process all sections. for (MCSection &S : Asm) { auto &Section = cast(S); Section.setOrdinal(++Ordinal); defineSectionSymbols(Section); } // Process all symbols for (const MCSymbol &Sym : Asm.symbols()) { if (Sym.isTemporary()) continue; auto &Symbol = cast(Sym); if (Symbol.hasLDAttributes()) { Symbol.setIndex(++Ordinal); defineLabel(Symbol); } } } void GOFFWriter::writeHeader() { OS.newRecord(GOFF::RT_HDR); OS.write_zeros(1); // Reserved OS.writebe(0); // Target Hardware Environment OS.writebe(0); // Target Operating System Environment OS.write_zeros(2); // Reserved OS.writebe(0); // CCSID OS.write_zeros(16); // Character Set name OS.write_zeros(16); // Language Product Identifier OS.writebe(1); // Architecture Level OS.writebe(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(Symbol.SymbolType); // Symbol Type OS.writebe(Symbol.EsdId); // ESDID OS.writebe(Symbol.ParentEsdId); // Parent or Owning ESDID OS.writebe(0); // Reserved OS.writebe( static_cast(Symbol.Offset)); // Offset or Address OS.writebe(0); // Reserved OS.writebe(Symbol.SectionLength); // Length OS.writebe(Symbol.EASectionEDEsdId); // Extended Attribute ESDID OS.writebe(Symbol.EASectionOffset); // Extended Attribute Offset OS.writebe(0); // Reserved OS.writebe(Symbol.NameSpace); // Name Space ID OS.writebe(Symbol.SymbolFlags); // Flags OS.writebe(Symbol.FillByteValue); // Fill-Byte Value OS.writebe(0); // Reserved OS.writebe(Symbol.ADAEsdId); // ADA ESDID OS.writebe(Symbol.SortKey); // Sort Priority OS.writebe(0); // Reserved for (auto F : Symbol.BehavAttrs.Attr) OS.writebe(F); // Behavioral Attributes OS.writebe(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() { 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::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(GOFF::Flags(4, 4, RecordStyle)); // Text Record Style OS.writebe(EsdId); // Element ESDID OS.writebe(0); // Reserved OS.writebe(static_cast(Offset)); // Offset OS.writebe(0); // Text Field True Length OS.writebe(0); // Text Encoding OS.writebe(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); } 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(GOFF::Flags(6, 2, F)); // Indicator flags OS.writebe(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(0); // Record Count OS.writebe(ESDID); // ESDID (of entry point) } uint64_t GOFFWriter::writeObject() { writeHeader(); defineSymbols(); for (const MCSection &Section : Asm) writeText(static_cast(&Section)); 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 MOTW, raw_pwrite_stream &OS) : TargetObjectWriter(std::move(MOTW)), OS(OS) {} GOFFObjectWriter::~GOFFObjectWriter() {} uint64_t GOFFObjectWriter::writeObject() { uint64_t Size = GOFFWriter(OS, *Asm).writeObject(); return Size; } std::unique_ptr llvm::createGOFFObjectWriter(std::unique_ptr MOTW, raw_pwrite_stream &OS) { return std::make_unique(std::move(MOTW), OS); }