root/usr/src/cmd/sgs/elfwrap/common/elfwrap.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include        <sys/types.h>
#include        <sys/stat.h>
#include        <sys/mman.h>
#include        <unistd.h>
#include        <fcntl.h>
#include        <libgen.h>
#include        <errno.h>
#include        <libelf.h>
#include        <stdio.h>
#include        <strings.h>
#include        <msg.h>
#include        <machdep.h>
#include        <_libelf.h>
#include        <_elfwrap.h>

/*
 * This module is compiled to support 32-bit and 64-bit class objects.  Define
 * the necessary interfaces for these classes.
 */
#if     defined(_ELF64)
#define input   input64
#define output  output64
#else
#define input   input32
#define output  output32
#endif

static StdSec_t StdSecs[] = {
        { MSG_ORIG(MSG_SCN_SYMTAB),     SHT_SYMTAB,     0 },
        { MSG_ORIG(MSG_SCN_STRTAB),     SHT_STRTAB,     SHF_STRINGS},
        { MSG_ORIG(MSG_SCN_SHSTRTAB),   SHT_STRTAB,     SHF_STRINGS},
        { NULL,                         0,              0 }
};

/*
 * Process all input files.  These contain the data that will be assigned to a
 * new ELF section.
 */
int
input(int argc, char **argv, const char *prog, const char *ofile,
    ObjDesc_t *odp)
{
        OutSec_t        outsec;
        StdSec_t        *stdsecs;
        size_t          ndx, cnt;
        int             ret = 0, fd = -1;

        /*
         * Make sure we have access to read each input file, and prepare an
         * output section descriptor for each.  Note, we assign section indexes
         * starting at 1, as section index 0 is special, and is created by
         * libelf.
         */
        for (ndx = 1; argc; argc--, argv++, ndx++) {
                char            *file = *argv;
                struct stat     status;
                size_t          namesz;

                /*
                 * Close any previously opened file.
                 */
                if (fd != -1)
                        (void) close(fd);

                /*
                 * Identify the section.
                 */
                outsec.os_name = basename(file);
                outsec.os_type = SHT_PROGBITS;
                outsec.os_flags = SHF_ALLOC;
                outsec.os_ndx = ndx;

                if ((fd = open(file, O_RDONLY)) == -1) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_ERR_OPEN),
                            prog, file, strerror(err));
                        ret = 1;
                        continue;
                }
                if (fstat(fd, &status) == -1) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_ERR_FSTAT),
                            prog, file, strerror(err));
                        ret = 1;
                        continue;
                }

                if ((outsec.os_size = status.st_size) == 0) {
                        (void) fprintf(stderr, MSG_INTL(MSG_WARN_ZERO),
                            prog, file);
                        continue;
                }

                if ((outsec.os_addr = mmap(0, outsec.os_size, PROT_READ,
                    MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_ERR_MMAP),
                            prog, file, strerror(err));
                        ret = 1;
                        continue;
                }

                if (alist_append(&(odp->od_outsecs), &outsec, sizeof (OutSec_t),
                    AL_CNT_WOSECS) == 0) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_ERR_ALLOC),
                            prog, file, strerror(err));
                        return (1);
                }

                /*
                 * Each data section contributes:
                 *
                 * i.   its basename, prefixed with a "dot", to the .shstrtab.
                 * ii.  a section symbol.
                 * iii. a data symbol, using the basename, with an
                 *      appended "_data" string.
                 * iv.  a data size symbol, using the basename with an
                 *      appended "_size" string.
                 */
                namesz = strlen(outsec.os_name) + 1;

                odp->od_symtabno += 3;
                odp->od_strtabsz += (namesz + MSG_STR_START_SIZE);
                odp->od_strtabsz += (namesz + MSG_STR_END_SIZE);
                odp->od_shstrtabsz += (namesz + MSG_STR_DOT_SIZE);
        }

        if (fd != -1)
                (void) close(fd);

        /*
         * If an error occurred, or no input files contributed data, bail now.
         */
        if (ret || (odp->od_outsecs == NULL))
                return (1);

        /*
         * Create section descriptors for .symtab, .strtab, and .shstrtab.
         */
        for (cnt = 0, stdsecs = &StdSecs[cnt]; stdsecs->ss_name; cnt++,
            ndx++, stdsecs = &StdSecs[cnt]) {

                /*
                 * Identify the section.
                 */
                outsec.os_name = stdsecs->ss_name;
                outsec.os_type = stdsecs->ss_type;
                outsec.os_flags = stdsecs->ss_flags;
                outsec.os_ndx = ndx;
                outsec.os_size = 0;
                outsec.os_addr = 0;

                if (alist_append(&(odp->od_outsecs), &outsec, sizeof (OutSec_t),
                    AL_CNT_WOSECS) == 0) {
                        int err = errno;
                        (void) fprintf(stderr, MSG_INTL(MSG_ERR_ALLOC),
                            prog, outsec.os_name, strerror(err));
                        return (1);
                }

                /*
                 * Each standard section contributes:
                 *
                 * i.   its section name to the .shstrtab.
                 * ii.  a section symbol.
                 */
                odp->od_symtabno++;
                odp->od_shstrtabsz += (strlen(outsec.os_name) + 1);
        }

        /*
         * The symbol table requires an initial NULL entry and a following
         * FILE entry.  Both string tables require an initial NULL byte.
         * The .strtab requires room for the output file name (STT_FILE).
         */
        odp->od_symtabno += 2;
        odp->od_strtabsz += strlen(ofile) + 2;
        odp->od_shstrtabsz++;

        return (0);
}

/*
 * Having captured all input data, create the output file.
 */
int
output(const char *prog, int fd, const char *ofile, ushort_t mach,
    ObjDesc_t *odp)
{
        Aliste          off;
        Elf             *melf, *oelf;
        Ehdr            *ehdr;
        Sym             *symtab, *secsymtabent, *glbsymtabent;
        char            *strtab, *strtabent, *shstrtab, *shstrtabent;
        OutSec_t        *outsec, *outsymtab, *outstrtab, *outshstrtab;
        size_t          len;
        TargDesc_t      tdesc;

        /*
         * Obtain any target specific ELF information.
         */
        if (mach == 0)
                mach = M_MACH;

        switch (mach) {
#if     !defined(lint)
                case EM_SPARC:
                        target_init_sparc(&tdesc);
                        break;
                case EM_SPARCV9:
                        target_init_sparcv9(&tdesc);
                        break;
                case EM_386:
                        target_init_i386(&tdesc);
                        break;
                case EM_AMD64:
                        target_init_amd64(&tdesc);
                        break;
#else
                default:
                        target_init(&tdesc);
                        break;
#endif
        }
        /*
         * Create a new ELF descriptor for the new output file.
         */
        if ((oelf = elf_begin(fd, ELF_C_WRITE, 0)) == NULL) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_BEGIN), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }

        /*
         * Create and initialize the new ELF header.
         */
        if ((ehdr = elf_newehdr(oelf)) == NULL) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_NEWEHDR), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }

        /*
         * Note, the ELF header is initialized to reflect the host running
         * elfwrap(1) rather than the target.  Using host byte order allows
         * elfwrap(1) to create the object data.  Prior to the final update,
         * the output ELF header is modified to reflect the target, causing
         * libelf to produce the output object using the correct byte order
         * and other target information.
         */
        ehdr->e_ident[EI_DATA] = M_DATA;
        ehdr->e_type = ET_REL;
        ehdr->e_version = EV_CURRENT;

        /*
         * Create the required number of new sections, their associated section
         * header, and an initial data buffer.
         */
        for (ALIST_TRAVERSE(odp->od_outsecs, off, outsec)) {
                Elf_Scn         *scn;
                Elf_Data        *data;
                Shdr            *shdr;

                if ((scn = elf_newscn(oelf)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_NEWSCN),
                            prog, outsec->os_name, elf_errmsg(elf_errno()));
                        return (1);
                }
                if ((shdr = elf_getshdr(scn)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETSHDR),
                            prog, outsec->os_name, elf_errmsg(elf_errno()));
                        return (1);
                }

                /*
                 * Assign the section type and flags.
                 */
                shdr->sh_type = outsec->os_type;
                shdr->sh_flags = outsec->os_flags;

                if ((data = elf_newdata(scn)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_NEWDATA),
                            prog, outsec->os_name, elf_errmsg(elf_errno()));
                        return (1);
                }

                switch (shdr->sh_type) {
                case SHT_PROGBITS:
                        /*
                         * If this is a PROGBITS section, then the data
                         * originates from an input file.  Assign the data
                         * buffer to this input file and provide a default
                         * alignment.
                         */
                        data->d_buf = outsec->os_addr;
                        data->d_type = ELF_T_BYTE;
                        data->d_size = outsec->os_size;
                        data->d_align = tdesc.td_align;
                        break;

                case SHT_SYMTAB:
                        /*
                         * If this is the symbol table, use the symbol count to
                         * reserve sufficient space for the symbols we need.
                         */
                        data->d_buf = 0;
                        data->d_type = ELF_T_SYM;
                        data->d_size = (odp->od_symtabno * tdesc.td_symsz);
                        data->d_align = tdesc.td_align;
                        break;

                case SHT_STRTAB:
                        /*
                         * If this is a string table, use the table size to
                         * reserve sufficient space for the strings we need.
                         */
                        data->d_buf = 0;
                        data->d_type = ELF_T_BYTE;
                        if (strcmp(outsec->os_name, MSG_ORIG(MSG_SCN_STRTAB)))
                                data->d_size = odp->od_shstrtabsz;
                        else
                                data->d_size = odp->od_strtabsz;
                        data->d_align = 1;
                        break;
                }
        }

        /*
         * Write the ELF data into a memory image.
         */
        if ((elf_update(oelf, ELF_C_WRIMAGE)) == -1) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_UPDATE), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }

        /*
         * Assign an ELF descriptor to the memory image.
         */
        if ((melf = elf_begin(0, ELF_C_IMAGE, oelf)) == NULL) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_BEGIN), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }

        /*
         * Get the ELF header from the memory image.
         */
        if ((ehdr = elf_getehdr(melf)) == NULL) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETEHDR), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }

        /*
         * Read the section header and data from the new sections of the
         * memory image.
         */
        for (ALIST_TRAVERSE(odp->od_outsecs, off, outsec)) {
                Elf_Scn         *scn;
                Shdr            *shdr;

                if ((scn = elf_getscn(melf, outsec->os_ndx)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETSCN),
                            prog, outsec->os_name, elf_errmsg(elf_errno()));
                        return (1);
                }
                if ((outsec->os_shdr = shdr = elf_getshdr(scn)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETSHDR),
                            prog, outsec->os_name, elf_errmsg(elf_errno()));
                        return (1);
                }
                if ((outsec->os_data = elf_getdata(scn, NULL)) == NULL) {
                        (void) fprintf(stderr, MSG_INTL(MSG_ELF_GETDATA),
                            prog, outsec->os_name, elf_errmsg(elf_errno()));
                        return (1);
                }

                if (shdr->sh_type == SHT_PROGBITS)
                        continue;

                /*
                 * Remember the symbol table and string tables, so that they
                 * can be filled in later.
                 */
                if (shdr->sh_type == SHT_SYMTAB) {
                        outsymtab = outsec;
                        symtab = (Sym *)outsec->os_data->d_buf;
                } else if (shdr->sh_type == SHT_STRTAB) {
                        if (strcmp(outsec->os_name, MSG_ORIG(MSG_SCN_STRTAB))) {
                                outshstrtab = outsec;
                                shstrtab = (char *)outsec->os_data->d_buf;
                        } else {
                                outstrtab = outsec;
                                strtab = (char *)outsec->os_data->d_buf;
                        }
                }
        }

        /*
         * Update the ELF header with the .shstrtab index.
         */
        ehdr->e_shstrndx = outshstrtab->os_ndx;

        /*
         * Set up the string table entries, and skip the first byte.
         */
        strtabent = strtab;
        strtabent++;

        shstrtabent = shstrtab;
        shstrtabent++;

        /*
         * Skip the first symbol table entry.  Write a FILE entry, and set
         * up for adding sections and data symbols.  Associate the symbol
         * table with the string table.
         */
        secsymtabent = symtab;
        secsymtabent++;
        secsymtabent->st_name = (strtabent - strtab);
        secsymtabent->st_info = ELF_ST_INFO(STB_LOCAL, STT_NOTYPE);
        secsymtabent->st_shndx = SHN_ABS;
        secsymtabent++;

        glbsymtabent = secsymtabent;
        glbsymtabent += alist_nitems(odp->od_outsecs);

        outsymtab->os_shdr->sh_link = outstrtab->os_ndx;

        /*
         * Write the output file name to the .strtab.
         */
        len = strlen(ofile) + 1;
        (void) memcpy(strtabent, ofile, len);
        strtabent += len;

        /*
         * Rescan all the new sections, adding symbols and strings as required.
         */
        for (ALIST_TRAVERSE(odp->od_outsecs, off, outsec)) {
                size_t  alen;

                /*
                 * Create a section symbol.
                 */
                secsymtabent->st_info = ELF_ST_INFO(STB_LOCAL, STT_SECTION);
                secsymtabent->st_shndx = outsec->os_ndx;
                secsymtabent++;

                /*
                 * Store the section name, (with an appended "." if the section
                 * name is derived from the input file name), and point the
                 * section header to this name.
                 */
                outsec->os_shdr->sh_name = (shstrtabent - shstrtab);

                if (outsec->os_shdr->sh_type == SHT_PROGBITS) {
                        (void) memcpy(shstrtabent, MSG_ORIG(MSG_STR_DOT),
                            MSG_STR_DOT_SIZE);
                        shstrtabent += MSG_STR_DOT_SIZE;
                }

                len = strlen(outsec->os_name) + 1;
                (void) memcpy(shstrtabent, outsec->os_name, len);
                shstrtabent += len;

                if (outsec->os_shdr->sh_type != SHT_PROGBITS)
                        continue;

                /*
                 * Add a symbol pointing to this PROGBITS section.  The value
                 * is the base offset of this section, which can only be 0.
                 * The size of the symbol can be taken straight from the section
                 * header information (that libelf generated).
                 */
                glbsymtabent->st_name = (strtabent - strtab);
                glbsymtabent->st_info = ELF_ST_INFO(STB_GLOBAL, STT_OBJECT);
                glbsymtabent->st_shndx = outsec->os_ndx;
                glbsymtabent->st_size = outsec->os_shdr->sh_size;
                glbsymtabent++;

                /*
                 * Store this symbol name (with an appended "_data") in the
                 * string table.
                 */
                len--;
                (void) memcpy(strtabent, outsec->os_name, len);
                strtabent += len;
                alen = (MSG_STR_START_SIZE + 1);
                (void) memcpy(strtabent, MSG_ORIG(MSG_STR_START), alen);
                strtabent += alen;

                /*
                 * Add a symbol indicating the size of this PROGBITS section.
                 */
                glbsymtabent->st_name = (strtabent - strtab);
                glbsymtabent->st_info = ELF_ST_INFO(STB_GLOBAL, STT_OBJECT);
                glbsymtabent->st_shndx = outsec->os_ndx;
                glbsymtabent->st_value = outsec->os_shdr->sh_size;
                glbsymtabent++;

                /*
                 * Store this symbol name (with an appended "_end") in the
                 * string table.
                 */
                (void) memcpy(strtabent, outsec->os_name, len);
                strtabent += len;
                alen = (MSG_STR_END_SIZE + 1);
                (void) memcpy(strtabent, MSG_ORIG(MSG_STR_END), alen);
                strtabent += alen;
        }

        /*
         * Update the .symtab section header with the index of the first
         * non-local symbol.  The only locals written are the section symbols.
         */
        outsymtab->os_shdr->sh_info = (secsymtabent - symtab);

        /*
         * Having updated the image following the byte order of elfwrap(), seed
         * the ELF header with the appropriate target information.
         */
        ehdr->e_ident[EI_CLASS] = tdesc.td_class;
        ehdr->e_ident[EI_DATA] = tdesc.td_data;
        ehdr->e_machine = tdesc.td_mach;

        /*
         * If the output relocatable object is targeted to a machine with a
         * different byte order than the host running elfwrap(1), swap the data
         * to the target byte order.
         */
        if ((_elf_sys_encoding() != ehdr->e_ident[EI_DATA]) &&
            (_elf_swap_wrimage(melf) != 0)) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_SWAP_WRIMAGE), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }
        (void) elf_end(melf);

        /*
         * Finally, write the updated memory image out to disc.
         */
        if ((elf_update(oelf, ELF_C_WRITE)) == -1) {
                (void) fprintf(stderr, MSG_INTL(MSG_ELF_UPDATE), prog,
                    elf_errmsg(elf_errno()));
                return (1);
        }
        (void) elf_end(oelf);

        return (0);
}