Index: include/llvm/Object/COFF.h =================================================================== --- include/llvm/Object/COFF.h +++ include/llvm/Object/COFF.h @@ -971,6 +971,15 @@ return nullptr; return reinterpret_cast(base()); } + std::error_code getCOFFHeader(const coff_file_header *&Res) const { + Res = COFFHeader; + return std::error_code(); + } + std::error_code + getCOFFBigObjHeader(const coff_bigobj_file_header *&Res) const { + Res = COFFBigObjHeader; + return std::error_code(); + } std::error_code getPE32Header(const pe32_header *&Res) const; std::error_code getPE32PlusHeader(const pe32plus_header *&Res) const; std::error_code getDataDirectory(uint32_t index, Index: test/tools/llvm-objcopy/COFF/basic-copy.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/COFF/basic-copy.test @@ -0,0 +1,30 @@ +RUN: llvm-objcopy %p/Inputs/i386.o %t.o +RUN: cmp %p/Inputs/i386.o %t.o + +RUN: llvm-objcopy %p/Inputs/x86_64.o %t.o +RUN: cmp %p/Inputs/x86_64.o %t.o + +RUN: llvm-objcopy %p/Inputs/i386-big.o %t.o +RUN: cmp %p/Inputs/i386-big.o %t.o + +RUN: llvm-objcopy %p/Inputs/x86_64-big.o %t.o +RUN: cmp %p/Inputs/x86_64-big.o %t.o + +RUN: llvm-objcopy %p/Inputs/i386.exe %t.exe +RUN: cmp %p/Inputs/i386.exe %t.exe + +RUN: llvm-objcopy %p/Inputs/x86_64.exe %t.exe +RUN: cmp %p/Inputs/x86_64.exe %t.exe + +Having exactly identical output, as this test requires, is pretty +brittle if considering any random input file. Details that can +vary are: +- The padding of executable sections (lld uses 0xcc, which is int3 on x86) +- The gap between headers and the contents of the first section + (lld currently can leave a whole empty sector inbetween) +- The actual layout of of the string table (it can be filled linearly, + strings can be dedupliated, the table can be optimized by sharing tails + of longer strings; different parts in llvm do each of these three options) +- The size indication for an empty/missing string table can either be 4 + or left out altogether +- Checksums Index: tools/llvm-objcopy/CMakeLists.txt =================================================================== --- tools/llvm-objcopy/CMakeLists.txt +++ tools/llvm-objcopy/CMakeLists.txt @@ -17,6 +17,8 @@ Buffer.cpp CopyConfig.cpp llvm-objcopy.cpp + COFF/COFFObjcopy.cpp + COFF/Object.cpp ELF/ELFObjcopy.cpp ELF/Object.cpp DEPENDS Index: tools/llvm-objcopy/COFF/COFFObjcopy.h =================================================================== --- /dev/null +++ tools/llvm-objcopy/COFF/COFFObjcopy.h @@ -0,0 +1,31 @@ +//===- COFFObjcopy.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_OBJCOPY_COFFOBJCOPY_H +#define LLVM_TOOLS_OBJCOPY_COFFOBJCOPY_H + +namespace llvm { + +namespace object { +class COFFObjectFile; +} // end namespace object + +namespace objcopy { +struct CopyConfig; +class Buffer; + +namespace coff { +void executeObjcopyOnBinary(const CopyConfig &Config, + object::COFFObjectFile &In, Buffer &Out); + +} // end namespace coff +} // end namespace objcopy +} // end namespace llvm + +#endif // LLVM_TOOLS_OBJCOPY_COFFOBJCOPY_H Index: tools/llvm-objcopy/COFF/COFFObjcopy.cpp =================================================================== --- /dev/null +++ tools/llvm-objcopy/COFF/COFFObjcopy.cpp @@ -0,0 +1,38 @@ +//===- COFFObjcopy.cpp ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "COFFObjcopy.h" +#include "Buffer.h" +#include "CopyConfig.h" +#include "Object.h" +#include "llvm-objcopy.h" + +#include "llvm/Object/Binary.h" +#include "llvm/Object/COFF.h" +#include + +namespace llvm { +namespace objcopy { +namespace coff { + +using namespace object; +using namespace COFF; + +void executeObjcopyOnBinary(const CopyConfig &Config, + object::COFFObjectFile &In, Buffer &Out) { + COFFReader Reader(In); + std::unique_ptr Obj = Reader.create(); + assert(Obj && "Unable to deserialize COFF object"); + COFFWriter Writer(*Obj, Out); + Writer.write(); +} + +} // end namespace coff +} // end namespace objcopy +} // end namespace llvm Index: tools/llvm-objcopy/COFF/Object.h =================================================================== --- /dev/null +++ tools/llvm-objcopy/COFF/Object.h @@ -0,0 +1,118 @@ +//===- Object.h -------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_OBJCOPY_COFF_OBJECT_H +#define LLVM_TOOLS_OBJCOPY_COFF_OBJECT_H + +#include "Buffer.h" +#include "CopyConfig.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/COFF.h" +#include "llvm/MC/StringTableBuilder.h" +#include "llvm/Object/COFF.h" +#include "llvm/Support/FileOutputBuffer.h" +#include +#include +#include +#include + +namespace llvm { +namespace objcopy { +namespace coff { + +class Object; + +using object::Binary; +using object::COFFObjectFile; + +class Writer { +protected: + Object &Obj; + Buffer &Buf; + +public: + virtual ~Writer(); + virtual void write() = 0; + + Writer(Object &O, Buffer &B) : Obj(O), Buf(B) {} +}; + +class COFFWriter : public Writer { +private: + size_t FileSize; + size_t FileAlignment; + StringTableBuilder StrTabBuilder; + + template + void finalize(CoffHeaderTy &CoffFileHeader, PeHeaderTy &PeHeader); + template + void write(CoffHeaderTy &CoffFileHeader, PeHeaderTy &PeHeader); + + void patchDebugDirectory(); + +public: + virtual ~COFFWriter() {} + + void write() override; + COFFWriter(Object &Obj, Buffer &Buf) + : Writer(Obj, Buf), StrTabBuilder(StringTableBuilder::WinCOFF) {} +}; + +class Reader { +public: + virtual ~Reader(); + virtual std::unique_ptr create() const = 0; +}; + +class COFFReader : public Reader { + const COFFObjectFile &COFFObj; + +public: + std::unique_ptr create() const override; + explicit COFFReader(const COFFObjectFile &O) : COFFObj(O) {} +}; + +struct Section { + object::coff_section Header; + ArrayRef Contents; + std::vector Relocs; + StringRef Name; +}; + +struct Symbol { + object::coff_symbol32 Sym; + StringRef Name; + ArrayRef AuxData; +}; + +struct Object { + bool IsPE = false; + + object::dos_header DosHeader; + ArrayRef DosStub; + + bool IsBigObj = false; + object::coff_file_header CoffFileHeader; + object::coff_bigobj_file_header CoffBigObjFileHeader; + + bool Is64 = false; + object::pe32_header PeHeader; + object::pe32plus_header PePlusHeader; + + std::vector DataDirectories; + std::vector
Sections; + std::vector Symbols; +}; + +} // end namespace coff +} // end namespace objcopy +} // end namespace llvm + +#endif // LLVM_TOOLS_OBJCOPY_COFF_OBJECT_H Index: tools/llvm-objcopy/COFF/Object.cpp =================================================================== --- /dev/null +++ tools/llvm-objcopy/COFF/Object.cpp @@ -0,0 +1,352 @@ +//===- Object.cpp ---------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Object.h" +#include "llvm-objcopy.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/COFF.h" +#include "llvm/Object/COFF.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileOutputBuffer.h" +#include +#include +#include +#include + +namespace llvm { +namespace objcopy { +namespace coff { + +using namespace object; +using namespace COFF; + +Writer::~Writer() {} + +Reader::~Reader() {} + +template static void copySymbol(A &Dest, const B &Src) { + static_assert(sizeof(Dest.Name.ShortName) == sizeof(Src.Name.ShortName), + "Mismatched name sizes"); + memcpy(Dest.Name.ShortName, Src.Name.ShortName, sizeof(Dest.Name.ShortName)); + Dest.Value = Src.Value; + Dest.SectionNumber = Src.SectionNumber; + Dest.Type = Src.Type; + Dest.StorageClass = Src.StorageClass; + Dest.NumberOfAuxSymbols = Src.NumberOfAuxSymbols; +} + +std::unique_ptr COFFReader::create() const { + auto Obj = llvm::make_unique(); + + const coff_file_header *CFH = nullptr; + const coff_bigobj_file_header *CBFH = nullptr; + COFFObj.getCOFFHeader(CFH); + COFFObj.getCOFFBigObjHeader(CBFH); + if (CFH) { + Obj->CoffFileHeader = *CFH; + Obj->IsBigObj = false; + } else { + if (!CBFH) + reportError(COFFObj.getFileName(), + make_error("No COFF file header returned", + object_error::parse_failed)); + Obj->CoffBigObjFileHeader = *CBFH; + Obj->IsBigObj = true; + } + + const dos_header *DH = COFFObj.getDOSHeader(); + Obj->Is64 = COFFObj.is64(); + if (DH) { + Obj->IsPE = true; + Obj->DosHeader = *DH; + if (DH->AddressOfNewExeHeader > sizeof(*DH)) + Obj->DosStub = + ArrayRef(reinterpret_cast(&DH[1]), + DH->AddressOfNewExeHeader - sizeof(*DH)); + + const pe32_header *PE32 = nullptr; + const pe32plus_header *PE32Plus = nullptr; + if (COFFObj.is64()) { + if (auto EC = COFFObj.getPE32PlusHeader(PE32Plus)) + reportError(COFFObj.getFileName(), std::move(EC)); + Obj->PePlusHeader = *PE32Plus; + } else { + if (auto EC = COFFObj.getPE32Header(PE32)) + reportError(COFFObj.getFileName(), std::move(EC)); + Obj->PeHeader = *PE32; + } + + size_t NumberOfDataDirectory = + PE32Plus ? PE32Plus->NumberOfRvaAndSize : PE32->NumberOfRvaAndSize; + for (size_t I = 0; I < NumberOfDataDirectory; I++) { + const data_directory *Dir; + if (auto EC = COFFObj.getDataDirectory(I, Dir)) + reportError(COFFObj.getFileName(), std::move(EC)); + Obj->DataDirectories.emplace_back(*Dir); + } + } + + for (size_t I = 1, E = COFFObj.getNumberOfSections(); I <= E; I++) { + const coff_section *Sec; + if (auto EC = COFFObj.getSection(I, Sec)) + reportError(COFFObj.getFileName(), std::move(EC)); + Obj->Sections.push_back(Section()); + Section &S = Obj->Sections.back(); + S.Header = *Sec; + if (auto EC = COFFObj.getSectionContents(Sec, S.Contents)) + reportError(COFFObj.getFileName(), std::move(EC)); + ArrayRef Relocs = COFFObj.getRelocations(Sec); + S.Relocs.insert(S.Relocs.end(), Relocs.begin(), Relocs.end()); + if (auto EC = COFFObj.getSectionName(Sec, S.Name)) + reportError(COFFObj.getFileName(), std::move(EC)); + if (Sec->hasExtendedRelocations()) + reportError( + COFFObj.getFileName(), + make_error("Extended relocations not supported yet", + object_error::parse_failed)); + } + + for (uint32_t I = 0, E = COFFObj.getRawNumberOfSymbols(); I < E;) { + Expected SymOrErr = COFFObj.getSymbol(I); + if (!SymOrErr) + reportError(COFFObj.getFileName(), SymOrErr.takeError()); + COFFSymbolRef SymRef = *SymOrErr; + + Obj->Symbols.push_back(Symbol()); + Symbol &Sym = Obj->Symbols.back(); + if (Obj->IsBigObj) + copySymbol(Sym.Sym, + *reinterpret_cast(SymRef.getRawPtr())); + else + copySymbol(Sym.Sym, + *reinterpret_cast(SymRef.getRawPtr())); + if (auto EC = COFFObj.getSymbolName(SymRef, Sym.Name)) + reportError(COFFObj.getFileName(), std::move(EC)); + Sym.AuxData = COFFObj.getSymbolAuxData(SymRef); + assert((Sym.AuxData.size() % (Obj->IsBigObj ? sizeof(coff_symbol32) + : sizeof(coff_symbol16))) == 0); + I += 1 + SymRef.getNumberOfAuxSymbols(); + } + + return Obj; +} + +template +void COFFWriter::finalize(CoffHeaderTy &CoffFileHeader, PeHeaderTy &PeHeader) { + size_t SizeOfHeaders = 0; + size_t FileAlignment = 1; + if (Obj.IsPE) { + Obj.DosHeader.AddressOfNewExeHeader = + sizeof(Obj.DosHeader) + Obj.DosStub.size(); + SizeOfHeaders += Obj.DosHeader.AddressOfNewExeHeader + sizeof(PEMagic); + + FileAlignment = PeHeader.FileAlignment; + PeHeader.NumberOfRvaAndSize = Obj.DataDirectories.size(); + + SizeOfHeaders += + sizeof(PeHeader) + sizeof(data_directory) * Obj.DataDirectories.size(); + } + CoffFileHeader.NumberOfSections = Obj.Sections.size(); + SizeOfHeaders += sizeof(CoffFileHeader); + SizeOfHeaders += sizeof(coff_section) * Obj.Sections.size(); + SizeOfHeaders = alignTo(SizeOfHeaders, FileAlignment); + + // Directly accessing Obj.CoffFileHeader, as CoffBigObjFileHeader doesn't + // have this field. + if (!Obj.IsBigObj && Obj.IsPE) + Obj.CoffFileHeader.SizeOfOptionalHeader = + sizeof(PeHeader) + sizeof(data_directory) * Obj.DataDirectories.size(); + + FileSize = SizeOfHeaders; + size_t SizeOfInitializedData = 0; + for (auto &S : Obj.Sections) { + if (S.Header.SizeOfRawData > 0) + S.Header.PointerToRawData = FileSize; + FileSize += S.Header.SizeOfRawData; // For executables, this is aligned to + // FileAlignment. + if (S.Header.NumberOfRelocations > 0) + S.Header.PointerToRelocations = FileSize; + FileSize += S.Relocs.size() * sizeof(coff_relocation); + FileSize = alignTo(FileSize, FileAlignment); + if (S.Name.size() > COFF::NameSize) + StrTabBuilder.add(S.Name); + if (S.Header.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) + SizeOfInitializedData += S.Header.SizeOfRawData; + } + if (Obj.IsPE) { + PeHeader.SizeOfHeaders = SizeOfHeaders; + PeHeader.SizeOfInitializedData = SizeOfInitializedData; + PeHeader.CheckSum = 0; + if (!Obj.Sections.empty()) { + const Section &S = Obj.Sections.back(); + PeHeader.SizeOfImage = + alignTo(S.Header.VirtualAddress + S.Header.VirtualSize, + PeHeader.SectionAlignment); + } + } + + size_t SymTabSize = Obj.Symbols.size() * sizeof(SymbolTy); + for (const auto &S : Obj.Symbols) { + SymTabSize += S.AuxData.size(); + if (S.Name.size() > COFF::NameSize) + StrTabBuilder.add(S.Name); + } + + StrTabBuilder.finalize(); + + for (auto &S : Obj.Sections) { + if (S.Name.size() > COFF::NameSize) { + snprintf(S.Header.Name, sizeof(S.Header.Name), "/%d", + (int)StrTabBuilder.getOffset(S.Name)); + } else { + strncpy(S.Header.Name, S.Name.data(), COFF::NameSize); + } + } + for (auto &S : Obj.Symbols) { + if (S.Name.size() > COFF::NameSize) { + S.Sym.Name.Offset.Zeroes = 0; + S.Sym.Name.Offset.Offset = StrTabBuilder.getOffset(S.Name); + } else { + strncpy(S.Sym.Name.ShortName, S.Name.data(), COFF::NameSize); + } + } + + size_t StrTabSize = StrTabBuilder.getSize(); + size_t PointerToSymbolTable = FileSize; + if (SymTabSize == 0 && StrTabSize <= 4) { + PointerToSymbolTable = 0; + // For executables, skip the length field of an empty string table. + if (Obj.IsPE) + StrTabSize = 0; + } + + size_t NumRawSymbols = SymTabSize / sizeof(SymbolTy); + CoffFileHeader.PointerToSymbolTable = PointerToSymbolTable; + CoffFileHeader.NumberOfSymbols = NumRawSymbols; + FileSize += SymTabSize + StrTabSize; + FileSize = alignTo(FileSize, FileAlignment); +} + +template +void COFFWriter::write(CoffHeaderTy &CoffFileHeader, PeHeaderTy &PeHeader) { + finalize(CoffFileHeader, PeHeader); + + Buf.allocate(FileSize); + + uint8_t *Ptr = Buf.getBufferStart(); + if (Obj.IsPE) { + memcpy(Ptr, &Obj.DosHeader, sizeof(Obj.DosHeader)); + Ptr += sizeof(Obj.DosHeader); + memcpy(Ptr, Obj.DosStub.data(), Obj.DosStub.size()); + Ptr += Obj.DosStub.size(); + memcpy(Ptr, PEMagic, sizeof(PEMagic)); + Ptr += sizeof(PEMagic); + } + memcpy(Ptr, &CoffFileHeader, sizeof(CoffFileHeader)); + Ptr += sizeof(CoffFileHeader); + if (Obj.IsPE) { + memcpy(Ptr, &PeHeader, sizeof(PeHeader)); + Ptr += sizeof(PeHeader); + for (const auto &DD : Obj.DataDirectories) { + memcpy(Ptr, &DD, sizeof(DD)); + Ptr += sizeof(DD); + } + } + for (const auto &S : Obj.Sections) { + memcpy(Ptr, &S.Header, sizeof(S.Header)); + Ptr += sizeof(S.Header); + } + + for (const auto &S : Obj.Sections) { + Ptr = Buf.getBufferStart() + S.Header.PointerToRawData; + memcpy(Ptr, S.Contents.data(), S.Contents.size()); + if ((S.Header.Characteristics & IMAGE_SCN_CNT_CODE) && + S.Header.SizeOfRawData > S.Contents.size()) + memset(Ptr + S.Contents.size(), 0xcc, + S.Header.SizeOfRawData - S.Contents.size()); + Ptr += S.Header.SizeOfRawData; + memcpy(Ptr, S.Relocs.data(), S.Relocs.size() * sizeof(coff_relocation)); + } + + Ptr = Buf.getBufferStart() + CoffFileHeader.PointerToSymbolTable; + for (const auto &S : Obj.Symbols) { + copySymbol(*reinterpret_cast(Ptr), + S.Sym); + Ptr += sizeof(SymbolTy); + memcpy(Ptr, S.AuxData.data(), S.AuxData.size()); + Ptr += S.AuxData.size(); + } + if (StrTabBuilder.getSize() > 4 || !Obj.IsPE) { + // Always write a string table in object files, even an empty one. + StrTabBuilder.write(Ptr); + Ptr += StrTabBuilder.getSize(); + } + + if (Obj.IsPE && Obj.DataDirectories.size() >= DEBUG_DIRECTORY) + patchDebugDirectory(); + + if (auto E = Buf.commit()) + reportError(Buf.getName(), errorToErrorCode(std::move(E))); +} + +void COFFWriter::patchDebugDirectory() { + const data_directory *Dir = &Obj.DataDirectories[DEBUG_DIRECTORY]; + if (Dir->Size <= 0) + return; + bool Found = false; + for (const auto &S : Obj.Sections) { + if (Dir->RelativeVirtualAddress >= S.Header.VirtualAddress && + Dir->RelativeVirtualAddress < + S.Header.VirtualAddress + S.Header.SizeOfRawData) { + if (Dir->RelativeVirtualAddress + Dir->Size > + S.Header.VirtualAddress + S.Header.SizeOfRawData) + reportError(Buf.getName(), + make_error( + "Debug directory extends past end of section", + object_error::parse_failed)); + size_t Offset = Dir->RelativeVirtualAddress - S.Header.VirtualAddress; + uint8_t *Ptr = Buf.getBufferStart() + S.Header.PointerToRawData + Offset; + uint8_t *End = Ptr + Dir->Size; + while (Ptr < End) { + debug_directory *Debug = reinterpret_cast(Ptr); + Debug->PointerToRawData = + S.Header.PointerToRawData + Offset + sizeof(debug_directory); + Ptr += sizeof(debug_directory) + Debug->SizeOfData; + Offset += sizeof(debug_directory) + Debug->SizeOfData; + } + Found = true; + break; + } + } + if (!Found) + reportError(Buf.getName(), + make_error("Debug directory missing", + object_error::parse_failed)); +} + +void COFFWriter::write() { + assert(!(Obj.IsBigObj && Obj.IsPE) && + "Can't have BigObj headers in a PE executable"); + if (Obj.IsBigObj) + write( + Obj.CoffBigObjFileHeader, Obj.PeHeader); + else if (Obj.Is64) + write(Obj.CoffFileHeader, + Obj.PePlusHeader); + else + write(Obj.CoffFileHeader, + Obj.PeHeader); +} + +} // end namespace coff +} // end namespace objcopy +} // end namespace llvm Index: tools/llvm-objcopy/llvm-objcopy.cpp =================================================================== --- tools/llvm-objcopy/llvm-objcopy.cpp +++ tools/llvm-objcopy/llvm-objcopy.cpp @@ -9,6 +9,7 @@ #include "llvm-objcopy.h" #include "Buffer.h" +#include "COFF/COFFObjcopy.h" #include "CopyConfig.h" #include "ELF/ELFObjcopy.h" @@ -19,6 +20,7 @@ #include "llvm/Object/Archive.h" #include "llvm/Object/ArchiveWriter.h" #include "llvm/Object/Binary.h" +#include "llvm/Object/COFF.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ELFTypes.h" #include "llvm/Object/Error.h" @@ -125,6 +127,8 @@ Buffer &Out) { if (auto *ELFBinary = dyn_cast(&In)) return elf::executeObjcopyOnBinary(Config, *ELFBinary, Out); + else if (auto *COFFBinary = dyn_cast(&In)) + return coff::executeObjcopyOnBinary(Config, *COFFBinary, Out); else error("Unsupported object file format"); }