From 4c137bad6b663ff342064c293a49fecf03498207 Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 27 Feb 2008 16:26:35 -0500 Subject: [PATCH] ENH: Add ELF file parsing - Enabled when system provides elf.h - Introduce cmELF class to parse ELF files - Use in cmSystemTools::GuessLibrarySOName to really get soname --- Source/CMakeLists.txt | 15 + Source/cmConfigure.cmake.h.in | 1 + Source/cmELF.cxx | 612 ++++++++++++++++++++++++++++++++++ Source/cmELF.h | 76 +++++ Source/cmSystemTools.cxx | 14 + 5 files changed, 718 insertions(+) create mode 100644 Source/cmELF.cxx create mode 100644 Source/cmELF.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 987e90831..39c0731d3 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -1,3 +1,11 @@ +# Check if we can build support for ELF parsing. +CHECK_INCLUDE_FILE("elf.h" HAVE_ELF_H) +IF(HAVE_ELF_H) + SET(CMAKE_USE_ELF_PARSER 1) +ELSE(HAVE_ELF_H) + SET(CMAKE_USE_ELF_PARSER) +ENDIF(HAVE_ELF_H) + # configure the .h file CONFIGURE_FILE( "${CMake_SOURCE_DIR}/Source/cmConfigure.cmake.h.in" @@ -75,6 +83,12 @@ IF(CMAKE_REGENERATE_YACCLEX) ENDIF(FLEX_EXECUTABLE) ENDIF(CMAKE_REGENERATE_YACCLEX) + +# Check if we can build the ELF parser. +IF(CMAKE_USE_ELF_PARSER) + SET(ELF_SRCS cmELF.h cmELF.cxx) +ENDIF(CMAKE_USE_ELF_PARSER) + # # Sources for CMakeLib # @@ -123,6 +137,7 @@ SET(SRCS cmDocumentVariables.cxx cmDynamicLoader.cxx cmDynamicLoader.h + ${ELF_SRCS} cmExprLexer.cxx cmExprParser.cxx cmExprParserHelper.cxx diff --git a/Source/cmConfigure.cmake.h.in b/Source/cmConfigure.cmake.h.in index dd0362766..ba4a6c9da 100644 --- a/Source/cmConfigure.cmake.h.in +++ b/Source/cmConfigure.cmake.h.in @@ -20,6 +20,7 @@ #cmakedefine CMAKE_NO_ANSI_FOR_SCOPE #cmakedefine HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE #cmakedefine HAVE_UNSETENV +#cmakedefine CMAKE_USE_ELF_PARSER #cmakedefine CMAKE_STRICT #define CMAKE_ROOT_DIR "${CMake_SOURCE_DIR}" #define CMAKE_BUILD_DIR "${CMake_BINARY_DIR}" diff --git a/Source/cmELF.cxx b/Source/cmELF.cxx new file mode 100644 index 000000000..36ac61f8b --- /dev/null +++ b/Source/cmELF.cxx @@ -0,0 +1,612 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#include "cmStandardIncludes.h" // to get CMAKE_USE_ELF_PARSER first +#include "cmELF.h" + +#include + +// Need the native byte order of the running CPU. +#include + +// Include the ELF format information system header. +#include + +//---------------------------------------------------------------------------- +// Low-level byte swapping implementation. +template struct cmELFByteSwapSize {}; +void cmELFByteSwap(char*, cmELFByteSwapSize<1> const&) +{ +} +void cmELFByteSwap(char* data, cmELFByteSwapSize<2> const&) +{ + char one_byte; + one_byte = data[0]; data[0] = data[1]; data[1] = one_byte; +} +void cmELFByteSwap(char* data, cmELFByteSwapSize<4> const&) +{ + char one_byte; + one_byte = data[0]; data[0] = data[3]; data[3] = one_byte; + one_byte = data[1]; data[1] = data[2]; data[2] = one_byte; +} +void cmELFByteSwap(char* data, cmELFByteSwapSize<8> const&) +{ + char one_byte; + one_byte = data[0]; data[0] = data[7]; data[7] = one_byte; + one_byte = data[1]; data[1] = data[6]; data[6] = one_byte; + one_byte = data[2]; data[2] = data[5]; data[5] = one_byte; + one_byte = data[3]; data[3] = data[4]; data[4] = one_byte; +} + +// Low-level byte swapping interface. +template +void cmELFByteSwap(T& x) +{ + cmELFByteSwap(reinterpret_cast(&x), cmELFByteSwapSize()); +} + +//---------------------------------------------------------------------------- +class cmELFInternal +{ +public: + enum ByteOrderType { ByteOrderMSB, ByteOrderLSB }; + + // Construct and take ownership of the file stream object. + cmELFInternal(cmELF* external, + cmsys::auto_ptr& fin, + ByteOrderType order): + External(external), + Stream(*fin.release()), + ByteOrder(order), + ELFType(cmELF::FileTypeInvalid) + { + // Decide whether we need to byte swap fields. +#if cmsys_CPU_ENDIAN_ID == cmsys_CPU_ENDIAN_ID_LITTLE + this->NeedSwap = (this->ByteOrder == ByteOrderMSB); +#elif cmsys_CPU_ENDIAN_ID == cmsys_CPU_ENDIAN_ID_BIG + this->NeedSwap = (this->ByteOrder == ByteOrderLSB); +#endif + + // We have not yet loaded the section info. + this->DynamicSectionIndex = -1; + } + + // Destruct and delete the file stream object. + ~cmELFInternal() + { + delete &this->Stream; + } + + // Forward to the per-class implementation. + virtual unsigned int GetNumberOfSections() const = 0; + virtual bool GetSOName(std::string& soname) = 0; + virtual void PrintInfo(std::ostream& os) const = 0; + + // Return the recorded ELF type. + cmELF::FileType GetFileType() const { return this->ELFType; } +protected: + // Data common to all ELF class implementations. + + // The external cmELF object. + cmELF* External; + + // The stream from which to read. + std::istream& Stream; + + // The byte order of the ELF file. + ByteOrderType ByteOrder; + + // The ELF file type. + cmELF::FileType ELFType; + + // Whether we need to byte-swap structures read from the stream. + bool NeedSwap; + + // The section header index of the DYNAMIC section (-1 if none). + int DynamicSectionIndex; + + // Helper methods for subclasses. + void SetErrorMessage(const char* msg) + { + this->External->ErrorMessage = msg; + this->ELFType = cmELF::FileTypeInvalid; + } +}; + +//---------------------------------------------------------------------------- +// Configure the implementation template for 32-bit ELF files. +struct cmELFTypes32 +{ + typedef Elf32_Ehdr ELF_Ehdr; + typedef Elf32_Shdr ELF_Shdr; + typedef Elf32_Dyn ELF_Dyn; + typedef Elf32_Half ELF_Half; + static const char* GetName() { return "32-bit"; } +}; + +// Configure the implementation template for 32-bit ELF files. +struct cmELFTypes64 +{ + typedef Elf64_Ehdr ELF_Ehdr; + typedef Elf64_Shdr ELF_Shdr; + typedef Elf64_Dyn ELF_Dyn; + typedef Elf64_Half ELF_Half; + static const char* GetName() { return "64-bit"; } +}; + +//---------------------------------------------------------------------------- +// Parser implementation template. +template +class cmELFInternalImpl: public cmELFInternal +{ +public: + // Copy the ELF file format types from our configuration parameter. + typedef typename Types::ELF_Ehdr ELF_Ehdr; + typedef typename Types::ELF_Shdr ELF_Shdr; + typedef typename Types::ELF_Dyn ELF_Dyn; + typedef typename Types::ELF_Half ELF_Half; + + // Construct with a stream and byte swap indicator. + cmELFInternalImpl(cmELF* external, + cmsys::auto_ptr& fin, + ByteOrderType order); + + // Return the number of sections as specified by the ELF header. + virtual unsigned int GetNumberOfSections() const + { + return static_cast(this->ELFHeader.e_shnum); + } + + // Lookup the SONAME in the DYNAMIC section. + virtual bool GetSOName(std::string& soname); + + // Print information about the ELF file. + virtual void PrintInfo(std::ostream& os) const + { + os << "ELF " << Types::GetName(); + if(this->ByteOrder == ByteOrderMSB) + { + os << " MSB"; + } + else if(this->ByteOrder == ByteOrderLSB) + { + os << " LSB"; + } + switch(this->ELFType) + { + case cmELF::FileTypeRelocatableObject: + os << " relocatable object"; + break; + case cmELF::FileTypeExecutable: + os << " executable"; + break; + case cmELF::FileTypeSharedLibrary: + os << " shared library"; + break; + case cmELF::FileTypeCore: + os << " core file"; + break; + } + os << "\n"; + } + +private: + void ByteSwap(ELF_Ehdr& elf_header) + { + cmELFByteSwap(elf_header.e_type); + cmELFByteSwap(elf_header.e_machine); + cmELFByteSwap(elf_header.e_version); + cmELFByteSwap(elf_header.e_entry); + cmELFByteSwap(elf_header.e_phoff); + cmELFByteSwap(elf_header.e_shoff); + cmELFByteSwap(elf_header.e_flags); + cmELFByteSwap(elf_header.e_ehsize); + cmELFByteSwap(elf_header.e_phentsize); + cmELFByteSwap(elf_header.e_phnum); + cmELFByteSwap(elf_header.e_shentsize); + cmELFByteSwap(elf_header.e_shnum); + cmELFByteSwap(elf_header.e_shstrndx); + } + + void ByteSwap(ELF_Shdr& sec_header) + { + cmELFByteSwap(sec_header.sh_name); + cmELFByteSwap(sec_header.sh_type); + cmELFByteSwap(sec_header.sh_flags); + cmELFByteSwap(sec_header.sh_addr); + cmELFByteSwap(sec_header.sh_offset); + cmELFByteSwap(sec_header.sh_size); + cmELFByteSwap(sec_header.sh_link); + cmELFByteSwap(sec_header.sh_info); + cmELFByteSwap(sec_header.sh_addralign); + cmELFByteSwap(sec_header.sh_entsize); + } + + void ByteSwap(ELF_Dyn& dyn) + { + cmELFByteSwap(dyn.d_tag); + switch (dyn.d_tag) + { + case DT_NULL: /* dyn.d_un ignored */ break; + case DT_NEEDED: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_PLTRELSZ: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_PLTGOT: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_HASH: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_STRTAB: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_SYMTAB: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_RELA: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_RELASZ: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_RELAENT: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_STRSZ: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_SYMENT: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_INIT: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_FINI: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_SONAME: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_RPATH: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_SYMBOLIC: /* dyn.d_un ignored */ break; + case DT_REL: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_RELSZ: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_RELENT: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_PLTREL: cmELFByteSwap(dyn.d_un.d_val); break; + case DT_DEBUG: cmELFByteSwap(dyn.d_un.d_ptr); break; + case DT_TEXTREL: /* dyn.d_un ignored */ break; + case DT_JMPREL: cmELFByteSwap(dyn.d_un.d_ptr); break; + } + } + + bool Read(ELF_Ehdr& x) + { + if(this->Stream.read(reinterpret_cast(&x), sizeof(x)) && + this->NeedSwap) + { + ByteSwap(x); + } + return this->Stream? true:false; + } + bool Read(ELF_Shdr& x) + { + if(this->Stream.read(reinterpret_cast(&x), sizeof(x)) && + this->NeedSwap) + { + ByteSwap(x); + } + return this->Stream? true:false; + } + bool Read(ELF_Dyn& x) + { + if(this->Stream.read(reinterpret_cast(&x), sizeof(x)) && + this->NeedSwap) + { + ByteSwap(x); + } + return this->Stream? true:false; + } + + bool LoadSectionHeader(ELF_Half i) + { + // Read the section header from the file. + this->Stream.seekg(this->ELFHeader.e_shoff + + this->ELFHeader.e_shentsize * i); + if(!this->Read(this->SectionHeaders[i])) + { + return false; + } + + // Identify some important sections. + if(this->SectionHeaders[i].sh_type == SHT_DYNAMIC) + { + this->DynamicSectionIndex = i; + } + return true; + } + + bool LoadDynamicSection(); + + // Store the main ELF header. + ELF_Ehdr ELFHeader; + + // Store all the section headers. + std::vector SectionHeaders; + + // Store all entries of the DYNAMIC section. + std::vector DynamicSectionEntries; + + // Store the SOName if it has been loaded. + std::string SOName; + bool SONameChecked; +}; + +//---------------------------------------------------------------------------- +template +cmELFInternalImpl +::cmELFInternalImpl(cmELF* external, + cmsys::auto_ptr& fin, + ByteOrderType order): + cmELFInternal(external, fin, order) +{ + // Initialize state. + this->SONameChecked = false; + + // Read the main header. + if(!this->Read(this->ELFHeader)) + { + return; + } + + // Determine the ELF file type. + switch(this->ELFHeader.e_type) + { + case 1: + this->ELFType = cmELF::FileTypeRelocatableObject; + break; + case 2: + this->ELFType = cmELF::FileTypeExecutable; + break; + case 3: + this->ELFType = cmELF::FileTypeSharedLibrary; + break; + case 4: + this->ELFType = cmELF::FileTypeCore; + break; + default: + return; + } + + // Load the section headers. + this->SectionHeaders.resize(this->ELFHeader.e_shnum); + for(ELF_Half i=0; i < this->ELFHeader.e_shnum; ++i) + { + if(!this->LoadSectionHeader(i)) + { + this->ELFType = cmELF::FileTypeInvalid; + return; + } + } +} + +//---------------------------------------------------------------------------- +template +bool cmELFInternalImpl::LoadDynamicSection() +{ + // If there is no dynamic section we are done. + if(this->DynamicSectionIndex < 0) + { + return false; + } + + // If the section was already loaded we are done. + if(!this->DynamicSectionEntries.empty()) + { + return true; + } + + // Allocate the dynamic section entries. + ELF_Shdr const& sec = this->SectionHeaders[this->DynamicSectionIndex]; + int n = sec.sh_size / sec.sh_entsize; + this->DynamicSectionEntries.resize(n); + + // Read each entry. + for(int j=0; j < n; ++j) + { + // Seek to the beginning of the section entry. + this->Stream.seekg(sec.sh_offset + sec.sh_entsize*j); + ELF_Dyn& dyn = this->DynamicSectionEntries[j]; + + // Try reading the entry. + if(!this->Read(dyn)) + { + this->SetErrorMessage("Error reading entry from DYNAMIC section."); + this->DynamicSectionIndex = -1; + return false; + } + } + return true; +} + +//---------------------------------------------------------------------------- +template +bool cmELFInternalImpl::GetSOName(std::string& soname) +{ + // Short-circuit if already checked. + if(this->SONameChecked) + { + soname = this->SOName; + return !soname.empty(); + } + this->SONameChecked = true; + + // Try reading the dynamic section. + if(!this->LoadDynamicSection()) + { + return false; + } + + // Get the string table referenced by the DYNAMIC section. + ELF_Shdr const& sec = this->SectionHeaders[this->DynamicSectionIndex]; + if(sec.sh_link >= this->SectionHeaders.size()) + { + this->SetErrorMessage("Section DYNAMIC has invalid string table index."); + return false; + } + ELF_Shdr const& strtab = this->SectionHeaders[sec.sh_link]; + + // Look for the soname entry. + for(typename std::vector::iterator + di = this->DynamicSectionEntries.begin(); + di != this->DynamicSectionEntries.end(); ++di) + { + ELF_Dyn& dyn = *di; + if(dyn.d_tag == DT_SONAME) + { + this->Stream.seekg(strtab.sh_offset + dyn.d_un.d_val); + char c; + while(this->Stream.get(c) && c != 0) + { + this->SOName += c; + } + if(!this->Stream) + { + this->SetErrorMessage("Dynamic section specifies unreadable SONAME."); + this->SOName = ""; + return false; + } + soname = this->SOName; + return true; + } + } + return false; +} + +//============================================================================ +// External class implementation. + +//---------------------------------------------------------------------------- +cmELF::cmELF(const char* fname): Internal(0) +{ + // Try to open the file. + cmsys::auto_ptr fin(new std::ifstream(fname)); + + // Quit now if the file could not be opened. + if(!fin.get() || !*fin) + { + this->ErrorMessage = "Error opening input file."; + return; + } + + // Read the ELF identification block. + char ident[EI_NIDENT]; + if(!fin->read(ident, EI_NIDENT)) + { + this->ErrorMessage = "Error reading ELF identification."; + return; + } + if(!fin->seekg(0)) + { + this->ErrorMessage = "Error seeking to beginning of file."; + return; + } + + // Verify the ELF identification. + if(!(ident[EI_MAG0] == ELFMAG0 && + ident[EI_MAG1] == ELFMAG1 && + ident[EI_MAG2] == ELFMAG2 && + ident[EI_MAG3] == ELFMAG3)) + { + this->ErrorMessage = "File does not have a valid ELF identification."; + return; + } + + // Check the byte order in which the rest of the file is encoded. + cmELFInternal::ByteOrderType order; + if(ident[EI_DATA] == ELFDATA2LSB) + { + // File is LSB. + order = cmELFInternal::ByteOrderLSB; + } + else if(ident[EI_DATA] == ELFDATA2MSB) + { + // File is MSB. + order = cmELFInternal::ByteOrderMSB; + } + else + { + this->ErrorMessage = "ELF file is not LSB or MSB encoded."; + return; + } + + // Check the class of the file and construct the corresponding + // parser implementation. + if(ident[EI_CLASS] == ELFCLASS32) + { + // 32-bit ELF + this->Internal = new cmELFInternalImpl(this, fin, order); + } + else if(ident[EI_CLASS] == ELFCLASS64) + { + // 64-bit ELF + this->Internal = new cmELFInternalImpl(this, fin, order); + } + else + { + this->ErrorMessage = "ELF file class is not 32-bit or 64-bit."; + return; + } +} + +//---------------------------------------------------------------------------- +cmELF::~cmELF() +{ + delete this->Internal; +} + +//---------------------------------------------------------------------------- +bool cmELF::Valid() const +{ + return this->Internal && this->Internal->GetFileType() != FileTypeInvalid; +} + +//---------------------------------------------------------------------------- +cmELF::FileType cmELF::GetFileType() const +{ + if(this->Valid()) + { + return this->Internal->GetFileType(); + } + else + { + return FileTypeInvalid; + } +} + +//---------------------------------------------------------------------------- +unsigned int cmELF::GetNumberOfSections() const +{ + if(this->Valid()) + { + return this->Internal->GetNumberOfSections(); + } + else + { + return 0; + } +} + +//---------------------------------------------------------------------------- +bool cmELF::GetSOName(std::string& soname) +{ + if(this->Valid() && + this->Internal->GetFileType() == cmELF::FileTypeSharedLibrary) + { + return this->Internal->GetSOName(soname); + } + else + { + return false; + } +} + +//---------------------------------------------------------------------------- +void cmELF::PrintInfo(std::ostream& os) const +{ + if(this->Valid()) + { + this->Internal->PrintInfo(os); + } + else + { + os << "Not a valid ELF file.\n"; + } +} diff --git a/Source/cmELF.h b/Source/cmELF.h new file mode 100644 index 000000000..11bf35473 --- /dev/null +++ b/Source/cmELF.h @@ -0,0 +1,76 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef cmELF_h +#define cmELF_h + +#if !defined(CMAKE_USE_ELF_PARSER) +# error "This file may be included only if CMAKE_USE_ELF_PARSER is enabled." +#endif + +class cmELFInternal; + +/** \class cmELF + * \brief Executable and Link Format (ELF) parser. + */ +class cmELF +{ +public: + /** Construct with the name of the ELF input file to parse. */ + cmELF(const char* fname); + + /** Destruct. */ + ~cmELF(); + + /** Get the error message if any. */ + std::string const& GetErrorMessage() const + { + return this->ErrorMessage; + } + + /** Boolean conversion. True if the ELF file is valid. */ + operator bool() const { return this->Valid(); } + + /** Enumeration of ELF file types. */ + enum FileType + { + FileTypeInvalid, + FileTypeRelocatableObject, + FileTypeExecutable, + FileTypeSharedLibrary, + FileTypeCore + }; + + /** Get the type of the file opened. */ + FileType GetFileType() const; + + /** Get the number of ELF sections present. */ + unsigned int GetNumberOfSections() const; + + /** Get the SONAME field if any. */ + bool GetSOName(std::string& soname); + + /** Print human-readable information about the ELF file. */ + void PrintInfo(std::ostream& os) const; + +private: + friend class cmELFInternal; + bool Valid() const; + cmELFInternal* Internal; + std::string ErrorMessage; +}; + +#endif diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index 8d13e11bd..aa4d1c2bf 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -52,6 +52,10 @@ # include #endif +#if defined(CMAKE_USE_ELF_PARSER) +# include "cmELF.h" +#endif + #if defined(__sgi) && !defined(__GNUC__) # pragma set woff 1375 /* base class destructor not virtual */ #endif @@ -2155,6 +2159,16 @@ void cmSystemTools::MakefileColorEcho(int color, const char* message, bool cmSystemTools::GuessLibrarySOName(std::string const& fullPath, std::string& soname) { + // For ELF shared libraries use a real parser to get the correct + // soname. +#if defined(CMAKE_USE_ELF_PARSER) + cmELF elf(fullPath.c_str()); + if(elf) + { + return elf.GetSOName(soname); + } +#endif + // If the file is not a symlink we have no guess for its soname. if(!cmSystemTools::FileIsSymlink(fullPath.c_str())) {