blob: 0a64752fffda26cc158ed7005a6cd28941450779 [file] [log] [blame]
//===- GNUArchiveReader.cpp -----------------------------------------------===//
//
// The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/MC/MCLDInfo.h>
#include <mcld/MC/MCLDInput.h>
#include <mcld/MC/InputTree.h>
#include <mcld/LD/GNUArchiveReader.h>
#include <mcld/Support/FileSystem.h>
#include <mcld/Support/MemoryArea.h>
#include <mcld/Support/MemoryRegion.h>
#include <mcld/Support/MemoryAreaFactory.h>
#include <mcld/Support/MsgHandling.h>
#include <mcld/ADT/SizeTraits.h>
#include <llvm/Support/system_error.h>
#include <llvm/Support/Host.h>
#include <sstream>
#include <string>
#include <vector>
#include <cstdlib>
using namespace mcld;
typedef uint32_t elfWord;
/// Archive Header, Magic number
const char GNUArchiveReader::ArchiveMagic[ArchiveMagicSize] = { '!', '<', 'a', 'r', 'c', 'h', '>', '\n' };
const char GNUArchiveReader::ThinArchiveMagic[ArchiveMagicSize] = { '!', '<', 't', 'h', 'i', 'n', '>', '\n' };
const char GNUArchiveReader::HeaderFinalMagic[HeaderFinalMagicSize] = { '`', '\n' };
struct GNUArchiveReader::SymbolTableEntry
{
off_t fileOffset;
std::string name;
};
/// convert string to size_t
template<class Type>
Type stringToType(const std::string &str)
{
Type n;
std::stringstream ss(str);
ss >> n;
return n;
}
/// GNUArchiveReader Operations
/// Public API
bool GNUArchiveReader::isMyFormat(Input &pInput) const
{
assert(pInput.hasMemArea());
MemoryRegion *region = pInput.memArea()->request(0, ArchiveMagicSize);
if (!region)
llvm::report_fatal_error("can't request MemoryRegion for archive magic");
const char *p_buffer = reinterpret_cast<char *> (region->getBuffer());
/// check archive format.
if (memcmp(p_buffer, ArchiveMagic, ArchiveMagicSize) != 0
&& memcmp(p_buffer, ThinArchiveMagic, ArchiveMagicSize) != 0) {
return false;
}
return true;
}
LDReader::Endian GNUArchiveReader::endian(Input& pFile) const
{
return LDReader::BigEndian;
}
InputTree *GNUArchiveReader::readArchive(Input &pInput)
{
return setupNewArchive(pInput, 0);
}
/// Read Input as archive. First create a null InputTree.
/// Then Construct Input object for corresponding member of this archive
/// and insert the Input object into the InputTree.
/// Finally, return the InputTree.
InputTree *GNUArchiveReader::setupNewArchive(Input &pInput,
size_t off)
{
assert(pInput.hasMemArea());
MemoryRegion *region = pInput.memArea()->request(off, ArchiveMagicSize);
if (!region)
llvm::report_fatal_error("can't request MemoryRegion for archive magic");
const char *pFile = reinterpret_cast<char *> (region->getBuffer());
/// check archive format.
bool isThinArchive;
isThinArchive = memcmp(pFile, ThinArchiveMagic, ArchiveMagicSize) == 0;
if(!isThinArchive && memcmp(pFile, ArchiveMagic, ArchiveMagicSize) != 0)
llvm::report_fatal_error("Fail : archive magic number is not matched");
InputTree *resultTree = new InputTree(m_pLDInfo.inputFactory());
std::vector<SymbolTableEntry> symbolTable;
std::string archiveMemberName;
std::string extendedName;
off += ArchiveMagicSize ;
size_t symbolTableSize = readMemberHeader(*pInput.memArea(), off, &archiveMemberName,
NULL, extendedName);
/// read archive symbol table
if(archiveMemberName.empty())
{
readSymbolTable(*pInput.memArea(), symbolTable,
off+sizeof(ArchiveMemberHeader), symbolTableSize);
off = off + sizeof(ArchiveMemberHeader) + symbolTableSize;
}
else
{
llvm::report_fatal_error("fatal error : need symbol table\n");
return NULL;
}
if((off&1) != 0)
++off;
size_t extendedSize = readMemberHeader(*pInput.memArea(), off, &archiveMemberName,
NULL, extendedName);
/// read long Name table if exist
if(archiveMemberName == "/")
{
off += sizeof(ArchiveMemberHeader);
MemoryRegion *extended_name_region = pInput.memArea()->request(off, extendedSize);
pFile = reinterpret_cast<char *>(extended_name_region->getBuffer());
extendedName.assign(pFile, extendedSize);
}
/// traverse all the archive members
InputTree::iterator node = resultTree->root();
std::set<std::string> haveSeen;
for(unsigned i=0 ; i<symbolTable.size() ; ++i)
{
/// We shall get each member at this archive.
/// Construct a corresponding mcld::Input, and insert it into
/// the original InputTree, resultTree.
off_t nestedOff = 0;
readMemberHeader(*pInput.memArea(), symbolTable[i].fileOffset, &archiveMemberName,
&nestedOff, extendedName);
if(haveSeen.find(archiveMemberName)==haveSeen.end())
haveSeen.insert(archiveMemberName);
else
continue;
if(!isThinArchive)
{
/// New a Input object and assign fileOffset in MCLDFile.
/// Insert the object to resultTree and move ahead.
off_t fileOffset = symbolTable[i].fileOffset + sizeof(ArchiveMemberHeader);
Input *insertObjectFile = m_pLDInfo.inputFactory().produce(archiveMemberName,
pInput.path(),
MCLDFile::Object,
fileOffset);
resultTree->insert<InputTree::Positional>(node, *insertObjectFile);
if(i==0)
node.move<InputTree::Inclusive>();
else
node.move<InputTree::Positional>();
continue;
}
/// TODO:(Duo)
/// adjust the relative pathname
/// For example
/// thin archive pathname : "/usr/lib/thin.a"
/// Member name : "member.a"
/// pathname after adjust : "/usr/lib/member.a"
sys::fs::RealPath realPath(archiveMemberName);
if(nestedOff > 0)
{
/// This is a member of a nested archive.
/// Create an Input for this archive ,and recursive call setupNewArchive
/// Finally, merge the new InputTree with the old one
Input *newArchive = m_pLDInfo.inputFactory().produce(archiveMemberName,
realPath,
MCLDFile::Archive,
0);
resultTree->insert<InputTree::Positional>(node, *newArchive);
if(i==0)
node.move<InputTree::Inclusive>();
else
node.move<InputTree::Positional>();
InputTree *newArchiveTree = setupNewArchive(*newArchive, 0);
resultTree->merge<InputTree::Inclusive>(node, *newArchiveTree);
continue;
}
/// External member , open it as normal object file
/// add new Input to InputTree
Input *insertObjectFile = m_pLDInfo.inputFactory().produce(archiveMemberName,
realPath,
MCLDFile::Object,
0);
resultTree->insert<InputTree::Positional>(node, *insertObjectFile);
if(i==0)
node.move<InputTree::Inclusive>();
else
node.move<InputTree::Positional>();
}
return resultTree;
}
/// Parse the member header and return the size of member
/// Archive member names in System 5 style :
///
/// "/ " - symbol table, must be the first member
/// "// " - long name table
/// "filename.o/ " - regular file with short name
/// "/5566 " - name at offset 5566 at long name table
size_t GNUArchiveReader::readMemberHeader(MemoryArea &pArea,
off_t off,
std::string *p_Name,
off_t *p_NestedOff,
std::string &p_ExtendedName)
{
MemoryRegion *region = pArea.request(off, sizeof(ArchiveMemberHeader));
const char *pFile = reinterpret_cast<char *>(region->getBuffer());
const ArchiveMemberHeader *header = reinterpret_cast<const ArchiveMemberHeader *>(pFile);
/// check magic number of member header
if(memcmp(header->finalMagic, HeaderFinalMagic, sizeof HeaderFinalMagic))
{
llvm::report_fatal_error("archive member header magic number false");
return 0;
}
/// evaluate member size
std::string sizeString(header->size, sizeof(header->size)+1);
size_t memberSize = stringToType<size_t>(sizeString);
if(memberSize == 0)
{
llvm::report_fatal_error("member Size Error");
return 0;
}
if(header->name[0] != '/')
{
/// This is a regular file with short name
const char* nameEnd = strchr(header->name, '/');
size_t nameLen = ((nameEnd == NULL) ? 0 : (nameEnd - header->name));
if((nameLen <= 0) || (nameLen >= sizeof(header->name)))
{
llvm::report_fatal_error("header name format error\n");
return 0;
}
p_Name->assign(header->name, nameLen);
if(!p_NestedOff)
p_NestedOff = 0;
}
else if(header->name[1] == ' ')
{
/// This is symbol table
if(!p_Name->empty())
p_Name->clear();
}
else if(header->name[1] == '/')
{
/// This is long name table
p_Name->assign(1,'/');
}
else
{
/// This is regular file with long name
char *end;
long extendedNameOff = strtol(header->name+1, &end, 10);
long nestedOff = 0;
if(*end == ':')
nestedOff = strtol(end+1, &end, 10);
if(*end != ' '
|| extendedNameOff < 0
|| static_cast<size_t>(extendedNameOff) >= p_ExtendedName.size())
{
llvm::report_fatal_error("extended name");
return 0;
}
const char *name = p_ExtendedName.data() + extendedNameOff;
const char *nameEnd = strchr(name, '\n');
if(nameEnd[-1] != '/'
|| static_cast<size_t>(nameEnd-name) > p_ExtendedName.size())
{
llvm::report_fatal_error("p_ExtendedName substring is not end with / \n");
return 0;
}
p_Name->assign(name, nameEnd-name-1);
if(p_NestedOff)
*p_NestedOff = nestedOff;
}
return memberSize;
}
void GNUArchiveReader::readSymbolTable(MemoryArea &pArea,
std::vector<SymbolTableEntry> &pSymbolTable,
off_t start,
size_t size)
{
MemoryRegion *region = pArea.request(start, size);
const char *pFile = reinterpret_cast<char *>(region->getBuffer());
const elfWord *p_Word = reinterpret_cast<const elfWord *>(pFile);
unsigned int symbolNum = *p_Word;
/// Portable Issue on Sparc platform
/// Intel, ARM and Mips are littel-endian , Sparc is little-endian after verion 9
/// symbolNum in symbol table is always big-endian
if(llvm::sys::isLittleEndianHost())
symbolNum = bswap32(symbolNum);
++p_Word;
const char *p_Name = reinterpret_cast<const char *>(p_Word + symbolNum);
for(unsigned int i=0 ; i<symbolNum ; ++i)
{
SymbolTableEntry entry;
/// member offset
unsigned int memberOffset = *p_Word;
if(llvm::sys::isLittleEndianHost())
memberOffset = bswap32(memberOffset);
entry.fileOffset = static_cast<off_t>(memberOffset);
++p_Word;
/// member name
off_t nameEnd = strlen(p_Name) + 1;
entry.name.assign(p_Name, nameEnd);
p_Name += nameEnd;
/// the symbol is found in symbol pool
if (m_pLDInfo.getNamePool().findSymbol(entry.name))
pSymbolTable.push_back(entry);
}
}