diff --git a/llvm/include/llvm/BinaryFormat/GOFF.h b/llvm/include/llvm/BinaryFormat/GOFF.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/BinaryFormat/GOFF.h @@ -0,0 +1,158 @@ +//===-- llvm/BinaryFormat/GOFF.h - GOFF definitions --------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This header contains common, non-processor-specific data structures and +// constants for the GOFF file format. +// +// GOFF specifics can be found in MVS Program Management: Advanced Facilities. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_BINARYFORMAT_GOFF_H +#define LLVM_BINARYFORMAT_GOFF_H + +#include "llvm/Support/DataTypes.h" + +namespace llvm { + +namespace GOFF { + +constexpr uint8_t RecordLength = 80; +const uint8_t RecordPrefixLength = 3; +const uint8_t PayloadLength = 77; + +// Prefix byte on every record. This indicates GOFF format. +constexpr uint8_t PTVPrefix = 0x03; + +enum RecordType { + RT_ESD = 0, + RT_TXT = 1, + RT_RLD = 2, + RT_LEN = 3, + RT_END = 4, + RT_HDR = 15, +}; + +enum ESDSymbolType : uint8_t { + ESD_ST_SectionDefinition = 0, + ESD_ST_ElementDefinition = 1, + ESD_ST_LabelDefinition = 2, + ESD_ST_PartReference = 3, + ESD_ST_ExternalReference = 4, +}; + +enum ESDNameSpaceId : uint8_t { + ESD_NS_ProgramManagementBinder = 0, + ESD_NS_NormalName = 1, + ESD_NS_PseudoRegister = 2, + ESD_NS_Parts = 3 +}; + +enum ESDReserveQwords { + ESD_RQ_0 = 0, + ESD_RQ_1 = 1, + ESD_RQ_2 = 2, + ESD_RQ_3 = 3 +}; + +enum ESDAmode : uint8_t { + ESD_AMODE_None = 0, + ESD_AMODE_24 = 1, + ESD_AMODE_31 = 2, + ESD_AMODE_ANY = 3, + ESD_AMODE_64 = 4, + ESD_AMODE_MIN = 16, +}; + +enum ESDRmode : uint8_t { + ESD_RMODE_None = 0, + ESD_RMODE_24 = 1, + ESD_RMODE_31 = 3, + ESD_RMODE_64 = 4, +}; + +enum ESDTextStyle { + ESD_TS_ByteOriented = 0, + ESD_TS_Structured = 1, + ESD_TS_Unstructured = 2, +}; + +enum ESDBindingAlgorithm { + ESD_BA_Concatenate = 0, + ESD_BA_Merge = 1, +}; + +enum ESDTaskingBehavior { + ESD_TA_Unspecified = 0, + ESD_TA_NonReus = 1, + ESD_TA_Reus = 2, + ESD_TA_Rent = 3, +}; + +enum ESDExecutable : uint8_t { + ESD_EXE_Unspecified = 0, + ESD_EXE_DATA = 1, + ESD_EXE_CODE = 2, +}; + +enum ESDDuplicateSymbolSeverity { + ESD_DSS_NoWarning = 0, + ESD_DSS_Warning = 1, + ESD_DSS_Error = 2, + ESD_DSS_Reserved = 3, +}; + +enum ESDBindingStrength { + ESD_BST_Strong = 0, + ESD_BST_Weak = 1, +}; + +enum ESDLoadingBehavior { + ESD_LB_Initial = 0, + ESD_LB_Deferred = 1, + ESD_LB_NoLoad = 2, + ESD_LB_Reserved = 3, +}; + +enum ESDBindingScope { + ESD_BSC_Unspecified = 0, + ESD_BSC_Section = 1, + ESD_BSC_Module = 2, + ESD_BSC_Library = 3, + ESD_BSC_ImportExport = 4, +}; + +enum ESDLinkageType { ESD_LT_OS = 0, ESD_LT_XPLink = 1 }; + +enum ESDAlignment { + ESD_ALIGN_Byte = 0, + ESD_ALIGN_Halfword = 1, + ESD_ALIGN_Fullword = 2, + ESD_ALIGN_Doubleword = 3, + ESD_ALIGN_Quadword = 4, + ESD_ALIGN_32byte = 5, + ESD_ALIGN_64byte = 6, + ESD_ALIGN_128byte = 7, + ESD_ALIGN_256byte = 8, + ESD_ALIGN_512byte = 9, + ESD_ALIGN_1024byte = 10, + ESD_ALIGN_2Kpage = 11, + ESD_ALIGN_4Kpage = 12, +}; + +enum ENDEntryPointRequest { + END_EPR_None = 0, + END_EPR_EsdidOffset = 1, + END_EPR_ExternalName = 2, + END_EPR_Reserved = 3, +}; +} // end namespace GOFF + +} // end namespace llvm + +#endif // LLVM_BINARYFORMAT_GOFF_H diff --git a/llvm/include/llvm/BinaryFormat/Magic.h b/llvm/include/llvm/BinaryFormat/Magic.h --- a/llvm/include/llvm/BinaryFormat/Magic.h +++ b/llvm/include/llvm/BinaryFormat/Magic.h @@ -49,6 +49,7 @@ xcoff_object_64, ///< 64-bit XCOFF object file wasm_object, ///< WebAssembly Object file pdb, ///< Windows PDB debug info file + goff_object, ///< GOFF object file tapi_file, ///< Text-based Dynamic Library Stub file }; diff --git a/llvm/include/llvm/Object/Binary.h b/llvm/include/llvm/Object/Binary.h --- a/llvm/include/llvm/Object/Binary.h +++ b/llvm/include/llvm/Object/Binary.h @@ -67,6 +67,7 @@ ID_MachO64L, // MachO 64-bit, little endian ID_MachO64B, // MachO 64-bit, big endian + ID_GOFF, ID_Wasm, ID_EndObjects @@ -141,6 +142,8 @@ return TypeID == ID_IR; } + bool isGOFF() const { return TypeID == ID_GOFF; } + bool isMinidump() const { return TypeID == ID_Minidump; } bool isTapiFile() const { return TypeID == ID_TapiFile; } @@ -159,6 +162,8 @@ return Triple::MachO; if (isELF()) return Triple::ELF; + if (isGOFF()) + return Triple::GOFF; return Triple::UnknownObjectFormat; } diff --git a/llvm/include/llvm/Object/GOFF.h b/llvm/include/llvm/Object/GOFF.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Object/GOFF.h @@ -0,0 +1,434 @@ +//===- GOFF.h - GOFF object file implementation -----------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file declares the GOFFObjectFile class. +// Record classes and derivatives are also declared and implemented. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_OBJECT_GOFF_H +#define LLVM_OBJECT_GOFF_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/BinaryFormat/GOFF.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +namespace object { + +/// \brief Represents a GOFF physical record. +/// +/// Specifies protected member functions to manipulate the record. These should +/// be called from deriving classes to change values as that record specifies. +class Record { + SmallVector Bytes; + +public: + Record() : Bytes(GOFF::RecordLength, (char)0x00) { + set(0, GOFF::PTVPrefix); + }; + + static Error GetContinuousData(const uint8_t *Record, uint16_t DataLength, + int DataIndex, + SmallString<256> &CompleteData) { + // First record. + const uint8_t *Slice = Record + DataIndex; + size_t SliceLength = + std::min(DataLength, (uint16_t)(GOFF::RecordLength - DataIndex)); + CompleteData.append(Slice, Slice + SliceLength); + DataLength -= SliceLength; + Slice += SliceLength; + + // Continuation records. + for (; DataLength > 0; + DataLength -= SliceLength, Slice += GOFF::PayloadLength) { + // Slice points to the start of the new record. + // Check that this block is a Continuation. + assert(Record::isContinuation(Slice) && "Continuation bit must be set"); + // Check that the last Continuation is terminated correctly. + if (DataLength <= 77 && Record::isContinued(Slice)) + return createStringError(object_error::parse_failed, + "continued bit should not be set"); + + SliceLength = std::min(DataLength, (uint16_t)GOFF::PayloadLength); + Slice += GOFF::RecordPrefixLength; + CompleteData.append(Slice, Slice + SliceLength); + } + return Error::success(); + } + + // Set PTV fields common to all records. + void setRecordType(GOFF::RecordType RecordType) { + setBits(1, 0, 4, RecordType); + } + + void setContinuation(bool Continuation) { setBits(1, 6, 1, Continuation); } + + void setContinued(bool Continued) { setBits(1, 7, 1, Continued); } + + static bool isContinued(const uint8_t *Record) { + uint8_t IsContinued; + getBits(Record, 1, 7, 1, IsContinued); + return IsContinued; + } + + static bool isContinuation(const uint8_t *Record) { + uint8_t IsContinuation; + getBits(Record, 1, 6, 1, IsContinuation); + return IsContinuation; + } + + const SmallVectorImpl &getBytes() const { return Bytes; } + +protected: + /// \brief Set bit field of specified byte. + /// + /// Used to pack bit fields into one byte. Fields are packed left to right. + /// Bit index zero is the most significant bit of the byte. + /// + /// \param ByteIndex index of byte the field is in. + /// \param BitIndex index of first bit of field. + /// \param Length length of bit field. + /// \param Value new value of bit field. + void setBits(uint8_t ByteIndex, uint8_t BitIndex, uint8_t Length, + uint8_t Value) { + assert(ByteIndex < GOFF::RecordLength && "Byte index out of bounds!"); + assert(BitIndex < 8 && "Bit index out of bounds!"); + assert(Length + BitIndex <= 8 && "Bit length too long!"); + + uint8_t Mask = ((1 << Length) - 1) << (8 - BitIndex - Length); + Value = Value << (8 - BitIndex - Length); + assert((Value & Mask) == Value && "Bits set outside of range!"); + + uint8_t PrevValue; + get(reinterpret_cast(Bytes.data()), ByteIndex, + PrevValue); + set(ByteIndex, (PrevValue & ~Mask) | Value); + } + + // Set byte of record. + template void set(uint8_t ByteIndex, T Value) { + assert(ByteIndex + sizeof(T) - 1 < GOFF::RecordLength && + "Byte index out of bounds!"); + support::endian::write( + &Bytes[ByteIndex], Value); + } + + static void getBits(const uint8_t *Bytes, uint8_t ByteIndex, uint8_t BitIndex, + uint8_t Length, uint8_t &Value) { + assert(ByteIndex < GOFF::RecordLength && "Byte index out of bounds!"); + assert(BitIndex < 8 && "Bit index out of bounds!"); + assert(Length + BitIndex <= 8 && "Bit length too long!"); + + get(Bytes, ByteIndex, Value); + Value = (Value >> (8 - BitIndex - Length)) & ((1 << Length) - 1); + } + + template + static void get(const uint8_t *Bytes, uint8_t ByteIndex, T &Value) { + assert(ByteIndex + sizeof(T) <= GOFF::RecordLength && + "Byte index out of bounds!"); + Value = support::endian::read( + &Bytes[ByteIndex]); + } +}; + +class HDRRecord : public Record { +public: + HDRRecord() : Record() { + setRecordType(GOFF::RT_HDR); + setArchitectureLevel(1); + } + + static Error GetData(const uint8_t *Record, SmallString<256> &CompleteData) { + uint16_t Length = getPropertyModuleLength(Record); + return GetContinuousData(Record, Length, 60, CompleteData); + } + + static uint16_t getPropertyModuleLength(const uint8_t *Record) { + uint16_t Length; + get(Record, 52, Length); + return Length; + } + + void setArchitectureLevel(uint32_t Level) { set(48, Level); } +}; + +class ESDRecord : public Record { +public: + /// \brief Number of bytes for name; any more must go in continuation. + /// This is the number of bytes that can fit into the data field of an ESD + /// record. + static const uint8_t ESDMaxUncontinuedNameLength = 8; + + /// \brief Maximum name length for ESD records and continuations. + /// This is the number of bytes that can fit into the data field of an ESD + /// record AND following continuations. This is limited fundamentally by the + /// 16 bit SIGNED length field. + static const uint16_t MaxNameLength = 32 * 1024; + +public: + ESDRecord() : Record() { setRecordType(GOFF::RT_ESD); } + + static Error GetData(const uint8_t *Record, SmallString<256> &CompleteData) { + uint16_t dataSize = getNameLength(Record); + return GetContinuousData(Record, dataSize, 72, CompleteData); + } + + void setSymbolType(GOFF::ESDSymbolType SymbolType) { + set(3, SymbolType); + } + void setEsdId(uint32_t EsdId) { set(4, EsdId); } + void setParentEsdId(uint32_t EsdId) { set(8, EsdId); } + void setOffset(uint32_t Offset) { set(16, Offset); } + void setLength(uint32_t Length) { set(24, Length); } + void setNameSpaceId(GOFF::ESDNameSpaceId Id) { set(40, Id); } + void setFillBytePresent(bool Present) { setBits(41, 0, 1, Present); } + void setNameMangled(bool Mangled) { setBits(41, 1, 1, Mangled); } + void setRenamable(bool Renamable) { setBits(41, 2, 1, Renamable); } + void setRemovable(bool Removable) { setBits(41, 3, 1, Removable); } + void setReserveQwords(GOFF::ESDReserveQwords Reserve) { + setBits(41, 5, 3, Reserve); + } + void setFillByteValue(uint8_t Fill) { set(42, Fill); } + void setAdaEsdId(uint32_t EsdId) { set(44, EsdId); } + void setSortPriority(uint32_t Priority) { set(48, Priority); } + void setAmode(GOFF::ESDAmode Amode) { set(60, Amode); } + void setRmode(GOFF::ESDRmode Rmode) { set(61, Rmode); } + void setTextStyle(GOFF::ESDTextStyle Style) { setBits(62, 0, 4, Style); } + void setBindingAlgorithm(GOFF::ESDBindingAlgorithm Algorithm) { + setBits(62, 4, 4, Algorithm); + } + void setTaskingBehavior(GOFF::ESDTaskingBehavior TaskingBehavior) { + setBits(63, 0, 3, TaskingBehavior); + } + void setReadOnly(bool ReadOnly) { setBits(63, 4, 1, ReadOnly); } + void setExecutable(GOFF::ESDExecutable Executable) { + setBits(63, 5, 3, Executable); + } + void setDuplicateSeverity(GOFF::ESDDuplicateSymbolSeverity DSS) { + setBits(64, 2, 2, DSS); + } + void setBindingStrength(GOFF::ESDBindingStrength Strength) { + setBits(64, 4, 4, Strength); + } + void setLoadingBehavior(GOFF::ESDLoadingBehavior Behavior) { + setBits(65, 0, 2, Behavior); + } + void setIndirectReference(bool Indirect) { setBits(65, 3, 1, Indirect); } + void setBindingScope(GOFF::ESDBindingScope Scope) { + setBits(65, 4, 4, Scope); + } + void setLinkageType(GOFF::ESDLinkageType Type) { setBits(66, 2, 1, Type); } + void setAlignment(GOFF::ESDAlignment Alignment) { + setBits(66, 3, 5, Alignment); + } + void setNameLength(uint16_t Length) { set(70, Length); } + + // ESD Get routines. + static void getSymbolType(const uint8_t *Record, + GOFF::ESDSymbolType &SymbolType) { + uint8_t Value; + get(Record, 3, Value); + SymbolType = (GOFF::ESDSymbolType)Value; + } + + static void getEsdId(const uint8_t *Record, uint32_t &EsdId) { + get(Record, 4, EsdId); + } + + static void getParentEsdId(const uint8_t *Record, uint32_t &EsdId) { + get(Record, 8, EsdId); + } + + static void getOffset(const uint8_t *Record, uint32_t &Offset) { + get(Record, 16, Offset); + } + + static void getLength(const uint8_t *Record, uint32_t &Length) { + get(Record, 24, Length); + } + + static void getNameSpaceId(const uint8_t *Record, GOFF::ESDNameSpaceId &Id) { + uint8_t Value; + get(Record, 40, Value); + Id = (GOFF::ESDNameSpaceId)Value; + } + + static void getFillBytePresent(const uint8_t *Record, bool &Present) { + uint8_t Value; + getBits(Record, 41, 0, 1, Value); + Present = (bool)Value; + } + + static void getNameMangled(const uint8_t *Record, bool &Mangled) { + uint8_t Value; + getBits(Record, 41, 1, 1, Value); + Mangled = (bool)Value; + } + + static void getRenamable(const uint8_t *Record, bool &Renamable) { + uint8_t Value; + getBits(Record, 41, 2, 1, Value); + Renamable = (bool)Value; + } + + static void getRemovable(const uint8_t *Record, bool &Removable) { + uint8_t Value; + getBits(Record, 41, 3, 1, Value); + Removable = (bool)Value; + } + + static void getFillByteValue(const uint8_t *Record, uint8_t &Fill) { + get(Record, 42, Fill); + } + + static void getAdaEsdId(const uint8_t *Record, uint32_t &EsdId) { + get(Record, 44, EsdId); + } + + static void getSortPriority(const uint8_t *Record, uint32_t &Priority) { + get(Record, 48, Priority); + } + + static void getAmode(const uint8_t *Record, GOFF::ESDAmode &Amode) { + uint8_t Value; + get(Record, 60, Value); + Amode = (GOFF::ESDAmode)Value; + } + + static void getRmode(const uint8_t *Record, GOFF::ESDRmode &Rmode) { + uint8_t Value; + get(Record, 61, Value); + Rmode = (GOFF::ESDRmode)Value; + } + + static void getTextStyle(const uint8_t *Record, GOFF::ESDTextStyle &Style) { + uint8_t Value; + getBits(Record, 62, 0, 4, Value); + Style = (GOFF::ESDTextStyle)Value; + } + + static void getBindingAlgorithm(const uint8_t *Record, + GOFF::ESDBindingAlgorithm &Algorithm) { + uint8_t Value; + getBits(Record, 62, 4, 4, Value); + Algorithm = (GOFF::ESDBindingAlgorithm)Value; + } + + static void getTaskingBehavior(const uint8_t *Record, + GOFF::ESDTaskingBehavior &TaskingBehavior) { + uint8_t Value; + getBits(Record, 63, 0, 3, Value); + TaskingBehavior = (GOFF::ESDTaskingBehavior)Value; + } + + static void getReadOnly(const uint8_t *Record, bool &ReadOnly) { + uint8_t Value; + getBits(Record, 63, 4, 1, Value); + ReadOnly = (bool)Value; + } + + static void getExecutable(const uint8_t *Record, + GOFF::ESDExecutable &Executable) { + uint8_t Value; + getBits(Record, 63, 5, 3, Value); + Executable = (GOFF::ESDExecutable)Value; + } + + static void getDuplicateSeverity(const uint8_t *Record, + GOFF::ESDDuplicateSymbolSeverity &DSS) { + uint8_t Value; + getBits(Record, 64, 2, 2, Value); + DSS = (GOFF::ESDDuplicateSymbolSeverity)Value; + } + + static void getBindingStrength(const uint8_t *Record, + GOFF::ESDBindingStrength &Strength) { + uint8_t Value; + getBits(Record, 64, 4, 4, Value); + Strength = (GOFF::ESDBindingStrength)Value; + } + + static void getLoadingBehavior(const uint8_t *Record, + GOFF::ESDLoadingBehavior &Behavior) { + uint8_t Value; + getBits(Record, 65, 0, 2, Value); + Behavior = (GOFF::ESDLoadingBehavior)Value; + } + + static void getIndirectReference(const uint8_t *Record, bool &Indirect) { + uint8_t Value; + getBits(Record, 65, 3, 1, Value); + Indirect = (bool)Value; + } + + static void getBindingScope(const uint8_t *Record, + GOFF::ESDBindingScope &Scope) { + uint8_t Value; + getBits(Record, 65, 4, 4, Value); + Scope = (GOFF::ESDBindingScope)Value; + } + + static void getLinkageType(const uint8_t *Record, + GOFF::ESDLinkageType &Type) { + uint8_t Value; + getBits(Record, 66, 2, 1, Value); + Type = (GOFF::ESDLinkageType)Value; + } + + static void getAlignment(const uint8_t *Record, + GOFF::ESDAlignment &Alignment) { + uint8_t Value; + getBits(Record, 66, 3, 5, Value); + Alignment = (GOFF::ESDAlignment)Value; + } + + static uint16_t getNameLength(const uint8_t *Record) { + uint16_t Length; + get(Record, 70, Length); + return Length; + } +}; + +class ENDRecord : public Record { +public: + ENDRecord() : Record() { + setRecordType(GOFF::RT_END); + setEntryPointRequestType(GOFF::END_EPR_None); // TODO Always None for now. + } + + static Error GetData(const uint8_t *Record, SmallString<256> &CompleteData) { + uint16_t Length = getNameLength(Record); + return GetContinuousData(Record, Length, 26, CompleteData); + } + + static uint16_t getNameLength(const uint8_t *Record) { + uint16_t Length; + get(Record, 24, Length); + return Length; + } + + void setEntryPointRequestType(GOFF::ENDEntryPointRequest Eprt) { + setBits(3, 6, 2, Eprt); + } + + void setEntryAmode(GOFF::ESDAmode Amode) { set(4, Amode); } + + void setEntryEsdId(uint32_t EsdId) { set(12, EsdId); } + + void setRecordCount(uint32_t RecordCount) { set(8, RecordCount); } +}; + +} // end namespace object +} // end namespace llvm + +#endif diff --git a/llvm/include/llvm/Object/GOFFObjectFile.h b/llvm/include/llvm/Object/GOFFObjectFile.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Object/GOFFObjectFile.h @@ -0,0 +1,127 @@ +//===- GOFF.h - GOFF object file implementation -----------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file declares the GOFFObjectFile class. +// Record classes and derivatives are also declared and implemented. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_OBJECT_GOFFOBJECTFILE_H +#define LLVM_OBJECT_GOFFOBJECTFILE_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/IndexedMap.h" +#include "llvm/ADT/Triple.h" +#include "llvm/BinaryFormat/GOFF.h" +#include "llvm/MC/SubtargetFeature.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/CharSet.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +namespace object { + +class GOFFObjectFile : public ObjectFile { + IndexedMap EsdPtrs; // Indexed by EsdId. + + mutable DenseMap EsdNamesCache; + + typedef DataRefImpl SectionEntryImpl; + // (EDID, 0) code, r/o data section + // (EDID,PRID) r/w data section + SmallVector SectionList; + mutable DenseMap SectionDataCache; + + CharSetConverter Converter; + +public: + Expected getSymbolName(SymbolRef Symbol) const; + + GOFFObjectFile(MemoryBufferRef Object, Error &Err); + static inline bool classof(const Binary *V) { return V->isGOFF(); } + section_iterator section_begin() const override; + section_iterator section_end() const override; + + uint8_t getBytesInAddress() const override { return 8; } + + StringRef getFileFormatName() const override { return "GOFF-SystemZ"; } + + Triple::ArchType getArch() const override { return Triple::systemz; } + + SubtargetFeatures getFeatures() const override { return SubtargetFeatures(); } + + bool isRelocatableObject() const override { return true; } + + void moveSymbolNext(DataRefImpl &Symb) const override; + basic_symbol_iterator symbol_begin() const override; + basic_symbol_iterator symbol_end() const override; + +private: + // SymbolRef. + Expected getSymbolName(DataRefImpl Symb) const override; + Expected getSymbolAddress(DataRefImpl Symb) const override; + uint64_t getSymbolValueImpl(DataRefImpl Symb) const override; + uint64_t getCommonSymbolSizeImpl(DataRefImpl Symb) const override; + Expected getSymbolFlags(DataRefImpl Symb) const override; + Expected getSymbolType(DataRefImpl Symb) const override; + Expected getSymbolSection(DataRefImpl Symb) const override; + + const uint8_t *getSymbolEsdRecord(DataRefImpl Symb) const; + bool isSymbolUnresolved(DataRefImpl Symb) const; + bool isSymbolIndirect(DataRefImpl Symb) const; + + // SectionRef. + void moveSectionNext(DataRefImpl &Sec) const override{}; + virtual Expected getSectionName(DataRefImpl Sec) const override { + return StringRef(); + } + uint64_t getSectionAddress(DataRefImpl Sec) const override { return 0; } + uint64_t getSectionSize(DataRefImpl Sec) const override { return 0; } + virtual Expected> + getSectionContents(DataRefImpl Sec) const override { + return ArrayRef(); + } + uint64_t getSectionIndex(DataRefImpl Sec) const override { return 0; } + uint64_t getSectionAlignment(DataRefImpl Sec) const override { return 0; } + bool isSectionCompressed(DataRefImpl Sec) const override { return false; } + bool isSectionText(DataRefImpl Sec) const override { return false; } + bool isSectionData(DataRefImpl Sec) const override { return false; } + bool isSectionBSS(DataRefImpl Sec) const override { return false; } + bool isSectionVirtual(DataRefImpl Sec) const override { return false; } + relocation_iterator section_rel_begin(DataRefImpl Sec) const override { + return relocation_iterator(RelocationRef(Sec, this)); + } + relocation_iterator section_rel_end(DataRefImpl Sec) const override { + return relocation_iterator(RelocationRef(Sec, this)); + } + + const uint8_t *getSectionEdEsdRecord(DataRefImpl &Sec) const; + const uint8_t *getSectionPrEsdRecord(DataRefImpl &Sec) const; + const uint8_t *getSectionEdEsdRecord(uint32_t SectionIndex) const; + const uint8_t *getSectionPrEsdRecord(uint32_t SectionIndex) const; + + // RelocationRef. + void moveRelocationNext(DataRefImpl &Rel) const override{}; + uint64_t getRelocationOffset(DataRefImpl Rel) const override { return 0; } + symbol_iterator getRelocationSymbol(DataRefImpl Rel) const override { + DataRefImpl Temp; + return basic_symbol_iterator(SymbolRef(Temp, this)); + } + uint64_t getRelocationType(DataRefImpl Rel) const override { return 0; } + void getRelocationTypeName(DataRefImpl Rel, + SmallVectorImpl &Result) const override{}; +}; + +} // namespace object + +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/Object/ObjectFile.h b/llvm/include/llvm/Object/ObjectFile.h --- a/llvm/include/llvm/Object/ObjectFile.h +++ b/llvm/include/llvm/Object/ObjectFile.h @@ -375,6 +375,9 @@ uint32_t UniversalCputype = 0, uint32_t UniversalIndex = 0); + static Expected> + createGOFFObjectFile(MemoryBufferRef Object); + static Expected> createWasmObjectFile(MemoryBufferRef Object); }; diff --git a/llvm/lib/BinaryFormat/Magic.cpp b/llvm/lib/BinaryFormat/Magic.cpp --- a/llvm/lib/BinaryFormat/Magic.cpp +++ b/llvm/lib/BinaryFormat/Magic.cpp @@ -83,6 +83,10 @@ if (startswith(Magic, "!\n") || startswith(Magic, "!\n")) return file_magic::archive; break; + case 0x03: + if (startswith(Magic, "\x03\xF0\x00")) + return file_magic::goff_object; + break; case '\177': if (startswith(Magic, "\177ELF") && Magic.size() >= 18) { diff --git a/llvm/lib/Object/Binary.cpp b/llvm/lib/Object/Binary.cpp --- a/llvm/lib/Object/Binary.cpp +++ b/llvm/lib/Object/Binary.cpp @@ -69,6 +69,7 @@ case file_magic::macho_kext_bundle: case file_magic::coff_object: case file_magic::coff_import_library: + case file_magic::goff_object: case file_magic::pecoff_executable: case file_magic::bitcode: case file_magic::xcoff_object_32: diff --git a/llvm/lib/Object/CMakeLists.txt b/llvm/lib/Object/CMakeLists.txt --- a/llvm/lib/Object/CMakeLists.txt +++ b/llvm/lib/Object/CMakeLists.txt @@ -9,6 +9,7 @@ ELF.cpp ELFObjectFile.cpp Error.cpp + GOFFObjectFile.cpp FaultMapParser.cpp IRObjectFile.cpp IRSymtab.cpp diff --git a/llvm/lib/Object/GOFFObjectFile.cpp b/llvm/lib/Object/GOFFObjectFile.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Object/GOFFObjectFile.cpp @@ -0,0 +1,422 @@ +//===- GOFFObjectFile.cpp - GOFF object file implementation -----*- 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 +// +//===----------------------------------------------------------------------===// +// +// Implementation of the GOFFObjectFile class. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/GOFFObjectFile.h" +#include "llvm/BinaryFormat/GOFF.h" +#include "llvm/Object/GOFF.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/raw_ostream.h" + +#ifndef DEBUG_TYPE +#define DEBUG_TYPE "goff" +#endif + +using namespace llvm; +using namespace object; + +Expected> +ObjectFile::createGOFFObjectFile(MemoryBufferRef Object) { + Error Err = Error::success(); + std::unique_ptr Ret(new GOFFObjectFile(Object, Err)); + if (Err) + return std::move(Err); + return std::move(Ret); +} + +GOFFObjectFile::GOFFObjectFile(MemoryBufferRef Object, Error &Err) + : ObjectFile(Binary::ID_GOFF, Object), + Converter(cantFail(errorOrToExpected(CharSetConverter::create( + CharSetConverter::CP_IBM1047, CharSetConverter::CP_UTF8)))) { + ErrorAsOutParameter ErrAsOutParam(&Err); + // Object file isn't the right size, bail out early. + if ((Object.getBufferSize() % GOFF::RecordLength) != 0) { + Err = createStringError( + object_error::unexpected_eof, + "object file is not the right size. Must be a multiple " + "of 80 bytes, but is " + + std::to_string(Object.getBufferSize()) + " bytes"); + return; + } + // Object file doesn't start/end with HDR/END records. + // Bail out early. + if (Object.getBufferSize() != 0) { + if ((base()[1] & 0xF0) >> 4 != GOFF::RT_HDR) { + Err = createStringError(object_error::parse_failed, + "object file must start with HDR record"); + return; + } + if ((base()[Object.getBufferSize() - GOFF::RecordLength + 1] & 0xF0) >> 4 != + GOFF::RT_END) { + Err = createStringError(object_error::parse_failed, + "object file must end with END record"); + return; + } + } + + SectionEntryImpl DummySection; + SectionList.emplace_back(DummySection); // Dummy entry at index 0. + + uint8_t PrevRecordType = 0; + uint8_t PrevContinuationBits = 0; + const uint8_t *End = reinterpret_cast(Data.getBufferEnd()); + for (const uint8_t *I = base(); I < End; I += GOFF::RecordLength) { + uint8_t RecordType = (I[1] & 0xF0) >> 4; + bool IsContinuation = I[1] & 0x02; + bool PrevWasContinued = PrevContinuationBits & 0x01; + + // If the previous record was continued, the current record should be a + // continuation. + if (PrevWasContinued && !IsContinuation) { + if (PrevRecordType == RecordType) { + Err = createStringError( + object_error::parse_failed, + "continued record is not followed by a continuation record"); + return; + } + } + // Don't parse continuations records, only parse initial record. + if (IsContinuation) { + if (RecordType != PrevRecordType) { + Err = createStringError( + object_error::parse_failed, + "continuation record type does not match previous record"); + return; + } + if (!PrevWasContinued) { + Err = createStringError(object_error::parse_failed, + "previous record was not continued"); + return; + } + PrevRecordType = RecordType; + PrevContinuationBits = I[1] & 0x03; + continue; + } + + for (size_t J = 0; J < GOFF::RecordLength; ++J) { + const uint8_t *P = I + J; + if (J % 8 == 0) + LLVM_DEBUG(dbgs() << " "); + + LLVM_DEBUG(dbgs() << format("%02hhX", *P)); + } + switch (RecordType) { + case GOFF::RT_ESD: { + // Save ESD record. + uint32_t EsdId; + ESDRecord::getEsdId(I, EsdId); + EsdPtrs.grow(EsdId); + EsdPtrs[EsdId] = I; + + // Determine and save the "sections" in GOFF. + // A section is saved as a tuple of the form + // case (1): (ED,child PR) + // - where the PR must have non-zero length. + // case (2a) (ED,0) + // - where the ED is of non-zero length. + // case (2b) (ED,0) + // - where the ED is zero length but + // contains a label (LD). + GOFF::ESDSymbolType SymbolType; + ESDRecord::getSymbolType(I, SymbolType); + SectionEntryImpl Section; + uint32_t Length; + ESDRecord::getLength(I, Length); + if (SymbolType == GOFF::ESD_ST_ElementDefinition) { + // case (2a) + if (Length != 0) { + Section.d.a = EsdId; + SectionList.emplace_back(Section); + } + } else if (SymbolType == GOFF::ESD_ST_PartReference) { + // case (1) + if (Length != 0) { + uint32_t SymEdId; + ESDRecord::getParentEsdId(I, SymEdId); + Section.d.a = SymEdId; + Section.d.b = EsdId; + SectionList.emplace_back(Section); + } + } else if (SymbolType == GOFF::ESD_ST_LabelDefinition) { + // case (2b) + uint32_t SymEdId; + ESDRecord::getParentEsdId(I, SymEdId); + const uint8_t *SymEdRecord = EsdPtrs[SymEdId]; + uint32_t EdLength; + ESDRecord::getLength(SymEdRecord, EdLength); + if (!EdLength) { // [ EDID, PRID ] + // LD child of a zero length parent ED. + // Add the section ED which was previously ignored. + Section.d.a = SymEdId; + SectionList.emplace_back(Section); + } + } + LLVM_DEBUG(dbgs() << " -- ESD " << EsdId << "\n"); + break; + } + case GOFF::RT_END: + LLVM_DEBUG(dbgs() << " -- END (GOFF record type) unhandled\n"); + break; + case GOFF::RT_HDR: + LLVM_DEBUG(dbgs() << " -- HDR (GOFF record type) unhandled\n"); + break; + default: + llvm_unreachable("Unknown record type"); + } + PrevRecordType = RecordType; + PrevContinuationBits = I[1] & 0x03; + } +} + +const uint8_t *GOFFObjectFile::getSymbolEsdRecord(DataRefImpl Symb) const { + const uint8_t *EsdRecord = EsdPtrs[Symb.d.a]; + return EsdRecord; +} + +Expected GOFFObjectFile::getSymbolName(DataRefImpl Symb) const { + if (EsdNamesCache.count(Symb.d.a)) + return EsdNamesCache[Symb.d.a]; + + SmallString<256> SymbolName; + if (auto Err = ESDRecord::getData(getSymbolEsdRecord(Symb), SymbolName)) + return std::move(Err); + + SmallString<256> SymbolNameConverted; + if (auto EC = Converter.convert(SymbolName, SymbolNameConverted)) + return errorCodeToError(EC); + EsdNamesCache[Symb.d.a].assign(SymbolNameConverted.c_str()); + return EsdNamesCache[Symb.d.a]; +} + +Expected GOFFObjectFile::getSymbolName(SymbolRef Symbol) const { + return getSymbolName(Symbol.getRawDataRefImpl()); +} + +Expected GOFFObjectFile::getSymbolAddress(DataRefImpl Symb) const { + uint32_t Offset; + const uint8_t *EsdRecord = getSymbolEsdRecord(Symb); + ESDRecord::getOffset(EsdRecord, Offset); + return static_cast(Offset); +} + +uint64_t GOFFObjectFile::getSymbolValueImpl(DataRefImpl Symb) const { + uint32_t Offset; + const uint8_t *EsdRecord = getSymbolEsdRecord(Symb); + ESDRecord::getOffset(EsdRecord, Offset); + return static_cast(Offset); +} + +uint64_t GOFFObjectFile::getCommonSymbolSizeImpl(DataRefImpl Symb) const { + return 0; +} + +bool GOFFObjectFile::isSymbolUnresolved(DataRefImpl Symb) const { + const uint8_t *Record = getSymbolEsdRecord(Symb); + GOFF::ESDSymbolType SymbolType; + ESDRecord::getSymbolType(Record, SymbolType); + + if (SymbolType == GOFF::ESD_ST_ExternalReference) + return true; + if (SymbolType == GOFF::ESD_ST_PartReference) { + uint32_t Length; + ESDRecord::getLength(Record, Length); + if (Length == 0) + return true; + } + return false; +} + +bool GOFFObjectFile::isSymbolIndirect(DataRefImpl Symb) const { + const uint8_t *Record = getSymbolEsdRecord(Symb); + bool Indirect; + ESDRecord::getIndirectReference(Record, Indirect); + return Indirect; +} + +Expected GOFFObjectFile::getSymbolFlags(DataRefImpl Symb) const { + uint32_t Flags = 0; + if (isSymbolUnresolved(Symb)) + Flags |= SymbolRef::SF_Undefined; + + const uint8_t *Record = getSymbolEsdRecord(Symb); + + GOFF::ESDBindingStrength BindingStrength; + ESDRecord::getBindingStrength(Record, BindingStrength); + if (BindingStrength == GOFF::ESD_BST_Weak) + Flags |= SymbolRef::SF_Weak; + + GOFF::ESDBindingScope BindingScope; + ESDRecord::getBindingScope(Record, BindingScope); + + if (BindingScope != GOFF::ESD_BSC_Section) { + Expected Name = getSymbolName(Symb); + if (Name && *Name != " ") { // Blank name is local. + Flags |= SymbolRef::SF_Global; + if (BindingScope == GOFF::ESD_BSC_ImportExport) + Flags |= SymbolRef::SF_Exported; + else if (!(Flags & SymbolRef::SF_Undefined)) + Flags |= SymbolRef::SF_Hidden; + } + } + + return Flags; +} + +Expected +GOFFObjectFile::getSymbolType(DataRefImpl Symb) const { + const uint8_t *Record = getSymbolEsdRecord(Symb); + GOFF::ESDSymbolType SymbolType; + ESDRecord::getSymbolType(Record, SymbolType); + GOFF::ESDExecutable Executable; + ESDRecord::getExecutable(Record, Executable); + + if (SymbolType != GOFF::ESD_ST_SectionDefinition && + SymbolType != GOFF::ESD_ST_ElementDefinition && + SymbolType != GOFF::ESD_ST_LabelDefinition && + SymbolType != GOFF::ESD_ST_PartReference && + SymbolType != GOFF::ESD_ST_ExternalReference) { + uint32_t EsdId; + ESDRecord::getEsdId(Record, EsdId); + return createStringError(llvm::errc::invalid_argument, + "ESD record %" PRIu32 + " has invalid symbol type 0x%02" PRIX8, + EsdId, SymbolType); + } + switch (SymbolType) { + case GOFF::ESD_ST_SectionDefinition: + case GOFF::ESD_ST_ElementDefinition: + return SymbolRef::ST_Other; + case GOFF::ESD_ST_LabelDefinition: + case GOFF::ESD_ST_PartReference: + case GOFF::ESD_ST_ExternalReference: + if (Executable != GOFF::ESD_EXE_CODE && Executable != GOFF::ESD_EXE_DATA && + Executable != GOFF::ESD_EXE_Unspecified) { + uint32_t EsdId; + ESDRecord::getEsdId(Record, EsdId); + return createStringError(llvm::errc::invalid_argument, + "ESD record %" PRIu32 + " has unknown Executable type 0x%02X", + EsdId, Executable); + } + switch (Executable) { + case GOFF::ESD_EXE_CODE: + return SymbolRef::ST_Function; + case GOFF::ESD_EXE_DATA: + return SymbolRef::ST_Data; + case GOFF::ESD_EXE_Unspecified: + return SymbolRef::ST_Unknown; + } + } +} + +Expected +GOFFObjectFile::getSymbolSection(DataRefImpl Symb) const { + DataRefImpl Sec; + + if (isSymbolUnresolved(Symb)) + return section_iterator(SectionRef(Sec, this)); + + const uint8_t *SymEsdRecord = EsdPtrs[Symb.d.a]; + uint32_t SymEdId; + ESDRecord::getParentEsdId(SymEsdRecord, SymEdId); + const uint8_t *SymEdRecord = EsdPtrs[SymEdId]; + + for (size_t I = 0, E = SectionList.size(); I < E; ++I) { + bool Found; + const uint8_t *SectionPrRecord = getSectionPrEsdRecord(I); + if (SectionPrRecord) { + Found = SymEsdRecord == SectionPrRecord; + } else { + const uint8_t *SectionEdRecord = getSectionEdEsdRecord(I); + Found = SymEdRecord == SectionEdRecord; + } + + if (Found) { + Sec.d.a = I; + return section_iterator(SectionRef(Sec, this)); + } + } + return createStringError(llvm::errc::invalid_argument, + "no symbol section found"); +} + +const uint8_t *GOFFObjectFile::getSectionEdEsdRecord(DataRefImpl &Sec) const { + SectionEntryImpl EsdIds = SectionList[Sec.d.a]; + const uint8_t *EsdRecord = EsdPtrs[EsdIds.d.a]; + return EsdRecord; +} + +const uint8_t *GOFFObjectFile::getSectionPrEsdRecord(DataRefImpl &Sec) const { + SectionEntryImpl EsdIds = SectionList[Sec.d.a]; + const uint8_t *EsdRecord = nullptr; + if (EsdIds.d.b) + EsdRecord = EsdPtrs[EsdIds.d.b]; + return EsdRecord; +} + +const uint8_t * +GOFFObjectFile::getSectionEdEsdRecord(uint32_t SectionIndex) const { + DataRefImpl Sec; + Sec.d.a = SectionIndex; + const uint8_t *EsdRecord = getSectionEdEsdRecord(Sec); + return EsdRecord; +} + +const uint8_t * +GOFFObjectFile::getSectionPrEsdRecord(uint32_t SectionIndex) const { + DataRefImpl Sec; + Sec.d.a = SectionIndex; + const uint8_t *EsdRecord = getSectionPrEsdRecord(Sec); + return EsdRecord; +} + +section_iterator GOFFObjectFile::section_begin() const { + DataRefImpl Sec; + moveSectionNext(Sec); + return section_iterator(SectionRef(Sec, this)); +} + +section_iterator GOFFObjectFile::section_end() const { + DataRefImpl Sec; + return section_iterator(SectionRef(Sec, this)); +} + +void GOFFObjectFile::moveSymbolNext(DataRefImpl &Symb) const { + for (uint32_t I = Symb.d.a + 1, E = EsdPtrs.size(); I < E; ++I) { + if (EsdPtrs[I]) { + const uint8_t *EsdRecord = EsdPtrs[I]; + GOFF::ESDSymbolType SymbolType; + ESDRecord::getSymbolType(EsdRecord, SymbolType); + // Skip EDs - i.e. section symbols. + bool IgnoreSpecialGOFFSymbols = true; + bool SkipSymbol = ((SymbolType == GOFF::ESD_ST_ElementDefinition) || + (SymbolType == GOFF::ESD_ST_SectionDefinition)) && + IgnoreSpecialGOFFSymbols; + if (!SkipSymbol) { + Symb.d.a = I; + return; + } + } + } + Symb.d.a = 0; +} + +basic_symbol_iterator GOFFObjectFile::symbol_begin() const { + DataRefImpl Symb; + moveSymbolNext(Symb); + return basic_symbol_iterator(SymbolRef(Symb, this)); +} + +basic_symbol_iterator GOFFObjectFile::symbol_end() const { + DataRefImpl Symb; + return basic_symbol_iterator(SymbolRef(Symb, this)); +} diff --git a/llvm/lib/Object/ObjectFile.cpp b/llvm/lib/Object/ObjectFile.cpp --- a/llvm/lib/Object/ObjectFile.cpp +++ b/llvm/lib/Object/ObjectFile.cpp @@ -126,6 +126,9 @@ // XCOFF implies AIX. TheTriple.setOS(Triple::AIX); TheTriple.setObjectFormat(Triple::XCOFF); + } else if (isGOFF()) { + TheTriple.setOS(Triple::ZOS); + TheTriple.setObjectFormat(Triple::GOFF); } return TheTriple; @@ -168,6 +171,8 @@ case file_magic::macho_dsym_companion: case file_magic::macho_kext_bundle: return createMachOObjectFile(Object); + case file_magic::goff_object: + return createGOFFObjectFile(Object); case file_magic::coff_object: case file_magic::coff_import_library: case file_magic::pecoff_executable: diff --git a/llvm/lib/Object/SymbolicFile.cpp b/llvm/lib/Object/SymbolicFile.cpp --- a/llvm/lib/Object/SymbolicFile.cpp +++ b/llvm/lib/Object/SymbolicFile.cpp @@ -64,6 +64,7 @@ case file_magic::macho_dsym_companion: case file_magic::macho_kext_bundle: case file_magic::pecoff_executable: + case file_magic::goff_object: case file_magic::xcoff_object_32: case file_magic::xcoff_object_64: case file_magic::wasm_object: diff --git a/llvm/unittests/BinaryFormat/TestFileMagic.cpp b/llvm/unittests/BinaryFormat/TestFileMagic.cpp --- a/llvm/unittests/BinaryFormat/TestFileMagic.cpp +++ b/llvm/unittests/BinaryFormat/TestFileMagic.cpp @@ -54,6 +54,7 @@ const char coff_import_library[] = "\x00\x00\xff\xff...."; const char elf_relocatable[] = {0x7f, 'E', 'L', 'F', 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +const char goff_object[] = "\x03\xF0\x00"; const char macho_universal_binary[] = "\xca\xfe\xba\xbe...\x00"; const char macho_object[] = "\xfe\xed\xfa\xce........\x00\x00\x00\x01............"; @@ -100,6 +101,7 @@ file_magic::coff_object}, DEFINE(coff_import_library), DEFINE(elf_relocatable), + DEFINE(goff_object), DEFINE(macho_universal_binary), DEFINE(macho_object), DEFINE(macho_executable), diff --git a/llvm/unittests/Object/CMakeLists.txt b/llvm/unittests/Object/CMakeLists.txt --- a/llvm/unittests/Object/CMakeLists.txt +++ b/llvm/unittests/Object/CMakeLists.txt @@ -9,6 +9,7 @@ ELFObjectFileTest.cpp ELFTypesTest.cpp ELFTest.cpp + GOFFObjectFileTest.cpp MinidumpTest.cpp ObjectFileTest.cpp SymbolSizeTest.cpp diff --git a/llvm/unittests/Object/GOFFObjectFileTest.cpp b/llvm/unittests/Object/GOFFObjectFileTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Object/GOFFObjectFileTest.cpp @@ -0,0 +1,499 @@ +//===- GOFFObjectFileTest.cpp - Tests for GOFFObjectFile ------------------===// +// +// 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/Object/GOFFObjectFile.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::GOFF; + +namespace { +char GOFFData[GOFF::RecordLength * 3] = {0x00}; + +void constructValidGOFF(size_t Size) { + StringRef ValidSize(GOFFData, Size); + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(ValidSize, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); +} + +void constructInvalidGOFF(size_t Size) { + // Construct GOFFObject with record of length != multiple of 80. + StringRef InvalidData(GOFFData, Size); + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(InvalidData, "dummyGOFF")); + + ASSERT_THAT_EXPECTED( + GOFFObjOrErr, + FailedWithMessage("object file is not the right size. Must be a multiple " + "of 80 bytes, but is " + + std::to_string(Size) + " bytes")); +} +} // namespace + +TEST(GOFFObjectFileTest, ConstructGOFFObjectValidSize) { + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + GOFFData[80] = 0x03; + GOFFData[81] = 0x40; + constructValidGOFF(160); + constructValidGOFF(0); +} + +TEST(GOFFObjectFileTest, ConstructGOFFObjectInvalidSize) { + constructInvalidGOFF(70); + constructInvalidGOFF(79); + constructInvalidGOFF(81); +} + +TEST(GOFFObjectFileTest, MissingHDR) { + char GOFFData[GOFF::RecordLength * 2] = {0x00}; + + // ESD record. + GOFFData[0] = 0x03; + + // END record. + GOFFData[GOFF::RecordLength] = 0x03; + GOFFData[GOFF::RecordLength + 1] = 0x40; + + StringRef Data(GOFFData, GOFF::RecordLength * 2); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED( + GOFFObjOrErr, + FailedWithMessage("object file must start with HDR record")); +} + +TEST(GOFFObjectFileTest, MissingEND) { + char GOFFData[GOFF::RecordLength * 2] = {0x00}; + + // HDR record. + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + + // ESD record. + GOFFData[GOFF::RecordLength] = 0x03; + + StringRef Data(GOFFData, GOFF::RecordLength * 2); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED( + GOFFObjOrErr, FailedWithMessage("object file must end with END record")); +} + +TEST(GOFFObjectFileTest, GetSymbolName) { + char GOFFData[GOFF::RecordLength * 3] = {0x00}; + + // HDR record. + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + + // ESD record. + GOFFData[GOFF::RecordLength] = 0x03; + GOFFData[GOFF::RecordLength + 3] = 0x02; + GOFFData[GOFF::RecordLength + 7] = 0x01; + GOFFData[GOFF::RecordLength + 11] = 0x01; + GOFFData[GOFF::RecordLength + 71] = 0x05; // Size of symbol name. + GOFFData[GOFF::RecordLength + 72] = 0xC8; // Symbol name is Hello. + GOFFData[GOFF::RecordLength + 73] = 0x85; + GOFFData[GOFF::RecordLength + 74] = 0x93; + GOFFData[GOFF::RecordLength + 75] = 0x93; + GOFFData[GOFF::RecordLength + 76] = 0x96; + + // END record. + GOFFData[GOFF::RecordLength * 2] = 0x03; + GOFFData[GOFF::RecordLength * 2 + 1] = 0x40; + + StringRef Data(GOFFData, GOFF::RecordLength * 3); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); + + GOFFObjectFile *GOFFObj = dyn_cast((*GOFFObjOrErr).get()); + + for (SymbolRef Symbol : GOFFObj->symbols()) { + Expected SymbolNameOrErr = GOFFObj->getSymbolName(Symbol); + ASSERT_THAT_EXPECTED(SymbolNameOrErr, Succeeded()); + StringRef SymbolName = SymbolNameOrErr.get(); + + EXPECT_EQ(SymbolName, "Hello"); + } +} + +TEST(GOFFObjectFileTest, ConcatenatedGOFFFile) { + char GOFFData[GOFF::RecordLength * 6] = {0x00}; + + // HDR record. + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + // ESD record. + GOFFData[GOFF::RecordLength] = 0x03; + // END record. + GOFFData[GOFF::RecordLength * 2] = 0x03; + GOFFData[GOFF::RecordLength * 2 + 1] = 0x40; + // HDR record. + GOFFData[GOFF::RecordLength * 3] = 0x03; + GOFFData[GOFF::RecordLength * 3 + 1] = 0xF0; + // ESD record. + GOFFData[GOFF::RecordLength * 4] = 0x03; + // END record. + GOFFData[GOFF::RecordLength * 5] = 0x03; + GOFFData[GOFF::RecordLength * 5 + 1] = 0x40; + + StringRef Data(GOFFData, GOFF::RecordLength * 6); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); +} + +TEST(GOFFObjectFileTest, ContinuationGetSymbolName) { + char GOFFContData[GOFF::RecordLength * 4] = {0x00}; + + // HDR record. + GOFFContData[0] = 0x03; + GOFFContData[1] = 0xF0; + + // ESD record. + GOFFContData[GOFF::RecordLength] = 0x03; + GOFFContData[GOFF::RecordLength + 1] = 0x01; + GOFFContData[GOFF::RecordLength + 3] = 0x02; + GOFFContData[GOFF::RecordLength + 7] = 0x01; + GOFFContData[GOFF::RecordLength + 11] = 0x01; + GOFFContData[GOFF::RecordLength + 71] = 0x0A; // Size of symbol name. + GOFFContData[GOFF::RecordLength + 72] = 0xC8; // Symbol name is HelloWorld. + GOFFContData[GOFF::RecordLength + 73] = 0x85; + GOFFContData[GOFF::RecordLength + 74] = 0x93; + GOFFContData[GOFF::RecordLength + 75] = 0x93; + GOFFContData[GOFF::RecordLength + 76] = 0x96; + GOFFContData[GOFF::RecordLength + 77] = 0xA6; + GOFFContData[GOFF::RecordLength + 78] = 0x96; + GOFFContData[GOFF::RecordLength + 79] = 0x99; + + // ESD continuation record. + GOFFContData[GOFF::RecordLength * 2] = 0x03; + GOFFContData[GOFF::RecordLength * 2 + 1] = 0x02; // No further continuations. + GOFFContData[GOFF::RecordLength * 2 + 3] = 0x93; + GOFFContData[GOFF::RecordLength * 2 + 4] = 0x84; + + // END record. + GOFFContData[GOFF::RecordLength * 3] = 0x03; + GOFFContData[GOFF::RecordLength * 3 + 1] = 0x40; + + StringRef Data(GOFFContData, GOFF::RecordLength * 4); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); + + GOFFObjectFile *GOFFObj = dyn_cast((*GOFFObjOrErr).get()); + + for (SymbolRef Symbol : GOFFObj->symbols()) { + Expected SymbolNameOrErr = GOFFObj->getSymbolName(Symbol); + ASSERT_THAT_EXPECTED(SymbolNameOrErr, Succeeded()); + StringRef SymbolName = SymbolNameOrErr.get(); + EXPECT_EQ(SymbolName, "Helloworld"); + } +} + +TEST(GOFFObjectFileTest, ContinuationBitNotSet) { + char GOFFContData[GOFF::RecordLength * 4] = {0x00}; + + // HDR record. + GOFFContData[0] = 0x03; + GOFFContData[1] = 0xF0; + + // ESD record. + GOFFContData[GOFF::RecordLength] = 0x03; + GOFFContData[GOFF::RecordLength + 1] = 0x01; + GOFFContData[GOFF::RecordLength + 3] = 0x02; + GOFFContData[GOFF::RecordLength + 7] = 0x01; + GOFFContData[GOFF::RecordLength + 11] = 0x01; + GOFFContData[GOFF::RecordLength + 71] = 0x0A; // Size of symbol name. + GOFFContData[GOFF::RecordLength + 72] = 0xC8; // Symbol name is HelloWorld. + GOFFContData[GOFF::RecordLength + 73] = 0x85; + GOFFContData[GOFF::RecordLength + 74] = 0x93; + GOFFContData[GOFF::RecordLength + 75] = 0x93; + GOFFContData[GOFF::RecordLength + 76] = 0x96; + GOFFContData[GOFF::RecordLength + 77] = 0xA6; + GOFFContData[GOFF::RecordLength + 78] = 0x96; + GOFFContData[GOFF::RecordLength + 79] = 0x99; + + // ESD continuation record. + GOFFContData[GOFF::RecordLength * 2] = 0x03; + GOFFContData[GOFF::RecordLength * 2 + 1] = 0x00; + GOFFContData[GOFF::RecordLength * 2 + 3] = 0x93; + GOFFContData[GOFF::RecordLength * 2 + 4] = 0x84; + + // END record. + GOFFContData[GOFF::RecordLength * 3] = 0x03; + GOFFContData[GOFF::RecordLength * 3 + 1] = 0x40; + + StringRef Data(GOFFContData, GOFF::RecordLength * 4); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + EXPECT_THAT_EXPECTED( + GOFFObjOrErr, + FailedWithMessage( + "continued record is not followed by a continuation record")); +} + +TEST(GOFFObjectFileTest, ContinuationRecordNotTerminated) { + char GOFFContData[GOFF::RecordLength * 4] = {0x00}; + + // HDR record. + GOFFContData[0] = 0x03; + GOFFContData[1] = 0xF0; + + // ESD record. + GOFFContData[GOFF::RecordLength] = 0x03; + GOFFContData[GOFF::RecordLength + 1] = 0x01; + GOFFContData[GOFF::RecordLength + 3] = 0x02; + GOFFContData[GOFF::RecordLength + 7] = 0x01; + GOFFContData[GOFF::RecordLength + 11] = 0x01; + GOFFContData[GOFF::RecordLength + 71] = 0x0A; // Size of symbol name. + GOFFContData[GOFF::RecordLength + 72] = 0xC8; // Symbol name is HelloWorld. + GOFFContData[GOFF::RecordLength + 73] = 0x85; + GOFFContData[GOFF::RecordLength + 74] = 0x93; + GOFFContData[GOFF::RecordLength + 75] = 0x93; + GOFFContData[GOFF::RecordLength + 76] = 0x96; + GOFFContData[GOFF::RecordLength + 77] = 0xA6; + GOFFContData[GOFF::RecordLength + 78] = 0x96; + GOFFContData[GOFF::RecordLength + 79] = 0x99; + + // ESD continuation record. + GOFFContData[GOFF::RecordLength * 2] = 0x03; + GOFFContData[GOFF::RecordLength * 2 + 1] = 0x03; // Continued bit set. + GOFFContData[GOFF::RecordLength * 2 + 3] = 0x93; + GOFFContData[GOFF::RecordLength * 2 + 4] = 0x84; + + // END record. + GOFFContData[GOFF::RecordLength * 3] = 0x03; + GOFFContData[GOFF::RecordLength * 3 + 1] = 0x40; + + StringRef Data(GOFFContData, GOFF::RecordLength * 4); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); + + GOFFObjectFile *GOFFObj = dyn_cast((*GOFFObjOrErr).get()); + + for (SymbolRef Symbol : GOFFObj->symbols()) { + Expected SymbolNameOrErr = GOFFObj->getSymbolName(Symbol); + EXPECT_THAT_EXPECTED(SymbolNameOrErr, + FailedWithMessage("continued bit should not be set")); + } +} + +TEST(GOFFObjectFileTest, PrevNotContinued) { + char GOFFContData[GOFF::RecordLength * 4] = {0x00}; + + // HDR record. + GOFFContData[0] = 0x03; + GOFFContData[1] = 0xF0; + + // ESD record, with continued bit not set. + GOFFContData[GOFF::RecordLength] = 0x03; + + // ESD continuation record. + GOFFContData[GOFF::RecordLength * 2] = 0x03; + GOFFContData[GOFF::RecordLength * 2 + 1] = 0x02; + + // END record. + GOFFContData[GOFF::RecordLength * 3] = 0x03; + GOFFContData[GOFF::RecordLength * 3 + 1] = 0x40; + + StringRef Data(GOFFContData, GOFF::RecordLength * 4); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, + FailedWithMessage("previous record was not continued")); +} + +TEST(GOFFObjectFileTest, ContinuationTypeMismatch) { + char GOFFContData[GOFF::RecordLength * 4] = {0x00}; + + // HDR record. + GOFFContData[0] = 0x03; + GOFFContData[1] = 0xF0; + + // ESD record. + GOFFContData[GOFF::RecordLength] = 0x03; + GOFFContData[GOFF::RecordLength + 1] = 0x01; // Continued to next record. + + // END continuation record. + GOFFContData[GOFF::RecordLength * 2] = 0x03; + GOFFContData[GOFF::RecordLength * 2 + 1] = 0x42; + + // END record. + GOFFContData[GOFF::RecordLength * 3] = 0x03; + GOFFContData[GOFF::RecordLength * 3 + 1] = 0x40; + + StringRef Data(GOFFContData, GOFF::RecordLength * 4); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED( + GOFFObjOrErr, + FailedWithMessage( + "continuation record type does not match previous record")); +} + +TEST(GOFFObjectFileTest, TwoSymbols) { + char GOFFData[GOFF::RecordLength * 4] = {0x00}; + + // HDR record. + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + + // ESD record 1. + GOFFData[GOFF::RecordLength] = 0x03; + GOFFData[GOFF::RecordLength + 3] = 0x00; + GOFFData[GOFF::RecordLength + 7] = 0x01; // ESDID. + GOFFData[GOFF::RecordLength + 71] = 0x01; // Size of symbol name. + GOFFData[GOFF::RecordLength + 72] = 0xa7; // Symbol name is x. + + // ESD record 2. + GOFFData[GOFF::RecordLength * 2] = 0x03; + GOFFData[GOFF::RecordLength * 2 + 3] = 0x03; + GOFFData[GOFF::RecordLength * 2 + 7] = 0x02; // ESDID. + GOFFData[GOFF::RecordLength * 2 + 11] = 0x01; // Parent ESDID. + GOFFData[GOFF::RecordLength * 2 + 71] = 0x05; // Size of symbol name. + GOFFData[GOFF::RecordLength * 2 + 72] = 0xC8; // Symbol name is Hello. + GOFFData[GOFF::RecordLength * 2 + 73] = 0x85; + GOFFData[GOFF::RecordLength * 2 + 74] = 0x93; + GOFFData[GOFF::RecordLength * 2 + 75] = 0x93; + GOFFData[GOFF::RecordLength * 2 + 76] = 0x96; + + // END record. + GOFFData[GOFF::RecordLength * 3] = 0x03; + GOFFData[GOFF::RecordLength * 3 + 1] = 0x40; + + StringRef Data(GOFFData, GOFF::RecordLength * 4); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); + + GOFFObjectFile *GOFFObj = dyn_cast((*GOFFObjOrErr).get()); + + for (SymbolRef Symbol : GOFFObj->symbols()) { + Expected SymbolNameOrErr = GOFFObj->getSymbolName(Symbol); + ASSERT_THAT_EXPECTED(SymbolNameOrErr, Succeeded()); + StringRef SymbolName = SymbolNameOrErr.get(); + EXPECT_EQ(SymbolName, "Hello"); + } +} + +TEST(GOFFObjectFileTest, InvalidSymbolType) { + char GOFFData[GOFF::RecordLength * 3] = {0x00}; + + // HDR record. + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + + // ESD record. + GOFFData[GOFF::RecordLength] = 0x03; + GOFFData[GOFF::RecordLength + 3] = 0x05; + GOFFData[GOFF::RecordLength + 7] = 0x01; + GOFFData[GOFF::RecordLength + 11] = 0x01; + GOFFData[GOFF::RecordLength + 71] = 0x01; // Size of symbol name. + GOFFData[GOFF::RecordLength + 72] = 0xC8; // Symbol name. + + // END record. + GOFFData[GOFF::RecordLength * 2] = 0x03; + GOFFData[GOFF::RecordLength * 2 + 1] = 0x40; + + StringRef Data(GOFFData, GOFF::RecordLength * 3); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); + + GOFFObjectFile *GOFFObj = dyn_cast((*GOFFObjOrErr).get()); + + for (SymbolRef Symbol : GOFFObj->symbols()) { + Expected SymbolType = Symbol.getType(); + EXPECT_THAT_EXPECTED( + SymbolType, + FailedWithMessage("ESD record 1 has invalid symbol type 0x05")); + + Expected SymSI = Symbol.getSection(); + ASSERT_THAT_EXPECTED(SymSI, FailedWithMessage("no symbol section found")); + } +} + +TEST(GOFFObjectFileTest, InvalidERSymbolType) { + char GOFFData[GOFF::RecordLength * 3] = {0x00}; + + // HDR record. + GOFFData[0] = 0x03; + GOFFData[1] = 0xF0; + + // ESD record. + GOFFData[GOFF::RecordLength] = 0x03; + GOFFData[GOFF::RecordLength + 3] = 0x04; + GOFFData[GOFF::RecordLength + 7] = 0x01; + GOFFData[GOFF::RecordLength + 11] = 0x01; + GOFFData[GOFF::RecordLength + 63] = 0x03; // Unknown executable type. + GOFFData[GOFF::RecordLength + 71] = 0x01; // Size of symbol name. + GOFFData[GOFF::RecordLength + 72] = 0xC8; // Symbol name. + + // END record. + GOFFData[GOFF::RecordLength * 2] = 0x03; + GOFFData[GOFF::RecordLength * 2 + 1] = 0x40; + + StringRef Data(GOFFData, GOFF::RecordLength * 3); + + Expected> GOFFObjOrErr = + object::ObjectFile::createGOFFObjectFile( + MemoryBufferRef(Data, "dummyGOFF")); + + ASSERT_THAT_EXPECTED(GOFFObjOrErr, Succeeded()); + + GOFFObjectFile *GOFFObj = dyn_cast((*GOFFObjOrErr).get()); + + for (SymbolRef Symbol : GOFFObj->symbols()) { + Expected SymbolType = Symbol.getType(); + EXPECT_THAT_EXPECTED( + SymbolType, + FailedWithMessage("ESD record 1 has unknown Executable type 0x03")); + } +}