rtems-tools/linkers/rld-elf.cpp
Chris Johns fd8a2c559f Add support to write a metadata ELF file.
This also adds support to the ELF classes that wrap libelf. While
this is now done and seems to work I will not be using an ELF
file to hold the metadata after all.
2012-11-20 19:53:24 +11:00

989 lines
24 KiB
C++

/*
* Copyright (c) 2011-2012, Chris Johns <chrisj@rtems.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* @file
*
* @ingroup rtems-ld
*
* @brief RTEMS Linker ELF module manages the ELF format images.
*
*/
#include <string.h>
#include <rld.h>
namespace rld
{
namespace elf
{
/**
* Throw an ELF error.
*
* @param where Where the error is raised.
*/
void libelf_error (const std::string& where)
{
throw rld::error (::elf_errmsg (-1), "libelf:" + where);
}
/**
* We record the first class, machine and .. type of object file we get the
* header of and all header must match. We cannot mix object module types.
*/
static unsigned int elf_object_class = ELFCLASSNONE;
static unsigned int elf_object_machinetype = EM_NONE;
static unsigned int elf_object_datatype = ELFDATANONE;
/**
* A single place to initialise the libelf library. This must be called
* before any libelf API calls are made.
*/
static void
libelf_initialise ()
{
static bool libelf_initialised = false;
if (!libelf_initialised)
{
if (::elf_version (EV_CURRENT) == EV_NONE)
libelf_error ("initialisation");
libelf_initialised = true;
}
}
section::section (file& file_,
int index_,
const std::string& name_,
elf_word type,
elf_xword alignment,
elf_xword flags,
elf_addr addr,
elf_off offset,
elf_xword size,
elf_word link,
elf_word info,
elf_xword entry_size)
: file_ (&file_),
index_ (index_),
name_ (name_),
scn (0),
data_ (0)
{
if (!file_.is_writable ())
throw rld::error ("not writable",
"elf:section" + file_.name () + " (" + name_ + ')');
scn = ::elf_newscn (file_.get_elf ());
if (!scn)
libelf_error ("elf_newscn: " + name_ + " (" + file_.name () + ')');
if (::gelf_getshdr(scn, &shdr) == 0)
libelf_error ("gelf_getshdr: " + name_ + " (" + file_.name () + ')');
shdr.sh_name = 0;
shdr.sh_type = type;
shdr.sh_flags = flags;
shdr.sh_addr = addr;
shdr.sh_offset = offset;
shdr.sh_size = size;
shdr.sh_link = link;
shdr.sh_info = info;
shdr.sh_addralign = alignment;
shdr.sh_entsize = entry_size;
if (type == SHT_NOBITS)
add_data (ELF_T_BYTE, alignment, size);
if (!gelf_update_shdr (scn, &shdr))
libelf_error ("gelf_update_shdr: " + name_ + " (" + file_.name () + ')');
}
section::section (file& file_, int index_)
: file_ (&file_),
index_ (index_),
scn (0),
data_ (0)
{
memset (&shdr, 0, sizeof (shdr));
scn = ::elf_getscn (file_.get_elf (), index_);
if (!scn)
libelf_error ("elf_getscn: " + file_.name ());
if (!::gelf_getshdr (scn, &shdr))
libelf_error ("gelf_getshdr: " + file_.name ());
if (shdr.sh_type != SHT_NULL)
{
name_ = file_.get_string (shdr.sh_name);
data_ = ::elf_getdata (scn, 0);
if (!data_)
libelf_error ("elf_getdata: " + name_ + '(' + file_.name () + ')');
}
}
section::section (const section& orig)
: file_ (orig.file_),
index_ (orig.index_),
name_ (orig.name_),
scn (orig.scn),
shdr (orig.shdr),
data_ (orig.data_)
{
}
section::section ()
: file_ (0),
index_ (-1),
scn (0),
data_ (0)
{
memset (&shdr, 0, sizeof (shdr));
}
void
section::add_data (elf_type type,
elf_xword alignment,
elf_xword size,
void* buffer,
elf_off offset)
{
check_writable ("add_data");
data_ = ::elf_newdata(scn);
if (!data_)
libelf_error ("elf_newdata: " + name_ + " (" + file_->name () + ')');
data_->d_type = type;
data_->d_off = offset;
data_->d_size = size;
data_->d_align = alignment;
data_->d_version = EV_CURRENT;
data_->d_buf = buffer;
if (!gelf_update_shdr (scn, &shdr))
libelf_error ("gelf_update_shdr: " + name_ + " (" + file_->name () + ')');
}
int
section::index () const
{
check ("index");
return index_;
}
const std::string&
section::name () const
{
check ("name");
return name_;
}
elf_data*
section::data ()
{
check ("data");
return data_;
}
elf_word
section::type () const
{
check ("type");
return shdr.sh_type;
}
elf_xword
section::flags () const
{
check ("flags");
return shdr.sh_flags;
}
elf_addr
section::address () const
{
check ("address");
return shdr.sh_addr;
}
elf_xword
section::alignment () const
{
check ("alignment");
return shdr.sh_addralign;
}
elf_off
section::offset () const
{
check ("offset");
return shdr.sh_offset;
}
elf_word
section::link () const
{
check ("link");
return shdr.sh_link;
}
elf_word
section::info () const
{
check ("info");
return shdr.sh_info;
}
elf_xword
section::size () const
{
check ("size");
return shdr.sh_size;
}
elf_xword
section::entry_size () const
{
check ("entry_size");
return shdr.sh_entsize;
}
int
section::entries () const
{
return size () / entry_size ();
}
void
section::set_name (unsigned int index)
{
check_writable ("set_name");
shdr.sh_name = index;
if (!gelf_update_shdr (scn, &shdr))
libelf_error ("gelf_update_shdr: " + name_ + " (" + file_->name () + ')');
}
void
section::check (const char* where) const
{
if (!file_ || (index_ < 0) || !scn)
{
std::string w = where;
throw rld::error ("Section not initialised.", "section:check:" + w);
}
}
void
section::check_writable (const char* where) const
{
check (where);
if (!file_->is_writable ())
{
std::string w = where;
throw rld::error ("File is read-only.", "section:check:");
}
}
program_header::program_header ()
{
memset (&phdr, 0, sizeof (phdr));
}
program_header::~program_header ()
{
}
void
program_header::set (elf_word type,
elf_word flags,
elf_off offset,
elf_xword filesz,
elf_xword memsz,
elf_xword align,
elf_addr vaddr,
elf_addr paddr)
{
phdr.p_type = type;
phdr.p_flags = flags;
phdr.p_offset = offset;
phdr.p_vaddr = vaddr;
phdr.p_paddr = paddr;
phdr.p_filesz = filesz;
phdr.p_memsz = memsz;
phdr.p_align = align;
}
file::file ()
: fd_ (-1),
archive (false),
writable (false),
elf_ (0),
oclass (0),
ident_str (0),
ident_size (0),
ehdr (0),
phdr (0)
{
}
file::~file ()
{
end ();
}
void
file::begin (const std::string& name__, int fd__, const bool writable_)
{
begin (name__, fd__, writable_, 0, 0);
}
void
file::begin (const std::string& name__, file& archive_, off_t offset)
{
archive_.check ("begin:archive");
if (archive_.writable)
throw rld::error ("archive is writable", "elf:file:begin");
begin (name__, archive_.fd_, false, &archive_, offset);
}
#define rld_archive_fhdr_size (60)
void
file::begin (const std::string& name__,
int fd__,
const bool writable_,
file* archive_,
off_t offset_)
{
if (fd__ < 0)
throw rld::error ("no file descriptor", "elf:file:begin");
/*
* Begin's are not nesting.
*/
if (elf_ || (fd_ >= 0))
throw rld::error ("already called", "elf:file:begin");
/*
* Cannot write directly into archive. Create a file then archive it.
*/
if (archive_ && writable_)
throw rld::error ("cannot write into archives directly",
"elf:file:begin");
libelf_initialise ();
/*
* Is this image part of an archive ?
*/
if (archive_)
{
ssize_t offset = offset_ - rld_archive_fhdr_size;
if (::elf_rand (archive_->elf_, offset) != offset)
libelf_error ("rand: " + archive_->name_);
}
/*
* Note, the elf passed is either the archive or NULL.
*/
elf* elf__ = ::elf_begin (fd__,
writable_ ? ELF_C_WRITE : ELF_C_READ,
archive_ ? archive_->elf_ : 0);
if (!elf__)
libelf_error ("begin: " + name__);
if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
std::cout << "elf::begin: " << elf__ << ' ' << name__ << std::endl;
elf_kind ek = ::elf_kind (elf__);
/*
* If this is inside an archive it must be an ELF file.
*/
if (archive_ && (ek != ELF_K_ELF))
throw rld::error ("File format in archive not ELF", "elf:file:begin: " + name__);
else
{
if (ek == ELF_K_AR)
archive = true;
else if (ek == ELF_K_ELF)
archive = false;
else
throw rld::error ("File format not ELF or archive",
"elf:file:begin: " + name__);
}
if (!writable_)
{
/*
* If an ELF file make sure they all match. On the first file that
* begins an ELF session record its settings.
*/
if (ek == ELF_K_ELF)
{
oclass = ::gelf_getclass (elf__);
ident_str = elf_getident (elf__, &ident_size);
}
}
fd_ = fd__;
name_ = name__;
writable = writable_;
elf_ = elf__;
if (!archive && !writable)
load_header ();
}
void
file::end ()
{
if (elf_)
{
if (rld::verbose () >= RLD_VERBOSE_FULL_DEBUG)
std::cout << "libelf::end: " << elf_
<< ' ' << name_ << std::endl;
::elf_end (elf_);
elf_ = 0;
}
if (fd_ >= 0)
{
if (!writable)
{
if (ehdr)
{
delete ehdr;
ehdr = 0;
}
if (phdr)
{
delete phdr;
phdr = 0;
}
}
fd_ = -1;
name_.clear ();
archive = false;
elf_ = 0;
oclass = 0;
ident_str = 0;
ident_size = 0;
writable = false;
secs.clear ();
}
}
void
file::write ()
{
check_writable ("write");
std::string shstrtab;
for (section_table::iterator sti = secs.begin ();
sti != secs.end ();
++sti)
{
section& sec = (*sti).second;
int added_at = shstrtab.size ();
shstrtab += '\0' + sec.name ();
sec.set_name (added_at + 1);
}
unsigned int shstrtab_name = shstrtab.size () + 1;
/*
* Done this way to clang happy on darwin.
*/
shstrtab += '\0';
shstrtab += ".shstrtab";
/*
* Create the string table section.
*/
section shstrsec (*this,
secs.size () + 1, /* index */
".shstrtab", /* name */
SHT_STRTAB, /* type */
1, /* alignment */
SHF_STRINGS | SHF_ALLOC, /* flags */
0, /* address */
0, /* offset */
shstrtab.size ()); /* size */
shstrsec.add_data (ELF_T_BYTE,
1,
shstrtab.size (),
(void*) shstrtab.c_str ());
shstrsec.set_name (shstrtab_name);
::elf_setshstrndx (elf_, shstrsec.index ());
::elf_flagehdr (elf_, ELF_C_SET, ELF_F_DIRTY);
if (elf_update (elf_, ELF_C_NULL) < 0)
libelf_error ("elf_update:layout: " + name_);
::elf_flagphdr (elf_, ELF_C_SET, ELF_F_DIRTY);
if (::elf_update (elf_, ELF_C_WRITE) < 0)
libelf_error ("elf_update:write: " + name_);
}
void
file::load_header ()
{
check ("load_header");
if (!ehdr)
{
if (!writable)
ehdr = new elf_ehdr;
else
{
throw rld::error ("No ELF header; set the header first",
"elf:file:load_header: " + name_);
}
}
if (::gelf_getehdr (elf_, ehdr) == 0)
error ("gelf_getehdr");
}
unsigned int
file::machinetype () const
{
check_ehdr ("machinetype");
return ehdr->e_machine;
}
unsigned int
file::type () const
{
check_ehdr ("type");
return ehdr->e_type;
}
unsigned int
file::object_class () const
{
check ("object_class");
return oclass;
}
unsigned int
file::data_type () const
{
check ("data_type");
if (!ident_str)
throw rld::error ("No ELF ident str", "elf:file:data_type: " + name_);
return ident_str[EI_DATA];
}
bool
file::is_archive () const
{
check ("is_archive");
return archive;
}
bool
file::is_executable () const
{
check_ehdr ("is_executable");
return ehdr->e_type != ET_REL;
}
bool
file::is_relocatable() const
{
check_ehdr ("is_relocatable");
return ehdr->e_type == ET_REL;
}
int
file::section_count () const
{
check_ehdr ("section_count");
return ehdr->e_shnum;
}
void
file::load_sections ()
{
if (secs.empty ())
{
check ("load_sections_headers");
for (int sn = 0; sn < section_count (); ++sn)
{
section sec = section (*this, sn);
secs[sec.name ()] = sec;
}
}
}
void
file::get_sections (sections& filtered_secs, unsigned int type)
{
load_sections ();
filtered_secs.clear ();
for (section_table::iterator si = secs.begin ();
si != secs.end ();
++si)
{
section& sec = (*si).second;
if ((type == 0) || (sec.type () == type))
filtered_secs.push_back (&sec);
}
}
void
file::load_symbols ()
{
if (symbols.empty ())
{
sections symbol_secs;
get_sections (symbol_secs, SHT_SYMTAB);
for (sections::iterator si = symbol_secs.begin ();
si != symbol_secs.end ();
++si)
{
section& sec = *(*si);
int syms = sec.entries ();
for (int s = 0; s < syms; ++s)
{
elf_sym esym;
if (!::gelf_getsym (sec.data (), s, &esym))
error ("gelf_getsym");
std::string name = get_string (sec.link (), esym.st_name);
if (!name.empty ())
{
symbols::symbol sym (name, esym);
if (rld::verbose () >= RLD_VERBOSE_TRACE)
{
std::cout << "elf::symbol: ";
sym.output (std::cout);
std::cout << std::endl;
}
symbols.push_back (sym);
}
}
}
}
}
void
file::get_symbols (symbols::pointers& filtered_syms,
bool unresolved,
bool local,
bool weak,
bool global)
{
if (rld::verbose () >= RLD_VERBOSE_DETAILS)
std::cout << "elf:get-syms: unresolved:" << unresolved
<< " local:" << local
<< " weak:" << weak
<< " global:" << global
<< " " << name_
<< std::endl;
load_symbols ();
filtered_syms.clear ();
for (symbols::bucket::iterator si = symbols.begin ();
si != symbols.end ();
++si)
{
symbols::symbol& sym = *si;
int stype = sym.type ();
int sbind = sym.binding ();
/*
* If wanting unresolved symbols and the type is no-type and the
* section is undefined, or, the type is no-type or object or function
* and the bind is local and we want local symbols, or the bind is weak
* and we want weak symbols, or the bind is global and we want global
* symbols then add the filtered symbols container.
*/
bool add = false;
if ((stype == STT_NOTYPE) && (sym.index () == SHN_UNDEF))
{
if (unresolved)
add = true;
}
else if (!unresolved)
{
if (((stype == STT_NOTYPE) ||
(stype == STT_OBJECT) ||
(stype == STT_FUNC)) &&
((local && (sbind == STB_LOCAL)) ||
(weak && (sbind == STB_WEAK)) ||
(global && (sbind == STB_GLOBAL))))
add = true;
}
if (add)
filtered_syms.push_back (&sym);
}
}
int
file::strings_section () const
{
check_ehdr ("strings_sections");
return ehdr->e_shstrndx;
}
std::string
file::get_string (int section, size_t offset)
{
check ("get_string");
char* s = ::elf_strptr (elf_, section, offset);
if (!s)
error ("elf_strptr");
return s;
}
std::string
file::get_string (size_t offset)
{
check ("get_string");
char* s = ::elf_strptr (elf_, strings_section (), offset);
if (!s)
error ("elf_strptr");
return s;
}
void
file::set_header (elf_half type,
int class_,
elf_half machinetype,
unsigned char datatype)
{
check_writable ("set_header");
if (ehdr)
throw rld::error ("ELF header already set",
"elf:file:set_header: " + name_);
ehdr = (elf_ehdr*) ::gelf_newehdr (elf_, class_);
if (ehdr == 0)
error ("gelf_newehdr");
if (::gelf_getehdr (elf_, ehdr) == 0)
error ("gelf_getehdr");
ehdr->e_type = type;
ehdr->e_machine = machinetype;
ehdr->e_flags = 0;
ehdr->e_ident[EI_DATA] = datatype;
ehdr->e_version = EV_CURRENT;
::elf_flagphdr (elf_, ELF_C_SET , ELF_F_DIRTY);
}
void
file::add (section& sec)
{
check_writable ("add");
secs[sec.name ()] = sec;
}
void
file::add (program_header& phdr)
{
check_writable ("add");
phdrs.push_back (phdr);
}
elf*
file::get_elf ()
{
return elf_;
}
const std::string&
file::name () const
{
return name_;
}
bool
file::is_writable () const
{
return writable;
}
void
file::check (const char* where) const
{
if (!elf_ || (fd_ < 0))
{
std::string w = where;
throw rld::error ("no elf file or file descriptor", "elf:file:" + w);
}
}
void
file::check_ehdr (const char* where) const
{
check (where);
if (!ehdr)
{
std::string w = where;
throw rld::error ("no elf header", "elf:file:" + w);
}
}
void
file::check_phdr (const char* where) const
{
check (where);
if (!phdr)
{
std::string w = where;
throw rld::error ("no elf program header", "elf:file:" + w);
}
}
void
file::check_writable (const char* where) const
{
check (where);
if (!writable)
{
std::string w = where;
throw rld::error ("not writable", "elf:file:" + w);
}
}
void
file::error (const char* where) const
{
std::string w = where;
libelf_error (w + ": " + name_);
}
const std::string
machine_type (unsigned int machinetype)
{
struct types_and_labels
{
const char* name; //< The RTEMS label.
unsigned int machinetype; //< The machine type.
};
types_and_labels types_to_labels[] =
{
{ "arm", EM_ARM },
{ "avr", EM_AVR },
{ "bfin", EM_BLACKFIN },
{ "h8300", EM_H8_300 },
{ "i386", EM_386 },
/* { "m32c", EM_M32C }, Not in libelf I imported */
{ "m32r", EM_M32R },
{ "m68k", EM_68K },
{ "m68k", EM_COLDFIRE },
{ "mips", EM_MIPS },
{ "powerpc", EM_PPC },
{ "sh", EM_SH },
{ "sparc", EM_SPARC },
{ "sparc64", EM_SPARC },
{ 0, EM_NONE }
};
int m = 0;
while (types_to_labels[m].machinetype != EM_NONE)
{
if (machinetype == types_to_labels[m].machinetype)
return types_to_labels[m].name;
++m;
}
std::ostringstream what;
what << "unknown machine type: " << elf_object_machinetype;
throw rld::error (what, "machine-type");
}
const std::string
machine_type ()
{
return machine_type (elf_object_machinetype);
}
unsigned int
object_class ()
{
return elf_object_class;
}
unsigned int
object_machine_type ()
{
return elf_object_machinetype;
}
unsigned int
object_datatype ()
{
return elf_object_datatype;
}
void
check_file(const file& file)
{
if (elf_object_machinetype == EM_NONE)
elf_object_machinetype = file.machinetype ();
else if (file.machinetype () != elf_object_machinetype)
{
std::ostringstream oss;
oss << "elf:check_file:" << file.name ()
<< ": " << elf_object_machinetype << '/' << file.machinetype ();
throw rld::error ("Mixed machine types not supported.", oss.str ());
}
if (elf_object_class == ELFCLASSNONE)
elf_object_class = file.object_class ();
else if (file.object_class () != elf_object_class)
throw rld::error ("Mixed classes not allowed (32bit/64bit).",
"elf:check_file: " + file.name ());
if (elf_object_datatype == ELFDATANONE)
elf_object_datatype = file.data_type ();
else if (elf_object_datatype != file.data_type ())
throw rld::error ("Mixed data types not allowed (LSB/MSB).",
"elf:check_file: " + file.name ());
}
}
}