2009-09-28 19:43:28 +04:00
|
|
|
/*============================================================================
|
|
|
|
CMake - Cross Platform Makefile Generator
|
|
|
|
Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
|
2008-02-28 00:26:35 +03:00
|
|
|
|
2009-09-28 19:43:28 +04:00
|
|
|
Distributed under the OSI-approved BSD License (the "License");
|
|
|
|
see accompanying file Copyright.txt for details.
|
2008-02-28 00:26:35 +03:00
|
|
|
|
2009-09-28 19:43:28 +04:00
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even the
|
|
|
|
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the License for more information.
|
|
|
|
============================================================================*/
|
2008-02-28 00:26:35 +03:00
|
|
|
#include "cmStandardIncludes.h" // to get CMAKE_USE_ELF_PARSER first
|
|
|
|
#include "cmELF.h"
|
|
|
|
|
|
|
|
#include <cmsys/auto_ptr.hxx>
|
|
|
|
|
|
|
|
// Need the native byte order of the running CPU.
|
2008-05-13 18:24:23 +04:00
|
|
|
#define cmsys_CPU_UNKNOWN_OKAY // We can decide at runtime if not known.
|
2008-02-28 00:26:35 +03:00
|
|
|
#include <cmsys/CPU.h>
|
|
|
|
|
|
|
|
// Include the ELF format information system header.
|
|
|
|
#include <elf.h>
|
2008-02-28 16:43:10 +03:00
|
|
|
#if defined(__sun)
|
|
|
|
# include <sys/link.h> // For dynamic section information
|
|
|
|
#endif
|
2008-02-28 00:26:35 +03:00
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Low-level byte swapping implementation.
|
|
|
|
template <size_t s> 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 <typename T>
|
|
|
|
void cmELFByteSwap(T& x)
|
|
|
|
{
|
|
|
|
cmELFByteSwap(reinterpret_cast<char*>(&x), cmELFByteSwapSize<sizeof(T)>());
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
class cmELFInternal
|
|
|
|
{
|
|
|
|
public:
|
2008-02-29 19:12:59 +03:00
|
|
|
typedef cmELF::StringEntry StringEntry;
|
2008-02-28 00:26:35 +03:00
|
|
|
enum ByteOrderType { ByteOrderMSB, ByteOrderLSB };
|
|
|
|
|
|
|
|
// Construct and take ownership of the file stream object.
|
|
|
|
cmELFInternal(cmELF* external,
|
|
|
|
cmsys::auto_ptr<std::ifstream>& fin,
|
|
|
|
ByteOrderType order):
|
|
|
|
External(external),
|
|
|
|
Stream(*fin.release()),
|
|
|
|
ByteOrder(order),
|
|
|
|
ELFType(cmELF::FileTypeInvalid)
|
|
|
|
{
|
2008-03-03 00:19:40 +03:00
|
|
|
// In most cases the processor-specific byte order will match that
|
|
|
|
// of the target execution environment. If we choose wrong here
|
|
|
|
// it is fixed when the header is read.
|
2008-02-28 00:26:35 +03:00
|
|
|
#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);
|
2008-05-13 18:34:24 +04:00
|
|
|
#else
|
|
|
|
this->NeedSwap = false; // Final decision is at runtime anyway.
|
2008-02-28 00:26:35 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// We have not yet loaded the section info.
|
|
|
|
this->DynamicSectionIndex = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destruct and delete the file stream object.
|
2008-02-28 16:32:05 +03:00
|
|
|
virtual ~cmELFInternal()
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
|
|
|
delete &this->Stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Forward to the per-class implementation.
|
|
|
|
virtual unsigned int GetNumberOfSections() const = 0;
|
2008-04-14 23:02:24 +04:00
|
|
|
virtual unsigned int GetDynamicEntryCount() = 0;
|
|
|
|
virtual unsigned long GetDynamicEntryPosition(int j) = 0;
|
2008-02-29 19:12:59 +03:00
|
|
|
virtual StringEntry const* GetDynamicSectionString(int tag) = 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
virtual void PrintInfo(std::ostream& os) const = 0;
|
|
|
|
|
2008-04-14 23:02:24 +04:00
|
|
|
bool ReadBytes(unsigned long pos, unsigned long size, char* buf)
|
|
|
|
{
|
|
|
|
this->Stream.seekg(pos);
|
|
|
|
this->Stream.read(buf, size);
|
|
|
|
return this->Stream?true:false;
|
|
|
|
}
|
|
|
|
|
2008-02-29 19:12:59 +03:00
|
|
|
// Lookup the SONAME in the DYNAMIC section.
|
|
|
|
StringEntry const* GetSOName()
|
|
|
|
{
|
|
|
|
return this->GetDynamicSectionString(DT_SONAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup the RPATH in the DYNAMIC section.
|
|
|
|
StringEntry const* GetRPath()
|
|
|
|
{
|
|
|
|
return this->GetDynamicSectionString(DT_RPATH);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup the RUNPATH in the DYNAMIC section.
|
|
|
|
StringEntry const* GetRunPath()
|
|
|
|
{
|
|
|
|
#if defined(DT_RUNPATH)
|
|
|
|
return this->GetDynamicSectionString(DT_RUNPATH);
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2008-02-28 00:26:35 +03:00
|
|
|
// 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;
|
|
|
|
}
|
2008-02-29 19:12:59 +03:00
|
|
|
|
|
|
|
// Store string table entry states.
|
|
|
|
std::map<int, StringEntry> DynamicSectionStrings;
|
2008-02-28 00:26:35 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// 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 Types>
|
|
|
|
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<std::ifstream>& fin,
|
|
|
|
ByteOrderType order);
|
|
|
|
|
|
|
|
// Return the number of sections as specified by the ELF header.
|
|
|
|
virtual unsigned int GetNumberOfSections() const
|
|
|
|
{
|
|
|
|
return static_cast<unsigned int>(this->ELFHeader.e_shnum);
|
|
|
|
}
|
|
|
|
|
2008-04-14 23:02:24 +04:00
|
|
|
// Get the file position and size of a dynamic section entry.
|
|
|
|
virtual unsigned int GetDynamicEntryCount();
|
|
|
|
virtual unsigned long GetDynamicEntryPosition(int j);
|
|
|
|
|
2008-02-29 19:12:59 +03:00
|
|
|
// Lookup a string from the dynamic section with the given tag.
|
|
|
|
virtual StringEntry const* GetDynamicSectionString(int tag);
|
2008-02-28 00:26:35 +03:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
2008-02-28 16:32:05 +03:00
|
|
|
case cmELF::FileTypeInvalid:
|
|
|
|
os << " invalid file";
|
|
|
|
break;
|
2008-02-28 00:26:35 +03:00
|
|
|
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;
|
2008-03-03 00:19:40 +03:00
|
|
|
case cmELF::FileTypeSpecificOS:
|
|
|
|
os << " os-specific type";
|
|
|
|
break;
|
|
|
|
case cmELF::FileTypeSpecificProc:
|
|
|
|
os << " processor-specific type";
|
|
|
|
break;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
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;
|
2008-02-29 19:12:59 +03:00
|
|
|
#ifdef T_BIND_NOW
|
|
|
|
case T_BIND_NOW: /* dyn.d_un ignored */ break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_INIT_ARRAY
|
|
|
|
case DT_INIT_ARRAY: cmELFByteSwap(dyn.d_un.d_ptr); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_FINI_ARRAY
|
|
|
|
case DT_FINI_ARRAY: cmELFByteSwap(dyn.d_un.d_ptr); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_INIT_ARRAYSZ
|
|
|
|
case DT_INIT_ARRAYSZ: cmELFByteSwap(dyn.d_un.d_val); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_FINI_ARRAYSZ
|
|
|
|
case DT_FINI_ARRAYSZ: cmELFByteSwap(dyn.d_un.d_val); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_RUNPATH
|
|
|
|
case DT_RUNPATH: cmELFByteSwap(dyn.d_un.d_val); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_FLAGS
|
|
|
|
case DT_FLAGS: cmELFByteSwap(dyn.d_un.d_val); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_PREINIT_ARRAY
|
|
|
|
case DT_PREINIT_ARRAY: cmELFByteSwap(dyn.d_un.d_ptr); break;
|
|
|
|
#endif
|
|
|
|
#ifdef DT_PREINIT_ARRAYSZ
|
|
|
|
case DT_PREINIT_ARRAYSZ: cmELFByteSwap(dyn.d_un.d_val); break;
|
|
|
|
#endif
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-03 16:48:37 +03:00
|
|
|
bool FileTypeValid(ELF_Half et)
|
|
|
|
{
|
|
|
|
unsigned int eti = static_cast<unsigned int>(et);
|
|
|
|
if(eti == ET_NONE || eti == ET_REL || eti == ET_EXEC ||
|
|
|
|
eti == ET_DYN || eti == ET_CORE)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#if defined(ET_LOOS) && defined(ET_HIOS)
|
|
|
|
if(eti >= ET_LOOS && eti <= ET_HIOS)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(ET_LOPROC) && defined(ET_HIPROC)
|
|
|
|
if(eti >= ET_LOPROC && eti <= ET_HIPROC)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2008-02-28 00:26:35 +03:00
|
|
|
bool Read(ELF_Ehdr& x)
|
|
|
|
{
|
2008-03-03 00:19:40 +03:00
|
|
|
// Read the header from the file.
|
|
|
|
if(!this->Stream.read(reinterpret_cast<char*>(&x), sizeof(x)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The byte order of ELF header fields may not match that of the
|
|
|
|
// processor-specific data. The header fields are ordered to
|
|
|
|
// match the target execution environment, so we may need to
|
|
|
|
// memorize the order of all platforms based on the e_machine
|
|
|
|
// value. As a heuristic, if the type is invalid but its
|
|
|
|
// swapped value is okay then flip our swap mode.
|
|
|
|
ELF_Half et = x.e_type;
|
|
|
|
if(this->NeedSwap)
|
|
|
|
{
|
|
|
|
cmELFByteSwap(et);
|
|
|
|
}
|
2008-03-03 16:48:37 +03:00
|
|
|
if(!this->FileTypeValid(et))
|
2008-03-03 00:19:40 +03:00
|
|
|
{
|
|
|
|
cmELFByteSwap(et);
|
2008-03-03 16:48:37 +03:00
|
|
|
if(this->FileTypeValid(et))
|
2008-03-03 00:19:40 +03:00
|
|
|
{
|
|
|
|
// The previous byte order guess was wrong. Flip it.
|
|
|
|
this->NeedSwap = !this->NeedSwap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix the byte order of the header.
|
|
|
|
if(this->NeedSwap)
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
|
|
|
ByteSwap(x);
|
|
|
|
}
|
2008-03-03 00:19:40 +03:00
|
|
|
return true;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
bool Read(ELF_Shdr& x)
|
|
|
|
{
|
|
|
|
if(this->Stream.read(reinterpret_cast<char*>(&x), sizeof(x)) &&
|
|
|
|
this->NeedSwap)
|
|
|
|
{
|
|
|
|
ByteSwap(x);
|
|
|
|
}
|
|
|
|
return this->Stream? true:false;
|
|
|
|
}
|
|
|
|
bool Read(ELF_Dyn& x)
|
|
|
|
{
|
|
|
|
if(this->Stream.read(reinterpret_cast<char*>(&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<ELF_Shdr> SectionHeaders;
|
|
|
|
|
|
|
|
// Store all entries of the DYNAMIC section.
|
|
|
|
std::vector<ELF_Dyn> DynamicSectionEntries;
|
|
|
|
};
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
template <class Types>
|
|
|
|
cmELFInternalImpl<Types>
|
|
|
|
::cmELFInternalImpl(cmELF* external,
|
|
|
|
cmsys::auto_ptr<std::ifstream>& fin,
|
|
|
|
ByteOrderType order):
|
|
|
|
cmELFInternal(external, fin, order)
|
|
|
|
{
|
|
|
|
// Read the main header.
|
|
|
|
if(!this->Read(this->ELFHeader))
|
|
|
|
{
|
2008-03-03 00:19:40 +03:00
|
|
|
this->SetErrorMessage("Failed to read main ELF header.");
|
2008-02-28 00:26:35 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the ELF file type.
|
|
|
|
switch(this->ELFHeader.e_type)
|
|
|
|
{
|
2008-03-03 00:19:40 +03:00
|
|
|
case ET_NONE:
|
2008-03-03 16:48:37 +03:00
|
|
|
this->SetErrorMessage("ELF file type is NONE.");
|
2008-03-03 00:19:40 +03:00
|
|
|
return;
|
|
|
|
case ET_REL:
|
2008-02-28 00:26:35 +03:00
|
|
|
this->ELFType = cmELF::FileTypeRelocatableObject;
|
|
|
|
break;
|
2008-03-03 00:19:40 +03:00
|
|
|
case ET_EXEC:
|
2008-02-28 00:26:35 +03:00
|
|
|
this->ELFType = cmELF::FileTypeExecutable;
|
|
|
|
break;
|
2008-03-03 00:19:40 +03:00
|
|
|
case ET_DYN:
|
2008-02-28 00:26:35 +03:00
|
|
|
this->ELFType = cmELF::FileTypeSharedLibrary;
|
|
|
|
break;
|
2008-03-03 00:19:40 +03:00
|
|
|
case ET_CORE:
|
2008-02-28 00:26:35 +03:00
|
|
|
this->ELFType = cmELF::FileTypeCore;
|
|
|
|
break;
|
|
|
|
default:
|
2008-03-03 16:48:37 +03:00
|
|
|
{
|
|
|
|
unsigned int eti = static_cast<unsigned int>(this->ELFHeader.e_type);
|
|
|
|
#if defined(ET_LOOS) && defined(ET_HIOS)
|
|
|
|
if(eti >= ET_LOOS && eti <= ET_HIOS)
|
2008-03-03 00:19:40 +03:00
|
|
|
{
|
|
|
|
this->ELFType = cmELF::FileTypeSpecificOS;
|
|
|
|
break;
|
|
|
|
}
|
2008-03-03 16:48:37 +03:00
|
|
|
#endif
|
|
|
|
#if defined(ET_LOPROC) && defined(ET_HIPROC)
|
|
|
|
if(eti >= ET_LOPROC && eti <= ET_HIPROC)
|
2008-03-03 00:19:40 +03:00
|
|
|
{
|
|
|
|
this->ELFType = cmELF::FileTypeSpecificProc;
|
|
|
|
break;
|
|
|
|
}
|
2008-03-03 16:48:37 +03:00
|
|
|
#endif
|
|
|
|
cmOStringStream e;
|
|
|
|
e << "Unknown ELF file type " << eti;
|
|
|
|
this->SetErrorMessage(e.str().c_str());
|
|
|
|
return;
|
|
|
|
}
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
{
|
2008-03-03 00:19:40 +03:00
|
|
|
this->SetErrorMessage("Failed to load section headers.");
|
2008-02-28 00:26:35 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
template <class Types>
|
|
|
|
bool cmELFInternalImpl<Types>::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];
|
2009-10-06 16:33:05 +04:00
|
|
|
int n = static_cast<int>(sec.sh_size / sec.sh_entsize);
|
2008-02-28 00:26:35 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-04-14 23:02:24 +04:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
template <class Types>
|
|
|
|
unsigned int cmELFInternalImpl<Types>::GetDynamicEntryCount()
|
|
|
|
{
|
|
|
|
if(!this->LoadDynamicSection())
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
for(unsigned int i = 0; i < this->DynamicSectionEntries.size(); ++i)
|
|
|
|
{
|
|
|
|
if(this->DynamicSectionEntries[i].d_tag == DT_NULL)
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this->DynamicSectionEntries.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
template <class Types>
|
|
|
|
unsigned long cmELFInternalImpl<Types>::GetDynamicEntryPosition(int j)
|
|
|
|
{
|
|
|
|
if(!this->LoadDynamicSection())
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2008-04-15 17:00:20 +04:00
|
|
|
if(j < 0 || j >= static_cast<int>(this->DynamicSectionEntries.size()))
|
2008-04-14 23:02:24 +04:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
ELF_Shdr const& sec = this->SectionHeaders[this->DynamicSectionIndex];
|
2009-10-06 16:33:05 +04:00
|
|
|
return static_cast<unsigned long>(sec.sh_offset + sec.sh_entsize*j);
|
2008-04-14 23:02:24 +04:00
|
|
|
}
|
|
|
|
|
2008-02-28 00:26:35 +03:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
template <class Types>
|
2008-02-29 19:12:59 +03:00
|
|
|
cmELF::StringEntry const*
|
|
|
|
cmELFInternalImpl<Types>::GetDynamicSectionString(int tag)
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
|
|
|
// Short-circuit if already checked.
|
2008-02-29 19:12:59 +03:00
|
|
|
std::map<int, StringEntry>::iterator dssi =
|
|
|
|
this->DynamicSectionStrings.find(tag);
|
|
|
|
if(dssi != this->DynamicSectionStrings.end())
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
2008-02-29 19:12:59 +03:00
|
|
|
if(dssi->second.Position > 0)
|
|
|
|
{
|
|
|
|
return &dssi->second;
|
|
|
|
}
|
|
|
|
return 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
2008-02-29 19:12:59 +03:00
|
|
|
|
|
|
|
// Create an entry for this tag. Assume it is missing until found.
|
|
|
|
StringEntry& se = this->DynamicSectionStrings[tag];
|
|
|
|
se.Position = 0;
|
2008-03-01 20:50:42 +03:00
|
|
|
se.Size = 0;
|
2008-04-14 23:02:24 +04:00
|
|
|
se.IndexInSection = -1;
|
2008-02-28 00:26:35 +03:00
|
|
|
|
|
|
|
// Try reading the dynamic section.
|
|
|
|
if(!this->LoadDynamicSection())
|
|
|
|
{
|
2008-02-29 19:12:59 +03:00
|
|
|
return 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.");
|
2008-02-29 19:12:59 +03:00
|
|
|
return 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
ELF_Shdr const& strtab = this->SectionHeaders[sec.sh_link];
|
|
|
|
|
2008-02-29 19:12:59 +03:00
|
|
|
// Look for the requested entry.
|
2008-02-28 00:26:35 +03:00
|
|
|
for(typename std::vector<ELF_Dyn>::iterator
|
|
|
|
di = this->DynamicSectionEntries.begin();
|
|
|
|
di != this->DynamicSectionEntries.end(); ++di)
|
|
|
|
{
|
|
|
|
ELF_Dyn& dyn = *di;
|
2008-02-29 19:12:59 +03:00
|
|
|
if(dyn.d_tag == tag)
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
2008-03-01 20:50:42 +03:00
|
|
|
// We found the tag requested.
|
|
|
|
// Make sure the position given is within the string section.
|
|
|
|
if(dyn.d_un.d_val >= strtab.sh_size)
|
|
|
|
{
|
|
|
|
this->SetErrorMessage("Section DYNAMIC references string beyond "
|
|
|
|
"the end of its string section.");
|
|
|
|
return 0;
|
|
|
|
}
|
2008-02-29 19:12:59 +03:00
|
|
|
|
2008-03-01 20:50:42 +03:00
|
|
|
// Seek to the position reported by the entry.
|
|
|
|
unsigned long first = static_cast<unsigned long>(dyn.d_un.d_val);
|
|
|
|
unsigned long last = first;
|
|
|
|
unsigned long end = static_cast<unsigned long>(strtab.sh_size);
|
|
|
|
this->Stream.seekg(strtab.sh_offset + first);
|
|
|
|
|
|
|
|
// Read the string. It may be followed by more than one NULL
|
|
|
|
// terminator. Count the total size of the region allocated to
|
|
|
|
// the string. This assumes that the next string in the table
|
|
|
|
// is non-empty, but the "chrpath" tool makes the same
|
|
|
|
// assumption.
|
|
|
|
bool terminated = false;
|
2008-02-28 00:26:35 +03:00
|
|
|
char c;
|
2008-03-01 20:50:42 +03:00
|
|
|
while(last != end && this->Stream.get(c) && !(terminated && c))
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
2008-03-01 20:50:42 +03:00
|
|
|
++last;
|
|
|
|
if(c)
|
|
|
|
{
|
|
|
|
se.Value += c;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
terminated = true;
|
|
|
|
}
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
2008-02-29 19:12:59 +03:00
|
|
|
|
|
|
|
// Make sure the whole value was read.
|
2008-02-28 00:26:35 +03:00
|
|
|
if(!this->Stream)
|
|
|
|
{
|
2008-02-29 19:12:59 +03:00
|
|
|
this->SetErrorMessage("Dynamic section specifies unreadable RPATH.");
|
|
|
|
se.Value = "";
|
|
|
|
return 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
2008-02-29 19:12:59 +03:00
|
|
|
|
|
|
|
// The value has been read successfully. Report it.
|
2008-03-01 20:50:42 +03:00
|
|
|
se.Position = static_cast<unsigned long>(strtab.sh_offset + first);
|
|
|
|
se.Size = last - first;
|
2010-05-12 18:41:06 +04:00
|
|
|
se.IndexInSection = static_cast<int>(di - this->DynamicSectionEntries.begin());
|
2008-02-29 19:12:59 +03:00
|
|
|
return &se;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
}
|
2008-02-29 19:12:59 +03:00
|
|
|
return 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
// External class implementation.
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
cmELF::cmELF(const char* fname): Internal(0)
|
|
|
|
{
|
|
|
|
// Try to open the file.
|
|
|
|
cmsys::auto_ptr<std::ifstream> 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<cmELFTypes32>(this, fin, order);
|
|
|
|
}
|
|
|
|
else if(ident[EI_CLASS] == ELFCLASS64)
|
|
|
|
{
|
|
|
|
// 64-bit ELF
|
|
|
|
this->Internal = new cmELFInternalImpl<cmELFTypes64>(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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-14 23:02:24 +04:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
unsigned int cmELF::GetDynamicEntryCount() const
|
|
|
|
{
|
|
|
|
if(this->Valid())
|
|
|
|
{
|
|
|
|
return this->Internal->GetDynamicEntryCount();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
unsigned long cmELF::GetDynamicEntryPosition(int index) const
|
|
|
|
{
|
|
|
|
if(this->Valid())
|
|
|
|
{
|
|
|
|
return this->Internal->GetDynamicEntryPosition(index);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool cmELF::ReadBytes(unsigned long pos, unsigned long size, char* buf) const
|
|
|
|
{
|
|
|
|
if(this->Valid())
|
|
|
|
{
|
|
|
|
return this->Internal->ReadBytes(pos, size, buf);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-28 00:26:35 +03:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool cmELF::GetSOName(std::string& soname)
|
2008-02-29 19:12:59 +03:00
|
|
|
{
|
|
|
|
if(StringEntry const* se = this->GetSOName())
|
|
|
|
{
|
|
|
|
soname = se->Value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
cmELF::StringEntry const* cmELF::GetSOName()
|
2008-02-28 00:26:35 +03:00
|
|
|
{
|
|
|
|
if(this->Valid() &&
|
|
|
|
this->Internal->GetFileType() == cmELF::FileTypeSharedLibrary)
|
|
|
|
{
|
2008-02-29 19:12:59 +03:00
|
|
|
return this->Internal->GetSOName();
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-02-29 19:12:59 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
cmELF::StringEntry const* cmELF::GetRPath()
|
|
|
|
{
|
|
|
|
if(this->Valid() &&
|
2008-08-04 17:38:38 +04:00
|
|
|
(this->Internal->GetFileType() == cmELF::FileTypeExecutable ||
|
|
|
|
this->Internal->GetFileType() == cmELF::FileTypeSharedLibrary))
|
2008-02-29 19:12:59 +03:00
|
|
|
{
|
|
|
|
return this->Internal->GetRPath();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
cmELF::StringEntry const* cmELF::GetRunPath()
|
|
|
|
{
|
|
|
|
if(this->Valid() &&
|
2008-08-04 17:38:38 +04:00
|
|
|
(this->Internal->GetFileType() == cmELF::FileTypeExecutable ||
|
|
|
|
this->Internal->GetFileType() == cmELF::FileTypeSharedLibrary))
|
2008-02-29 19:12:59 +03:00
|
|
|
{
|
|
|
|
return this->Internal->GetRunPath();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 0;
|
2008-02-28 00:26:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void cmELF::PrintInfo(std::ostream& os) const
|
|
|
|
{
|
|
|
|
if(this->Valid())
|
|
|
|
{
|
|
|
|
this->Internal->PrintInfo(os);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
os << "Not a valid ELF file.\n";
|
|
|
|
}
|
|
|
|
}
|