| //===- ArchiveWriter.cpp - ar File Format 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 defines the writeArchive function. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Object/ArchiveWriter.h" | |
| #include "llvm/ADT/ArrayRef.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/BinaryFormat/Magic.h" | |
| #include "llvm/IR/LLVMContext.h" | |
| #include "llvm/Object/Archive.h" | |
| #include "llvm/Object/COFF.h" | |
| #include "llvm/Object/COFFImportFile.h" | |
| #include "llvm/Object/Error.h" | |
| #include "llvm/Object/GOFFObjectFile.h" | |
| #include "llvm/Object/IRObjectFile.h" | |
| #include "llvm/Object/MachO.h" | |
| #include "llvm/Object/ObjectFile.h" | |
| #include "llvm/Object/SymbolicFile.h" | |
| #include "llvm/Object/XCOFFObjectFile.h" | |
| #include "llvm/Support/Alignment.h" | |
| #include "llvm/Support/EndianStream.h" | |
| #include "llvm/Support/Errc.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/Format.h" | |
| #include "llvm/Support/MathExtras.h" | |
| #include "llvm/Support/Path.h" | |
| #include "llvm/Support/SmallVectorMemoryBuffer.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <cerrno> | |
| #include <map> | |
| #if !defined(_MSC_VER) && !defined(__MINGW32__) | |
| #include <unistd.h> | |
| #else | |
| #include <io.h> | |
| #endif | |
| using namespace llvm; | |
| using namespace llvm::object; | |
| struct SymMap { | |
| bool UseECMap = false; | |
| std::map<std::string, uint16_t> Map; | |
| std::map<std::string, uint16_t> ECMap; | |
| }; | |
| NewArchiveMember::NewArchiveMember(MemoryBufferRef BufRef) | |
| : Buf(MemoryBuffer::getMemBuffer(BufRef, false)), | |
| MemberName(BufRef.getBufferIdentifier()) {} | |
| object::Archive::Kind NewArchiveMember::detectKindFromObject() const { | |
| auto MemBufferRef = this->Buf->getMemBufferRef(); | |
| Expected<std::unique_ptr<object::ObjectFile>> OptionalObject = | |
| object::ObjectFile::createObjectFile(MemBufferRef); | |
| if (OptionalObject) { | |
| if (isa<object::MachOObjectFile>(**OptionalObject)) | |
| return object::Archive::K_DARWIN; | |
| if (isa<object::XCOFFObjectFile>(**OptionalObject)) | |
| return object::Archive::K_AIXBIG; | |
| if (isa<object::COFFObjectFile>(**OptionalObject) || | |
| isa<object::COFFImportFile>(**OptionalObject)) | |
| return object::Archive::K_COFF; | |
| if (isa<object::GOFFObjectFile>(**OptionalObject)) | |
| return object::Archive::K_ZOS; | |
| return object::Archive::K_GNU; | |
| } | |
| // Squelch the error in case we had a non-object file. | |
| consumeError(OptionalObject.takeError()); | |
| // If we're adding a bitcode file to the archive, detect the Archive kind | |
| // based on the target triple. | |
| LLVMContext Context; | |
| if (identify_magic(MemBufferRef.getBuffer()) == file_magic::bitcode) { | |
| if (auto ObjOrErr = object::SymbolicFile::createSymbolicFile( | |
| MemBufferRef, file_magic::bitcode, &Context)) { | |
| auto &IRObject = cast<object::IRObjectFile>(**ObjOrErr); | |
| auto TargetTriple = Triple(IRObject.getTargetTriple()); | |
| return object::Archive::getDefaultKindForTriple(TargetTriple); | |
| } else { | |
| // Squelch the error in case this was not a SymbolicFile. | |
| consumeError(ObjOrErr.takeError()); | |
| } | |
| } | |
| return object::Archive::getDefaultKind(); | |
| } | |
| Expected<NewArchiveMember> | |
| NewArchiveMember::getOldMember(const object::Archive::Child &OldMember, | |
| bool Deterministic) { | |
| Expected<llvm::MemoryBufferRef> BufOrErr = OldMember.getMemoryBufferRef(); | |
| if (!BufOrErr) | |
| return BufOrErr.takeError(); | |
| NewArchiveMember M; | |
| M.Buf = MemoryBuffer::getMemBuffer(*BufOrErr, false); | |
| M.MemberName = M.Buf->getBufferIdentifier(); | |
| if (!Deterministic) { | |
| auto ModTimeOrErr = OldMember.getLastModified(); | |
| if (!ModTimeOrErr) | |
| return ModTimeOrErr.takeError(); | |
| M.ModTime = ModTimeOrErr.get(); | |
| Expected<unsigned> UIDOrErr = OldMember.getUID(); | |
| if (!UIDOrErr) | |
| return UIDOrErr.takeError(); | |
| M.UID = UIDOrErr.get(); | |
| Expected<unsigned> GIDOrErr = OldMember.getGID(); | |
| if (!GIDOrErr) | |
| return GIDOrErr.takeError(); | |
| M.GID = GIDOrErr.get(); | |
| Expected<sys::fs::perms> AccessModeOrErr = OldMember.getAccessMode(); | |
| if (!AccessModeOrErr) | |
| return AccessModeOrErr.takeError(); | |
| M.Perms = AccessModeOrErr.get(); | |
| } | |
| return std::move(M); | |
| } | |
| Expected<NewArchiveMember> NewArchiveMember::getFile(StringRef FileName, | |
| bool Deterministic) { | |
| sys::fs::file_status Status; | |
| auto FDOrErr = sys::fs::openNativeFileForRead(FileName); | |
| if (!FDOrErr) | |
| return FDOrErr.takeError(); | |
| sys::fs::file_t FD = *FDOrErr; | |
| assert(FD.isValid()); | |
| if (auto EC = sys::fs::status(FD, Status)) | |
| return errorCodeToError(EC); | |
| // Opening a directory doesn't make sense. Let it fail. | |
| // Linux cannot open directories with open(2), although | |
| // cygwin and *bsd can. | |
| if (Status.type() == sys::fs::file_type::directory_file) | |
| return errorCodeToError(make_error_code(errc::is_a_directory)); | |
| ErrorOr<std::unique_ptr<MemoryBuffer>> MemberBufferOrErr = | |
| MemoryBuffer::getOpenFile(FD, FileName, Status.getSize(), false); | |
| if (!MemberBufferOrErr) | |
| return errorCodeToError(MemberBufferOrErr.getError()); | |
| if (auto EC = sys::fs::closeFile(FD)) | |
| return errorCodeToError(EC); | |
| NewArchiveMember M; | |
| M.Buf = std::move(*MemberBufferOrErr); | |
| M.MemberName = M.Buf->getBufferIdentifier(); | |
| if (!Deterministic) { | |
| M.ModTime = std::chrono::time_point_cast<std::chrono::seconds>( | |
| Status.getLastModificationTime()); | |
| M.UID = Status.getUser(); | |
| M.GID = Status.getGroup(); | |
| M.Perms = Status.permissions(); | |
| } | |
| return std::move(M); | |
| } | |
| template <typename T> | |
| static void printWithSpacePadding(raw_ostream &OS, T Data, unsigned Size) { | |
| uint64_t OldPos = OS.tell(); | |
| OS << Data; | |
| unsigned SizeSoFar = OS.tell() - OldPos; | |
| assert(SizeSoFar <= Size && "Data doesn't fit in Size"); | |
| OS.indent(Size - SizeSoFar); | |
| } | |
| static bool isDarwin(object::Archive::Kind Kind) { | |
| return Kind == object::Archive::K_DARWIN || | |
| Kind == object::Archive::K_DARWIN64; | |
| } | |
| static bool isAIXBigArchive(object::Archive::Kind Kind) { | |
| return Kind == object::Archive::K_AIXBIG; | |
| } | |
| static bool isCOFFArchive(object::Archive::Kind Kind) { | |
| return Kind == object::Archive::K_COFF; | |
| } | |
| static bool isZOSArchive(object::Archive::Kind Kind) { | |
| return Kind == object::Archive::K_ZOS; | |
| } | |
| static bool isBSDLike(object::Archive::Kind Kind) { | |
| switch (Kind) { | |
| case object::Archive::K_GNU: | |
| case object::Archive::K_GNU64: | |
| case object::Archive::K_AIXBIG: | |
| case object::Archive::K_COFF: | |
| case object::Archive::K_ZOS: | |
| return false; | |
| case object::Archive::K_BSD: | |
| case object::Archive::K_DARWIN: | |
| case object::Archive::K_DARWIN64: | |
| return true; | |
| } | |
| llvm_unreachable("not supported for writting"); | |
| } | |
| template <class T> | |
| static void print(raw_ostream &Out, object::Archive::Kind Kind, T Val) { | |
| support::endian::write(Out, Val, | |
| isBSDLike(Kind) ? llvm::endianness::little | |
| : llvm::endianness::big); | |
| } | |
| template <class T> static void printLE(raw_ostream &Out, T Val) { | |
| support::endian::write(Out, Val, llvm::endianness::little); | |
| } | |
| static void printRestOfMemberHeader( | |
| raw_ostream &Out, const sys::TimePoint<std::chrono::seconds> &ModTime, | |
| unsigned UID, unsigned GID, unsigned Perms, uint64_t Size) { | |
| printWithSpacePadding(Out, sys::toTimeT(ModTime), 12); | |
| // The format has only 6 chars for uid and gid. Truncate if the provided | |
| // values don't fit. | |
| printWithSpacePadding(Out, UID % 1000000, 6); | |
| printWithSpacePadding(Out, GID % 1000000, 6); | |
| printWithSpacePadding(Out, format("%o", Perms), 8); | |
| printWithSpacePadding(Out, Size, 10); | |
| Out << "`\n"; | |
| } | |
| static void | |
| printGNUSmallMemberHeader(raw_ostream &Out, StringRef Name, | |
| const sys::TimePoint<std::chrono::seconds> &ModTime, | |
| unsigned UID, unsigned GID, unsigned Perms, | |
| uint64_t Size) { | |
| printWithSpacePadding(Out, Twine(Name) + "/", 16); | |
| printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, Size); | |
| } | |
| static void | |
| printBSDMemberHeader(raw_ostream &Out, uint64_t Pos, StringRef Name, | |
| const sys::TimePoint<std::chrono::seconds> &ModTime, | |
| unsigned UID, unsigned GID, unsigned Perms, uint64_t Size) { | |
| uint64_t PosAfterHeader = Pos + 60 + Name.size(); | |
| // Pad so that even 64 bit object files are aligned. | |
| unsigned Pad = offsetToAlignment(PosAfterHeader, Align(8)); | |
| unsigned NameWithPadding = Name.size() + Pad; | |
| printWithSpacePadding(Out, Twine("#1/") + Twine(NameWithPadding), 16); | |
| printRestOfMemberHeader(Out, ModTime, UID, GID, Perms, | |
| NameWithPadding + Size); | |
| Out << Name; | |
| while (Pad--) | |
| Out.write(uint8_t(0)); | |
| } | |
| static void | |
| printZOSMemberHeader(raw_ostream &Out, StringRef Name, | |
| const sys::TimePoint<std::chrono::seconds> &ModTime, | |
| unsigned UID, unsigned GID, unsigned Perms, | |
| uint64_t Size) { | |
| std::string AHeader; | |
| raw_string_ostream AOut(AHeader); | |
| if (Name.size() <= 16) { | |
| printWithSpacePadding(AOut, Twine(Name), 16); | |
| printRestOfMemberHeader(AOut, ModTime, UID, GID, Perms, Size); | |
| } else { | |
| // z/OS ar stores the exact name length inline with no extra alignment | |
| // padding, unlike the BSD format which pads to an 8-byte boundary. | |
| printWithSpacePadding(AOut, Twine("#1/") + Twine(Name.size()), 16); | |
| printRestOfMemberHeader(AOut, ModTime, UID, GID, Perms, Name.size() + Size); | |
| AOut << Name; | |
| } | |
| SmallString<256> EHeader; | |
| if (std::error_code EC = ConverterEBCDIC::convertToEBCDIC(AHeader, EHeader)) | |
| report_fatal_error( | |
| Twine("failed to convert z/OS member header to EBCDIC: ") + | |
| EC.message()); | |
| Out << EHeader.str(); | |
| } | |
| static void | |
| printBigArchiveMemberHeader(raw_ostream &Out, StringRef Name, | |
| const sys::TimePoint<std::chrono::seconds> &ModTime, | |
| unsigned UID, unsigned GID, unsigned Perms, | |
| uint64_t Size, uint64_t PrevOffset, | |
| uint64_t NextOffset) { | |
| unsigned NameLen = Name.size(); | |
| printWithSpacePadding(Out, Size, 20); // File member size | |
| printWithSpacePadding(Out, NextOffset, 20); // Next member header offset | |
| printWithSpacePadding(Out, PrevOffset, 20); // Previous member header offset | |
| printWithSpacePadding(Out, sys::toTimeT(ModTime), 12); // File member date | |
| // The big archive format has 12 chars for uid and gid. | |
| printWithSpacePadding(Out, UID % 1000000000000, 12); // UID | |
| printWithSpacePadding(Out, GID % 1000000000000, 12); // GID | |
| printWithSpacePadding(Out, format("%o", Perms), 12); // Permission | |
| printWithSpacePadding(Out, NameLen, 4); // Name length | |
| if (NameLen) { | |
| printWithSpacePadding(Out, Name, NameLen); // Name | |
| if (NameLen % 2) | |
| Out.write(uint8_t(0)); // Null byte padding | |
| } | |
| Out << "`\n"; // Terminator | |
| } | |
| static bool useStringTable(bool Thin, StringRef Name) { | |
| return Thin || Name.size() >= 16 || Name.contains('/'); | |
| } | |
| static bool is64BitKind(object::Archive::Kind Kind) { | |
| switch (Kind) { | |
| case object::Archive::K_GNU: | |
| case object::Archive::K_BSD: | |
| case object::Archive::K_DARWIN: | |
| case object::Archive::K_COFF: | |
| case object::Archive::K_ZOS: | |
| return false; | |
| case object::Archive::K_AIXBIG: | |
| case object::Archive::K_DARWIN64: | |
| case object::Archive::K_GNU64: | |
| return true; | |
| } | |
| llvm_unreachable("not supported for writting"); | |
| } | |
| static void | |
| printMemberHeader(raw_ostream &Out, uint64_t Pos, raw_ostream &StringTable, | |
| StringMap<uint64_t> &MemberNames, object::Archive::Kind Kind, | |
| bool Thin, const NewArchiveMember &M, StringRef MemberName, | |
| sys::TimePoint<std::chrono::seconds> ModTime, uint64_t Size) { | |
| if (isBSDLike(Kind)) | |
| return printBSDMemberHeader(Out, Pos, MemberName, ModTime, M.UID, M.GID, | |
| M.Perms, Size); | |
| if (isZOSArchive(Kind)) | |
| return printZOSMemberHeader(Out, MemberName, ModTime, M.UID, M.GID, M.Perms, | |
| Size); | |
| if (!useStringTable(Thin, MemberName)) | |
| return printGNUSmallMemberHeader(Out, MemberName, ModTime, M.UID, M.GID, | |
| M.Perms, Size); | |
| Out << '/'; | |
| uint64_t NamePos; | |
| if (Thin) { | |
| NamePos = StringTable.tell(); | |
| StringTable << MemberName << "/\n"; | |
| } else { | |
| auto Insertion = MemberNames.insert({MemberName, uint64_t(0)}); | |
| if (Insertion.second) { | |
| Insertion.first->second = StringTable.tell(); | |
| StringTable << MemberName; | |
| if (isCOFFArchive(Kind)) | |
| StringTable << '\0'; | |
| else | |
| StringTable << "/\n"; | |
| } | |
| NamePos = Insertion.first->second; | |
| } | |
| printWithSpacePadding(Out, NamePos, 15); | |
| printRestOfMemberHeader(Out, ModTime, M.UID, M.GID, M.Perms, Size); | |
| } | |
| namespace { | |
| struct MemberData { | |
| std::vector<unsigned> Symbols; | |
| // z/OS archive attribute bits per symbol. Entry i of SymbolAttrs corresponds | |
| // to Symbols[i]. These attributes are empty for non-z/OS archives. | |
| std::vector<uint32_t> SymbolAttrs; | |
| std::string Header; | |
| StringRef Data; | |
| StringRef Padding; | |
| uint64_t PreHeadPadSize = 0; | |
| std::unique_ptr<SymbolicFile> SymFile = nullptr; | |
| std::string HybridName = ""; | |
| std::unique_ptr<MemoryBuffer> NativeBuf = nullptr; | |
| }; | |
| } // namespace | |
| static MemberData computeStringTable(StringRef Names) { | |
| unsigned Size = Names.size(); | |
| unsigned Pad = offsetToAlignment(Size, Align(2)); | |
| std::string Header; | |
| raw_string_ostream Out(Header); | |
| printWithSpacePadding(Out, "//", 48); | |
| printWithSpacePadding(Out, Size + Pad, 10); | |
| Out << "`\n"; | |
| return {{}, {}, std::move(Header), Names, Pad ? "\n" : ""}; | |
| } | |
| static sys::TimePoint<std::chrono::seconds> now(bool Deterministic) { | |
| using namespace std::chrono; | |
| if (!Deterministic) | |
| return time_point_cast<seconds>(system_clock::now()); | |
| return sys::TimePoint<seconds>(); | |
| } | |
| static bool isArchiveSymbol(const object::BasicSymbolRef &S) { | |
| Expected<uint32_t> SymFlagsOrErr = S.getFlags(); | |
| if (!SymFlagsOrErr) | |
| // TODO: Actually report errors helpfully. | |
| report_fatal_error(SymFlagsOrErr.takeError()); | |
| if (*SymFlagsOrErr & object::SymbolRef::SF_FormatSpecific) | |
| return false; | |
| if (!(*SymFlagsOrErr & object::SymbolRef::SF_Global)) | |
| return false; | |
| if (*SymFlagsOrErr & object::SymbolRef::SF_Undefined) | |
| return false; | |
| return true; | |
| } | |
| static void printNBits(raw_ostream &Out, object::Archive::Kind Kind, | |
| uint64_t Val) { | |
| if (is64BitKind(Kind)) | |
| print<uint64_t>(Out, Kind, Val); | |
| else | |
| print<uint32_t>(Out, Kind, Val); | |
| } | |
| static uint64_t computeSymbolTableSize(object::Archive::Kind Kind, | |
| uint64_t NumSyms, uint64_t OffsetSize, | |
| uint64_t StringTableSize, | |
| uint32_t *Padding = nullptr) { | |
| assert((OffsetSize == 4 || OffsetSize == 8) && "Unsupported OffsetSize"); | |
| uint64_t Size = OffsetSize; // Number of entries | |
| // Each symbol table entry consists of a member offset. | |
| // For BSD, each entry also includes a string table offset. | |
| // For z/OS, each entry instead also includes a flag field. | |
| if (isBSDLike(Kind) || isZOSArchive(Kind)) | |
| Size += NumSyms * OffsetSize * 2; // Table | |
| else | |
| Size += NumSyms * OffsetSize; // Table | |
| if (isBSDLike(Kind)) | |
| Size += OffsetSize; // byte count | |
| Size += StringTableSize; | |
| // ld64 expects the members to be 8-byte aligned for 64-bit content and at | |
| // least 4-byte aligned for 32-bit content. Opt for the larger encoding | |
| // uniformly. | |
| // We do this for all bsd formats because it simplifies aligning members. | |
| // For the big archive format, the symbol table is the last member, so there | |
| // is no need to align. | |
| uint32_t Pad = isAIXBigArchive(Kind) | |
| ? 0 | |
| : offsetToAlignment(Size, Align(isBSDLike(Kind) ? 8 : 2)); | |
| Size += Pad; | |
| if (Padding) | |
| *Padding = Pad; | |
| return Size; | |
| } | |
| static uint64_t computeSymbolMapSize(uint64_t NumObj, SymMap &SymMap, | |
| uint32_t *Padding = nullptr) { | |
| uint64_t Size = sizeof(uint32_t) * 2; // Number of symbols and objects entries | |
| Size += NumObj * sizeof(uint32_t); // Offset table | |
| for (auto S : SymMap.Map) | |
| Size += sizeof(uint16_t) + S.first.length() + 1; | |
| uint32_t Pad = offsetToAlignment(Size, Align(2)); | |
| Size += Pad; | |
| if (Padding) | |
| *Padding = Pad; | |
| return Size; | |
| } | |
| static uint64_t computeECSymbolsSize(SymMap &SymMap, | |
| uint32_t *Padding = nullptr) { | |
| uint64_t Size = sizeof(uint32_t); // Number of symbols | |
| for (auto S : SymMap.ECMap) | |
| Size += sizeof(uint16_t) + S.first.length() + 1; | |
| uint32_t Pad = offsetToAlignment(Size, Align(2)); | |
| Size += Pad; | |
| if (Padding) | |
| *Padding = Pad; | |
| return Size; | |
| } | |
| static void writeSymbolTableHeader(raw_ostream &Out, object::Archive::Kind Kind, | |
| bool Deterministic, uint64_t Size, | |
| uint64_t PrevMemberOffset = 0, | |
| uint64_t NextMemberOffset = 0) { | |
| if (isBSDLike(Kind)) { | |
| const char *Name = is64BitKind(Kind) ? "__.SYMDEF_64" : "__.SYMDEF"; | |
| printBSDMemberHeader(Out, Out.tell(), Name, now(Deterministic), 0, 0, 0, | |
| Size); | |
| } else if (isAIXBigArchive(Kind)) { | |
| printBigArchiveMemberHeader(Out, "", now(Deterministic), 0, 0, 0, Size, | |
| PrevMemberOffset, NextMemberOffset); | |
| } else if (isZOSArchive(Kind)) { | |
| const char *Name = "__.SYMDEF"; | |
| printZOSMemberHeader(Out, Name, now(Deterministic), 0, 0, 0, Size); | |
| } else { | |
| const char *Name = is64BitKind(Kind) ? "/SYM64" : ""; | |
| printGNUSmallMemberHeader(Out, Name, now(Deterministic), 0, 0, 0, Size); | |
| } | |
| } | |
| static uint64_t computeHeadersSize(object::Archive::Kind Kind, | |
| uint64_t NumMembers, | |
| uint64_t StringMemberSize, uint64_t NumSyms, | |
| uint64_t SymNamesSize, SymMap *SymMap) { | |
| uint32_t OffsetSize = is64BitKind(Kind) ? 8 : 4; | |
| uint64_t SymtabSize = | |
| computeSymbolTableSize(Kind, NumSyms, OffsetSize, SymNamesSize); | |
| auto computeSymbolTableHeaderSize = [=] { | |
| SmallString<0> TmpBuf; | |
| raw_svector_ostream Tmp(TmpBuf); | |
| writeSymbolTableHeader(Tmp, Kind, true, SymtabSize); | |
| return TmpBuf.size(); | |
| }; | |
| uint32_t HeaderSize = computeSymbolTableHeaderSize(); | |
| uint64_t Size = strlen("!<arch>\n") + HeaderSize + SymtabSize; | |
| if (SymMap) { | |
| Size += HeaderSize + computeSymbolMapSize(NumMembers, *SymMap); | |
| if (SymMap->ECMap.size()) | |
| Size += HeaderSize + computeECSymbolsSize(*SymMap); | |
| } | |
| return Size + StringMemberSize; | |
| } | |
| static Expected<std::unique_ptr<SymbolicFile>> | |
| getSymbolicFile(MemoryBufferRef Buf, LLVMContext &Context, | |
| object::Archive::Kind Kind, function_ref<void(Error)> Warn) { | |
| const file_magic Type = identify_magic(Buf.getBuffer()); | |
| // Don't attempt to read non-symbolic file types. | |
| if (!object::SymbolicFile::isSymbolicFile(Type, &Context)) | |
| return nullptr; | |
| if (Type == file_magic::bitcode) { | |
| auto ObjOrErr = object::SymbolicFile::createSymbolicFile( | |
| Buf, file_magic::bitcode, &Context); | |
| // An error reading a bitcode file most likely indicates that the file | |
| // was created by a compiler from the future. Normally we don't try to | |
| // implement forwards compatibility for bitcode files, but when creating an | |
| // archive we can implement best-effort forwards compatibility by treating | |
| // the file as a blob and not creating symbol index entries for it. lld and | |
| // mold ignore the archive symbol index, so provided that you use one of | |
| // these linkers, LTO will work as long as lld or the gold plugin is newer | |
| // than the compiler. We only ignore errors if the archive format is one | |
| // that is supported by a linker that is known to ignore the index, | |
| // otherwise there's no chance of this working so we may as well error out. | |
| // We print a warning on read failure so that users of linkers that rely on | |
| // the symbol index can diagnose the issue. | |
| // | |
| // This is the same behavior as GNU ar when the linker plugin returns an | |
| // error when reading the input file. If the bitcode file is actually | |
| // malformed, it will be diagnosed at link time. | |
| if (!ObjOrErr) { | |
| switch (Kind) { | |
| case object::Archive::K_BSD: | |
| case object::Archive::K_GNU: | |
| case object::Archive::K_GNU64: | |
| Warn(ObjOrErr.takeError()); | |
| return nullptr; | |
| case object::Archive::K_AIXBIG: | |
| case object::Archive::K_COFF: | |
| case object::Archive::K_DARWIN: | |
| case object::Archive::K_DARWIN64: | |
| case object::Archive::K_ZOS: | |
| return ObjOrErr.takeError(); | |
| } | |
| } | |
| return std::move(*ObjOrErr); | |
| } else { | |
| auto ObjOrErr = object::SymbolicFile::createSymbolicFile(Buf); | |
| if (!ObjOrErr) | |
| return ObjOrErr.takeError(); | |
| return std::move(*ObjOrErr); | |
| } | |
| } | |
| static bool is64BitSymbolicFile(const SymbolicFile *SymObj) { | |
| return SymObj != nullptr ? SymObj->is64Bit() : false; | |
| } | |
| // Log2 of PAGESIZE(4096) on an AIX system. | |
| static const uint32_t Log2OfAIXPageSize = 12; | |
| // In the AIX big archive format, since the data content follows the member file | |
| // name, if the name ends on an odd byte, an extra byte will be added for | |
| // padding. This ensures that the data within the member file starts at an even | |
| // byte. | |
| static const uint32_t MinBigArchiveMemDataAlign = 2; | |
| template <typename AuxiliaryHeader> | |
| uint16_t getAuxMaxAlignment(uint16_t AuxHeaderSize, AuxiliaryHeader *AuxHeader, | |
| uint16_t Log2OfMaxAlign) { | |
| // If the member doesn't have an auxiliary header, it isn't a loadable object | |
| // and so it just needs aligning at the minimum value. | |
| if (AuxHeader == nullptr) | |
| return MinBigArchiveMemDataAlign; | |
| // If the auxiliary header does not have both MaxAlignOfData and | |
| // MaxAlignOfText field, it is not a loadable shared object file, so align at | |
| // the minimum value. The 'ModuleType' member is located right after | |
| // 'MaxAlignOfData' in the AuxiliaryHeader. | |
| if (AuxHeaderSize < offsetof(AuxiliaryHeader, ModuleType)) | |
| return MinBigArchiveMemDataAlign; | |
| // If the XCOFF object file does not have a loader section, it is not | |
| // loadable, so align at the minimum value. | |
| if (AuxHeader->SecNumOfLoader == 0) | |
| return MinBigArchiveMemDataAlign; | |
| // The content of the loadable member file needs to be aligned at MAX(maximum | |
| // alignment of .text, maximum alignment of .data) if there are both fields. | |
| // If the desired alignment is > PAGESIZE, 32-bit members are aligned on a | |
| // word boundary, while 64-bit members are aligned on a PAGESIZE(2^12=4096) | |
| // boundary. | |
| uint16_t Log2OfAlign = | |
| std::max(AuxHeader->MaxAlignOfText, AuxHeader->MaxAlignOfData); | |
| return 1 << (Log2OfAlign > Log2OfAIXPageSize ? Log2OfMaxAlign : Log2OfAlign); | |
| } | |
| // AIX big archives may contain shared object members. The AIX OS requires these | |
| // members to be aligned if they are 64-bit and recommends it for 32-bit | |
| // members. This ensures that when these members are loaded they are aligned in | |
| // memory. | |
| static uint32_t getMemberAlignment(SymbolicFile *SymObj) { | |
| XCOFFObjectFile *XCOFFObj = dyn_cast_or_null<XCOFFObjectFile>(SymObj); | |
| if (!XCOFFObj) | |
| return MinBigArchiveMemDataAlign; | |
| // If the desired alignment is > PAGESIZE, 32-bit members are aligned on a | |
| // word boundary, while 64-bit members are aligned on a PAGESIZE boundary. | |
| return XCOFFObj->is64Bit() | |
| ? getAuxMaxAlignment(XCOFFObj->fileHeader64()->AuxHeaderSize, | |
| XCOFFObj->auxiliaryHeader64(), | |
| Log2OfAIXPageSize) | |
| : getAuxMaxAlignment(XCOFFObj->fileHeader32()->AuxHeaderSize, | |
| XCOFFObj->auxiliaryHeader32(), 2); | |
| } | |
| static void writeSymbolTable(raw_ostream &Out, object::Archive::Kind Kind, | |
| bool Deterministic, ArrayRef<MemberData> Members, | |
| StringRef StringTable, uint64_t MembersOffset, | |
| unsigned NumSyms, uint64_t PrevMemberOffset = 0, | |
| uint64_t NextMemberOffset = 0, | |
| bool Is64Bit = false) { | |
| // We don't write a symbol table on an archive with no members -- except on | |
| // Darwin, where the linker will abort unless the archive has a symbol table. | |
| if (StringTable.empty() && !isDarwin(Kind) && !isCOFFArchive(Kind)) | |
| return; | |
| uint64_t OffsetSize = is64BitKind(Kind) ? 8 : 4; | |
| uint32_t Pad; | |
| uint64_t Size = computeSymbolTableSize(Kind, NumSyms, OffsetSize, | |
| StringTable.size(), &Pad); | |
| // Padding size is not included in the Size field of the z/OS symbol table | |
| // header. | |
| int64_t HeaderSize = Size; | |
| if (isZOSArchive(Kind)) | |
| HeaderSize -= Pad; | |
| writeSymbolTableHeader(Out, Kind, Deterministic, HeaderSize, PrevMemberOffset, | |
| NextMemberOffset); | |
| if (isBSDLike(Kind)) | |
| printNBits(Out, Kind, NumSyms * 2 * OffsetSize); | |
| else | |
| printNBits(Out, Kind, NumSyms); | |
| uint64_t Pos = MembersOffset; | |
| for (const MemberData &M : Members) { | |
| if (isAIXBigArchive(Kind)) { | |
| Pos += M.PreHeadPadSize; | |
| if (is64BitSymbolicFile(M.SymFile.get()) != Is64Bit) { | |
| Pos += M.Header.size() + M.Data.size() + M.Padding.size(); | |
| continue; | |
| } | |
| } | |
| assert((!isZOSArchive(Kind) || M.Symbols.size() == M.SymbolAttrs.size()) && | |
| "Incorrect number of symbol attributes!"); | |
| for (size_t I = 0, E = M.Symbols.size(); I != E; ++I) { | |
| if (isBSDLike(Kind)) | |
| printNBits(Out, Kind, M.Symbols[I]); | |
| printNBits(Out, Kind, Pos); // member offset | |
| if (isZOSArchive(Kind)) | |
| printNBits(Out, Kind, M.SymbolAttrs[I]); // symbol attributes | |
| } | |
| Pos += M.Header.size() + M.Data.size() + M.Padding.size(); | |
| } | |
| if (isBSDLike(Kind)) | |
| // byte count of the string table | |
| printNBits(Out, Kind, StringTable.size()); | |
| if (isZOSArchive(Kind)) { | |
| SmallString<256> EStringTable; | |
| if (std::error_code EC = | |
| ConverterEBCDIC::convertToEBCDIC(StringTable, EStringTable)) | |
| report_fatal_error( | |
| Twine("failed to convert z/OS symbol table to EBCDIC: ") + | |
| EC.message()); | |
| Out << EStringTable.str(); | |
| } else { | |
| Out << StringTable; | |
| } | |
| while (Pad--) | |
| Out.write(uint8_t(0)); | |
| } | |
| static void writeSymbolMap(raw_ostream &Out, object::Archive::Kind Kind, | |
| bool Deterministic, ArrayRef<MemberData> Members, | |
| SymMap &SymMap, uint64_t MembersOffset) { | |
| uint32_t Pad; | |
| uint64_t Size = computeSymbolMapSize(Members.size(), SymMap, &Pad); | |
| writeSymbolTableHeader(Out, Kind, Deterministic, Size, 0); | |
| uint32_t Pos = MembersOffset; | |
| printLE<uint32_t>(Out, Members.size()); | |
| for (const MemberData &M : Members) { | |
| printLE(Out, Pos); // member offset | |
| Pos += M.Header.size() + M.Data.size() + M.Padding.size(); | |
| } | |
| printLE<uint32_t>(Out, SymMap.Map.size()); | |
| for (auto S : SymMap.Map) | |
| printLE(Out, S.second); | |
| for (auto S : SymMap.Map) | |
| Out << S.first << '\0'; | |
| while (Pad--) | |
| Out.write(uint8_t(0)); | |
| } | |
| static void writeECSymbols(raw_ostream &Out, object::Archive::Kind Kind, | |
| bool Deterministic, ArrayRef<MemberData> Members, | |
| SymMap &SymMap) { | |
| uint32_t Pad; | |
| uint64_t Size = computeECSymbolsSize(SymMap, &Pad); | |
| printGNUSmallMemberHeader(Out, "/<ECSYMBOLS>", now(Deterministic), 0, 0, 0, | |
| Size); | |
| printLE<uint32_t>(Out, SymMap.ECMap.size()); | |
| for (auto S : SymMap.ECMap) | |
| printLE(Out, S.second); | |
| for (auto S : SymMap.ECMap) | |
| Out << S.first << '\0'; | |
| while (Pad--) | |
| Out.write(uint8_t(0)); | |
| } | |
| static bool isECObject(object::SymbolicFile &Obj) { | |
| if (Obj.isCOFF()) | |
| return cast<llvm::object::COFFObjectFile>(&Obj)->getMachine() != | |
| COFF::IMAGE_FILE_MACHINE_ARM64; | |
| if (Obj.isCOFFImportFile()) | |
| return cast<llvm::object::COFFImportFile>(&Obj)->getMachine() != | |
| COFF::IMAGE_FILE_MACHINE_ARM64; | |
| if (Obj.isIR()) { | |
| Expected<std::string> TripleStr = | |
| getBitcodeTargetTriple(Obj.getMemoryBufferRef()); | |
| if (!TripleStr) | |
| return false; | |
| Triple T(std::move(*TripleStr)); | |
| return T.isWindowsArm64EC() || T.getArch() == Triple::x86_64; | |
| } | |
| return false; | |
| } | |
| static bool isAnyArm64COFF(object::SymbolicFile &Obj) { | |
| if (Obj.isCOFF()) | |
| return COFF::isAnyArm64(cast<COFFObjectFile>(&Obj)->getMachine()); | |
| if (Obj.isCOFFImportFile()) | |
| return COFF::isAnyArm64(cast<COFFImportFile>(&Obj)->getMachine()); | |
| if (Obj.isIR()) { | |
| Expected<std::string> TripleStr = | |
| getBitcodeTargetTriple(Obj.getMemoryBufferRef()); | |
| if (!TripleStr) | |
| return false; | |
| Triple T(std::move(*TripleStr)); | |
| return T.isOSWindows() && T.getArch() == Triple::aarch64; | |
| } | |
| return false; | |
| } | |
| bool isImportDescriptor(StringRef Name) { | |
| return Name.starts_with(ImportDescriptorPrefix) || | |
| Name == StringRef{NullImportDescriptorSymbolName} || | |
| (Name.starts_with(NullThunkDataPrefix) && | |
| Name.ends_with(NullThunkDataSuffix)); | |
| } | |
| static Expected<std::vector<unsigned>> getSymbols(SymbolicFile *Obj, | |
| uint16_t Index, | |
| raw_ostream &SymNames, | |
| SymMap *SymMap) { | |
| std::vector<unsigned> Ret; | |
| if (Obj == nullptr) | |
| return Ret; | |
| std::map<std::string, uint16_t> *Map = nullptr; | |
| if (SymMap) | |
| Map = SymMap->UseECMap && isECObject(*Obj) ? &SymMap->ECMap : &SymMap->Map; | |
| for (const object::BasicSymbolRef &S : Obj->symbols()) { | |
| if (!isArchiveSymbol(S)) | |
| continue; | |
| if (Map) { | |
| std::string Name; | |
| raw_string_ostream NameStream(Name); | |
| if (Error E = S.printName(NameStream)) | |
| return std::move(E); | |
| if (!Map->try_emplace(Name, Index).second) | |
| continue; // ignore duplicated symbol | |
| if (Map == &SymMap->Map) { | |
| Ret.push_back(SymNames.tell()); | |
| SymNames << Name << '\0'; | |
| // If EC is enabled, then the import descriptors are NOT put into EC | |
| // objects so we need to copy them to the EC map manually. | |
| if (SymMap->UseECMap && isImportDescriptor(Name)) | |
| SymMap->ECMap[Name] = Index; | |
| } | |
| } else { | |
| Ret.push_back(SymNames.tell()); | |
| if (Error E = S.printName(SymNames)) | |
| return std::move(E); | |
| SymNames << '\0'; | |
| } | |
| } | |
| return Ret; | |
| } | |
| static Expected<std::vector<MemberData>> | |
| computeMemberData(raw_ostream &StringTable, raw_ostream &SymNames, | |
| object::Archive::Kind Kind, bool Thin, bool Deterministic, | |
| SymtabWritingMode NeedSymbols, SymMap *SymMap, | |
| LLVMContext &Context, ArrayRef<NewArchiveMember> NewMembers, | |
| std::optional<bool> IsEC, function_ref<void(Error)> Warn) { | |
| static char PaddingData[8] = {'\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n'}; | |
| static char ZOSPaddingData[8] = {0x15, 0x15, 0x15, 0x15, | |
| 0x15, 0x15, 0x15, 0x15}; // EBCDIC newlines. | |
| uint64_t Pos = | |
| isAIXBigArchive(Kind) ? sizeof(object::BigArchive::FixLenHdr) : 0; | |
| std::vector<MemberData> Ret; | |
| bool HasObject = false; | |
| // Deduplicate long member names in the string table and reuse earlier name | |
| // offsets. This especially saves space for COFF Import libraries where all | |
| // members have the same name. | |
| StringMap<uint64_t> MemberNames; | |
| // UniqueTimestamps is a special case to improve debugging on Darwin: | |
| // | |
| // The Darwin linker does not link debug info into the final | |
| // binary. Instead, it emits entries of type N_OSO in the output | |
| // binary's symbol table, containing references to the linked-in | |
| // object files. Using that reference, the debugger can read the | |
| // debug data directly from the object files. Alternatively, an | |
| // invocation of 'dsymutil' will link the debug data from the object | |
| // files into a dSYM bundle, which can be loaded by the debugger, | |
| // instead of the object files. | |
| // | |
| // For an object file, the N_OSO entries contain the absolute path | |
| // path to the file, and the file's timestamp. For an object | |
| // included in an archive, the path is formatted like | |
| // "/absolute/path/to/archive.a(member.o)", and the timestamp is the | |
| // archive member's timestamp, rather than the archive's timestamp. | |
| // | |
| // However, this doesn't always uniquely identify an object within | |
| // an archive -- an archive file can have multiple entries with the | |
| // same filename. (This will happen commonly if the original object | |
| // files started in different directories.) The only way they get | |
| // distinguished, then, is via the timestamp. But this process is | |
| // unable to find the correct object file in the archive when there | |
| // are two files of the same name and timestamp. | |
| // | |
| // Additionally, timestamp==0 is treated specially, and causes the | |
| // timestamp to be ignored as a match criteria. | |
| // | |
| // That will "usually" work out okay when creating an archive not in | |
| // deterministic timestamp mode, because the objects will probably | |
| // have been created at different timestamps. | |
| // | |
| // To ameliorate this problem, in deterministic archive mode (which | |
| // is the default), on Darwin we will emit a unique non-zero | |
| // timestamp for each entry with a duplicated name. This is still | |
| // deterministic: the only thing affecting that timestamp is the | |
| // order of the files in the resultant archive. | |
| // | |
| // See also the functions that handle the lookup: | |
| // in lldb: ObjectContainerBSDArchive::Archive::FindObject() | |
| // in llvm/tools/dsymutil: BinaryHolder::GetArchiveMemberBuffers(). | |
| bool UniqueTimestamps = Deterministic && isDarwin(Kind); | |
| std::map<StringRef, unsigned> FilenameCount; | |
| if (UniqueTimestamps) { | |
| for (const NewArchiveMember &M : NewMembers) | |
| FilenameCount[M.MemberName]++; | |
| for (auto &Entry : FilenameCount) | |
| Entry.second = Entry.second > 1 ? 1 : 0; | |
| } | |
| uint32_t LastZosObjIndex = | |
| UINT_MAX; // Only set when writing symbol table in z/OS archive. | |
| for (const NewArchiveMember &M : NewMembers) { | |
| MemberData &D = Ret.emplace_back(); | |
| D.Data = M.Buf->getBuffer(); | |
| if (NeedSymbols != SymtabWritingMode::NoSymtab || isAIXBigArchive(Kind)) { | |
| Expected<std::unique_ptr<SymbolicFile>> SymFileOrErr = getSymbolicFile( | |
| M.Buf->getMemBufferRef(), Context, Kind, [&](Error Err) { | |
| Warn(createFileError(M.MemberName, std::move(Err))); | |
| }); | |
| if (!SymFileOrErr) | |
| return createFileError(M.MemberName, SymFileOrErr.takeError()); | |
| D.SymFile = std::move(*SymFileOrErr); | |
| if (SymMap && D.SymFile.get()) { | |
| auto COFFObj = dyn_cast<COFFObjectFile>(D.SymFile.get()); | |
| std::optional<MemoryBufferRef> HybridView; | |
| if (COFFObj && (HybridView = COFFObj->findHybridObjectSection())) { | |
| // Strip the hybrid section. | |
| D.NativeBuf = COFFObj->stripHybridSection(); | |
| D.Data = D.NativeBuf->getBuffer(); | |
| // Create a separate archive member for the hybrid ARM64X object. | |
| MemberData &ECData = Ret.emplace_back(); | |
| ECData.Data = HybridView->getBuffer(); | |
| SymFileOrErr = | |
| getSymbolicFile(*HybridView, Context, Kind, [&](Error Err) { | |
| Warn(createFileError(M.MemberName, std::move(Err))); | |
| }); | |
| if (!SymFileOrErr) | |
| return createFileError(M.MemberName, SymFileOrErr.takeError()); | |
| ECData.SymFile = std::move(*SymFileOrErr); | |
| // Use obj.arm64ec subdirectory for the hybrid object name. | |
| size_t Pos = M.MemberName.find_last_of("/\\"); | |
| Pos = Pos == StringRef::npos ? 0 : Pos + 1; | |
| ECData.HybridName = (M.MemberName.substr(0, Pos) + "obj.arm64ec/" + | |
| M.MemberName.substr(Pos)) | |
| .str(); | |
| } | |
| } | |
| if (isZOSArchive(Kind) && D.SymFile.get()) | |
| LastZosObjIndex = Ret.size() - 1; | |
| } | |
| } | |
| if (SymMap) { | |
| if (IsEC) { | |
| SymMap->UseECMap = *IsEC; | |
| } else { | |
| // When IsEC is not specified by the caller, use it when we have both | |
| // any ARM64 object (ARM64 or ARM64EC) and any EC object (ARM64EC or | |
| // AMD64). This may be a single ARM64EC object, but may also be separate | |
| // ARM64 and AMD64 objects. | |
| bool HaveArm64 = false, HaveEC = false; | |
| for (const MemberData &D : Ret) { | |
| if (!D.SymFile) | |
| continue; | |
| if (!HaveArm64) | |
| HaveArm64 = isAnyArm64COFF(*D.SymFile); | |
| if (!HaveEC) | |
| HaveEC = isECObject(*D.SymFile); | |
| if (HaveArm64 && HaveEC) { | |
| SymMap->UseECMap = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // The big archive format needs to know the offset of the previous member | |
| // header. | |
| uint64_t PrevOffset = 0; | |
| uint64_t NextMemHeadPadSize = 0; | |
| for (uint32_t Index = 0, MemberIndex = 0; Index < Ret.size(); ++Index) { | |
| MemberData &D = Ret[Index]; | |
| const NewArchiveMember *M = &NewMembers[MemberIndex]; | |
| // Native COFF members (resulting from stripping a hybrid object section) | |
| // are followed by an extracted hybrid object member, using the same | |
| // NewArchiveMember. | |
| if (!D.NativeBuf.get()) | |
| ++MemberIndex; | |
| raw_string_ostream Out(D.Header); | |
| uint64_t Size = D.Data.size(); | |
| if (Thin) | |
| D.Data = ""; | |
| // ld64 expects the members to be 8-byte aligned for 64-bit content and at | |
| // least 4-byte aligned for 32-bit content. Opt for the larger encoding | |
| // uniformly. This matches the behaviour with cctools and ensures that ld64 | |
| // is happy with archives that we generate. | |
| unsigned MemberPadding = | |
| isDarwin(Kind) ? offsetToAlignment(D.Data.size(), Align(8)) : 0; | |
| StringRef MemberName = D.HybridName.size() ? D.HybridName : M->MemberName; | |
| // z/OS stores long member names inline using their exact byte length. | |
| // Include the inline name when computing alignment. | |
| uint64_t PaddingBase = D.Data.size() + MemberPadding; | |
| if (isZOSArchive(Kind) && MemberName.size() > 16) | |
| PaddingBase += MemberName.size(); | |
| unsigned TailPadding = offsetToAlignment(PaddingBase, Align(2)); | |
| D.Padding = StringRef(isZOSArchive(Kind) ? ZOSPaddingData : PaddingData, | |
| MemberPadding + TailPadding); | |
| sys::TimePoint<std::chrono::seconds> ModTime; | |
| if (UniqueTimestamps) | |
| // Increment timestamp for each file of a given name. | |
| ModTime = sys::toTimePoint(FilenameCount[MemberName]++); | |
| else | |
| ModTime = M->ModTime; | |
| Size += MemberPadding; | |
| if (Size > object::Archive::MaxMemberSize) { | |
| std::string StringMsg = | |
| "File " + MemberName.str() + " exceeds size limit"; | |
| return make_error<object::GenericBinaryError>( | |
| std::move(StringMsg), object::object_error::parse_failed); | |
| } | |
| // In the big archive file format, we need to calculate and include the next | |
| // member offset and previous member offset in the file member header. | |
| if (isAIXBigArchive(Kind)) { | |
| uint64_t OffsetToMemData = | |
| Pos + sizeof(object::BigArMemHdrType) + alignTo(MemberName.size(), 2); | |
| if (Index == 0) | |
| NextMemHeadPadSize = | |
| alignToPowerOf2(OffsetToMemData, | |
| getMemberAlignment(D.SymFile.get())) - | |
| OffsetToMemData; | |
| D.PreHeadPadSize = NextMemHeadPadSize; | |
| Pos += D.PreHeadPadSize; | |
| uint64_t NextOffset = Pos + sizeof(object::BigArMemHdrType) + | |
| alignTo(MemberName.size(), 2) + alignTo(Size, 2); | |
| // If there is another member file after this, we need to calculate the | |
| // padding before the header. | |
| if (Index + 1 != Ret.size()) { | |
| uint64_t OffsetToNextMemData = | |
| NextOffset + sizeof(object::BigArMemHdrType) + | |
| alignTo(NewMembers[MemberIndex].MemberName.size(), 2); | |
| NextMemHeadPadSize = | |
| alignToPowerOf2(OffsetToNextMemData, | |
| getMemberAlignment(Ret[Index + 1].SymFile.get())) - | |
| OffsetToNextMemData; | |
| NextOffset += NextMemHeadPadSize; | |
| } | |
| printBigArchiveMemberHeader(Out, MemberName, ModTime, M->UID, M->GID, | |
| M->Perms, Size, PrevOffset, NextOffset); | |
| PrevOffset = Pos; | |
| } else { | |
| printMemberHeader(Out, Pos, StringTable, MemberNames, Kind, Thin, *M, | |
| MemberName, ModTime, Size); | |
| } | |
| if (NeedSymbols != SymtabWritingMode::NoSymtab) { | |
| Expected<std::vector<unsigned>> SymbolsOrErr = | |
| getSymbols(D.SymFile.get(), Index + 1, SymNames, SymMap); | |
| if (!SymbolsOrErr) | |
| return createFileError(MemberName, SymbolsOrErr.takeError()); | |
| D.Symbols = std::move(*SymbolsOrErr); | |
| // For z/OS, populate SymbolAttrs in lockstep with Symbols so that | |
| // writeSymbolTable() can emit the per-symbol attribute word. | |
| if (isZOSArchive(Kind)) { | |
| auto *GOFFObj = dyn_cast_or_null<GOFFObjectFile>(D.SymFile.get()); | |
| if (GOFFObj) { | |
| for (object::BasicSymbolRef S : GOFFObj->symbols()) { | |
| if (!isArchiveSymbol(S)) | |
| continue; | |
| D.SymbolAttrs.push_back( | |
| GOFFObj->getZOSSymbolArchiveAttributes(S.getRawDataRefImpl())); | |
| } | |
| } else { | |
| // For non-GOFF symbolic files (e.g. bitcode/IR), there is no z/OS | |
| // archive attribute data available. Pad SymbolAttrs to stay in sync | |
| // with Symbols. | |
| D.SymbolAttrs.resize(D.Symbols.size()); | |
| } | |
| } | |
| if (D.SymFile) | |
| HasObject = true; | |
| // On z/OS, when there are no symbols, add a dummy blank symbol | |
| // into the symbol table. This is done since the z/OS binder: | |
| // - emits an error if there is no symbol table in the archive | |
| // - emits an error if the symbol table has 0 symbols | |
| // - should not find any references to a blank symbol | |
| if (isZOSArchive(Kind) && (LastZosObjIndex == Index) && | |
| (SymNames.tell() == 0)) { | |
| D.Symbols.push_back(0); | |
| D.SymbolAttrs.push_back(0); | |
| SymNames << ' ' << '\0'; | |
| } | |
| } | |
| Pos += D.Header.size() + D.Data.size() + D.Padding.size(); | |
| } | |
| // If there are no symbols, emit an empty symbol table, to satisfy Solaris | |
| // tools, older versions of which expect a symbol table in a non-empty | |
| // archive, regardless of whether there are any symbols in it. | |
| if (HasObject && SymNames.tell() == 0 && !isCOFFArchive(Kind)) | |
| SymNames << '\0' << '\0' << '\0'; | |
| return std::move(Ret); | |
| } | |
| namespace llvm { | |
| static ErrorOr<SmallString<128>> canonicalizePath(StringRef P) { | |
| SmallString<128> Ret = P; | |
| std::error_code Err = sys::fs::make_absolute(Ret); | |
| if (Err) | |
| return Err; | |
| sys::path::remove_dots(Ret, /*removedotdot*/ true); | |
| return Ret; | |
| } | |
| // Compute the relative path from From to To. | |
| Expected<std::string> computeArchiveRelativePath(StringRef From, StringRef To) { | |
| ErrorOr<SmallString<128>> PathToOrErr = canonicalizePath(To); | |
| ErrorOr<SmallString<128>> DirFromOrErr = canonicalizePath(From); | |
| if (!PathToOrErr || !DirFromOrErr) | |
| return errorCodeToError(errnoAsErrorCode()); | |
| const SmallString<128> &PathTo = *PathToOrErr; | |
| const SmallString<128> &DirFrom = sys::path::parent_path(*DirFromOrErr); | |
| // Can't construct a relative path between different roots | |
| if (sys::path::root_name(PathTo) != sys::path::root_name(DirFrom)) | |
| return sys::path::convert_to_slash(PathTo); | |
| // Skip common prefixes | |
| auto FromTo = | |
| std::mismatch(sys::path::begin(DirFrom), sys::path::end(DirFrom), | |
| sys::path::begin(PathTo)); | |
| auto FromI = FromTo.first; | |
| auto ToI = FromTo.second; | |
| // Construct relative path | |
| SmallString<128> Relative; | |
| for (auto FromE = sys::path::end(DirFrom); FromI != FromE; ++FromI) | |
| sys::path::append(Relative, sys::path::Style::posix, ".."); | |
| for (auto ToE = sys::path::end(PathTo); ToI != ToE; ++ToI) | |
| sys::path::append(Relative, sys::path::Style::posix, *ToI); | |
| return std::string(Relative); | |
| } | |
| Error writeArchiveToStream(raw_ostream &Out, | |
| ArrayRef<NewArchiveMember> NewMembers, | |
| SymtabWritingMode WriteSymtab, | |
| object::Archive::Kind Kind, bool Deterministic, | |
| bool Thin, std::optional<bool> IsEC, | |
| function_ref<void(Error)> Warn) { | |
| assert((!Thin || !isBSDLike(Kind)) && "Only the gnu format has a thin mode"); | |
| SmallString<0> SymNamesBuf; | |
| raw_svector_ostream SymNames(SymNamesBuf); | |
| SmallString<0> StringTableBuf; | |
| raw_svector_ostream StringTable(StringTableBuf); | |
| SymMap SymMap; | |
| bool ShouldWriteSymtab = WriteSymtab != SymtabWritingMode::NoSymtab; | |
| // COFF symbol map uses 16-bit indexes, so we can't use it if there are too | |
| // many members. COFF format also requires symbol table presence, so use | |
| // GNU format when NoSymtab is requested. | |
| if (isCOFFArchive(Kind) && (NewMembers.size() > 0xfffe || !ShouldWriteSymtab)) | |
| Kind = object::Archive::K_GNU; | |
| // In the scenario when LLVMContext is populated SymbolicFile will contain a | |
| // reference to it, thus SymbolicFile should be destroyed first. | |
| LLVMContext Context; | |
| Expected<std::vector<MemberData>> DataOrErr = computeMemberData( | |
| StringTable, SymNames, Kind, Thin, Deterministic, WriteSymtab, | |
| isCOFFArchive(Kind) ? &SymMap : nullptr, Context, NewMembers, IsEC, Warn); | |
| if (Error E = DataOrErr.takeError()) | |
| return E; | |
| std::vector<MemberData> &Data = *DataOrErr; | |
| uint64_t StringTableSize = 0; | |
| MemberData StringTableMember; | |
| if (!StringTableBuf.empty() && !isAIXBigArchive(Kind)) { | |
| StringTableMember = computeStringTable(StringTableBuf); | |
| StringTableSize = StringTableMember.Header.size() + | |
| StringTableMember.Data.size() + | |
| StringTableMember.Padding.size(); | |
| } | |
| // We would like to detect if we need to switch to a 64-bit symbol table. | |
| uint64_t LastMemberEndOffset = 0; | |
| uint64_t LastMemberHeaderOffset = 0; | |
| uint64_t NumSyms = 0; | |
| uint64_t NumSyms32 = 0; // Store symbol number of 32-bit member files. | |
| for (const auto &M : Data) { | |
| // Record the start of the member's offset | |
| LastMemberEndOffset += M.PreHeadPadSize; | |
| LastMemberHeaderOffset = LastMemberEndOffset; | |
| // Account for the size of each part associated with the member. | |
| LastMemberEndOffset += M.Header.size() + M.Data.size() + M.Padding.size(); | |
| NumSyms += M.Symbols.size(); | |
| // AIX big archive files may contain two global symbol tables. The | |
| // first global symbol table locates 32-bit file members that define global | |
| // symbols; the second global symbol table does the same for 64-bit file | |
| // members. As a big archive can have both 32-bit and 64-bit file members, | |
| // we need to know the number of symbols in each symbol table individually. | |
| if (isAIXBigArchive(Kind) && ShouldWriteSymtab) { | |
| if (!is64BitSymbolicFile(M.SymFile.get())) | |
| NumSyms32 += M.Symbols.size(); | |
| } | |
| } | |
| std::optional<uint64_t> HeadersSize; | |
| // The symbol table is put at the end of the big archive file. The symbol | |
| // table is at the start of the archive file for other archive formats. | |
| if (ShouldWriteSymtab && !is64BitKind(Kind)) { | |
| // We assume 32-bit offsets to see if 32-bit symbols are possible or not. | |
| HeadersSize = computeHeadersSize(Kind, Data.size(), StringTableSize, | |
| NumSyms, SymNamesBuf.size(), | |
| isCOFFArchive(Kind) ? &SymMap : nullptr); | |
| // The SYM64 format is used when an archive's member offsets are larger than | |
| // 32-bits can hold. The need for this shift in format is detected by | |
| // writeArchive. To test this we need to generate a file with a member that | |
| // has an offset larger than 32-bits but this demands a very slow test. To | |
| // speed the test up we use this environment variable to pretend like the | |
| // cutoff happens before 32-bits and instead happens at some much smaller | |
| // value. | |
| uint64_t Sym64Threshold = 1ULL << 32; | |
| const char *Sym64Env = std::getenv("SYM64_THRESHOLD"); | |
| if (Sym64Env) | |
| StringRef(Sym64Env).getAsInteger(10, Sym64Threshold); | |
| // If LastMemberHeaderOffset isn't going to fit in a 32-bit varible we need | |
| // to switch to 64-bit. Note that the file can be larger than 4GB as long as | |
| // the last member starts before the 4GB offset. | |
| if (*HeadersSize + LastMemberHeaderOffset >= Sym64Threshold) { | |
| switch (Kind) { | |
| case object::Archive::K_COFF: | |
| // COFF format has no 64-bit version, so we use GNU64 instead. | |
| if (!SymMap.Map.empty() && !SymMap.ECMap.empty()) | |
| // Only the COFF format supports the ECSYMBOLS section, so don’t use | |
| // GNU64 when two symbol maps are required. | |
| return make_error<object::GenericBinaryError>( | |
| "Archive is too large: ARM64X does not support archives larger " | |
| "than 4GB"); | |
| // Since this changes the headers, we need to recalculate everything. | |
| return writeArchiveToStream(Out, NewMembers, WriteSymtab, | |
| object::Archive::K_GNU64, Deterministic, | |
| Thin, IsEC, Warn); | |
| case object::Archive::K_DARWIN: | |
| Kind = object::Archive::K_DARWIN64; | |
| break; | |
| default: | |
| Kind = object::Archive::K_GNU64; | |
| break; | |
| } | |
| HeadersSize.reset(); | |
| } | |
| } | |
| if (Thin) | |
| Out << "!<thin>\n"; | |
| else if (isAIXBigArchive(Kind)) | |
| Out << "<bigaf>\n"; | |
| else if (isZOSArchive(Kind)) | |
| Out << ZOSArchiveMagic; | |
| else | |
| Out << "!<arch>\n"; | |
| if (!isAIXBigArchive(Kind)) { | |
| if (ShouldWriteSymtab) { | |
| if (!HeadersSize) | |
| HeadersSize = computeHeadersSize( | |
| Kind, Data.size(), StringTableSize, NumSyms, SymNamesBuf.size(), | |
| isCOFFArchive(Kind) ? &SymMap : nullptr); | |
| writeSymbolTable(Out, Kind, Deterministic, Data, SymNamesBuf, | |
| *HeadersSize, NumSyms); | |
| if (isCOFFArchive(Kind)) | |
| writeSymbolMap(Out, Kind, Deterministic, Data, SymMap, *HeadersSize); | |
| } | |
| if (StringTableSize) | |
| Out << StringTableMember.Header << StringTableMember.Data | |
| << StringTableMember.Padding; | |
| if (ShouldWriteSymtab && SymMap.ECMap.size()) | |
| writeECSymbols(Out, Kind, Deterministic, Data, SymMap); | |
| for (const MemberData &M : Data) | |
| Out << M.Header << M.Data << M.Padding; | |
| } else { | |
| HeadersSize = sizeof(object::BigArchive::FixLenHdr); | |
| LastMemberEndOffset += *HeadersSize; | |
| LastMemberHeaderOffset += *HeadersSize; | |
| // For the big archive (AIX) format, compute a table of member names and | |
| // offsets, used in the member table. | |
| uint64_t MemberTableNameStrTblSize = 0; | |
| std::vector<size_t> MemberOffsets; | |
| std::vector<StringRef> MemberNames; | |
| // Loop across object to find offset and names. | |
| uint64_t MemberEndOffset = sizeof(object::BigArchive::FixLenHdr); | |
| for (size_t I = 0, Size = NewMembers.size(); I != Size; ++I) { | |
| const NewArchiveMember &Member = NewMembers[I]; | |
| MemberTableNameStrTblSize += Member.MemberName.size() + 1; | |
| MemberEndOffset += Data[I].PreHeadPadSize; | |
| MemberOffsets.push_back(MemberEndOffset); | |
| MemberNames.push_back(Member.MemberName); | |
| // File member name ended with "`\n". The length is included in | |
| // BigArMemHdrType. | |
| MemberEndOffset += sizeof(object::BigArMemHdrType) + | |
| alignTo(Data[I].Data.size(), 2) + | |
| alignTo(Member.MemberName.size(), 2); | |
| } | |
| // AIX member table size. | |
| uint64_t MemberTableSize = 20 + // Number of members field | |
| 20 * MemberOffsets.size() + | |
| MemberTableNameStrTblSize; | |
| SmallString<0> SymNamesBuf32; | |
| SmallString<0> SymNamesBuf64; | |
| raw_svector_ostream SymNames32(SymNamesBuf32); | |
| raw_svector_ostream SymNames64(SymNamesBuf64); | |
| if (ShouldWriteSymtab && NumSyms) | |
| // Generate the symbol names for the members. | |
| for (const auto &M : Data) { | |
| Expected<std::vector<unsigned>> SymbolsOrErr = getSymbols( | |
| M.SymFile.get(), 0, | |
| is64BitSymbolicFile(M.SymFile.get()) ? SymNames64 : SymNames32, | |
| nullptr); | |
| if (!SymbolsOrErr) | |
| return SymbolsOrErr.takeError(); | |
| } | |
| uint64_t MemberTableEndOffset = | |
| LastMemberEndOffset + | |
| alignTo(sizeof(object::BigArMemHdrType) + MemberTableSize, 2); | |
| // In AIX OS, The 'GlobSymOffset' field in the fixed-length header contains | |
| // the offset to the 32-bit global symbol table, and the 'GlobSym64Offset' | |
| // contains the offset to the 64-bit global symbol table. | |
| uint64_t GlobalSymbolOffset = | |
| (ShouldWriteSymtab && | |
| (WriteSymtab != SymtabWritingMode::BigArchive64) && NumSyms32 > 0) | |
| ? MemberTableEndOffset | |
| : 0; | |
| uint64_t GlobalSymbolOffset64 = 0; | |
| uint64_t NumSyms64 = NumSyms - NumSyms32; | |
| if (ShouldWriteSymtab && (WriteSymtab != SymtabWritingMode::BigArchive32) && | |
| NumSyms64 > 0) { | |
| if (GlobalSymbolOffset == 0) | |
| GlobalSymbolOffset64 = MemberTableEndOffset; | |
| else | |
| // If there is a global symbol table for 32-bit members, | |
| // the 64-bit global symbol table is after the 32-bit one. | |
| GlobalSymbolOffset64 = | |
| GlobalSymbolOffset + sizeof(object::BigArMemHdrType) + | |
| (NumSyms32 + 1) * 8 + alignTo(SymNamesBuf32.size(), 2); | |
| } | |
| // Fixed Sized Header. | |
| printWithSpacePadding(Out, NewMembers.size() ? LastMemberEndOffset : 0, | |
| 20); // Offset to member table | |
| // If there are no file members in the archive, there will be no global | |
| // symbol table. | |
| printWithSpacePadding(Out, GlobalSymbolOffset, 20); | |
| printWithSpacePadding(Out, GlobalSymbolOffset64, 20); | |
| printWithSpacePadding(Out, | |
| NewMembers.size() | |
| ? sizeof(object::BigArchive::FixLenHdr) + | |
| Data[0].PreHeadPadSize | |
| : 0, | |
| 20); // Offset to first archive member | |
| printWithSpacePadding(Out, NewMembers.size() ? LastMemberHeaderOffset : 0, | |
| 20); // Offset to last archive member | |
| printWithSpacePadding( | |
| Out, 0, | |
| 20); // Offset to first member of free list - Not supported yet | |
| for (const MemberData &M : Data) { | |
| Out << std::string(M.PreHeadPadSize, '\0'); | |
| Out << M.Header << M.Data; | |
| if (M.Data.size() % 2) | |
| Out << '\0'; | |
| } | |
| if (NewMembers.size()) { | |
| // Member table. | |
| printBigArchiveMemberHeader(Out, "", sys::toTimePoint(0), 0, 0, 0, | |
| MemberTableSize, LastMemberHeaderOffset, | |
| GlobalSymbolOffset ? GlobalSymbolOffset | |
| : GlobalSymbolOffset64); | |
| printWithSpacePadding(Out, MemberOffsets.size(), 20); // Number of members | |
| for (uint64_t MemberOffset : MemberOffsets) | |
| printWithSpacePadding(Out, MemberOffset, | |
| 20); // Offset to member file header. | |
| for (StringRef MemberName : MemberNames) | |
| Out << MemberName << '\0'; // Member file name, null byte padding. | |
| if (MemberTableNameStrTblSize % 2) | |
| Out << '\0'; // Name table must be tail padded to an even number of | |
| // bytes. | |
| if (ShouldWriteSymtab) { | |
| // Write global symbol table for 32-bit file members. | |
| if (GlobalSymbolOffset) { | |
| writeSymbolTable(Out, Kind, Deterministic, Data, SymNamesBuf32, | |
| *HeadersSize, NumSyms32, LastMemberEndOffset, | |
| GlobalSymbolOffset64); | |
| // Add padding between the symbol tables, if needed. | |
| if (GlobalSymbolOffset64 && (SymNamesBuf32.size() % 2)) | |
| Out << '\0'; | |
| } | |
| // Write global symbol table for 64-bit file members. | |
| if (GlobalSymbolOffset64) | |
| writeSymbolTable(Out, Kind, Deterministic, Data, SymNamesBuf64, | |
| *HeadersSize, NumSyms64, | |
| GlobalSymbolOffset ? GlobalSymbolOffset | |
| : LastMemberEndOffset, | |
| 0, true); | |
| } | |
| } | |
| } | |
| Out.flush(); | |
| return Error::success(); | |
| } | |
| void warnToStderr(Error Err) { | |
| llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "warning: "); | |
| } | |
| Error writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers, | |
| SymtabWritingMode WriteSymtab, object::Archive::Kind Kind, | |
| bool Deterministic, bool Thin, | |
| std::unique_ptr<MemoryBuffer> OldArchiveBuf, | |
| std::optional<bool> IsEC, function_ref<void(Error)> Warn) { | |
| Expected<sys::fs::TempFile> Temp = | |
| sys::fs::TempFile::create(ArcName + ".temp-archive-%%%%%%%.a"); | |
| if (!Temp) | |
| return Temp.takeError(); | |
| raw_fd_ostream Out(Temp->FD, false); | |
| if (Error E = writeArchiveToStream(Out, NewMembers, WriteSymtab, Kind, | |
| Deterministic, Thin, IsEC, Warn)) { | |
| if (Error DiscardError = Temp->discard()) | |
| return joinErrors(std::move(E), std::move(DiscardError)); | |
| return E; | |
| } | |
| // At this point, we no longer need whatever backing memory | |
| // was used to generate the NewMembers. On Windows, this buffer | |
| // could be a mapped view of the file we want to replace (if | |
| // we're updating an existing archive, say). In that case, the | |
| // rename would still succeed, but it would leave behind a | |
| // temporary file (actually the original file renamed) because | |
| // a file cannot be deleted while there's a handle open on it, | |
| // only renamed. So by freeing this buffer, this ensures that | |
| // the last open handle on the destination file, if any, is | |
| // closed before we attempt to rename. | |
| OldArchiveBuf.reset(); | |
| return Temp->keep(ArcName); | |
| } | |
| Expected<std::unique_ptr<MemoryBuffer>> | |
| writeArchiveToBuffer(ArrayRef<NewArchiveMember> NewMembers, | |
| SymtabWritingMode WriteSymtab, object::Archive::Kind Kind, | |
| bool Deterministic, bool Thin, | |
| function_ref<void(Error)> Warn) { | |
| SmallVector<char, 0> ArchiveBufferVector; | |
| raw_svector_ostream ArchiveStream(ArchiveBufferVector); | |
| if (Error E = | |
| writeArchiveToStream(ArchiveStream, NewMembers, WriteSymtab, Kind, | |
| Deterministic, Thin, std::nullopt, Warn)) | |
| return std::move(E); | |
| return std::make_unique<SmallVectorMemoryBuffer>( | |
| std::move(ArchiveBufferVector), /*RequiresNullTerminator=*/false); | |
| } | |
| } // namespace llvm |