rtems-tools/linkers/rtems-rapper.cpp
Chris Johns 287569cc8a Add an overlay output.
An overlay shows the hex dump of the section data with the relocation
records so you can see the relationship between the relocations and
the section data.
2012-12-23 16:57:35 +11:00

1072 lines
26 KiB
C++

/*
* Copyright (c) 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_rld
*
* @brief RTEMS RAP Manager lets you look at and play with RAP files.
*
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <iomanip>
#include <iostream>
#include <vector>
#include <cxxabi.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <rld.h>
#include <rld-compression.h>
#include <rld-files.h>
#include <rld-process.h>
#include <rld-rap.h>
#include <rtems-utils.h>
#ifndef HAVE_KILL
#define kill(p,s) raise(s)
#endif
/**
* RTEMS Application.
*/
namespace rap
{
/**
* A relocation record.
*/
struct relocation
{
uint32_t info;
uint32_t offset;
uint32_t addend;
std::string symname;
off_t rap_off;
relocation ();
void output ();
};
typedef std::vector < relocation > relocations;
/**
* A RAP section.
*/
struct section
{
std::string name;
uint32_t size;
uint32_t alignment;
uint8_t* data;
uint32_t relocs_size;
relocations relocs;
bool rela;
off_t rap_off;
section ();
~section ();
void load_data (rld::compress::compressor& comp);
void load_relocs (rld::compress::compressor& comp);
};
/**
* A RAP file.
*/
struct file
{
enum {
rap_comp_buffer = 2 * 1024
};
std::string header;
size_t rhdr_len;
uint32_t rhdr_length;
uint32_t rhdr_version;
std::string rhdr_compression;
uint32_t rhdr_checksum;
off_t machine_rap_off;
uint32_t machinetype;
uint32_t datatype;
uint32_t class_;
off_t layout_rap_off;
std::string init;
uint32_t init_off;
std::string fini;
uint32_t fini_off;
off_t strtab_rap_off;
uint32_t strtab_size;
uint8_t* strtab;
off_t symtab_rap_off;
uint32_t symtab_size;
uint8_t* symtab;
off_t relocs_rap_off;
uint32_t relocs_size; /* not used */
section secs[rld::rap::rap_secs];
/**
* Open a RAP file and read the header.
*/
file (const std::string& name, bool warnings);
/**
* Close the RAP file.
*/
~file ();
/**
* Parse header.
*/
void parse_header ();
/**
* Load the file.
*/
void load ();
/**
* Expand the image.
*/
void expand ();
/**
* The name.
*/
const std::string name () const;
/**
* The number of symbols in the symbol table.
*/
int symbols () const;
/**
* Return a symbol given an index.
*/
void symbol (int index,
uint32_t& data,
uint32_t& name,
uint32_t& value) const;
/**
* Return the string from the string table.
*/
const char* string (int index);
private:
bool warnings;
rld::files::image image;
};
template < typename T > T
get_value (const uint8_t* data)
{
T v = 0;
for (size_t b = 0; b < sizeof (T); ++b)
{
v <<= 8;
v |= (T) data[b];
}
return v;
}
relocation::relocation ()
: info (0),
offset (0),
addend (0),
rap_off (0)
{
}
void
relocation::output ()
{
std::cout << std::hex << std::setfill ('0')
<< "0x" << std::setw (8) << info
<< " 0x" << std::setw (8) << offset
<< " 0x" << std::setw(8) << addend
<< std::dec << std::setfill (' ')
<< " " << symname;
}
section::section ()
: size (0),
alignment (0),
data (0),
relocs_size (0),
relocs (0),
rela (false),
rap_off (0)
{
}
section::~section ()
{
if (data)
delete [] data;
}
void
section::load_data (rld::compress::compressor& comp)
{
rap_off = comp.offset ();
if (size)
{
data = new uint8_t[size];
if (comp.read (data, size) != size)
throw rld::error ("Reading section data failed", "rapper");
}
}
void
section::load_relocs (rld::compress::compressor& comp)
{
uint32_t header;
comp >> header;
rela = header & RAP_RELOC_RELA ? true : false;
relocs_size = header & ~RAP_RELOC_RELA;
if (relocs_size)
{
for (uint32_t r = 0; r < relocs_size; ++r)
{
relocation reloc;
reloc.rap_off = comp.offset ();
comp >> reloc.info
>> reloc.offset;
if (((reloc.info & RAP_RELOC_STRING) == 0) || rela)
comp >> reloc.addend;
if ((reloc.info & RAP_RELOC_STRING) != 0)
{
if ((reloc.info & RAP_RELOC_STRING_EMBED) == 0)
{
size_t symname_size = (reloc.info & ~(3 << 30)) >> 8;
reloc.symname.resize (symname_size);
size_t symname_read = comp.read ((void*) reloc.symname.c_str (), symname_size);
if (symname_read != symname_size)
throw rld::error ("Reading reloc symbol name failed", "rapper");
}
}
relocs.push_back (reloc);
}
}
}
file::file (const std::string& name, bool warnings)
: rhdr_len (0),
rhdr_length (0),
rhdr_version (0),
rhdr_checksum (0),
machine_rap_off (0),
machinetype (0),
datatype (0),
class_ (0),
layout_rap_off (0),
init_off (0),
fini_off (0),
strtab_rap_off (0),
strtab_size (0),
strtab (0),
symtab_rap_off (0),
symtab_size (0),
symtab (0),
relocs_rap_off (0),
relocs_size (0),
warnings (warnings),
image (name)
{
for (int s = 0; s < rld::rap::rap_secs; ++s)
secs[s].name = rld::rap::section_name (s);
image.open ();
parse_header ();
}
file::~file ()
{
image.close ();
if (symtab)
delete [] symtab;
if (strtab)
delete [] strtab;
}
void
file::parse_header ()
{
std::string name = image.name ().full ();
char rhdr[64];
image.seek_read (0, (uint8_t*) rhdr, 64);
if ((rhdr[0] != 'R') || (rhdr[1] != 'A') || (rhdr[2] != 'P') || (rhdr[3] != ','))
throw rld::error ("Invalid RAP file", "open: " + name);
char* sptr = rhdr + 4;
char* eptr;
rhdr_length = ::strtoul (sptr, &eptr, 10);
if (*eptr != ',')
throw rld::error ("Cannot parse RAP header", "open: " + name);
sptr = eptr + 1;
rhdr_version = ::strtoul (sptr, &eptr, 10);
if (*eptr != ',')
throw rld::error ("Cannot parse RAP header", "open: " + name);
sptr = eptr + 1;
if ((sptr[0] == 'N') &&
(sptr[1] == 'O') &&
(sptr[2] == 'N') &&
(sptr[3] == 'E'))
{
rhdr_compression = "NONE";
eptr = sptr + 4;
}
else if ((sptr[0] == 'L') &&
(sptr[1] == 'Z') &&
(sptr[2] == '7') &&
(sptr[3] == '7'))
{
rhdr_compression = "LZ77";
eptr = sptr + 4;
}
else
throw rld::error ("Cannot parse RAP header", "open: " + name);
if (*eptr != ',')
throw rld::error ("Cannot parse RAP header", "open: " + name);
sptr = eptr + 1;
rhdr_checksum = strtoul (sptr, &eptr, 16);
if (*eptr != '\n')
throw rld::error ("Cannot parse RAP header", "open: " + name);
rhdr_len = eptr - rhdr + 1;
if (warnings && (rhdr_length != image.size ()))
std::cout << " warning: header length does not match file size: header="
<< rhdr_length
<< " file-size=" << image.size ()
<< std::endl;
header.insert (0, rhdr, rhdr_len);
image.seek (rhdr_len);
}
void
file::load ()
{
image.seek (rhdr_len);
rld::compress::compressor comp (image, rap_comp_buffer, false);
/*
* uint32_t: machinetype
* uint32_t: datatype
* uint32_t: class
*/
machine_rap_off = comp.offset ();
comp >> machinetype
>> datatype
>> class_;
/*
* uint32_t: init
* uint32_t: fini
* uint32_t: symtab_size
* uint32_t: strtab_size
* uint32_t: relocs_size
*/
layout_rap_off = comp.offset ();
comp >> init_off
>> fini_off
>> symtab_size
>> strtab_size
>> relocs_size;
/*
* uint32_t: text_size
* uint32_t: text_alignment
* uint32_t: const_size
* uint32_t: const_alignment
* uint32_t: ctor_size
* uint32_t: ctor_alignment
* uint32_t: dtor_size
* uint32_t: dtor_alignment
* uint32_t: data_size
* uint32_t: data_alignment
* uint32_t: bss_size
* uint32_t: bss_alignment
*/
for (int s = 0; s < rld::rap::rap_secs; ++s)
comp >> secs[s].size
>> secs[s].alignment;
/*
* Load sections.
*/
for (int s = 0; s < rld::rap::rap_secs; ++s)
if (s != rld::rap::rap_bss)
secs[s].load_data (comp);
/*
* Load the string table.
*/
strtab_rap_off = comp.offset ();
if (strtab_size)
{
strtab = new uint8_t[strtab_size];
if (comp.read (strtab, strtab_size) != strtab_size)
throw rld::error ("Reading string table failed", "rapper");
}
/*
* Load the symbol table.
*/
symtab_rap_off = comp.offset ();
if (symtab_size)
{
symtab = new uint8_t[symtab_size];
if (comp.read (symtab, symtab_size) != symtab_size)
throw rld::error ("Reading symbol table failed", "rapper");
}
/*
* Load the relocation tables.
*/
relocs_rap_off = comp.offset ();
for (int s = 0; s < rld::rap::rap_secs; ++s)
secs[s].load_relocs (comp);
}
void
file::expand ()
{
std::string name = image.name ().full ();
std::string extension = rld::files::extension (image.name ().full ());
name = name.substr (0, name.size () - extension.size ()) + ".xrap";
image.seek (rhdr_len);
rld::compress::compressor comp (image, rap_comp_buffer, false);
rld::files::image out (name);
out.open (true);
out.seek (0);
while (true)
{
if (comp.read (out, rap_comp_buffer) != rap_comp_buffer)
break;
}
out.close ();
}
const std::string
file::name () const
{
return image.name ().full ();
}
int
file::symbols () const
{
return symtab_size / (3 * sizeof (uint32_t));
}
void
file::symbol (int index, uint32_t& data, uint32_t& name, uint32_t& value) const
{
if (index < symbols ())
{
uint8_t* sym = symtab + (index * 3 * sizeof (uint32_t));
data = get_value < uint32_t > (sym);
name = get_value < uint32_t > (sym + (1 * sizeof (uint32_t)));
value = get_value < uint32_t > (symtab + (2 * sizeof (uint32_t)));
}
}
const char*
file::string (int index)
{
std::string name = image.name ().full ();
if (strtab_size == 0)
throw rld::error ("No string table", "string: " + name);
uint32_t offset = 0;
int count = 0;
while (offset < strtab_size)
{
if (count++ == index)
return (const char*) &strtab[offset];
offset = ::strlen ((const char*) &strtab[offset]) + 1;
}
throw rld::error ("Invalid string index", "string: " + name);
}
}
void
rap_show (rld::files::paths& raps,
bool warnings,
bool show_header,
bool show_machine,
bool show_layout,
bool show_strings,
bool show_symbols,
bool show_relocs)
{
for (rld::files::paths::iterator pi = raps.begin();
pi != raps.end();
++pi)
{
std::cout << *pi << ':' << std::endl;
rap::file r (*pi, warnings);
try
{
r.load ();
}
catch (rld::error re)
{
std::cout << " error: "
<< re.where << ": " << re.what
<< std::endl
<< " warning: file read failed, some data may be corrupt or not present."
<< std::endl;
}
if (show_header)
{
std::cout << " Header:" << std::endl
<< " string: " << r.header
<< " length: " << r.rhdr_len << std::endl
<< " version: " << r.rhdr_version << std::endl
<< " compression: " << r.rhdr_compression << std::endl
<< std::hex << std::setfill ('0')
<< " checksum: " << std::setw (8) << r.rhdr_checksum << std::endl
<< std::dec << std::setfill(' ');
}
if (show_machine)
{
std::cout << " Machine: 0x"
<< std::hex << std::setfill ('0')
<< std::setw (8) << r.machine_rap_off
<< std::setfill (' ') << std::dec
<< " (" << r.machine_rap_off << ')' << std::endl
<< " machinetype: "<< r.machinetype << std::endl
<< " datatype: "<< r.datatype << std::endl
<< " class: "<< r.class_ << std::endl;
}
if (show_layout)
{
std::cout << " Layout: 0x"
<< std::hex << std::setfill ('0')
<< std::setw (8) << r.layout_rap_off
<< std::setfill (' ') << std::dec
<< " (" << r.layout_rap_off << ')' << std::endl
<< std::setw (18) << " "
<< " size align offset " << std::endl
<< std::setw (16) << "strtab" << ": "
<< std::setw (6) << r.strtab_size << std::endl
<< std::setw (16) << "symtab" << ": "
<< std::setw (6) << r.symtab_size << std::endl;
for (int s = 0; s < rld::rap::rap_secs; ++s)
{
std::cout << std::setw (16) << rld::rap::section_name (s)
<< ": " << std::setw (6) << r.secs[s].size
<< std::setw (7) << r.secs[s].alignment;
if (s != rld::rap::rap_bss)
std::cout << std::hex << std::setfill ('0')
<< " 0x" << std::setw (8) << r.secs[s].rap_off
<< std::setfill (' ') << std::dec
<< " (" << r.secs[s].rap_off << ')';
std::cout << std::endl;
}
}
if (show_strings)
{
if (r.strtab_size)
{
std::cout << " Strings: 0x"
<< std::hex << std::setfill ('0')
<< std::setw (8) << r.strtab_rap_off
<< std::setfill (' ') << std::dec
<< " (" << r.strtab_rap_off << ')' << std::endl;
uint32_t offset = 0;
int count = 0;
while (offset < r.strtab_size)
{
std::cout << std::setw (16) << count++ << ": "
<< (char*) &r.strtab[offset] << std::endl;
offset += ::strlen ((char*) &r.strtab[offset]) + 1;
}
}
else
{
std::cout << std::setw (16) << " "
<< "No string table found." << std::endl;
}
}
if (show_symbols)
{
std::cout << " Symbols: 0x"
<< std::hex << std::setfill ('0')
<< std::setw (8) << r.symtab_rap_off
<< std::setfill (' ') << std::dec
<< " (" << r.symtab_rap_off << ')' << std::endl;
if (r.symtab_size)
{
std::cout << std::setw (18) << " "
<< " data section value name" << std::endl;
for (int s = 0; s < r.symbols (); ++s)
{
uint32_t data;
uint32_t name;
uint32_t value;
r.symbol (s, data, name, value);
std::cout << std::setw (16) << s << ": "
<< std::hex << std::setfill ('0')
<< "0x" << std::setw (4) << (data & 0xffff)
<< std::dec << std::setfill (' ')
<< " " << std::setw (8) << rld::rap::section_name (data >> 16)
<< std::hex << std::setfill ('0')
<< " 0x" << std::setw(8) << value
<< " " << &r.strtab[name]
<< std::dec << std::setfill (' ')
<< std::endl;
}
}
else
{
std::cout << std::setw (16) << " "
<< "No symbol table found." << std::endl;
}
}
if (show_relocs)
{
std::cout << " Relocations: 0x"
<< std::hex << std::setfill ('0')
<< std::setw (8) << r.relocs_rap_off
<< std::setfill (' ') << std::dec
<< " (" << r.relocs_rap_off << ')' << std::endl;
int count = 0;
for (int s = 0; s < rld::rap::rap_secs; ++s)
{
if (r.secs[s].relocs.size ())
{
std::cout << std::setw (16) << r.secs[s].name
<< ": info offset addend symbol name" << std::endl;
for (size_t f = 0; f < r.secs[s].relocs.size (); ++f)
{
rap::relocation& reloc = r.secs[s].relocs[f];
std::cout << std::setw (16) << count++ << ": ";
reloc.output ();
std::cout << std::endl;
}
}
}
}
else
{
std::cout << std::setw (16) << " "
<< "No relocation table found." << std::endl;
}
}
}
void
rap_overlay (rld::files::paths& raps, bool warnings)
{
std::cout << "Overlay .... " << std::endl;
for (rld::files::paths::iterator pi = raps.begin();
pi != raps.end();
++pi)
{
rap::file r (*pi, warnings);
std::cout << r.name () << std::endl;
r.load ();
for (int s = 0; s < rld::rap::rap_secs; ++s)
{
rap::section& sec = r.secs[s];
if (sec.size && sec.data)
{
std::cout << rld::rap::section_name (s) << ':' << std::endl;
size_t line_length = 16;
uint32_t offset = 0;
size_t reloc = 0;
while (offset < sec.size)
{
size_t length = sec.size - offset;
if (reloc < sec.relocs.size ())
length = sec.relocs[reloc].offset - offset;
if ((offset + length) < sec.size)
{
length += line_length;
length -= length % line_length;
}
rtems::utils::dump (sec.data + offset,
length,
sizeof (uint8_t),
false,
line_length,
offset);
const int indent = 8;
std::ostringstream line;
line << std::setw (indent) << ' ';
while ((reloc < sec.relocs.size ()) &&
(sec.relocs[reloc].offset >= offset) &&
(sec.relocs[reloc].offset < (offset + length)))
{
int spaces = ((((sec.relocs[reloc].offset + 1) % line_length) * 3) +
indent - 1);
spaces -= line.str ().size ();
line << std::setw (spaces) << " ^" << reloc
<< ':' << std::hex << sec.relocs[reloc].offset << std::dec;
++reloc;
}
std::cout << line.str () << std::endl;
offset += length;
}
if (sec.relocs.size ())
{
int count = 0;
std::cout << " info offset addend symbol name" << std::endl;
for (size_t f = 0; f < r.secs[s].relocs.size (); ++f)
{
rap::relocation& reloc = r.secs[s].relocs[f];
std::cout << std::setw (4) << count++ << ' ';
reloc.output ();
std::cout << std::endl;
}
}
}
}
}
}
void
rap_expander (rld::files::paths& raps, bool warnings)
{
std::cout << "Expanding .... " << std::endl;
for (rld::files::paths::iterator pi = raps.begin();
pi != raps.end();
++pi)
{
rap::file r (*pi, warnings);
std::cout << ' ' << r.name () << std::endl;
r.expand ();
}
}
/**
* RTEMS RAP options.
*/
static struct option rld_opts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' },
{ "no-warn", no_argument, NULL, 'n' },
{ "all", no_argument, NULL, 'a' },
{ "header", no_argument, NULL, 'H' },
{ "machine", no_argument, NULL, 'm' },
{ "layout", no_argument, NULL, 'l' },
{ "strings", no_argument, NULL, 's' },
{ "symbols", no_argument, NULL, 'S' },
{ "relocs", no_argument, NULL, 'r' },
{ "map", no_argument, NULL, 'm' },
{ "expand", no_argument, NULL, 'x' },
{ NULL, 0, NULL, 0 }
};
void
usage (int exit_code)
{
std::cout << "rtems-rap [options] objects" << std::endl
<< "Options and arguments:" << std::endl
<< " -h : help (also --help)" << std::endl
<< " -V : print linker version number and exit (also --version)" << std::endl
<< " -v : verbose (trace import parts), can be supply multiple times" << std::endl
<< " to increase verbosity (also --verbose)" << std::endl
<< " -n : no warnings (also --no-warn)" << std::endl
<< " -a : show all (also --all)" << std::endl
<< " -H : show header (also --header)" << std::endl
<< " -m : show machine details (also --machine)" << std::endl
<< " -l : show layout (also --layout)" << std::endl
<< " -s : show strings (also --strings)" << std::endl
<< " -S : show symbols (also --symbols)" << std::endl
<< " -r : show relocations (also --relocs)" << std::endl
<< " -o : linkage overlay (also --overlay)" << std::endl
<< " -x : expand (also --expand)" << std::endl;
::exit (exit_code);
}
static void
fatal_signal (int signum)
{
signal (signum, SIG_DFL);
rld::process::temporaries.clean_up ();
/*
* Get the same signal again, this time not handled, so its normal effect
* occurs.
*/
kill (getpid (), signum);
}
static void
setup_signals (void)
{
if (signal (SIGINT, SIG_IGN) != SIG_IGN)
signal (SIGINT, fatal_signal);
#ifdef SIGHUP
if (signal (SIGHUP, SIG_IGN) != SIG_IGN)
signal (SIGHUP, fatal_signal);
#endif
if (signal (SIGTERM, SIG_IGN) != SIG_IGN)
signal (SIGTERM, fatal_signal);
#ifdef SIGPIPE
if (signal (SIGPIPE, SIG_IGN) != SIG_IGN)
signal (SIGPIPE, fatal_signal);
#endif
#ifdef SIGCHLD
signal (SIGCHLD, SIG_DFL);
#endif
}
int
main (int argc, char* argv[])
{
int ec = 0;
setup_signals ();
try
{
rld::files::paths raps;
bool warnings = true;
bool show = false;
bool show_header = false;
bool show_machine = false;
bool show_layout = false;
bool show_strings = false;
bool show_symbols = false;
bool show_relocs = false;
bool overlay = false;
bool expand = false;
while (true)
{
int opt = ::getopt_long (argc, argv, "hvVnaHlsSrox", rld_opts, NULL);
if (opt < 0)
break;
switch (opt)
{
case 'V':
std::cout << "rtems-rap (RTEMS RAP Manager) " << rld::version ()
<< std::endl;
::exit (0);
break;
case 'v':
rld::verbose_inc ();
break;
case 'n':
warnings = false;
break;
case 'a':
show = true;
show_header = true;
show_machine = true;
show_layout = true;
show_strings = true;
show_symbols = true;
show_relocs = true;
break;
case 'H':
show = true;
show_header = true;
break;
case 'm':
show = true;
show_machine = true;
break;
case 'l':
show = true;
show_layout = true;
break;
case 's':
show = true;
show_strings = true;
break;
case 'S':
show = true;
show_symbols = true;
break;
case 'r':
show = true;
show_relocs = true;
break;
case 'o':
overlay = true;
break;
case 'x':
expand = true;
break;
case '?':
case 'h':
usage (0);
break;
}
}
argc -= optind;
argv += optind;
std::cout << "RTEMS RAP " << rld::version () << std::endl << std::endl;
/*
* If there are no RAP files so there is nothing to do.
*/
if (argc == 0)
throw rld::error ("no RAP files", "options");
/*
* Load the remaining command line arguments into a container.
*/
while (argc--)
raps.push_back (*argv++);
if (show)
rap_show (raps,
warnings,
show_header,
show_machine,
show_layout,
show_strings,
show_symbols,
show_relocs);
if (overlay)
rap_overlay (raps, warnings);
if (expand)
rap_expander (raps, warnings);
}
catch (rld::error re)
{
std::cerr << "error: "
<< re.where << ": " << re.what
<< std::endl;
ec = 10;
}
catch (std::exception e)
{
int status;
char* realname;
realname = abi::__cxa_demangle (e.what(), 0, 0, &status);
std::cerr << "error: exception: " << realname << " [";
::free (realname);
const std::type_info &ti = typeid (e);
realname = abi::__cxa_demangle (ti.name(), 0, 0, &status);
std::cerr << realname << "] " << e.what () << std::endl;
::free (realname);
ec = 11;
}
catch (...)
{
/*
* Helps to know if this happens.
*/
std::cout << "error: unhandled exception" << std::endl;
ec = 12;
}
return ec;
}