From 0860de920b16b8ddd556a0557e02d21e33b2cbfb Mon Sep 17 00:00:00 2001 From: Daniel Paoliello Date: Wed, 24 Apr 2024 15:09:28 -0700 Subject: [PATCH] Add the ability to create PE import libraries --- Cargo.toml | 2 +- reference/COFF.h | 820 +++++++++++++++++++++++++++++++ reference/COFFImportFile.cpp | 708 +++++++++++++++++++++++++++ reference/COFFImportFile.h | 43 +- reference/Mangler.cpp | 331 +++++++++++++ reference/Readme.md | 3 + rust-toolchain.toml | 3 +- src/archive_writer.rs | 23 +- src/coff.rs | 78 +++ src/coff_import_file.rs | 908 +++++++++++++++++++++++++++++++++++ src/lib.rs | 4 + src/mangler.rs | 49 ++ tests/common.rs | 44 +- tests/import_library.def | 16 + tests/import_library.rs | 217 +++++++++ 15 files changed, 3209 insertions(+), 40 deletions(-) create mode 100644 reference/COFF.h create mode 100644 reference/COFFImportFile.cpp create mode 100644 reference/Mangler.cpp create mode 100644 src/coff.rs create mode 100644 src/mangler.rs create mode 100644 tests/import_library.def create mode 100644 tests/import_library.rs diff --git a/Cargo.toml b/Cargo.toml index 076f7ec..3a95095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ar_archive_writer" -version = "0.3.0" +version = "0.3.1" edition = "2021" license = "Apache-2.0 WITH LLVM-exception" description = "A writer for object file ar archives" diff --git a/reference/COFF.h b/reference/COFF.h new file mode 100644 index 0000000..2c18859 --- /dev/null +++ b/reference/COFF.h @@ -0,0 +1,820 @@ +//===-- llvm/BinaryFormat/COFF.h --------------------------------*- 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 contains an definitions used in Windows COFF Files. +// +// Structures and enums defined within this file where created using +// information from Microsoft's publicly available PE/COFF format document: +// +// Microsoft Portable Executable and Common Object File Format Specification +// Revision 8.1 - February 15, 2008 +// +// As of 5/2/2010, hosted by Microsoft at: +// http://www.microsoft.com/whdc/system/platform/firmware/pecoff.mspx +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_BINARYFORMAT_COFF_H +#define LLVM_BINARYFORMAT_COFF_H + +#include "llvm/Support/DataTypes.h" +#include + +namespace llvm { +namespace COFF { + +// The maximum number of sections that a COFF object can have (inclusive). +const int32_t MaxNumberOfSections16 = 65279; + +// The PE signature bytes that follows the DOS stub header. +static const char PEMagic[] = {'P', 'E', '\0', '\0'}; + +static const char BigObjMagic[] = { + '\xc7', '\xa1', '\xba', '\xd1', '\xee', '\xba', '\xa9', '\x4b', + '\xaf', '\x20', '\xfa', '\xf6', '\x6a', '\xa4', '\xdc', '\xb8', +}; + +static const char ClGlObjMagic[] = { + '\x38', '\xfe', '\xb3', '\x0c', '\xa5', '\xd9', '\xab', '\x4d', + '\xac', '\x9b', '\xd6', '\xb6', '\x22', '\x26', '\x53', '\xc2', +}; + +// The signature bytes that start a .res file. +static const char WinResMagic[] = { + '\x00', '\x00', '\x00', '\x00', '\x20', '\x00', '\x00', '\x00', + '\xff', '\xff', '\x00', '\x00', '\xff', '\xff', '\x00', '\x00', +}; + +// Sizes in bytes of various things in the COFF format. +enum { + Header16Size = 20, + Header32Size = 56, + NameSize = 8, + Symbol16Size = 18, + Symbol32Size = 20, + SectionSize = 40, + RelocationSize = 10 +}; + +struct header { + uint16_t Machine; + int32_t NumberOfSections; + uint32_t TimeDateStamp; + uint32_t PointerToSymbolTable; + uint32_t NumberOfSymbols; + uint16_t SizeOfOptionalHeader; + uint16_t Characteristics; +}; + +struct BigObjHeader { + enum : uint16_t { MinBigObjectVersion = 2 }; + + uint16_t Sig1; ///< Must be IMAGE_FILE_MACHINE_UNKNOWN (0). + uint16_t Sig2; ///< Must be 0xFFFF. + uint16_t Version; + uint16_t Machine; + uint32_t TimeDateStamp; + uint8_t UUID[16]; + uint32_t unused1; + uint32_t unused2; + uint32_t unused3; + uint32_t unused4; + uint32_t NumberOfSections; + uint32_t PointerToSymbolTable; + uint32_t NumberOfSymbols; +}; + +enum MachineTypes : unsigned { + MT_Invalid = 0xffff, + + IMAGE_FILE_MACHINE_UNKNOWN = 0x0, + IMAGE_FILE_MACHINE_AM33 = 0x1D3, + IMAGE_FILE_MACHINE_AMD64 = 0x8664, + IMAGE_FILE_MACHINE_ARM = 0x1C0, + IMAGE_FILE_MACHINE_ARMNT = 0x1C4, + IMAGE_FILE_MACHINE_ARM64 = 0xAA64, + IMAGE_FILE_MACHINE_ARM64EC = 0xA641, + IMAGE_FILE_MACHINE_ARM64X = 0xA64E, + IMAGE_FILE_MACHINE_EBC = 0xEBC, + IMAGE_FILE_MACHINE_I386 = 0x14C, + IMAGE_FILE_MACHINE_IA64 = 0x200, + IMAGE_FILE_MACHINE_M32R = 0x9041, + IMAGE_FILE_MACHINE_MIPS16 = 0x266, + IMAGE_FILE_MACHINE_MIPSFPU = 0x366, + IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466, + IMAGE_FILE_MACHINE_POWERPC = 0x1F0, + IMAGE_FILE_MACHINE_POWERPCFP = 0x1F1, + IMAGE_FILE_MACHINE_R4000 = 0x166, + IMAGE_FILE_MACHINE_RISCV32 = 0x5032, + IMAGE_FILE_MACHINE_RISCV64 = 0x5064, + IMAGE_FILE_MACHINE_RISCV128 = 0x5128, + IMAGE_FILE_MACHINE_SH3 = 0x1A2, + IMAGE_FILE_MACHINE_SH3DSP = 0x1A3, + IMAGE_FILE_MACHINE_SH4 = 0x1A6, + IMAGE_FILE_MACHINE_SH5 = 0x1A8, + IMAGE_FILE_MACHINE_THUMB = 0x1C2, + IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169 +}; + +template bool isArm64EC(T Machine) { + return Machine == IMAGE_FILE_MACHINE_ARM64EC || + Machine == IMAGE_FILE_MACHINE_ARM64X; +} + +template bool isAnyArm64(T Machine) { + return Machine == IMAGE_FILE_MACHINE_ARM64 || isArm64EC(Machine); +} + +template bool is64Bit(T Machine) { + return Machine == IMAGE_FILE_MACHINE_AMD64 || isAnyArm64(Machine); +} + +enum Characteristics : unsigned { + C_Invalid = 0, + + /// The file does not contain base relocations and must be loaded at its + /// preferred base. If this cannot be done, the loader will error. + IMAGE_FILE_RELOCS_STRIPPED = 0x0001, + /// The file is valid and can be run. + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002, + /// COFF line numbers have been stripped. This is deprecated and should be + /// 0. + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004, + /// COFF symbol table entries for local symbols have been removed. This is + /// deprecated and should be 0. + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008, + /// Aggressively trim working set. This is deprecated and must be 0. + IMAGE_FILE_AGGRESSIVE_WS_TRIM = 0x0010, + /// Image can handle > 2GiB addresses. + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020, + /// Little endian: the LSB precedes the MSB in memory. This is deprecated + /// and should be 0. + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080, + /// Machine is based on a 32bit word architecture. + IMAGE_FILE_32BIT_MACHINE = 0x0100, + /// Debugging info has been removed. + IMAGE_FILE_DEBUG_STRIPPED = 0x0200, + /// If the image is on removable media, fully load it and copy it to swap. + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400, + /// If the image is on network media, fully load it and copy it to swap. + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800, + /// The image file is a system file, not a user program. + IMAGE_FILE_SYSTEM = 0x1000, + /// The image file is a DLL. + IMAGE_FILE_DLL = 0x2000, + /// This file should only be run on a uniprocessor machine. + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000, + /// Big endian: the MSB precedes the LSB in memory. This is deprecated + /// and should be 0. + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 +}; + +enum ResourceTypeID : unsigned { + RID_Cursor = 1, + RID_Bitmap = 2, + RID_Icon = 3, + RID_Menu = 4, + RID_Dialog = 5, + RID_String = 6, + RID_FontDir = 7, + RID_Font = 8, + RID_Accelerator = 9, + RID_RCData = 10, + RID_MessageTable = 11, + RID_Group_Cursor = 12, + RID_Group_Icon = 14, + RID_Version = 16, + RID_DLGInclude = 17, + RID_PlugPlay = 19, + RID_VXD = 20, + RID_AniCursor = 21, + RID_AniIcon = 22, + RID_HTML = 23, + RID_Manifest = 24, +}; + +struct symbol { + char Name[NameSize]; + uint32_t Value; + int32_t SectionNumber; + uint16_t Type; + uint8_t StorageClass; + uint8_t NumberOfAuxSymbols; +}; + +enum SymbolSectionNumber : int32_t { + IMAGE_SYM_DEBUG = -2, + IMAGE_SYM_ABSOLUTE = -1, + IMAGE_SYM_UNDEFINED = 0 +}; + +/// Storage class tells where and what the symbol represents +enum SymbolStorageClass { + SSC_Invalid = 0xff, + + IMAGE_SYM_CLASS_END_OF_FUNCTION = -1, ///< Physical end of function + IMAGE_SYM_CLASS_NULL = 0, ///< No symbol + IMAGE_SYM_CLASS_AUTOMATIC = 1, ///< Stack variable + IMAGE_SYM_CLASS_EXTERNAL = 2, ///< External symbol + IMAGE_SYM_CLASS_STATIC = 3, ///< Static + IMAGE_SYM_CLASS_REGISTER = 4, ///< Register variable + IMAGE_SYM_CLASS_EXTERNAL_DEF = 5, ///< External definition + IMAGE_SYM_CLASS_LABEL = 6, ///< Label + IMAGE_SYM_CLASS_UNDEFINED_LABEL = 7, ///< Undefined label + IMAGE_SYM_CLASS_MEMBER_OF_STRUCT = 8, ///< Member of structure + IMAGE_SYM_CLASS_ARGUMENT = 9, ///< Function argument + IMAGE_SYM_CLASS_STRUCT_TAG = 10, ///< Structure tag + IMAGE_SYM_CLASS_MEMBER_OF_UNION = 11, ///< Member of union + IMAGE_SYM_CLASS_UNION_TAG = 12, ///< Union tag + IMAGE_SYM_CLASS_TYPE_DEFINITION = 13, ///< Type definition + IMAGE_SYM_CLASS_UNDEFINED_STATIC = 14, ///< Undefined static + IMAGE_SYM_CLASS_ENUM_TAG = 15, ///< Enumeration tag + IMAGE_SYM_CLASS_MEMBER_OF_ENUM = 16, ///< Member of enumeration + IMAGE_SYM_CLASS_REGISTER_PARAM = 17, ///< Register parameter + IMAGE_SYM_CLASS_BIT_FIELD = 18, ///< Bit field + /// ".bb" or ".eb" - beginning or end of block + IMAGE_SYM_CLASS_BLOCK = 100, + /// ".bf" or ".ef" - beginning or end of function + IMAGE_SYM_CLASS_FUNCTION = 101, + IMAGE_SYM_CLASS_END_OF_STRUCT = 102, ///< End of structure + IMAGE_SYM_CLASS_FILE = 103, ///< File name + /// Line number, reformatted as symbol + IMAGE_SYM_CLASS_SECTION = 104, + IMAGE_SYM_CLASS_WEAK_EXTERNAL = 105, ///< Duplicate tag + /// External symbol in dmert public lib + IMAGE_SYM_CLASS_CLR_TOKEN = 107 +}; + +enum SymbolBaseType : unsigned { + IMAGE_SYM_TYPE_NULL = 0, ///< No type information or unknown base type. + IMAGE_SYM_TYPE_VOID = 1, ///< Used with void pointers and functions. + IMAGE_SYM_TYPE_CHAR = 2, ///< A character (signed byte). + IMAGE_SYM_TYPE_SHORT = 3, ///< A 2-byte signed integer. + IMAGE_SYM_TYPE_INT = 4, ///< A natural integer type on the target. + IMAGE_SYM_TYPE_LONG = 5, ///< A 4-byte signed integer. + IMAGE_SYM_TYPE_FLOAT = 6, ///< A 4-byte floating-point number. + IMAGE_SYM_TYPE_DOUBLE = 7, ///< An 8-byte floating-point number. + IMAGE_SYM_TYPE_STRUCT = 8, ///< A structure. + IMAGE_SYM_TYPE_UNION = 9, ///< An union. + IMAGE_SYM_TYPE_ENUM = 10, ///< An enumerated type. + IMAGE_SYM_TYPE_MOE = 11, ///< A member of enumeration (a specific value). + IMAGE_SYM_TYPE_BYTE = 12, ///< A byte; unsigned 1-byte integer. + IMAGE_SYM_TYPE_WORD = 13, ///< A word; unsigned 2-byte integer. + IMAGE_SYM_TYPE_UINT = 14, ///< An unsigned integer of natural size. + IMAGE_SYM_TYPE_DWORD = 15 ///< An unsigned 4-byte integer. +}; + +enum SymbolComplexType : unsigned { + IMAGE_SYM_DTYPE_NULL = 0, ///< No complex type; simple scalar variable. + IMAGE_SYM_DTYPE_POINTER = 1, ///< A pointer to base type. + IMAGE_SYM_DTYPE_FUNCTION = 2, ///< A function that returns a base type. + IMAGE_SYM_DTYPE_ARRAY = 3, ///< An array of base type. + + /// Type is formed as (base + (derived << SCT_COMPLEX_TYPE_SHIFT)) + SCT_COMPLEX_TYPE_SHIFT = 4 +}; + +enum AuxSymbolType { IMAGE_AUX_SYMBOL_TYPE_TOKEN_DEF = 1 }; + +struct section { + char Name[NameSize]; + uint32_t VirtualSize; + uint32_t VirtualAddress; + uint32_t SizeOfRawData; + uint32_t PointerToRawData; + uint32_t PointerToRelocations; + uint32_t PointerToLineNumbers; + uint16_t NumberOfRelocations; + uint16_t NumberOfLineNumbers; + uint32_t Characteristics; +}; + +enum SectionCharacteristics : uint32_t { + SC_Invalid = 0xffffffff, + + IMAGE_SCN_TYPE_NOLOAD = 0x00000002, + IMAGE_SCN_TYPE_NO_PAD = 0x00000008, + IMAGE_SCN_CNT_CODE = 0x00000020, + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040, + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080, + IMAGE_SCN_LNK_OTHER = 0x00000100, + IMAGE_SCN_LNK_INFO = 0x00000200, + IMAGE_SCN_LNK_REMOVE = 0x00000800, + IMAGE_SCN_LNK_COMDAT = 0x00001000, + IMAGE_SCN_GPREL = 0x00008000, + IMAGE_SCN_MEM_PURGEABLE = 0x00020000, + IMAGE_SCN_MEM_16BIT = 0x00020000, + IMAGE_SCN_MEM_LOCKED = 0x00040000, + IMAGE_SCN_MEM_PRELOAD = 0x00080000, + IMAGE_SCN_ALIGN_1BYTES = 0x00100000, + IMAGE_SCN_ALIGN_2BYTES = 0x00200000, + IMAGE_SCN_ALIGN_4BYTES = 0x00300000, + IMAGE_SCN_ALIGN_8BYTES = 0x00400000, + IMAGE_SCN_ALIGN_16BYTES = 0x00500000, + IMAGE_SCN_ALIGN_32BYTES = 0x00600000, + IMAGE_SCN_ALIGN_64BYTES = 0x00700000, + IMAGE_SCN_ALIGN_128BYTES = 0x00800000, + IMAGE_SCN_ALIGN_256BYTES = 0x00900000, + IMAGE_SCN_ALIGN_512BYTES = 0x00A00000, + IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000, + IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000, + IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000, + IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000, + IMAGE_SCN_ALIGN_MASK = 0x00F00000, + IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000, + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000, + IMAGE_SCN_MEM_NOT_CACHED = 0x04000000, + IMAGE_SCN_MEM_NOT_PAGED = 0x08000000, + IMAGE_SCN_MEM_SHARED = 0x10000000, + IMAGE_SCN_MEM_EXECUTE = 0x20000000, + IMAGE_SCN_MEM_READ = 0x40000000, + IMAGE_SCN_MEM_WRITE = 0x80000000 +}; + +struct relocation { + uint32_t VirtualAddress; + uint32_t SymbolTableIndex; + uint16_t Type; +}; + +enum RelocationTypeI386 : unsigned { + IMAGE_REL_I386_ABSOLUTE = 0x0000, + IMAGE_REL_I386_DIR16 = 0x0001, + IMAGE_REL_I386_REL16 = 0x0002, + IMAGE_REL_I386_DIR32 = 0x0006, + IMAGE_REL_I386_DIR32NB = 0x0007, + IMAGE_REL_I386_SEG12 = 0x0009, + IMAGE_REL_I386_SECTION = 0x000A, + IMAGE_REL_I386_SECREL = 0x000B, + IMAGE_REL_I386_TOKEN = 0x000C, + IMAGE_REL_I386_SECREL7 = 0x000D, + IMAGE_REL_I386_REL32 = 0x0014 +}; + +enum RelocationTypeAMD64 : unsigned { + IMAGE_REL_AMD64_ABSOLUTE = 0x0000, + IMAGE_REL_AMD64_ADDR64 = 0x0001, + IMAGE_REL_AMD64_ADDR32 = 0x0002, + IMAGE_REL_AMD64_ADDR32NB = 0x0003, + IMAGE_REL_AMD64_REL32 = 0x0004, + IMAGE_REL_AMD64_REL32_1 = 0x0005, + IMAGE_REL_AMD64_REL32_2 = 0x0006, + IMAGE_REL_AMD64_REL32_3 = 0x0007, + IMAGE_REL_AMD64_REL32_4 = 0x0008, + IMAGE_REL_AMD64_REL32_5 = 0x0009, + IMAGE_REL_AMD64_SECTION = 0x000A, + IMAGE_REL_AMD64_SECREL = 0x000B, + IMAGE_REL_AMD64_SECREL7 = 0x000C, + IMAGE_REL_AMD64_TOKEN = 0x000D, + IMAGE_REL_AMD64_SREL32 = 0x000E, + IMAGE_REL_AMD64_PAIR = 0x000F, + IMAGE_REL_AMD64_SSPAN32 = 0x0010 +}; + +enum RelocationTypesARM : unsigned { + IMAGE_REL_ARM_ABSOLUTE = 0x0000, + IMAGE_REL_ARM_ADDR32 = 0x0001, + IMAGE_REL_ARM_ADDR32NB = 0x0002, + IMAGE_REL_ARM_BRANCH24 = 0x0003, + IMAGE_REL_ARM_BRANCH11 = 0x0004, + IMAGE_REL_ARM_TOKEN = 0x0005, + IMAGE_REL_ARM_BLX24 = 0x0008, + IMAGE_REL_ARM_BLX11 = 0x0009, + IMAGE_REL_ARM_REL32 = 0x000A, + IMAGE_REL_ARM_SECTION = 0x000E, + IMAGE_REL_ARM_SECREL = 0x000F, + IMAGE_REL_ARM_MOV32A = 0x0010, + IMAGE_REL_ARM_MOV32T = 0x0011, + IMAGE_REL_ARM_BRANCH20T = 0x0012, + IMAGE_REL_ARM_BRANCH24T = 0x0014, + IMAGE_REL_ARM_BLX23T = 0x0015, + IMAGE_REL_ARM_PAIR = 0x0016, +}; + +enum RelocationTypesARM64 : unsigned { + IMAGE_REL_ARM64_ABSOLUTE = 0x0000, + IMAGE_REL_ARM64_ADDR32 = 0x0001, + IMAGE_REL_ARM64_ADDR32NB = 0x0002, + IMAGE_REL_ARM64_BRANCH26 = 0x0003, + IMAGE_REL_ARM64_PAGEBASE_REL21 = 0x0004, + IMAGE_REL_ARM64_REL21 = 0x0005, + IMAGE_REL_ARM64_PAGEOFFSET_12A = 0x0006, + IMAGE_REL_ARM64_PAGEOFFSET_12L = 0x0007, + IMAGE_REL_ARM64_SECREL = 0x0008, + IMAGE_REL_ARM64_SECREL_LOW12A = 0x0009, + IMAGE_REL_ARM64_SECREL_HIGH12A = 0x000A, + IMAGE_REL_ARM64_SECREL_LOW12L = 0x000B, + IMAGE_REL_ARM64_TOKEN = 0x000C, + IMAGE_REL_ARM64_SECTION = 0x000D, + IMAGE_REL_ARM64_ADDR64 = 0x000E, + IMAGE_REL_ARM64_BRANCH19 = 0x000F, + IMAGE_REL_ARM64_BRANCH14 = 0x0010, + IMAGE_REL_ARM64_REL32 = 0x0011, +}; + +enum COMDATType : uint8_t { + IMAGE_COMDAT_SELECT_NODUPLICATES = 1, + IMAGE_COMDAT_SELECT_ANY, + IMAGE_COMDAT_SELECT_SAME_SIZE, + IMAGE_COMDAT_SELECT_EXACT_MATCH, + IMAGE_COMDAT_SELECT_ASSOCIATIVE, + IMAGE_COMDAT_SELECT_LARGEST, + IMAGE_COMDAT_SELECT_NEWEST +}; + +// Auxiliary Symbol Formats +struct AuxiliaryFunctionDefinition { + uint32_t TagIndex; + uint32_t TotalSize; + uint32_t PointerToLinenumber; + uint32_t PointerToNextFunction; + char unused[2]; +}; + +struct AuxiliarybfAndefSymbol { + uint8_t unused1[4]; + uint16_t Linenumber; + uint8_t unused2[6]; + uint32_t PointerToNextFunction; + uint8_t unused3[2]; +}; + +struct AuxiliaryWeakExternal { + uint32_t TagIndex; + uint32_t Characteristics; + uint8_t unused[10]; +}; + +enum WeakExternalCharacteristics : unsigned { + IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY = 1, + IMAGE_WEAK_EXTERN_SEARCH_LIBRARY = 2, + IMAGE_WEAK_EXTERN_SEARCH_ALIAS = 3, + IMAGE_WEAK_EXTERN_ANTI_DEPENDENCY = 4 +}; + +struct AuxiliarySectionDefinition { + uint32_t Length; + uint16_t NumberOfRelocations; + uint16_t NumberOfLinenumbers; + uint32_t CheckSum; + uint32_t Number; + uint8_t Selection; + char unused; +}; + +struct AuxiliaryCLRToken { + uint8_t AuxType; + uint8_t unused1; + uint32_t SymbolTableIndex; + char unused2[12]; +}; + +union Auxiliary { + AuxiliaryFunctionDefinition FunctionDefinition; + AuxiliarybfAndefSymbol bfAndefSymbol; + AuxiliaryWeakExternal WeakExternal; + AuxiliarySectionDefinition SectionDefinition; +}; + +/// The Import Directory Table. +/// +/// There is a single array of these and one entry per imported DLL. +struct ImportDirectoryTableEntry { + uint32_t ImportLookupTableRVA; + uint32_t TimeDateStamp; + uint32_t ForwarderChain; + uint32_t NameRVA; + uint32_t ImportAddressTableRVA; +}; + +/// The PE32 Import Lookup Table. +/// +/// There is an array of these for each imported DLL. It represents either +/// the ordinal to import from the target DLL, or a name to lookup and import +/// from the target DLL. +/// +/// This also happens to be the same format used by the Import Address Table +/// when it is initially written out to the image. +struct ImportLookupTableEntry32 { + uint32_t data; + + /// Is this entry specified by ordinal, or name? + bool isOrdinal() const { return data & 0x80000000; } + + /// Get the ordinal value of this entry. isOrdinal must be true. + uint16_t getOrdinal() const { + assert(isOrdinal() && "ILT entry is not an ordinal!"); + return data & 0xFFFF; + } + + /// Set the ordinal value and set isOrdinal to true. + void setOrdinal(uint16_t o) { + data = o; + data |= 0x80000000; + } + + /// Get the Hint/Name entry RVA. isOrdinal must be false. + uint32_t getHintNameRVA() const { + assert(!isOrdinal() && "ILT entry is not a Hint/Name RVA!"); + return data; + } + + /// Set the Hint/Name entry RVA and set isOrdinal to false. + void setHintNameRVA(uint32_t rva) { data = rva; } +}; + +/// The DOS compatible header at the front of all PEs. +struct DOSHeader { + uint16_t Magic; + uint16_t UsedBytesInTheLastPage; + uint16_t FileSizeInPages; + uint16_t NumberOfRelocationItems; + uint16_t HeaderSizeInParagraphs; + uint16_t MinimumExtraParagraphs; + uint16_t MaximumExtraParagraphs; + uint16_t InitialRelativeSS; + uint16_t InitialSP; + uint16_t Checksum; + uint16_t InitialIP; + uint16_t InitialRelativeCS; + uint16_t AddressOfRelocationTable; + uint16_t OverlayNumber; + uint16_t Reserved[4]; + uint16_t OEMid; + uint16_t OEMinfo; + uint16_t Reserved2[10]; + uint32_t AddressOfNewExeHeader; +}; + +struct PE32Header { + enum { PE32 = 0x10b, PE32_PLUS = 0x20b }; + + uint16_t Magic; + uint8_t MajorLinkerVersion; + uint8_t MinorLinkerVersion; + uint32_t SizeOfCode; + uint32_t SizeOfInitializedData; + uint32_t SizeOfUninitializedData; + uint32_t AddressOfEntryPoint; // RVA + uint32_t BaseOfCode; // RVA + uint32_t BaseOfData; // RVA + uint64_t ImageBase; + uint32_t SectionAlignment; + uint32_t FileAlignment; + uint16_t MajorOperatingSystemVersion; + uint16_t MinorOperatingSystemVersion; + uint16_t MajorImageVersion; + uint16_t MinorImageVersion; + uint16_t MajorSubsystemVersion; + uint16_t MinorSubsystemVersion; + uint32_t Win32VersionValue; + uint32_t SizeOfImage; + uint32_t SizeOfHeaders; + uint32_t CheckSum; + uint16_t Subsystem; + // FIXME: This should be DllCharacteristics to match the COFF spec. + uint16_t DLLCharacteristics; + uint64_t SizeOfStackReserve; + uint64_t SizeOfStackCommit; + uint64_t SizeOfHeapReserve; + uint64_t SizeOfHeapCommit; + uint32_t LoaderFlags; + // FIXME: This should be NumberOfRvaAndSizes to match the COFF spec. + uint32_t NumberOfRvaAndSize; +}; + +struct DataDirectory { + uint32_t RelativeVirtualAddress; + uint32_t Size; +}; + +enum DataDirectoryIndex : unsigned { + EXPORT_TABLE = 0, + IMPORT_TABLE, + RESOURCE_TABLE, + EXCEPTION_TABLE, + CERTIFICATE_TABLE, + BASE_RELOCATION_TABLE, + DEBUG_DIRECTORY, + ARCHITECTURE, + GLOBAL_PTR, + TLS_TABLE, + LOAD_CONFIG_TABLE, + BOUND_IMPORT, + IAT, + DELAY_IMPORT_DESCRIPTOR, + CLR_RUNTIME_HEADER, + + NUM_DATA_DIRECTORIES +}; + +enum WindowsSubsystem : unsigned { + IMAGE_SUBSYSTEM_UNKNOWN = 0, ///< An unknown subsystem. + IMAGE_SUBSYSTEM_NATIVE = 1, ///< Device drivers and native Windows processes + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2, ///< The Windows GUI subsystem. + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3, ///< The Windows character subsystem. + IMAGE_SUBSYSTEM_OS2_CUI = 5, ///< The OS/2 character subsystem. + IMAGE_SUBSYSTEM_POSIX_CUI = 7, ///< The POSIX character subsystem. + IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8, ///< Native Windows 9x driver. + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9, ///< Windows CE. + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10, ///< An EFI application. + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11, ///< An EFI driver with boot + /// services. + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12, ///< An EFI driver with run-time + /// services. + IMAGE_SUBSYSTEM_EFI_ROM = 13, ///< An EFI ROM image. + IMAGE_SUBSYSTEM_XBOX = 14, ///< XBOX. + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 ///< A BCD application. +}; + +enum DLLCharacteristics : unsigned { + /// ASLR with 64 bit address space. + IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020, + /// DLL can be relocated at load time. + IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040, + /// Code integrity checks are enforced. + IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY = 0x0080, + ///< Image is NX compatible. + IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100, + /// Isolation aware, but do not isolate the image. + IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION = 0x0200, + /// Does not use structured exception handling (SEH). No SEH handler may be + /// called in this image. + IMAGE_DLL_CHARACTERISTICS_NO_SEH = 0x0400, + /// Do not bind the image. + IMAGE_DLL_CHARACTERISTICS_NO_BIND = 0x0800, + ///< Image should execute in an AppContainer. + IMAGE_DLL_CHARACTERISTICS_APPCONTAINER = 0x1000, + ///< A WDM driver. + IMAGE_DLL_CHARACTERISTICS_WDM_DRIVER = 0x2000, + ///< Image supports Control Flow Guard. + IMAGE_DLL_CHARACTERISTICS_GUARD_CF = 0x4000, + /// Terminal Server aware. + IMAGE_DLL_CHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 +}; + +enum ExtendedDLLCharacteristics : unsigned { + /// Image is CET compatible + IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001 +}; + +enum DebugType : unsigned { + IMAGE_DEBUG_TYPE_UNKNOWN = 0, + IMAGE_DEBUG_TYPE_COFF = 1, + IMAGE_DEBUG_TYPE_CODEVIEW = 2, + IMAGE_DEBUG_TYPE_FPO = 3, + IMAGE_DEBUG_TYPE_MISC = 4, + IMAGE_DEBUG_TYPE_EXCEPTION = 5, + IMAGE_DEBUG_TYPE_FIXUP = 6, + IMAGE_DEBUG_TYPE_OMAP_TO_SRC = 7, + IMAGE_DEBUG_TYPE_OMAP_FROM_SRC = 8, + IMAGE_DEBUG_TYPE_BORLAND = 9, + IMAGE_DEBUG_TYPE_RESERVED10 = 10, + IMAGE_DEBUG_TYPE_CLSID = 11, + IMAGE_DEBUG_TYPE_VC_FEATURE = 12, + IMAGE_DEBUG_TYPE_POGO = 13, + IMAGE_DEBUG_TYPE_ILTCG = 14, + IMAGE_DEBUG_TYPE_MPX = 15, + IMAGE_DEBUG_TYPE_REPRO = 16, + IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS = 20, +}; + +enum BaseRelocationType : unsigned { + IMAGE_REL_BASED_ABSOLUTE = 0, + IMAGE_REL_BASED_HIGH = 1, + IMAGE_REL_BASED_LOW = 2, + IMAGE_REL_BASED_HIGHLOW = 3, + IMAGE_REL_BASED_HIGHADJ = 4, + IMAGE_REL_BASED_MIPS_JMPADDR = 5, + IMAGE_REL_BASED_ARM_MOV32A = 5, + IMAGE_REL_BASED_ARM_MOV32T = 7, + IMAGE_REL_BASED_MIPS_JMPADDR16 = 9, + IMAGE_REL_BASED_DIR64 = 10 +}; + +enum ImportType : unsigned { + IMPORT_CODE = 0, + IMPORT_DATA = 1, + IMPORT_CONST = 2 +}; + +enum ImportNameType : unsigned { + /// Import is by ordinal. This indicates that the value in the Ordinal/Hint + /// field of the import header is the import's ordinal. If this constant is + /// not specified, then the Ordinal/Hint field should always be interpreted + /// as the import's hint. + IMPORT_ORDINAL = 0, + /// The import name is identical to the public symbol name + IMPORT_NAME = 1, + /// The import name is the public symbol name, but skipping the leading ?, + /// @, or optionally _. + IMPORT_NAME_NOPREFIX = 2, + /// The import name is the public symbol name, but skipping the leading ?, + /// @, or optionally _, and truncating at the first @. + IMPORT_NAME_UNDECORATE = 3, + /// The import name is specified as a separate string in the import library + /// object file. + IMPORT_NAME_EXPORTAS = 4 +}; + +enum class GuardFlags : uint32_t { + /// Module performs control flow integrity checks using system-supplied + /// support. + CF_INSTRUMENTED = 0x100, + /// Module performs control flow and write integrity checks. + CFW_INSTRUMENTED = 0x200, + /// Module contains valid control flow target metadata. + CF_FUNCTION_TABLE_PRESENT = 0x400, + /// Module does not make use of the /GS security cookie. + SECURITY_COOKIE_UNUSED = 0x800, + /// Module supports read only delay load IAT. + PROTECT_DELAYLOAD_IAT = 0x1000, + /// Delayload import table in its own .didat section (with nothing else in it) + /// that can be freely reprotected. + DELAYLOAD_IAT_IN_ITS_OWN_SECTION = 0x2000, + /// Module contains suppressed export information. This also infers that the + /// address taken IAT table is also present in the load config. + CF_EXPORT_SUPPRESSION_INFO_PRESENT = 0x4000, + /// Module enables suppression of exports. + CF_ENABLE_EXPORT_SUPPRESSION = 0x8000, + /// Module contains longjmp target information. + CF_LONGJUMP_TABLE_PRESENT = 0x10000, + /// Module contains EH continuation target information. + EH_CONTINUATION_TABLE_PRESENT = 0x400000, + /// Mask for the subfield that contains the stride of Control Flow Guard + /// function table entries (that is, the additional count of bytes per table + /// entry). + CF_FUNCTION_TABLE_SIZE_MASK = 0xF0000000, + CF_FUNCTION_TABLE_SIZE_5BYTES = 0x10000000, + CF_FUNCTION_TABLE_SIZE_6BYTES = 0x20000000, + CF_FUNCTION_TABLE_SIZE_7BYTES = 0x30000000, + CF_FUNCTION_TABLE_SIZE_8BYTES = 0x40000000, + CF_FUNCTION_TABLE_SIZE_9BYTES = 0x50000000, + CF_FUNCTION_TABLE_SIZE_10BYTES = 0x60000000, + CF_FUNCTION_TABLE_SIZE_11BYTES = 0x70000000, + CF_FUNCTION_TABLE_SIZE_12BYTES = 0x80000000, + CF_FUNCTION_TABLE_SIZE_13BYTES = 0x90000000, + CF_FUNCTION_TABLE_SIZE_14BYTES = 0xA0000000, + CF_FUNCTION_TABLE_SIZE_15BYTES = 0xB0000000, + CF_FUNCTION_TABLE_SIZE_16BYTES = 0xC0000000, + CF_FUNCTION_TABLE_SIZE_17BYTES = 0xD0000000, + CF_FUNCTION_TABLE_SIZE_18BYTES = 0xE0000000, + CF_FUNCTION_TABLE_SIZE_19BYTES = 0xF0000000, +}; + +struct ImportHeader { + uint16_t Sig1; ///< Must be IMAGE_FILE_MACHINE_UNKNOWN (0). + uint16_t Sig2; ///< Must be 0xFFFF. + uint16_t Version; + uint16_t Machine; + uint32_t TimeDateStamp; + uint32_t SizeOfData; + uint16_t OrdinalHint; + uint16_t TypeInfo; + + ImportType getType() const { return static_cast(TypeInfo & 0x3); } + + ImportNameType getNameType() const { + return static_cast((TypeInfo & 0x1C) >> 2); + } +}; + +enum CodeViewIdentifiers { + DEBUG_SECTION_MAGIC = 0x4, + DEBUG_HASHES_SECTION_MAGIC = 0x133C9C5 +}; + +// These flags show up in the @feat.00 symbol. They appear to be some kind of +// compiler features bitfield read by link.exe. +enum Feat00Flags : uint32_t { + // Object is compatible with /safeseh. + SafeSEH = 0x1, + // Object was compiled with /GS. + GuardStack = 0x100, + // Object was compiled with /sdl. + SDL = 0x200, + // Object was compiled with /guard:cf. + GuardCF = 0x800, + // Object was compiled with /guard:ehcont. + GuardEHCont = 0x4000, + // Object was compiled with /kernel. + Kernel = 0x40000000, +}; + +inline bool isReservedSectionNumber(int32_t SectionNumber) { + return SectionNumber <= 0; +} + +/// Encode section name based on string table offset. +/// The size of Out must be at least COFF::NameSize. +bool encodeSectionName(char *Out, uint64_t Offset); + +} // End namespace COFF. +} // End namespace llvm. + +#endif \ No newline at end of file diff --git a/reference/COFFImportFile.cpp b/reference/COFFImportFile.cpp new file mode 100644 index 0000000..79bd327 --- /dev/null +++ b/reference/COFFImportFile.cpp @@ -0,0 +1,708 @@ +//===- COFFImportFile.cpp - COFF short import file implementation ---------===// +// +// 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 writeImportLibrary function. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/COFFImportFile.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Object/COFF.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Path.h" + +#include +#include +#include + +using namespace llvm::COFF; +using namespace llvm::object; +using namespace llvm; + +namespace llvm { +namespace object { + +StringRef COFFImportFile::getFileFormatName() const { + switch (getMachine()) { + case COFF::IMAGE_FILE_MACHINE_I386: + return "COFF-import-file-i386"; + case COFF::IMAGE_FILE_MACHINE_AMD64: + return "COFF-import-file-x86-64"; + case COFF::IMAGE_FILE_MACHINE_ARMNT: + return "COFF-import-file-ARM"; + case COFF::IMAGE_FILE_MACHINE_ARM64: + return "COFF-import-file-ARM64"; + case COFF::IMAGE_FILE_MACHINE_ARM64EC: + return "COFF-import-file-ARM64EC"; + case COFF::IMAGE_FILE_MACHINE_ARM64X: + return "COFF-import-file-ARM64X"; + default: + return "COFF-import-file-"; + } +} + +StringRef COFFImportFile::getExportName() const { + const coff_import_header *hdr = getCOFFImportHeader(); + StringRef name = Data.getBuffer().substr(sizeof(*hdr)).split('\0').first; + + auto ltrim1 = [](StringRef s, StringRef chars) { + return !s.empty() && chars.contains(s[0]) ? s.substr(1) : s; + }; + + switch (hdr->getNameType()) { + case IMPORT_ORDINAL: + name = ""; + break; + case IMPORT_NAME_NOPREFIX: + name = ltrim1(name, "?@_"); + break; + case IMPORT_NAME_UNDECORATE: + name = ltrim1(name, "?@_"); + name = name.substr(0, name.find('@')); + break; + case IMPORT_NAME_EXPORTAS: { + // Skip DLL name + name = Data.getBuffer().substr(sizeof(*hdr) + name.size() + 1); + name = name.split('\0').second.split('\0').first; + break; + } + default: + break; + } + + return name; +} + +static uint16_t getImgRelRelocation(MachineTypes Machine) { + switch (Machine) { + default: + llvm_unreachable("unsupported machine"); + case IMAGE_FILE_MACHINE_AMD64: + return IMAGE_REL_AMD64_ADDR32NB; + case IMAGE_FILE_MACHINE_ARMNT: + return IMAGE_REL_ARM_ADDR32NB; + case IMAGE_FILE_MACHINE_ARM64: + case IMAGE_FILE_MACHINE_ARM64EC: + case IMAGE_FILE_MACHINE_ARM64X: + return IMAGE_REL_ARM64_ADDR32NB; + case IMAGE_FILE_MACHINE_I386: + return IMAGE_REL_I386_DIR32NB; + } +} + +template static void append(std::vector &B, const T &Data) { + size_t S = B.size(); + B.resize(S + sizeof(T)); + memcpy(&B[S], &Data, sizeof(T)); +} + +static void writeStringTable(std::vector &B, + ArrayRef Strings) { + // The COFF string table consists of a 4-byte value which is the size of the + // table, including the length field itself. This value is followed by the + // string content itself, which is an array of null-terminated C-style + // strings. The termination is important as they are referenced to by offset + // by the symbol entity in the file format. + + size_t Pos = B.size(); + size_t Offset = B.size(); + + // Skip over the length field, we will fill it in later as we will have + // computed the length while emitting the string content itself. + Pos += sizeof(uint32_t); + + for (const auto &S : Strings) { + B.resize(Pos + S.length() + 1); + std::copy(S.begin(), S.end(), std::next(B.begin(), Pos)); + B[Pos + S.length()] = 0; + Pos += S.length() + 1; + } + + // Backfill the length of the table now that it has been computed. + support::ulittle32_t Length(B.size() - Offset); + support::endian::write32le(&B[Offset], Length); +} + +static ImportNameType getNameType(StringRef Sym, StringRef ExtName, + MachineTypes Machine, bool MinGW) { + // A decorated stdcall function in MSVC is exported with the + // type IMPORT_NAME, and the exported function name includes the + // the leading underscore. In MinGW on the other hand, a decorated + // stdcall function still omits the underscore (IMPORT_NAME_NOPREFIX). + // See the comment in isDecorated in COFFModuleDefinition.cpp for more + // details. + if (ExtName.starts_with("_") && ExtName.contains('@') && !MinGW) + return IMPORT_NAME; + if (Sym != ExtName) + return IMPORT_NAME_UNDECORATE; + if (Machine == IMAGE_FILE_MACHINE_I386 && Sym.starts_with("_")) + return IMPORT_NAME_NOPREFIX; + return IMPORT_NAME; +} + +static Expected replace(StringRef S, StringRef From, + StringRef To) { + size_t Pos = S.find(From); + + // From and To may be mangled, but substrings in S may not. + if (Pos == StringRef::npos && From.starts_with("_") && To.starts_with("_")) { + From = From.substr(1); + To = To.substr(1); + Pos = S.find(From); + } + + if (Pos == StringRef::npos) { + return make_error( + StringRef(Twine(S + ": replacing '" + From + + "' with '" + To + "' failed").str()), object_error::parse_failed); + } + + return (Twine(S.substr(0, Pos)) + To + S.substr(Pos + From.size())).str(); +} + +namespace { +// This class constructs various small object files necessary to support linking +// symbols imported from a DLL. The contents are pretty strictly defined and +// nearly entirely static. The details of the structures files are defined in +// WINNT.h and the PE/COFF specification. +class ObjectFactory { + using u16 = support::ulittle16_t; + using u32 = support::ulittle32_t; + MachineTypes NativeMachine; + BumpPtrAllocator Alloc; + StringRef ImportName; + StringRef Library; + std::string ImportDescriptorSymbolName; + std::string NullThunkSymbolName; + +public: + ObjectFactory(StringRef S, MachineTypes M) + : NativeMachine(M), ImportName(S), Library(llvm::sys::path::stem(S)), + ImportDescriptorSymbolName((ImportDescriptorPrefix + Library).str()), + NullThunkSymbolName( + (NullThunkDataPrefix + Library + NullThunkDataSuffix).str()) {} + + // Creates an Import Descriptor. This is a small object file which contains a + // reference to the terminators and contains the library name (entry) for the + // import name table. It will force the linker to construct the necessary + // structure to import symbols from the DLL. + NewArchiveMember createImportDescriptor(std::vector &Buffer); + + // Creates a NULL import descriptor. This is a small object file whcih + // contains a NULL import descriptor. It is used to terminate the imports + // from a specific DLL. + NewArchiveMember createNullImportDescriptor(std::vector &Buffer); + + // Create a NULL Thunk Entry. This is a small object file which contains a + // NULL Import Address Table entry and a NULL Import Lookup Table Entry. It + // is used to terminate the IAT and ILT. + NewArchiveMember createNullThunk(std::vector &Buffer); + + // Create a short import file which is described in PE/COFF spec 7. Import + // Library Format. + NewArchiveMember createShortImport(StringRef Sym, uint16_t Ordinal, + ImportType Type, ImportNameType NameType, + StringRef ExportName, + MachineTypes Machine); + + // Create a weak external file which is described in PE/COFF Aux Format 3. + NewArchiveMember createWeakExternal(StringRef Sym, StringRef Weak, bool Imp, + MachineTypes Machine); + + bool is64Bit() const { return COFF::is64Bit(NativeMachine); } +}; +} // namespace + +NewArchiveMember +ObjectFactory::createImportDescriptor(std::vector &Buffer) { + const uint32_t NumberOfSections = 2; + const uint32_t NumberOfSymbols = 7; + const uint32_t NumberOfRelocations = 3; + + // COFF Header + coff_file_header Header{ + u16(NativeMachine), + u16(NumberOfSections), + u32(0), + u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section)) + + // .idata$2 + sizeof(coff_import_directory_table_entry) + + NumberOfRelocations * sizeof(coff_relocation) + + // .idata$4 + (ImportName.size() + 1)), + u32(NumberOfSymbols), + u16(0), + u16(is64Bit() ? C_Invalid : IMAGE_FILE_32BIT_MACHINE), + }; + append(Buffer, Header); + + // Section Header Table + const coff_section SectionTable[NumberOfSections] = { + {{'.', 'i', 'd', 'a', 't', 'a', '$', '2'}, + u32(0), + u32(0), + u32(sizeof(coff_import_directory_table_entry)), + u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section)), + u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section) + + sizeof(coff_import_directory_table_entry)), + u32(0), + u16(NumberOfRelocations), + u16(0), + u32(IMAGE_SCN_ALIGN_4BYTES | IMAGE_SCN_CNT_INITIALIZED_DATA | + IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)}, + {{'.', 'i', 'd', 'a', 't', 'a', '$', '6'}, + u32(0), + u32(0), + u32(ImportName.size() + 1), + u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section) + + sizeof(coff_import_directory_table_entry) + + NumberOfRelocations * sizeof(coff_relocation)), + u32(0), + u32(0), + u16(0), + u16(0), + u32(IMAGE_SCN_ALIGN_2BYTES | IMAGE_SCN_CNT_INITIALIZED_DATA | + IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)}, + }; + append(Buffer, SectionTable); + + // .idata$2 + const coff_import_directory_table_entry ImportDescriptor{ + u32(0), u32(0), u32(0), u32(0), u32(0), + }; + append(Buffer, ImportDescriptor); + + const coff_relocation RelocationTable[NumberOfRelocations] = { + {u32(offsetof(coff_import_directory_table_entry, NameRVA)), u32(2), + u16(getImgRelRelocation(NativeMachine))}, + {u32(offsetof(coff_import_directory_table_entry, ImportLookupTableRVA)), + u32(3), u16(getImgRelRelocation(NativeMachine))}, + {u32(offsetof(coff_import_directory_table_entry, ImportAddressTableRVA)), + u32(4), u16(getImgRelRelocation(NativeMachine))}, + }; + append(Buffer, RelocationTable); + + // .idata$6 + auto S = Buffer.size(); + Buffer.resize(S + ImportName.size() + 1); + memcpy(&Buffer[S], ImportName.data(), ImportName.size()); + Buffer[S + ImportName.size()] = '\0'; + + // Symbol Table + coff_symbol16 SymbolTable[NumberOfSymbols] = { + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(1), + u16(0), + IMAGE_SYM_CLASS_EXTERNAL, + 0}, + {{{'.', 'i', 'd', 'a', 't', 'a', '$', '2'}}, + u32(0), + u16(1), + u16(0), + IMAGE_SYM_CLASS_SECTION, + 0}, + {{{'.', 'i', 'd', 'a', 't', 'a', '$', '6'}}, + u32(0), + u16(2), + u16(0), + IMAGE_SYM_CLASS_STATIC, + 0}, + {{{'.', 'i', 'd', 'a', 't', 'a', '$', '4'}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_SECTION, + 0}, + {{{'.', 'i', 'd', 'a', 't', 'a', '$', '5'}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_SECTION, + 0}, + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_EXTERNAL, + 0}, + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_EXTERNAL, + 0}, + }; + // TODO: Name.Offset.Offset here and in the all similar places below + // suggests a names refactoring. Maybe StringTableOffset.Value? + SymbolTable[0].Name.Offset.Offset = + sizeof(uint32_t); + SymbolTable[5].Name.Offset.Offset = + sizeof(uint32_t) + ImportDescriptorSymbolName.length() + 1; + SymbolTable[6].Name.Offset.Offset = + sizeof(uint32_t) + ImportDescriptorSymbolName.length() + 1 + + NullImportDescriptorSymbolName.length() + 1; + append(Buffer, SymbolTable); + + // String Table + writeStringTable(Buffer, + {ImportDescriptorSymbolName, NullImportDescriptorSymbolName, + NullThunkSymbolName}); + + StringRef F{reinterpret_cast(Buffer.data()), Buffer.size()}; + return {MemoryBufferRef(F, ImportName)}; +} + +NewArchiveMember +ObjectFactory::createNullImportDescriptor(std::vector &Buffer) { + const uint32_t NumberOfSections = 1; + const uint32_t NumberOfSymbols = 1; + + // COFF Header + coff_file_header Header{ + u16(NativeMachine), + u16(NumberOfSections), + u32(0), + u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section)) + + // .idata$3 + sizeof(coff_import_directory_table_entry)), + u32(NumberOfSymbols), + u16(0), + u16(is64Bit() ? C_Invalid : IMAGE_FILE_32BIT_MACHINE), + }; + append(Buffer, Header); + + // Section Header Table + const coff_section SectionTable[NumberOfSections] = { + {{'.', 'i', 'd', 'a', 't', 'a', '$', '3'}, + u32(0), + u32(0), + u32(sizeof(coff_import_directory_table_entry)), + u32(sizeof(coff_file_header) + + (NumberOfSections * sizeof(coff_section))), + u32(0), + u32(0), + u16(0), + u16(0), + u32(IMAGE_SCN_ALIGN_4BYTES | IMAGE_SCN_CNT_INITIALIZED_DATA | + IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)}, + }; + append(Buffer, SectionTable); + + // .idata$3 + const coff_import_directory_table_entry ImportDescriptor{ + u32(0), u32(0), u32(0), u32(0), u32(0), + }; + append(Buffer, ImportDescriptor); + + // Symbol Table + coff_symbol16 SymbolTable[NumberOfSymbols] = { + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(1), + u16(0), + IMAGE_SYM_CLASS_EXTERNAL, + 0}, + }; + SymbolTable[0].Name.Offset.Offset = sizeof(uint32_t); + append(Buffer, SymbolTable); + + // String Table + writeStringTable(Buffer, {NullImportDescriptorSymbolName}); + + StringRef F{reinterpret_cast(Buffer.data()), Buffer.size()}; + return {MemoryBufferRef(F, ImportName)}; +} + +NewArchiveMember ObjectFactory::createNullThunk(std::vector &Buffer) { + const uint32_t NumberOfSections = 2; + const uint32_t NumberOfSymbols = 1; + uint32_t VASize = is64Bit() ? 8 : 4; + + // COFF Header + coff_file_header Header{ + u16(NativeMachine), + u16(NumberOfSections), + u32(0), + u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section)) + + // .idata$5 + VASize + + // .idata$4 + VASize), + u32(NumberOfSymbols), + u16(0), + u16(is64Bit() ? C_Invalid : IMAGE_FILE_32BIT_MACHINE), + }; + append(Buffer, Header); + + // Section Header Table + const coff_section SectionTable[NumberOfSections] = { + {{'.', 'i', 'd', 'a', 't', 'a', '$', '5'}, + u32(0), + u32(0), + u32(VASize), + u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section)), + u32(0), + u32(0), + u16(0), + u16(0), + u32((is64Bit() ? IMAGE_SCN_ALIGN_8BYTES : IMAGE_SCN_ALIGN_4BYTES) | + IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | + IMAGE_SCN_MEM_WRITE)}, + {{'.', 'i', 'd', 'a', 't', 'a', '$', '4'}, + u32(0), + u32(0), + u32(VASize), + u32(sizeof(coff_file_header) + NumberOfSections * sizeof(coff_section) + + VASize), + u32(0), + u32(0), + u16(0), + u16(0), + u32((is64Bit() ? IMAGE_SCN_ALIGN_8BYTES : IMAGE_SCN_ALIGN_4BYTES) | + IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | + IMAGE_SCN_MEM_WRITE)}, + }; + append(Buffer, SectionTable); + + // .idata$5, ILT + append(Buffer, u32(0)); + if (is64Bit()) + append(Buffer, u32(0)); + + // .idata$4, IAT + append(Buffer, u32(0)); + if (is64Bit()) + append(Buffer, u32(0)); + + // Symbol Table + coff_symbol16 SymbolTable[NumberOfSymbols] = { + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(1), + u16(0), + IMAGE_SYM_CLASS_EXTERNAL, + 0}, + }; + SymbolTable[0].Name.Offset.Offset = sizeof(uint32_t); + append(Buffer, SymbolTable); + + // String Table + writeStringTable(Buffer, {NullThunkSymbolName}); + + StringRef F{reinterpret_cast(Buffer.data()), Buffer.size()}; + return {MemoryBufferRef{F, ImportName}}; +} + +NewArchiveMember +ObjectFactory::createShortImport(StringRef Sym, uint16_t Ordinal, + ImportType ImportType, ImportNameType NameType, + StringRef ExportName, MachineTypes Machine) { + size_t ImpSize = ImportName.size() + Sym.size() + 2; // +2 for NULs + if (!ExportName.empty()) + ImpSize += ExportName.size() + 1; + size_t Size = sizeof(coff_import_header) + ImpSize; + char *Buf = Alloc.Allocate(Size); + memset(Buf, 0, Size); + char *P = Buf; + + // Write short import library. + auto *Imp = reinterpret_cast(P); + P += sizeof(*Imp); + Imp->Sig2 = 0xFFFF; + Imp->Machine = Machine; + Imp->SizeOfData = ImpSize; + if (Ordinal > 0) + Imp->OrdinalHint = Ordinal; + Imp->TypeInfo = (NameType << 2) | ImportType; + + // Write symbol name and DLL name. + memcpy(P, Sym.data(), Sym.size()); + P += Sym.size() + 1; + memcpy(P, ImportName.data(), ImportName.size()); + if (!ExportName.empty()) { + P += ImportName.size() + 1; + memcpy(P, ExportName.data(), ExportName.size()); + } + + return {MemoryBufferRef(StringRef(Buf, Size), ImportName)}; +} + +NewArchiveMember ObjectFactory::createWeakExternal(StringRef Sym, + StringRef Weak, bool Imp, + MachineTypes Machine) { + std::vector Buffer; + const uint32_t NumberOfSections = 1; + const uint32_t NumberOfSymbols = 5; + + // COFF Header + coff_file_header Header{ + u16(Machine), + u16(NumberOfSections), + u32(0), + u32(sizeof(Header) + (NumberOfSections * sizeof(coff_section))), + u32(NumberOfSymbols), + u16(0), + u16(0), + }; + append(Buffer, Header); + + // Section Header Table + const coff_section SectionTable[NumberOfSections] = { + {{'.', 'd', 'r', 'e', 'c', 't', 'v', 'e'}, + u32(0), + u32(0), + u32(0), + u32(0), + u32(0), + u32(0), + u16(0), + u16(0), + u32(IMAGE_SCN_LNK_INFO | IMAGE_SCN_LNK_REMOVE)}}; + append(Buffer, SectionTable); + + // Symbol Table + coff_symbol16 SymbolTable[NumberOfSymbols] = { + {{{'@', 'c', 'o', 'm', 'p', '.', 'i', 'd'}}, + u32(0), + u16(0xFFFF), + u16(0), + IMAGE_SYM_CLASS_STATIC, + 0}, + {{{'@', 'f', 'e', 'a', 't', '.', '0', '0'}}, + u32(0), + u16(0xFFFF), + u16(0), + IMAGE_SYM_CLASS_STATIC, + 0}, + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_EXTERNAL, + 0}, + {{{0, 0, 0, 0, 0, 0, 0, 0}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_WEAK_EXTERNAL, + 1}, + {{{2, 0, 0, 0, IMAGE_WEAK_EXTERN_SEARCH_ALIAS, 0, 0, 0}}, + u32(0), + u16(0), + u16(0), + IMAGE_SYM_CLASS_NULL, + 0}, + }; + SymbolTable[2].Name.Offset.Offset = sizeof(uint32_t); + + //__imp_ String Table + StringRef Prefix = Imp ? "__imp_" : ""; + SymbolTable[3].Name.Offset.Offset = + sizeof(uint32_t) + Sym.size() + Prefix.size() + 1; + append(Buffer, SymbolTable); + writeStringTable(Buffer, {(Prefix + Sym).str(), + (Prefix + Weak).str()}); + + // Copied here so we can still use writeStringTable + char *Buf = Alloc.Allocate(Buffer.size()); + memcpy(Buf, Buffer.data(), Buffer.size()); + return {MemoryBufferRef(StringRef(Buf, Buffer.size()), ImportName)}; +} + +Error writeImportLibrary(StringRef ImportName, StringRef Path, + ArrayRef Exports, + MachineTypes Machine, bool MinGW) { + + MachineTypes NativeMachine = + isArm64EC(Machine) ? IMAGE_FILE_MACHINE_ARM64 : Machine; + + std::vector Members; + ObjectFactory OF(llvm::sys::path::filename(ImportName), NativeMachine); + + std::vector ImportDescriptor; + Members.push_back(OF.createImportDescriptor(ImportDescriptor)); + + std::vector NullImportDescriptor; + Members.push_back(OF.createNullImportDescriptor(NullImportDescriptor)); + + std::vector NullThunk; + Members.push_back(OF.createNullThunk(NullThunk)); + + for (const COFFShortExport &E : Exports) { + if (E.Private) + continue; + + ImportType ImportType = IMPORT_CODE; + if (E.Data) + ImportType = IMPORT_DATA; + if (E.Constant) + ImportType = IMPORT_CONST; + + StringRef SymbolName = E.SymbolName.empty() ? E.Name : E.SymbolName; + std::string Name; + + if (E.ExtName.empty()) { + Name = std::string(SymbolName); + } else { + Expected ReplacedName = + replace(SymbolName, E.Name, E.ExtName); + if (!ReplacedName) + return ReplacedName.takeError(); + Name.swap(*ReplacedName); + } + + if (!E.AliasTarget.empty() && Name != E.AliasTarget) { + Members.push_back( + OF.createWeakExternal(E.AliasTarget, Name, false, Machine)); + Members.push_back( + OF.createWeakExternal(E.AliasTarget, Name, true, Machine)); + continue; + } + + ImportNameType NameType; + std::string ExportName; + if (E.Noname) { + NameType = IMPORT_ORDINAL; + } else { + NameType = getNameType(SymbolName, E.Name, Machine, MinGW); + } + + // On ARM64EC, use EXPORTAS to import demangled name for mangled symbols. + if (ImportType == IMPORT_CODE && isArm64EC(Machine)) { + if (std::optional MangledName = + getArm64ECMangledFunctionName(Name)) { + if (ExportName.empty()) { + NameType = IMPORT_NAME_EXPORTAS; + ExportName.swap(Name); + } + Name = std::move(*MangledName); + } else if (ExportName.empty()) { + NameType = IMPORT_NAME_EXPORTAS; + ExportName = std::move(*getArm64ECDemangledFunctionName(Name)); + } + } + + Members.push_back(OF.createShortImport(Name, E.Ordinal, ImportType, + NameType, ExportName, Machine)); + } + + return writeArchive(Path, Members, SymtabWritingMode::NormalSymtab, + MinGW ? object::Archive::K_GNU : object::Archive::K_COFF, + /*Deterministic*/ true, /*Thin*/ false, + /*OldArchiveBuf*/ nullptr, isArm64EC(Machine)); +} + +} // namespace object +} // namespace llvm \ No newline at end of file diff --git a/reference/COFFImportFile.h b/reference/COFFImportFile.h index ab168d0..969964c 100644 --- a/reference/COFFImportFile.h +++ b/reference/COFFImportFile.h @@ -17,7 +17,6 @@ #define LLVM_OBJECT_COFFIMPORTFILE_H #include "llvm/ADT/ArrayRef.h" -#include "llvm/IR/Mangler.h" #include "llvm/Object/COFF.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Object/SymbolicFile.h" @@ -45,7 +44,26 @@ class COFFImportFile : public SymbolicFile { void moveSymbolNext(DataRefImpl &Symb) const override { ++Symb.p; } - Error printSymbolName(raw_ostream &OS, DataRefImpl Symb) const override; + Error printSymbolName(raw_ostream &OS, DataRefImpl Symb) const override { + switch (Symb.p) { + case ImpSymbol: + OS << "__imp_"; + break; + case ECAuxSymbol: + OS << "__imp_aux_"; + break; + } + const char *Name = Data.getBufferStart() + sizeof(coff_import_header); + if (Symb.p != ECThunkSymbol && COFF::isArm64EC(getMachine())) { + if (std::optional DemangledName = + getArm64ECDemangledFunctionName(Name)) { + OS << StringRef(*DemangledName); + return Error::success(); + } + } + OS << StringRef(Name); + return Error::success(); + } Expected getSymbolFlags(DataRefImpl Symb) const override { return SymbolRef::SF_Global; @@ -102,10 +120,6 @@ struct COFFShortExport { /// file, this is "baz" in "EXPORTS\nfoo = bar == baz". std::string AliasTarget; - /// Specifies EXPORTAS name. In a .def file, this is "bar" in - /// "EXPORTS\nfoo EXPORTAS bar". - std::string ExportAs; - uint16_t Ordinal = 0; bool Noname = false; bool Data = false; @@ -123,20 +137,9 @@ struct COFFShortExport { } }; -/// Writes a COFF import library containing entries described by the Exports -/// array. -/// -/// For hybrid targets such as ARM64EC, additional native entry points can be -/// exposed using the NativeExports parameter. When NativeExports is used, the -/// output import library will expose these native ARM64 imports alongside the -/// entries described in the Exports array. Such a library can be used for -/// linking both ARM64EC and pure ARM64 objects, and the linker will pick only -/// the exports relevant to the target platform. For non-hybrid targets, -/// the NativeExports parameter should not be used. -Error writeImportLibrary( - StringRef ImportName, StringRef Path, ArrayRef Exports, - COFF::MachineTypes Machine, bool MinGW, - ArrayRef NativeExports = std::nullopt); +Error writeImportLibrary(StringRef ImportName, StringRef Path, + ArrayRef Exports, + COFF::MachineTypes Machine, bool MinGW); } // namespace object } // namespace llvm diff --git a/reference/Mangler.cpp b/reference/Mangler.cpp new file mode 100644 index 0000000..72e2bc1 --- /dev/null +++ b/reference/Mangler.cpp @@ -0,0 +1,331 @@ +//===-- Mangler.cpp - Self-contained c/asm llvm name mangler --------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Unified name mangler for assembly backends. +// +//===----------------------------------------------------------------------===// + +#include "llvm/IR/Mangler.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/Twine.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Triple.h" + +using namespace llvm; + +namespace { +enum ManglerPrefixTy { + Default, ///< Emit default string before each symbol. + Private, ///< Emit "private" prefix before each symbol. + LinkerPrivate ///< Emit "linker private" prefix before each symbol. +}; +} + +static void getNameWithPrefixImpl(raw_ostream &OS, const Twine &GVName, + ManglerPrefixTy PrefixTy, + const DataLayout &DL, char Prefix) { + SmallString<256> TmpData; + StringRef Name = GVName.toStringRef(TmpData); + assert(!Name.empty() && "getNameWithPrefix requires non-empty name"); + + // No need to do anything special if the global has the special "do not + // mangle" flag in the name. + if (Name[0] == '\1') { + OS << Name.substr(1); + return; + } + + if (DL.doNotMangleLeadingQuestionMark() && Name[0] == '?') + Prefix = '\0'; + + if (PrefixTy == Private) + OS << DL.getPrivateGlobalPrefix(); + else if (PrefixTy == LinkerPrivate) + OS << DL.getLinkerPrivateGlobalPrefix(); + + if (Prefix != '\0') + OS << Prefix; + + // If this is a simple string that doesn't need escaping, just append it. + OS << Name; +} + +static void getNameWithPrefixImpl(raw_ostream &OS, const Twine &GVName, + const DataLayout &DL, + ManglerPrefixTy PrefixTy) { + char Prefix = DL.getGlobalPrefix(); + return getNameWithPrefixImpl(OS, GVName, PrefixTy, DL, Prefix); +} + +void Mangler::getNameWithPrefix(raw_ostream &OS, const Twine &GVName, + const DataLayout &DL) { + return getNameWithPrefixImpl(OS, GVName, DL, Default); +} + +void Mangler::getNameWithPrefix(SmallVectorImpl &OutName, + const Twine &GVName, const DataLayout &DL) { + raw_svector_ostream OS(OutName); + char Prefix = DL.getGlobalPrefix(); + return getNameWithPrefixImpl(OS, GVName, Default, DL, Prefix); +} + +static bool hasByteCountSuffix(CallingConv::ID CC) { + switch (CC) { + case CallingConv::X86_FastCall: + case CallingConv::X86_StdCall: + case CallingConv::X86_VectorCall: + return true; + default: + return false; + } +} + +/// Microsoft fastcall and stdcall functions require a suffix on their name +/// indicating the number of words of arguments they take. +static void addByteCountSuffix(raw_ostream &OS, const Function *F, + const DataLayout &DL) { + // Calculate arguments size total. + unsigned ArgWords = 0; + + const unsigned PtrSize = DL.getPointerSize(); + + for (const Argument &A : F->args()) { + // For the purposes of the byte count suffix, structs returned by pointer + // do not count as function arguments. + if (A.hasStructRetAttr()) + continue; + + // 'Dereference' type in case of byval or inalloca parameter attribute. + uint64_t AllocSize = A.hasPassPointeeByValueCopyAttr() ? + A.getPassPointeeByValueCopySize(DL) : + DL.getTypeAllocSize(A.getType()); + + // Size should be aligned to pointer size. + ArgWords += alignTo(AllocSize, PtrSize); + } + + OS << '@' << ArgWords; +} + +void Mangler::getNameWithPrefix(raw_ostream &OS, const GlobalValue *GV, + bool CannotUsePrivateLabel) const { + ManglerPrefixTy PrefixTy = Default; + assert(GV != nullptr && "Invalid Global Value"); + if (GV->hasPrivateLinkage()) { + if (CannotUsePrivateLabel) + PrefixTy = LinkerPrivate; + else + PrefixTy = Private; + } + + const DataLayout &DL = GV->getParent()->getDataLayout(); + if (!GV->hasName()) { + // Get the ID for the global, assigning a new one if we haven't got one + // already. + unsigned &ID = AnonGlobalIDs[GV]; + if (ID == 0) + ID = AnonGlobalIDs.size(); + + // Must mangle the global into a unique ID. + getNameWithPrefixImpl(OS, "__unnamed_" + Twine(ID), DL, PrefixTy); + return; + } + + StringRef Name = GV->getName(); + char Prefix = DL.getGlobalPrefix(); + + // Mangle functions with Microsoft calling conventions specially. Only do + // this mangling for x86_64 vectorcall and 32-bit x86. + const Function *MSFunc = dyn_cast_or_null(GV->getAliaseeObject()); + + // Don't add byte count suffixes when '\01' or '?' are in the first + // character. + if (Name.starts_with("\01") || + (DL.doNotMangleLeadingQuestionMark() && Name.starts_with("?"))) + MSFunc = nullptr; + + CallingConv::ID CC = + MSFunc ? MSFunc->getCallingConv() : (unsigned)CallingConv::C; + if (!DL.hasMicrosoftFastStdCallMangling() && + CC != CallingConv::X86_VectorCall) + MSFunc = nullptr; + if (MSFunc) { + if (CC == CallingConv::X86_FastCall) + Prefix = '@'; // fastcall functions have an @ prefix instead of _. + else if (CC == CallingConv::X86_VectorCall) + Prefix = '\0'; // vectorcall functions have no prefix. + } + + getNameWithPrefixImpl(OS, Name, PrefixTy, DL, Prefix); + + if (!MSFunc) + return; + + // If we are supposed to add a microsoft-style suffix for stdcall, fastcall, + // or vectorcall, add it. These functions have a suffix of @N where N is the + // cumulative byte size of all of the parameters to the function in decimal. + if (CC == CallingConv::X86_VectorCall) + OS << '@'; // vectorcall functions use a double @ suffix. + FunctionType *FT = MSFunc->getFunctionType(); + if (hasByteCountSuffix(CC) && + // "Pure" variadic functions do not receive @0 suffix. + (!FT->isVarArg() || FT->getNumParams() == 0 || + (FT->getNumParams() == 1 && MSFunc->hasStructRetAttr()))) + addByteCountSuffix(OS, MSFunc, DL); +} + +void Mangler::getNameWithPrefix(SmallVectorImpl &OutName, + const GlobalValue *GV, + bool CannotUsePrivateLabel) const { + raw_svector_ostream OS(OutName); + getNameWithPrefix(OS, GV, CannotUsePrivateLabel); +} + +// Check if the name needs quotes to be safe for the linker to interpret. +static bool canBeUnquotedInDirective(char C) { + return isAlnum(C) || C == '_' || C == '@' || C == '#'; +} + +static bool canBeUnquotedInDirective(StringRef Name) { + if (Name.empty()) + return false; + + // If any of the characters in the string is an unacceptable character, force + // quotes. + for (char C : Name) { + if (!canBeUnquotedInDirective(C)) + return false; + } + + return true; +} + +void llvm::emitLinkerFlagsForGlobalCOFF(raw_ostream &OS, const GlobalValue *GV, + const Triple &TT, Mangler &Mangler) { + if (GV->hasDLLExportStorageClass() && !GV->isDeclaration()) { + + if (TT.isWindowsMSVCEnvironment()) + OS << " /EXPORT:"; + else + OS << " -export:"; + + bool NeedQuotes = GV->hasName() && !canBeUnquotedInDirective(GV->getName()); + if (NeedQuotes) + OS << "\""; + if (TT.isWindowsGNUEnvironment() || TT.isWindowsCygwinEnvironment()) { + std::string Flag; + raw_string_ostream FlagOS(Flag); + Mangler.getNameWithPrefix(FlagOS, GV, false); + FlagOS.flush(); + if (Flag[0] == GV->getParent()->getDataLayout().getGlobalPrefix()) + OS << Flag.substr(1); + else + OS << Flag; + } else { + Mangler.getNameWithPrefix(OS, GV, false); + } + if (TT.isWindowsArm64EC()) { + // Use EXPORTAS for mangled ARM64EC symbols. + // FIXME: During LTO, we're invoked prior to the EC lowering pass, + // so symbols are not yet mangled. Emitting the unmangled name + // typically functions correctly; the linker can resolve the export + // with the demangled alias. + if (std::optional demangledName = + getArm64ECDemangledFunctionName(GV->getName())) + OS << ",EXPORTAS," << *demangledName; + } + if (NeedQuotes) + OS << "\""; + + if (!GV->getValueType()->isFunctionTy()) { + if (TT.isWindowsMSVCEnvironment()) + OS << ",DATA"; + else + OS << ",data"; + } + } + if (GV->hasHiddenVisibility() && !GV->isDeclaration() && TT.isOSCygMing()) { + + OS << " -exclude-symbols:"; + + bool NeedQuotes = GV->hasName() && !canBeUnquotedInDirective(GV->getName()); + if (NeedQuotes) + OS << "\""; + + std::string Flag; + raw_string_ostream FlagOS(Flag); + Mangler.getNameWithPrefix(FlagOS, GV, false); + FlagOS.flush(); + if (Flag[0] == GV->getParent()->getDataLayout().getGlobalPrefix()) + OS << Flag.substr(1); + else + OS << Flag; + + if (NeedQuotes) + OS << "\""; + } +} + +void llvm::emitLinkerFlagsForUsedCOFF(raw_ostream &OS, const GlobalValue *GV, + const Triple &T, Mangler &M) { + if (!T.isWindowsMSVCEnvironment()) + return; + + OS << " /INCLUDE:"; + bool NeedQuotes = GV->hasName() && !canBeUnquotedInDirective(GV->getName()); + if (NeedQuotes) + OS << "\""; + M.getNameWithPrefix(OS, GV, false); + if (NeedQuotes) + OS << "\""; +} + +std::optional llvm::getArm64ECMangledFunctionName(StringRef Name) { + bool IsCppFn = Name[0] == '?'; + if (IsCppFn && Name.find("$$h") != std::string::npos) + return std::nullopt; + if (!IsCppFn && Name[0] == '#') + return std::nullopt; + + StringRef Prefix = "$$h"; + size_t InsertIdx = 0; + if (IsCppFn) { + InsertIdx = Name.find("@@"); + size_t ThreeAtSignsIdx = Name.find("@@@"); + if (InsertIdx != std::string::npos && InsertIdx != ThreeAtSignsIdx) { + InsertIdx += 2; + } else { + InsertIdx = Name.find("@"); + if (InsertIdx != std::string::npos) + InsertIdx++; + } + } else { + Prefix = "#"; + } + + return std::optional( + (Name.substr(0, InsertIdx) + Prefix + Name.substr(InsertIdx)).str()); +} + +std::optional +llvm::getArm64ECDemangledFunctionName(StringRef Name) { + if (Name[0] == '#') + return std::optional(Name.substr(1)); + if (Name[0] != '?') + return std::nullopt; + + std::pair Pair = Name.split("$$h"); + if (Pair.second.empty()) + return std::nullopt; + return std::optional((Pair.first + Pair.second).str()); +} diff --git a/reference/Readme.md b/reference/Readme.md index 0c92118..8b797d0 100644 --- a/reference/Readme.md +++ b/reference/Readme.md @@ -6,12 +6,15 @@ last time that this project was "synced" with LLVM. Currently that sync point is 18.1.3, commit [ef6d1ec](https://github.com/llvm/llvm-project/tree/ef6d1ec07c693352c4a60dd58db08d2d8558f6ea). These files were originally located at: +* `llvm/include/llvm/BinaryFormat/COFF.h` * `llvm/include/llvm/Object/Archive.h` * `llvm/include/llvm/Object/ArchiveWriter.h` * `llvm/include/llvm/Object/COFFImportFile.h` * `llvm/include/llvm/Support/Alignment.h` * `llvm/include/llvm/Support/MathExtras.h` +* `llvm/lib/IR/Mangler.cpp` * `llvm/lib/Object/ArchiveWriter.cpp` +* `llvm/lib/Object/COFFImportFile.cpp` When syncing, make sure to update these files and the commit above. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d1d59f6..735f0c0 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,3 @@ [toolchain] -# FIXME: LLVM 18 is in Rust 1.78, which is currently beta. -channel = "beta" +channel = "stable" components = ["llvm-tools"] diff --git a/src/archive_writer.rs b/src/archive_writer.rs index b520e29..eb1389e 100644 --- a/src/archive_writer.rs +++ b/src/archive_writer.rs @@ -41,6 +41,24 @@ pub struct NewArchiveMember<'a> { pub perms: u32, } +impl<'a> NewArchiveMember<'a> { + pub fn new + 'a>( + buf: T, + object_reader: &'static ObjectReader, + member_name: String, + ) -> Self { + Self { + buf: Box::new(buf), + object_reader, + member_name, + mtime: 0, + uid: 0, + gid: 0, + perms: 0o644, + } + } +} + fn is_darwin(kind: ArchiveKind) -> bool { matches!(kind, ArchiveKind::Darwin | ArchiveKind::Darwin64) } @@ -546,7 +564,10 @@ fn write_symbols( (Some(&mut sym_map.ec_map), None) } else { is_using_map = true; - (Some(&mut sym_map.map), Some(&mut sym_map.ec_map)) + ( + Some(&mut sym_map.map), + sym_map.use_ec_map.then_some(&mut sym_map.ec_map), + ) } } else { (None, None) diff --git a/src/coff.rs b/src/coff.rs new file mode 100644 index 0000000..187cde7 --- /dev/null +++ b/src/coff.rs @@ -0,0 +1,78 @@ +// Derived from code in LLVM, which is: +// 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 + +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[repr(u16)] +#[allow(clippy::upper_case_acronyms)] +pub enum MachineTypes { + AMD64 = 0x8664, + ARMNT = 0x1C4, + ARM64 = 0xAA64, + ARM64EC = 0xA641, + ARM64X = 0xA64E, + I386 = 0x14C, +} + +impl From for u16 { + fn from(val: MachineTypes) -> Self { + val as u16 + } +} + +pub fn is_arm64ec(machine: MachineTypes) -> bool { + machine == MachineTypes::ARM64EC || machine == MachineTypes::ARM64X +} + +pub fn is_any_arm64(machine: MachineTypes) -> bool { + machine == MachineTypes::ARM64 || is_arm64ec(machine) +} + +pub fn is_64_bit(machine: MachineTypes) -> bool { + machine == MachineTypes::AMD64 || is_any_arm64(machine) +} + +#[derive(PartialEq, Eq, Copy, Clone)] +#[repr(u16)] +pub enum ImportType { + /// An executable code symbol. + Code = 0, + /// A data symbol. + Data = 1, + /// A constant value. + Const = 2, +} + +impl From for u16 { + fn from(val: ImportType) -> Self { + val as u16 + } +} + +#[derive(PartialEq, Eq, Copy, Clone)] +#[repr(u16)] +pub enum ImportNameType { + /// Import is by ordinal. This indicates that the value in the Ordinal/Hint + /// field of the import header is the import's ordinal. If this constant is + /// not specified, then the Ordinal/Hint field should always be interpreted + /// as the import's hint. + Ordinal = 0, + /// The import name is identical to the public symbol name + Name = 1, + /// The import name is the public symbol name, but skipping the leading ?, + /// @, or optionally _. + NameNoprefix = 2, + /// The import name is the public symbol name, but skipping the leading ?, + /// @, or optionally _, and truncating at the first @. + NameUndecorate = 3, + /// The import name is specified as a separate string in the import library + /// object file. + NameExportas = 4, +} + +impl From for u16 { + fn from(val: ImportNameType) -> Self { + val as u16 + } +} diff --git a/src/coff_import_file.rs b/src/coff_import_file.rs index 20d7d79..4e98493 100644 --- a/src/coff_import_file.rs +++ b/src/coff_import_file.rs @@ -3,7 +3,915 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +use std::borrow::Cow; +use std::io::{Error, ErrorKind, Result, Seek, Write}; +use std::mem::{offset_of, size_of}; +use std::path::PathBuf; +use std::str::from_utf8; + +use object::pe::{ + ImageFileHeader, ImageImportDescriptor, ImageRelocation, ImageSectionHeader, ImageSymbol, + ImportObjectHeader, IMAGE_FILE_32BIT_MACHINE, IMAGE_REL_AMD64_ADDR32NB, + IMAGE_REL_ARM64_ADDR32NB, IMAGE_REL_ARM_ADDR32NB, IMAGE_REL_I386_DIR32NB, + IMAGE_SCN_ALIGN_2BYTES, IMAGE_SCN_ALIGN_4BYTES, IMAGE_SCN_ALIGN_8BYTES, + IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_LNK_INFO, IMAGE_SCN_LNK_REMOVE, IMAGE_SCN_MEM_READ, + IMAGE_SCN_MEM_WRITE, IMAGE_SYM_CLASS_EXTERNAL, IMAGE_SYM_CLASS_NULL, IMAGE_SYM_CLASS_SECTION, + IMAGE_SYM_CLASS_STATIC, IMAGE_SYM_CLASS_WEAK_EXTERNAL, IMAGE_WEAK_EXTERN_SEARCH_ALIAS, +}; +use object::pod::bytes_of; + +use crate::coff::{is_arm64ec, ImportNameType, ImportType, MachineTypes}; +use crate::mangler::{get_arm64ec_demangled_function_name, get_arm64ec_mangled_function_name}; +use crate::{write_archive_to_stream, ArchiveKind, NewArchiveMember, DEFAULT_OBJECT_READER}; + pub(crate) const IMPORT_DESCRIPTOR_PREFIX: &[u8] = b"__IMPORT_DESCRIPTOR_"; pub(crate) const NULL_IMPORT_DESCRIPTOR_SYMBOL_NAME: &[u8] = b"__NULL_IMPORT_DESCRIPTOR"; pub(crate) const NULL_THUNK_DATA_PREFIX: &[u8] = b"\x7f"; pub(crate) const NULL_THUNK_DATA_SUFFIX: &[u8] = b"_NULL_THUNK_DATA"; + +macro_rules! u16 { + ($val:expr) => { + object::U16::new(object::NativeEndian, $val) + }; +} + +macro_rules! u32 { + ($val:expr) => { + object::U32::new(object::NativeEndian, $val) + }; +} + +// Derived from COFFImportFile::printSymbolName and COFFImportFile::symbol_end. +fn get_short_import_symbol(buf: &[u8], f: &mut dyn FnMut(&[u8]) -> Result<()>) -> Result { + let mut offset = 0; + let header = ImportObjectHeader::parse(buf, &mut offset).map_err(Error::other)?; + let data = header.parse_data(buf, &mut offset).map_err(Error::other)?; + let is_ec = header.machine.get(object::NativeEndian) == object::pe::IMAGE_FILE_MACHINE_ARM64EC; + + let name = data.symbol(); + let demangled_name = is_ec + .then(|| get_arm64ec_demangled_function_name(from_utf8(name).unwrap())) + .flatten() + .map_or_else( + || Cow::Borrowed(name), + |demangled_name| Cow::Owned(demangled_name.into_bytes()), + ); + + // Import symbol is first. + const IMP_PREFIX: &[u8] = b"__imp_"; + f(&IMP_PREFIX + .iter() + .chain(demangled_name.as_ref()) + .copied() + .collect::>())?; + + // For data, only the import symbol is needed. + if header.import_type() == ImportType::Data.into() { + return Ok(true); + } + + // Next, thunk. + f(demangled_name.as_ref())?; + + // For Arm64EC, also the EC import symbol and thunk. + if header.machine.get(object::NativeEndian) == object::pe::IMAGE_FILE_MACHINE_ARM64EC { + const IMP_PREFIX: &[u8] = b"__imp_aux_"; + f(&IMP_PREFIX + .iter() + .chain(demangled_name.as_ref()) + .copied() + .collect::>())?; + + f(name)?; + } + + Ok(true) +} + +const READER_FOR_SHORT_IMPORT: crate::ObjectReader = crate::ObjectReader { + get_symbols: get_short_import_symbol, + ..crate::DEFAULT_OBJECT_READER +}; + +pub struct COFFShortExport { + /// The name of the export as specified in the .def file or on the command + /// line, i.e. "foo" in "/EXPORT:foo", and "bar" in "/EXPORT:foo=bar". This + /// may lack mangling, such as underscore prefixing and stdcall suffixing. + pub name: String, + + /// The external, exported name. Only non-empty when export renaming is in + /// effect, i.e. "foo" in "/EXPORT:foo=bar". + pub ext_name: Option, + + /// The real, mangled symbol name from the object file. Given + /// "/export:foo=bar", this could be "_bar@8" if bar is stdcall. + pub symbol_name: Option, + + /// Creates a weak alias. This is the name of the weak aliasee. In a .def + /// file, this is "baz" in "EXPORTS\nfoo = bar == baz". + pub alias_target: Option, + + pub ordinal: u16, + pub noname: bool, + pub data: bool, + pub private: bool, + pub constant: bool, +} + +fn set_name_to_string_table_entry(symbol: &mut ImageSymbol, offset: usize) { + // If first 4 bytes are 0, then second 4 bytes are offset into string table. + symbol.name[..4].copy_from_slice(&[0; 4]); + symbol.name[4..].copy_from_slice(&u32::try_from(offset).unwrap().to_le_bytes()); +} + +fn get_img_rel_relocation(machine: MachineTypes) -> object::U16 { + u16!(match machine { + MachineTypes::AMD64 => IMAGE_REL_AMD64_ADDR32NB, + MachineTypes::ARMNT => IMAGE_REL_ARM_ADDR32NB, + MachineTypes::ARM64 | MachineTypes::ARM64EC | MachineTypes::ARM64X => + IMAGE_REL_ARM64_ADDR32NB, + MachineTypes::I386 => IMAGE_REL_I386_DIR32NB, + }) +} + +fn write_string_table(b: &mut Vec, strings: &[&[u8]]) -> Result<()> { + // The COFF string table consists of a 4-byte value which is the size of the + // table, including the length field itself. This value is followed by the + // string content itself, which is an array of null-terminated C-style + // strings. The termination is important as they are referenced to by offset + // by the symbol entity in the file format. + + let offset = b.len(); + + // Skip over the length field, we will fill it in later as we will have + // computed the length while emitting the string content itself. + b.extend(0u32.to_le_bytes()); + + for s in strings { + b.write_all(s)?; + b.write_all(&[0])?; + } + + // Backfill the length of the table now that it has been computed. + let length: u32 = (b.len() - offset).try_into().unwrap(); + b[offset..offset + size_of::()].copy_from_slice(&length.to_le_bytes()); + + Ok(()) +} + +fn get_name_type(sym: &str, ext_name: &str, machine: MachineTypes, mingw: bool) -> ImportNameType { + // A decorated stdcall function in MSVC is exported with the + // type IMPORT_NAME, and the exported function name includes the + // the leading underscore. In MinGW on the other hand, a decorated + // stdcall function still omits the underscore (IMPORT_NAME_NOPREFIX). + // See the comment in isDecorated in COFFModuleDefinition.cpp for more + // details. + if ext_name.starts_with('_') && ext_name.contains('@') && !mingw { + ImportNameType::Name + } else if sym != ext_name { + ImportNameType::NameUndecorate + } else if machine == MachineTypes::I386 && sym.starts_with('_') { + ImportNameType::NameNoprefix + } else { + ImportNameType::Name + } +} + +fn replace(s: &str, mut from: &str, mut to: &str) -> Result { + if let Some((before, after)) = s.split_once(from) { + return Ok(format!("{before}{to}{after}")); + } + + // From and To may be mangled, but substrings in S may not. + if from.starts_with('_') && to.starts_with('_') { + from = &from[1..]; + to = &to[1..]; + if let Some((before, after)) = s.split_once(from) { + return Ok(format!("{before}{to}{after}")); + } + } + + Err(Error::other(format!( + "{s}: replacing '{from}' with '{to}' failed" + ))) +} + +/// This class constructs various small object files necessary to support linking +/// symbols imported from a DLL. The contents are pretty strictly defined and +/// nearly entirely static. The details of the structures files are defined in +/// WINNT.h and the PE/COFF specification. +struct ObjectFactory<'a> { + native_machine: MachineTypes, + import_name: &'a str, + import_descriptor_symbol_name: Vec, + null_thunk_symbol_name: Vec, +} + +impl<'a> ObjectFactory<'a> { + fn new(s: &'a str, m: MachineTypes) -> Result { + let import_as_path = PathBuf::from(s); + let library = import_as_path + .file_stem() + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidInput, + "Import name did not end with a file name", + ) + })? + .to_str() + .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "Import name is not valid UTF-8"))?; + let library = library.as_bytes(); + Ok(Self { + native_machine: m, + import_name: s, + import_descriptor_symbol_name: IMPORT_DESCRIPTOR_PREFIX + .iter() + .chain(library) + .copied() + .collect(), + null_thunk_symbol_name: NULL_THUNK_DATA_PREFIX + .iter() + .chain(library) + .chain(NULL_THUNK_DATA_SUFFIX) + .copied() + .collect(), + }) + } + + fn is_64_bit(&self) -> bool { + crate::coff::is_64_bit(self.native_machine) + } + + /// Creates an Import Descriptor. This is a small object file which contains a + /// reference to the terminators and contains the library name (entry) for the + /// import name table. It will force the linker to construct the necessary + /// structure to import symbols from the DLL. + fn create_import_descriptor(&self) -> Result { + let mut buffer = Vec::new(); + + const NUMBER_OF_SECTIONS: usize = 2; + const NUMBER_OF_SYMBOLS: usize = 7; + const NUMBER_OF_RELOCATIONS: usize = 3; + + // COFF Header + let header = ImageFileHeader { + machine: u16!(self.native_machine.into()), + number_of_sections: u16!(NUMBER_OF_SECTIONS.try_into().unwrap()), + time_date_stamp: u32!(0), + pointer_to_symbol_table: u32!((size_of::() + (NUMBER_OF_SECTIONS * size_of::()) + + // .idata$2 + size_of::() + + NUMBER_OF_RELOCATIONS * size_of::() + + // .idata$4 + (self.import_name.len() + 1)).try_into().unwrap()), + number_of_symbols: u32!(NUMBER_OF_SYMBOLS.try_into().unwrap()), + size_of_optional_header: u16!(0), + characteristics: u16!(if self.is_64_bit() { + 0 + } else { + IMAGE_FILE_32BIT_MACHINE + }), + }; + buffer.write_all(bytes_of(&header))?; + + // Section Header Table + let section_table: [_; NUMBER_OF_SECTIONS] = [ + ImageSectionHeader { + name: *b".idata$2", + virtual_size: u32!(0), + virtual_address: u32!(0), + size_of_raw_data: u32!(size_of::().try_into().unwrap()), + pointer_to_raw_data: u32!((size_of::() + + NUMBER_OF_SECTIONS * size_of::()) + .try_into() + .unwrap()), + pointer_to_relocations: u32!((size_of::() + + NUMBER_OF_SECTIONS * size_of::() + + size_of::()) + .try_into() + .unwrap()), + pointer_to_linenumbers: u32!(0), + number_of_relocations: u16!(NUMBER_OF_RELOCATIONS.try_into().unwrap()), + number_of_linenumbers: u16!(0), + characteristics: u32!( + IMAGE_SCN_ALIGN_4BYTES + | IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_MEM_WRITE + ), + }, + ImageSectionHeader { + name: *b".idata$6", + virtual_size: u32!(0), + virtual_address: u32!(0), + size_of_raw_data: u32!((self.import_name.len() + 1).try_into().unwrap()), + pointer_to_raw_data: u32!((size_of::() + + NUMBER_OF_SECTIONS * size_of::() + + size_of::() + + NUMBER_OF_RELOCATIONS * size_of::()) + .try_into() + .unwrap()), + pointer_to_relocations: u32!(0), + pointer_to_linenumbers: u32!(0), + number_of_relocations: u16!(0), + number_of_linenumbers: u16!(0), + characteristics: u32!( + IMAGE_SCN_ALIGN_2BYTES + | IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_MEM_WRITE + ), + }, + ]; + buffer.write_all(bytes_of(§ion_table))?; + + // .idata$2 + let import_descriptor = ImageImportDescriptor { + original_first_thunk: u32!(0), + time_date_stamp: u32!(0), + forwarder_chain: u32!(0), + name: u32!(0), + first_thunk: u32!(0), + }; + buffer.write_all(bytes_of(&import_descriptor))?; + + let relocation_table: [_; NUMBER_OF_RELOCATIONS] = [ + ImageRelocation { + virtual_address: u32!((offset_of!(ImageImportDescriptor, name)) + .try_into() + .unwrap()), + symbol_table_index: u32!(2), + typ: get_img_rel_relocation(self.native_machine), + }, + ImageRelocation { + virtual_address: u32!(offset_of!(ImageImportDescriptor, original_first_thunk) + .try_into() + .unwrap()), + symbol_table_index: u32!(3), + typ: get_img_rel_relocation(self.native_machine), + }, + ImageRelocation { + virtual_address: u32!(offset_of!(ImageImportDescriptor, first_thunk) + .try_into() + .unwrap()), + symbol_table_index: u32!(4), + typ: get_img_rel_relocation(self.native_machine), + }, + ]; + buffer.write_all(bytes_of(&relocation_table))?; + + // .idata$6 + buffer.write_all(self.import_name.as_bytes())?; + buffer.write_all(&[0])?; + + // Symbol Table + let mut symbol_table: [_; NUMBER_OF_SYMBOLS] = [ + ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(1), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_EXTERNAL, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: *b".idata$2", + value: u32!(0), + section_number: u16!(1), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_SECTION, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: *b".idata$6", + value: u32!(0), + section_number: u16!(2), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_STATIC, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: *b".idata$4", + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_SECTION, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: *b".idata$5", + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_SECTION, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_EXTERNAL, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_EXTERNAL, + number_of_aux_symbols: 0, + }, + ]; + // TODO: Name.Offset.Offset here and in the all similar places below + // suggests a names refactoring. Maybe StringTableOffset.Value? + set_name_to_string_table_entry(&mut symbol_table[0], size_of::()); + set_name_to_string_table_entry( + &mut symbol_table[5], + size_of::() + self.import_descriptor_symbol_name.len() + 1, + ); + set_name_to_string_table_entry( + &mut symbol_table[6], + size_of::() + + self.import_descriptor_symbol_name.len() + + 1 + + NULL_IMPORT_DESCRIPTOR_SYMBOL_NAME.len() + + 1, + ); + buffer.write_all(bytes_of(&symbol_table))?; + + // String Table + write_string_table( + &mut buffer, + &[ + &self.import_descriptor_symbol_name, + NULL_IMPORT_DESCRIPTOR_SYMBOL_NAME, + &self.null_thunk_symbol_name, + ], + )?; + + Ok(NewArchiveMember::new( + buffer.into_boxed_slice(), + &DEFAULT_OBJECT_READER, + self.import_name.to_string(), + )) + } + + /// Creates a NULL import descriptor. This is a small object file whcih + /// contains a NULL import descriptor. It is used to terminate the imports + /// from a specific DLL. + fn create_null_import_descriptor(&self) -> Result { + let mut buffer = Vec::new(); + + const NUMBER_OF_SECTIONS: usize = 1; + const NUMBER_OF_SYMBOLS: usize = 1; + + // COFF Header + let header = ImageFileHeader { + machine: u16!(self.native_machine.into()), + number_of_sections: u16!(NUMBER_OF_SECTIONS.try_into().unwrap()), + time_date_stamp: u32!(0), + pointer_to_symbol_table: u32!((size_of::() + (NUMBER_OF_SECTIONS * size_of::()) + + // .idata$3 + size_of::()).try_into().unwrap()), + number_of_symbols: u32!(NUMBER_OF_SYMBOLS.try_into().unwrap()), + size_of_optional_header: u16!(0), + characteristics: u16!(if self.is_64_bit() { + 0 + } else { + IMAGE_FILE_32BIT_MACHINE + }), + }; + buffer.write_all(bytes_of(&header))?; + + // Section Header Table + let section_table: [_; NUMBER_OF_SECTIONS] = [ImageSectionHeader { + name: *b".idata$3", + virtual_size: u32!(0), + virtual_address: u32!(0), + size_of_raw_data: u32!(size_of::().try_into().unwrap()), + pointer_to_raw_data: u32!((size_of::() + + NUMBER_OF_SECTIONS * size_of::()) + .try_into() + .unwrap()), + pointer_to_relocations: u32!(0), + pointer_to_linenumbers: u32!(0), + number_of_relocations: u16!(0), + number_of_linenumbers: u16!(0), + characteristics: u32!( + IMAGE_SCN_ALIGN_4BYTES + | IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_MEM_WRITE + ), + }]; + buffer.write_all(bytes_of(§ion_table))?; + + // .idata$3 + let import_descriptor = ImageImportDescriptor { + original_first_thunk: u32!(0), + time_date_stamp: u32!(0), + forwarder_chain: u32!(0), + name: u32!(0), + first_thunk: u32!(0), + }; + buffer.write_all(bytes_of(&import_descriptor))?; + + // Symbol Table + let mut symbol_table: [_; NUMBER_OF_SYMBOLS] = [ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(1), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_EXTERNAL, + number_of_aux_symbols: 0, + }]; + set_name_to_string_table_entry(&mut symbol_table[0], size_of::()); + buffer.write_all(bytes_of(&symbol_table))?; + + // String Table + write_string_table(&mut buffer, &[NULL_IMPORT_DESCRIPTOR_SYMBOL_NAME])?; + + Ok(NewArchiveMember::new( + buffer.into_boxed_slice(), + &DEFAULT_OBJECT_READER, + self.import_name.to_string(), + )) + } + + /// Create a NULL Thunk Entry. This is a small object file which contains a + /// NULL Import Address Table entry and a NULL Import Lookup Table Entry. It + /// is used to terminate the IAT and ILT. + fn create_null_thunk(&self) -> Result { + let mut buffer = Vec::new(); + + const NUMBER_OF_SECTIONS: usize = 2; + const NUMBER_OF_SYMBOLS: usize = 1; + let va_size = if self.is_64_bit() { 8 } else { 4 }; + + // COFF Header + let header = ImageFileHeader { + machine: u16!(self.native_machine.into()), + number_of_sections: u16!(NUMBER_OF_SECTIONS.try_into().unwrap()), + time_date_stamp: u32!(0), + pointer_to_symbol_table: u32!((size_of::() + (NUMBER_OF_SECTIONS * size_of::()) + + // .idata$5 + va_size + + // .idata$4 + va_size).try_into().unwrap()), + number_of_symbols: u32!(NUMBER_OF_SYMBOLS.try_into().unwrap()), + size_of_optional_header: u16!(0), + characteristics: u16!(if self.is_64_bit() { + 0 + } else { + IMAGE_FILE_32BIT_MACHINE + }), + }; + buffer.write_all(bytes_of(&header))?; + + // Section Header Table + let alignment = if self.is_64_bit() { + IMAGE_SCN_ALIGN_8BYTES + } else { + IMAGE_SCN_ALIGN_4BYTES + }; + let section_table: [_; NUMBER_OF_SECTIONS] = [ + ImageSectionHeader { + name: *b".idata$5", + virtual_size: u32!(0), + virtual_address: u32!(0), + size_of_raw_data: u32!(va_size.try_into().unwrap()), + pointer_to_raw_data: u32!((size_of::() + + NUMBER_OF_SECTIONS * size_of::()) + .try_into() + .unwrap()), + pointer_to_relocations: u32!(0), + pointer_to_linenumbers: u32!(0), + number_of_relocations: u16!(0), + number_of_linenumbers: u16!(0), + characteristics: u32!( + alignment + | IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_MEM_WRITE + ), + }, + ImageSectionHeader { + name: *b".idata$4", + virtual_size: u32!(0), + virtual_address: u32!(0), + size_of_raw_data: u32!(va_size.try_into().unwrap()), + pointer_to_raw_data: u32!((size_of::() + + NUMBER_OF_SECTIONS * size_of::() + + va_size) + .try_into() + .unwrap()), + pointer_to_relocations: u32!(0), + pointer_to_linenumbers: u32!(0), + number_of_relocations: u16!(0), + number_of_linenumbers: u16!(0), + characteristics: u32!( + alignment + | IMAGE_SCN_CNT_INITIALIZED_DATA + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_MEM_WRITE + ), + }, + ]; + buffer.write_all(bytes_of(§ion_table))?; + + // .idata$5, ILT + buffer.write_all(&vec![0; va_size])?; + + // .idata$4, IAT + buffer.write_all(&vec![0; va_size])?; + + // Symbol Table + let mut symbol_table: [_; NUMBER_OF_SYMBOLS] = [ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(1), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_EXTERNAL, + number_of_aux_symbols: 0, + }]; + set_name_to_string_table_entry(&mut symbol_table[0], size_of::()); + buffer.write_all(bytes_of(&symbol_table))?; + + // String Table + write_string_table(&mut buffer, &[&self.null_thunk_symbol_name])?; + + Ok(NewArchiveMember::new( + buffer.into_boxed_slice(), + &DEFAULT_OBJECT_READER, + self.import_name.to_string(), + )) + } + + /// Create a short import file which is described in PE/COFF spec 7. Import + /// Library Format. + fn create_short_import( + &self, + sym: &str, + ordinal: u16, + import_type: ImportType, + name_type: ImportNameType, + export_name: Option<&str>, + machine: MachineTypes, + ) -> Result { + let mut imp_size = self.import_name.len() + sym.len() + 2; // +2 for NULs + if let Some(export_name) = export_name { + imp_size += export_name.len() + 1; + } + let size = size_of::() + imp_size; + let mut buf = Vec::new(); + buf.reserve_exact(size); + + // Write short import library. + let imp = ImportObjectHeader { + sig1: u16!(0), + sig2: u16!(0xFFFF), + version: u16!(0), + machine: u16!(machine.into()), + time_date_stamp: u32!(0), + size_of_data: u32!(imp_size.try_into().unwrap()), + ordinal_or_hint: u16!(ordinal), + name_type: u16!((u16::from(name_type) << 2) | u16::from(import_type)), + }; + buf.write_all(bytes_of(&imp))?; + + // Write symbol name and DLL name. + buf.write_all(sym.as_bytes())?; + buf.write_all(&[0])?; + buf.write_all(self.import_name.as_bytes())?; + buf.write_all(&[0])?; + if let Some(export_name) = export_name { + buf.write_all(export_name.as_bytes())?; + buf.write_all(&[0])?; + } + + Ok(NewArchiveMember::new( + buf.into_boxed_slice(), + &READER_FOR_SHORT_IMPORT, + self.import_name.to_string(), + )) + } + + /// Create a weak external file which is described in PE/COFF Aux Format 3. + fn create_weak_external( + &self, + sym: &str, + weak: &str, + imp: bool, + machine: MachineTypes, + ) -> Result { + let mut buffer = Vec::new(); + const NUMBER_OF_SECTIONS: usize = 1; + const NUMBER_OF_SYMBOLS: usize = 5; + + // COFF Header + let header = ImageFileHeader { + machine: u16!(machine.into()), + number_of_sections: u16!(NUMBER_OF_SECTIONS.try_into().unwrap()), + time_date_stamp: u32!(0), + pointer_to_symbol_table: u32!((size_of::() + + (NUMBER_OF_SECTIONS * size_of::())) + .try_into() + .unwrap()), + number_of_symbols: u32!(NUMBER_OF_SYMBOLS.try_into().unwrap()), + size_of_optional_header: u16!(0), + characteristics: u16!(0), + }; + buffer.write_all(bytes_of(&header))?; + + // Section Header Table + let section_table: [_; NUMBER_OF_SECTIONS] = [ImageSectionHeader { + name: *b".drectve", + virtual_size: u32!(0), + virtual_address: u32!(0), + size_of_raw_data: u32!(0), + pointer_to_raw_data: u32!(0), + pointer_to_relocations: u32!(0), + pointer_to_linenumbers: u32!(0), + number_of_relocations: u16!(0), + number_of_linenumbers: u16!(0), + characteristics: u32!(IMAGE_SCN_LNK_INFO | IMAGE_SCN_LNK_REMOVE), + }]; + buffer.write_all(bytes_of(§ion_table))?; + + // Symbol Table + let mut symbol_table: [_; NUMBER_OF_SYMBOLS] = [ + ImageSymbol { + name: *b"@comp.id", + value: u32!(0), + section_number: u16!(0xFFFF), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_STATIC, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: *b"@feat.00", + value: u32!(0), + section_number: u16!(0xFFFF), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_STATIC, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_EXTERNAL, + number_of_aux_symbols: 0, + }, + ImageSymbol { + name: [0; 8], + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_WEAK_EXTERNAL, + number_of_aux_symbols: 1, + }, + ImageSymbol { + name: [2, 0, 0, 0, IMAGE_WEAK_EXTERN_SEARCH_ALIAS as u8, 0, 0, 0], + value: u32!(0), + section_number: u16!(0), + typ: u16!(0), + storage_class: IMAGE_SYM_CLASS_NULL, + number_of_aux_symbols: 0, + }, + ]; + set_name_to_string_table_entry(&mut symbol_table[2], size_of::()); + + //__imp_ String Table + let prefix: &[u8] = if imp { b"__imp_" } else { b"" }; + set_name_to_string_table_entry( + &mut symbol_table[3], + size_of::() + sym.len() + prefix.len() + 1, + ); + buffer.write_all(bytes_of(&symbol_table))?; + + write_string_table( + &mut buffer, + &[ + &prefix + .iter() + .chain(sym.as_bytes()) + .copied() + .collect::>(), + &prefix + .iter() + .chain(weak.as_bytes()) + .copied() + .collect::>(), + ], + )?; + + // Copied here so we can still use writeStringTable + Ok(NewArchiveMember::new( + buffer.into_boxed_slice(), + &DEFAULT_OBJECT_READER, + self.import_name.to_string(), + )) + } +} + +pub fn write_import_library( + w: &mut W, + import_name: &str, + exports: &[COFFShortExport], + machine: MachineTypes, + mingw: bool, +) -> Result<()> { + let native_machine = if machine == MachineTypes::ARM64EC { + MachineTypes::ARM64 + } else { + machine + }; + + let of = ObjectFactory::new(import_name, native_machine)?; + let mut members = Vec::new(); + + members.push(of.create_import_descriptor()?); + + members.push(of.create_null_import_descriptor()?); + + members.push(of.create_null_thunk()?); + + for e in exports { + if e.private { + continue; + } + + let mut import_type = ImportType::Code; + if e.data { + import_type = ImportType::Data; + } + if e.constant { + import_type = ImportType::Const; + } + + let symbol_name = if let Some(symbol_name) = e.symbol_name.as_ref() { + symbol_name + } else { + &e.name + }; + + let mut name: Cow<'_, str> = if let Some(ext_name) = e.ext_name.as_ref() { + Cow::Owned(replace(symbol_name, &e.name, ext_name)?) + } else { + Cow::Borrowed(symbol_name) + }; + + if let Some(alias_target) = e.alias_target.as_ref() { + if name.as_ref() != alias_target { + members.push(of.create_weak_external(alias_target, &name, false, machine)?); + members.push(of.create_weak_external(alias_target, &name, true, machine)?); + continue; + } + } + + let mut name_type = if e.noname { + ImportNameType::Ordinal + } else { + get_name_type(symbol_name, &e.name, machine, mingw) + }; + + // On ARM64EC, use EXPORTAS to import demangled name for mangled symbols. + let export_name = if import_type == ImportType::Code && crate::coff::is_arm64ec(machine) { + if let Some(mangled_name) = get_arm64ec_mangled_function_name(&name) { + name_type = ImportNameType::NameExportas; + let export_name = name; + name = Cow::Owned(mangled_name); + Some(export_name) + } else { + name_type = ImportNameType::NameExportas; + get_arm64ec_demangled_function_name(&name).map(Cow::Owned) + } + } else { + None + }; + + members.push(of.create_short_import( + &name, + e.ordinal, + import_type, + name_type, + export_name.as_deref(), + machine, + )?); + } + + write_archive_to_stream( + w, + &members, + if mingw { + ArchiveKind::Gnu + } else { + ArchiveKind::Coff + }, + false, + is_arm64ec(machine), + ) +} diff --git a/src/lib.rs b/src/lib.rs index 9d3315b..05b3e99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,16 @@ mod alignment; mod archive; mod archive_writer; +mod coff; mod coff_import_file; +mod mangler; mod math_extras; mod object_reader; pub use archive::ArchiveKind; pub use archive_writer::{write_archive_to_stream, NewArchiveMember}; +pub use coff::MachineTypes; +pub use coff_import_file::{write_import_library, COFFShortExport}; pub type GetSymbolsFn = fn(buf: &[u8], f: &mut dyn FnMut(&[u8]) -> std::io::Result<()>) -> std::io::Result; diff --git a/src/mangler.rs b/src/mangler.rs new file mode 100644 index 0000000..13c6585 --- /dev/null +++ b/src/mangler.rs @@ -0,0 +1,49 @@ +// Derived from code in LLVM, which is: +// 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 + +pub fn get_arm64ec_mangled_function_name(name: &str) -> Option { + let first_char = name.chars().next().unwrap(); + let is_cpp_fn = first_char == '?'; + if is_cpp_fn && name.contains("$$h") { + return None; + } + if !is_cpp_fn && first_char == '#' { + return None; + } + + let mut prefix = "$$h"; + let insert_idx = if is_cpp_fn { + match name.find("@@") { + Some(two_at_signs_idx) if Some(two_at_signs_idx) != name.find("@@@") => { + two_at_signs_idx + 2 + } + _ => name.find('@').map_or(name.len(), |idx| idx + 1), + } + } else { + prefix = "#"; + 0 + }; + + Some(format!( + "{}{prefix}{}", + &name[..insert_idx], + &name[insert_idx..] + )) +} + +pub fn get_arm64ec_demangled_function_name(name: &str) -> Option { + let first_char = name.chars().next().unwrap(); + if first_char == '#' { + return Some(name[1..].to_string()); + } + if first_char != '?' { + return None; + } + + match name.split_once("$$h") { + Some((first, second)) if !second.is_empty() => Some(format!("{first}{second}")), + _ => None, + } +} diff --git a/tests/common.rs b/tests/common.rs index f561429..dd2be49 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -27,6 +27,28 @@ pub fn create_tmp_dir(test_name: &str) -> PathBuf { tmpdir } +/// Creates a symlink to `llvm-ar` so that it acts like `llvm-lib`. +pub fn create_llvm_lib_tool(tmp_dir: &Path) -> PathBuf { + let ar_path = cargo_binutils::Tool::Ar.path().unwrap(); + let lib_path = tmp_dir.join("llvm-lib"); + #[cfg(unix)] + std::os::unix::fs::symlink(ar_path, &lib_path).unwrap(); + #[cfg(windows)] + std::os::windows::fs::symlink_file(ar_path, &lib_path).unwrap(); + lib_path +} + +/// Creates a symlink to `llvm-ar` so that it acts like `llvm-dlltool`. +pub fn create_llvm_dlltool_tool(tmp_dir: &Path) -> PathBuf { + let ar_path = cargo_binutils::Tool::Ar.path().unwrap(); + let lib_path = tmp_dir.join("llvm-dlltool"); + #[cfg(unix)] + std::os::unix::fs::symlink(ar_path, &lib_path).unwrap(); + #[cfg(windows)] + std::os::windows::fs::symlink_file(ar_path, &lib_path).unwrap(); + lib_path +} + fn run_llvm_ar( object_paths: &[PathBuf], archive_path: &Path, @@ -34,17 +56,10 @@ fn run_llvm_ar( thin: bool, is_ec: bool, ) { - let ar_path = cargo_binutils::Tool::Ar.path().unwrap(); - // FIXME: LLVM 19 adds support for "coff" as a format argument, so in the // meantime, we'll instruct llvm-ar to pretend to be llvm-lib. let output = if archive_kind == ArchiveKind::Coff { - let lib_path = archive_path.parent().unwrap().join("llvm-lib"); - #[cfg(unix)] - std::os::unix::fs::symlink(&ar_path, &lib_path).unwrap(); - #[cfg(windows)] - std::os::windows::fs::symlink_file(&ar_path, &lib_path).unwrap(); - + let lib_path = create_llvm_lib_tool(archive_path.parent().unwrap()); let mut command = Command::new(lib_path); if is_ec { @@ -60,6 +75,7 @@ fn run_llvm_ar( .output() .unwrap() } else { + let ar_path = cargo_binutils::Tool::Ar.path().unwrap(); let mut command = Command::new(ar_path); let format_arg = match archive_kind { @@ -151,15 +167,11 @@ pub fn create_archive_with_ar_archive_writer<'a>( .to_string() }; - NewArchiveMember { - buf: Box::new(bytes) as Box>, - object_reader: &ar_archive_writer::DEFAULT_OBJECT_READER, + NewArchiveMember::new( + bytes, + &ar_archive_writer::DEFAULT_OBJECT_READER, member_name, - mtime: 0, - uid: 0, - gid: 0, - perms: 0o644, - } + ) }) .collect::>(); let mut output_bytes = Cursor::new(Vec::new()); diff --git a/tests/import_library.def b/tests/import_library.def new file mode 100644 index 0000000..a819cb1 --- /dev/null +++ b/tests/import_library.def @@ -0,0 +1,16 @@ +; This must match get_members() in import_library.rs +LIBRARY MyLibrary +EXPORTS + NormalFunc + NormalData DATA + NormalConstant CONSTANT + PrivateFunc PRIVATE + FuncWithOrdinal @1 + FuncWithNoName @2 NONAME + RenamedFunc=InternalName + ReexportedFunc=OtherModule.OtherName + ReexportedViaOrd=OtherModule.#42 + FuncWithImportName==ImportName + ?CppFunc@SingleAt + ?CppFunc@@DoubleAt + ?CppFunc@@@TripleAt diff --git a/tests/import_library.rs b/tests/import_library.rs new file mode 100644 index 0000000..ec62d37 --- /dev/null +++ b/tests/import_library.rs @@ -0,0 +1,217 @@ +use std::fs; +use std::io::Cursor; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; + +use ar_archive_writer::{COFFShortExport, MachineTypes}; +use pretty_assertions::assert_eq; + +mod common; + +const DEFAULT_EXPORT: COFFShortExport = COFFShortExport { + name: String::new(), + ext_name: None, + symbol_name: None, + alias_target: None, + ordinal: 0, + noname: false, + data: false, + private: false, + constant: false, +}; + +fn get_members(machine_type: MachineTypes) -> Vec { + let prefix = match machine_type { + MachineTypes::I386 => "_", + _ => "", + }; + // This must match import_library.def. + vec![ + COFFShortExport { + name: format!("{prefix}NormalFunc"), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}NormalData"), + data: true, + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}NormalConstant"), + constant: true, + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}PrivateFunc"), + private: true, + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}FuncWithOrdinal"), + ordinal: 1, + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}FuncWithNoName"), + ordinal: 2, + noname: true, + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}InternalName"), + ext_name: Some(format!("{prefix}RenamedFunc")), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}OtherModule.OtherName"), + ext_name: Some(format!("{prefix}ReexportedFunc")), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}OtherModule.#42"), + ext_name: Some(format!("{prefix}ReexportedViaOrd")), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: format!("{prefix}FuncWithImportName"), + alias_target: Some(format!("{prefix}ImportName")), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: "?CppFunc@SingleAt".to_string(), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: "?CppFunc@@DoubleAt".to_string(), + ..DEFAULT_EXPORT + }, + COFFShortExport { + name: "?CppFunc@@@TripleAt".to_string(), + ..DEFAULT_EXPORT + }, + ] +} + +fn create_import_library_with_ar_archive_writer( + temp_dir: &Path, + machine_type: MachineTypes, + mingw: bool, +) -> Vec { + let mut output_bytes = Cursor::new(Vec::new()); + ar_archive_writer::write_import_library( + &mut output_bytes, + "MyLibrary.dll", + &get_members(machine_type), + machine_type, + mingw, + ) + .unwrap(); + + let output_archive_bytes = output_bytes.into_inner(); + let ar_archive_writer_file_path = temp_dir.join("output_ar_archive_writer.a"); + fs::write(ar_archive_writer_file_path, &output_archive_bytes).unwrap(); + + output_archive_bytes +} + +#[test] +fn compare_to_lib() { + for machine_type in [ + MachineTypes::I386, + MachineTypes::AMD64, + MachineTypes::ARMNT, + MachineTypes::ARM64, + MachineTypes::ARM64EC, + ] { + let temp_dir = common::create_tmp_dir("import_library_compare_to_lib"); + + let archive_writer_bytes = + create_import_library_with_ar_archive_writer(&temp_dir, machine_type, false); + + let llvm_lib_bytes = { + let machine_arg = match machine_type { + MachineTypes::I386 => "X86", + MachineTypes::AMD64 => "X64", + MachineTypes::ARMNT => "ARM", + MachineTypes::ARM64 => "ARM64", + MachineTypes::ARM64EC => "ARM64EC", + _ => panic!("Unsupported machine type"), + }; + + let llvm_lib_tool_path = common::create_llvm_lib_tool(&temp_dir); + let output_library_path = temp_dir.join("output_llvm_lib.a"); + let def_path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/import_library.def"); + let output = Command::new(llvm_lib_tool_path) + .arg(format!("/MACHINE:{machine_arg}")) + .arg(format!("/DEF:{}", def_path.to_string_lossy())) + .arg(format!("/OUT:{}", output_library_path.to_string_lossy())) + .output() + .unwrap(); + + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "", + "llvm-lib failed. archive: {output_library_path:?}" + ); + fs::read(output_library_path).unwrap() + }; + + assert_eq!( + llvm_lib_bytes, archive_writer_bytes, + "Import library differs. Machine type: {machine_type:?}", + ); + } +} + +#[test] +fn compare_to_dlltool() { + for machine_type in [ + MachineTypes::I386, + MachineTypes::AMD64, + MachineTypes::ARMNT, + MachineTypes::ARM64, + ] { + let temp_dir = common::create_tmp_dir("import_library_compare_to_dlltool"); + + let archive_writer_bytes = + create_import_library_with_ar_archive_writer(&temp_dir, machine_type, true); + + let llvm_lib_bytes = { + let machine_arg = match machine_type { + MachineTypes::I386 => "i386", + MachineTypes::AMD64 => "i386:x86-64", + MachineTypes::ARMNT => "arm", + MachineTypes::ARM64 => "arm64", + _ => panic!("Unsupported machine type"), + }; + + let llvm_lib_tool_path = common::create_llvm_dlltool_tool(&temp_dir); + let output_library_path = temp_dir.join("output_llvm_lib.a"); + let def_path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/import_library.def"); + let output = Command::new(llvm_lib_tool_path) + .arg("--machine") + .arg(machine_arg) + .arg("--input-def") + .arg(def_path) + .arg("--output-lib") + .arg(&output_library_path) + .output() + .unwrap(); + + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "", + "llvm-lib failed. archive: {output_library_path:?}" + ); + fs::read(output_library_path).unwrap() + }; + + assert_eq!( + llvm_lib_bytes, archive_writer_bytes, + "Import library differs. Machine type: {machine_type:?}", + ); + } +}