diff --git a/llvm/include/llvm/BinaryFormat/XCOFF.h b/llvm/include/llvm/BinaryFormat/XCOFF.h --- a/llvm/include/llvm/BinaryFormat/XCOFF.h +++ b/llvm/include/llvm/BinaryFormat/XCOFF.h @@ -22,6 +22,20 @@ enum { SectionNameSize = 8, SymbolNameSize = 8 }; enum ReservedSectionNum { N_DEBUG = -2, N_ABS = -1, N_UNDEF = 0 }; +// Flags for the headers f_flag field. +enum HeaderFlags { + F_RELFLG = 0x0001, + F_EXEC = 0x0002, + F_LNNO = 0x0004, + F_FDPR_PROF = 0x0010, + F_FDPR_OPTI = 0x0020, + F_DSA = 0x0040, + F_VARPG = 0x0100, + F_DYNLOAD = 0x1000, + F_SHROBJ = 0x2000, + F_LOADONLY = 0x4000 +}; + // Flags for defining the section type. Used for the s_flags field of // the section header structure. Defined in the system header `scnhdr.h`. enum SectionTypeFlags { diff --git a/llvm/include/llvm/Object/COFF.h b/llvm/include/llvm/Object/COFF.h --- a/llvm/include/llvm/Object/COFF.h +++ b/llvm/include/llvm/Object/COFF.h @@ -1063,6 +1063,7 @@ StringRef &PDBFileName) const; bool isRelocatableObject() const override; + bool isExecutableObject() const override; bool is64() const { return PE32PlusHeader; } StringRef mapDebugSectionName(StringRef Name) const override; diff --git a/llvm/include/llvm/Object/ELFObjectFile.h b/llvm/include/llvm/Object/ELFObjectFile.h --- a/llvm/include/llvm/Object/ELFObjectFile.h +++ b/llvm/include/llvm/Object/ELFObjectFile.h @@ -433,6 +433,7 @@ elf_symbol_iterator_range getDynamicSymbolIterators() const override; bool isRelocatableObject() const override; + bool isExecutableObject() const override; }; using ELF32LEObjectFile = ELFObjectFile; @@ -1198,6 +1199,10 @@ return EF.getHeader()->e_type == ELF::ET_REL; } +template bool ELFObjectFile::isExecutableObject() const { + return EF.getHeader()->e_type == ELF::ET_EXEC; +} + } // end namespace object } // end namespace llvm diff --git a/llvm/include/llvm/Object/MachO.h b/llvm/include/llvm/Object/MachO.h --- a/llvm/include/llvm/Object/MachO.h +++ b/llvm/include/llvm/Object/MachO.h @@ -574,7 +574,8 @@ static Triple getHostArch(); bool isRelocatableObject() const override; - + bool isExecutableObject() const override; + StringRef mapDebugSectionName(StringRef Name) const override; bool hasPageZeroSegment() const { return HasPageZeroSegment; } diff --git a/llvm/include/llvm/Object/ObjectFile.h b/llvm/include/llvm/Object/ObjectFile.h --- a/llvm/include/llvm/Object/ObjectFile.h +++ b/llvm/include/llvm/Object/ObjectFile.h @@ -336,6 +336,9 @@ /// True if this is a relocatable object (.o/.obj). virtual bool isRelocatableObject() const = 0; + + /// True if this is an executable object. + virtual bool isExecutableObject() const = 0; /// @returns Pointer to ObjectFile subclass to handle this type of object. /// @param ObjectPath The path to the object file. ObjectPath.isObject must diff --git a/llvm/include/llvm/Object/Wasm.h b/llvm/include/llvm/Object/Wasm.h --- a/llvm/include/llvm/Object/Wasm.h +++ b/llvm/include/llvm/Object/Wasm.h @@ -202,6 +202,7 @@ Triple::ArchType getArch() const override; SubtargetFeatures getFeatures() const override; bool isRelocatableObject() const override; + bool isExecutableObject() const override; bool isSharedObject() const; struct ReadContext { diff --git a/llvm/include/llvm/Object/XCOFFObjectFile.h b/llvm/include/llvm/Object/XCOFFObjectFile.h --- a/llvm/include/llvm/Object/XCOFFObjectFile.h +++ b/llvm/include/llvm/Object/XCOFFObjectFile.h @@ -155,6 +155,7 @@ SubtargetFeatures getFeatures() const override; Expected getStartAddress() const override; bool isRelocatableObject() const override; + bool isExecutableObject() const override; XCOFFObjectFile(MemoryBufferRef Object, std::error_code &EC); diff --git a/llvm/lib/Object/COFFObjectFile.cpp b/llvm/lib/Object/COFFObjectFile.cpp --- a/llvm/lib/Object/COFFObjectFile.cpp +++ b/llvm/lib/Object/COFFObjectFile.cpp @@ -1307,6 +1307,10 @@ return !DataDirectory; } +bool COFFObjectFile::isExecutableObject() const { + return getCharacteristics() & COFF::IMAGE_FILE_EXECUTABLE_IMAGE; +} + StringRef COFFObjectFile::mapDebugSectionName(StringRef Name) const { return StringSwitch(Name) .Case("eh_fram", "eh_frame") diff --git a/llvm/lib/Object/MachOObjectFile.cpp b/llvm/lib/Object/MachOObjectFile.cpp --- a/llvm/lib/Object/MachOObjectFile.cpp +++ b/llvm/lib/Object/MachOObjectFile.cpp @@ -4631,6 +4631,10 @@ return getHeader().filetype == MachO::MH_OBJECT; } +bool MachOObjectFile::isExecutableObject() const { + return getHeader().filetype == MachO::MH_EXECUTE; +} + Expected> ObjectFile::createMachOObjectFile(MemoryBufferRef Buffer, uint32_t UniversalCputype, diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp --- a/llvm/lib/Object/WasmObjectFile.cpp +++ b/llvm/lib/Object/WasmObjectFile.cpp @@ -1537,6 +1537,10 @@ bool WasmObjectFile::isRelocatableObject() const { return HasLinkingSection; } +bool WasmObjectFile::isExecutableObject() const { + return !isRelocatableObject() && !isSharedObject(); +} + bool WasmObjectFile::isSharedObject() const { return HasDylinkSection; } const WasmSection &WasmObjectFile::getWasmSection(DataRefImpl Ref) const { diff --git a/llvm/lib/Object/XCOFFObjectFile.cpp b/llvm/lib/Object/XCOFFObjectFile.cpp --- a/llvm/lib/Object/XCOFFObjectFile.cpp +++ b/llvm/lib/Object/XCOFFObjectFile.cpp @@ -329,6 +329,10 @@ return Result; } +bool XCOFFObjectFile::isExecutableObject() const { + return getFileHeader()->Flags == XCOFF::F_EXEC; +} + Expected XCOFFObjectFile::getStartAddress() const { // TODO FIXME Should get from auxiliary_header->o_entry when support for the // auxiliary_header is added. diff --git a/llvm/unittests/Object/CMakeLists.txt b/llvm/unittests/Object/CMakeLists.txt --- a/llvm/unittests/Object/CMakeLists.txt +++ b/llvm/unittests/Object/CMakeLists.txt @@ -7,6 +7,7 @@ MinidumpTest.cpp SymbolSizeTest.cpp SymbolicFileTest.cpp + ObjectFileTest.cpp ) target_link_libraries(ObjectTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/Object/ObjectFileTest.cpp b/llvm/unittests/Object/ObjectFileTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Object/ObjectFileTest.cpp @@ -0,0 +1,203 @@ +//===- ObjectFileTest.cpp - Tests for object::ObjectFile ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/ObjectFile.h" +#include "llvm/Object/COFF.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/ELFTypes.h" +#include "llvm/Object/MachO.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace llvm; +using namespace object; + +static void createGenericELFHeader(ELF64LE::Ehdr &Header) { + memset(&Header, 0, sizeof(Header)); + const char e_ident[16] = { + '\177', 'E', 'L', 'F', + ELF::ELFCLASS64, + ELF::ELFDATA2LSB, + ELF::EV_CURRENT, + 0, + 0, + 9, 0, 0, 0, 0, 0, + 16 + }; + memcpy(&Header.e_ident, e_ident, 16); + Header.e_machine = ELF::EM_X86_64; + Header.e_version = ELF::EV_CURRENT; + Header.e_ehsize = sizeof(Header); +} + +std::vector ELF_ETypes = { + ELF::ET_NONE, + ELF::ET_REL, + ELF::ET_DYN, + ELF::ET_EXEC, + ELF::ET_CORE, + // these are arbitarily chosen between LOPROC and HIPROC. + ELF::ET_LOPROC + 20, + ELF::ET_LOPROC + 38, + ELF::ET_LOPROC + 50, + ELF::ET_LOPROC + 72, +}; + +std::function SetELFType = + [](ELF64LE::Ehdr &Header, unsigned Type) { Header.e_type = Type; }; + +static void createGenericCOFFHeader(coff_file_header &Header) { + memset(&Header, 0, sizeof(Header)); + Header.Machine = COFF::MachineTypes::IMAGE_FILE_MACHINE_AMD64; +} + +std::vector COFF_Characteristcs{ + COFF::IMAGE_FILE_EXECUTABLE_IMAGE | COFF::IMAGE_FILE_DEBUG_STRIPPED, + COFF::IMAGE_FILE_LARGE_ADDRESS_AWARE | COFF::IMAGE_FILE_DLL | + COFF::IMAGE_FILE_LOCAL_SYMS_STRIPPED, + COFF::IMAGE_FILE_BYTES_REVERSED_HI | COFF::IMAGE_FILE_LINE_NUMS_STRIPPED, + COFF::IMAGE_FILE_AGGRESSIVE_WS_TRIM, +}; + +std::function SetCOFFType = + [](coff_file_header &Header, unsigned Type) { + Header.Characteristics = Type; + }; + +static void createGenericMachHeader(MachO::mach_header_64 &Header) { + memset(&Header, 0, sizeof(Header)); + Header.magic = MachO::MH_MAGIC_64; + Header.cputype = MachO::CPU_TYPE_X86; +} + +std::vector MachO_Filetypes = { + MachO::MH_OBJECT, MachO::MH_EXECUTE, MachO::MH_FVMLIB, + MachO::MH_CORE, MachO::MH_PRELOAD, MachO::MH_DYLINKER, + MachO::MH_BUNDLE, MachO::MH_DSYM, MachO::MH_KEXT_BUNDLE}; + +auto isExecutable = [](ObjectFile &Object) -> bool { + return Object.isExecutableObject(); +}; + +auto isRelocatable = [](ObjectFile &Object) -> bool { + return Object.isRelocatableObject(); +}; + +template +static void testFileType(header_t Header, unsigned Type, + std::function TestMethod, + std::function KnownTrue, + std::function SetType, + std::vector PossibleTypes, + file_magic magic = file_magic::unknown) { + + StringRef Bytes(reinterpret_cast(&Header), sizeof(Header)); + auto ObjFileOrErr = + ObjectFile::createObjectFile(MemoryBufferRef(Bytes, ""), magic); + ASSERT_TRUE(!!ObjFileOrErr) << "invalid object file format"; + + ObjectFile &ObjFile = *ObjFileOrErr.get(); + + // COFFObjectFile and ELFObjectFile just use the buffer directly, + // it doesn't copy so theres no reason to call + // ObjectFile::createObjectFile every time, this is enough. + for (auto CurrType : PossibleTypes) { + SetType(Header, CurrType); + if (KnownTrue(CurrType)) + EXPECT_TRUE(TestMethod(ObjFile)); + else + EXPECT_FALSE(TestMethod(ObjFile)); + } + + // Already would have been tested but feels more comfortable to explicitly + // test what we are testing. + SetType(Header, Type); + EXPECT_TRUE(TestMethod(ObjFile)); +} + +TEST(IsExecutableObject, ELF) { + ELF64LE::Ehdr Header; + createGenericELFHeader(Header); + + std::function KnownTrue = [](unsigned Type) -> bool { + return Type == ELF::ET_EXEC; + }; + + testFileType(Header, ELF::ET_EXEC, isExecutable, KnownTrue, SetELFType, + ELF_ETypes); +} + +TEST(IsRelocatableObject, ELF) { + ELF64LE::Ehdr Header; + createGenericELFHeader(Header); + + auto KnownTrue = [](unsigned Type) -> bool { return Type == ELF::ET_REL; }; + + testFileType(Header, ELF::ET_REL, isRelocatable, KnownTrue, SetELFType, + ELF_ETypes); +} + +TEST(IsExecutableObject, COFF) { + coff_file_header Header; + createGenericCOFFHeader(Header); + + auto KnownTrue = [](unsigned Type) -> bool { + return Type & COFF::IMAGE_FILE_EXECUTABLE_IMAGE; + }; + + testFileType(Header, COFF::IMAGE_FILE_EXECUTABLE_IMAGE, isExecutable, + KnownTrue, SetCOFFType, COFF_Characteristcs, + file_magic::coff_object); +} + +// A test for isRelocatableObject for COFF is notably missing because +// this isn't identified in the header for COFF and is more involved, not what +// testFileType can do. + +// MachO are tested differently because they are constructed differently +// parseHeader uses memcpy so we can't test in the same way that we do with +// COFF and ELF which don't do this. It made testFileType too ugly to accomadate +// this. +TEST(IsExecutableObject, MachO) { + MachO::mach_header_64 Header; + createGenericMachHeader(Header); + + StringRef Bytes(reinterpret_cast(&Header), sizeof(Header)); + + for (auto Type : MachO_Filetypes) { + Header.filetype = Type; + auto ObjFileOrErr = ObjectFile::createObjectFile(MemoryBufferRef(Bytes, ""), + file_magic::macho_object); + ASSERT_TRUE(!!ObjFileOrErr); + if (Type == MachO::MH_EXECUTE) + EXPECT_TRUE(ObjFileOrErr.get()->isExecutableObject()); + else + EXPECT_FALSE(ObjFileOrErr.get()->isExecutableObject()); + } +} + +TEST(IsRelocatableObject, MachO) { + MachO::mach_header_64 Header; + createGenericMachHeader(Header); + + StringRef Bytes(reinterpret_cast(&Header), sizeof(Header)); + + for (auto Type : MachO_Filetypes) { + Header.filetype = Type; + auto ObjFileOrErr = ObjectFile::createObjectFile(MemoryBufferRef(Bytes, ""), + file_magic::macho_object); + ASSERT_TRUE(!!ObjFileOrErr); + if (Type == MachO::MH_OBJECT) + EXPECT_TRUE(ObjFileOrErr.get()->isRelocatableObject()); + else + EXPECT_FALSE(ObjFileOrErr.get()->isRelocatableObject()); + } +}