/*
 * ELF object format
 *
 *  Copyright (C) 2003-2007  Michael Urman
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <util.h>

/* Notes
 *
 * elf-objfmt uses the "linking" view of an ELF file:
 * ELF header, an optional program header table, several sections,
 * and a section header table
 *
 * The ELF header tells us some overall program information,
 *   where to find the PHT (if it exists) with phnum and phentsize, 
 *   and where to find the SHT with shnum and shentsize
 *
 * The PHT doesn't seem to be generated by NASM for elftest.asm
 *
 * The SHT
 *
 * Each Section is spatially disjoint, and has exactly one SHT entry.
 */

#include <libyasm.h>

#include "elf.h"
#include "elf-machine.h"

typedef struct yasm_objfmt_elf {
    yasm_objfmt_base objfmt;            /* base structure */

    elf_symtab_head* elf_symtab;        /* symbol table of indexed syms */
    elf_strtab_head* shstrtab;          /* section name strtab */
    elf_strtab_head* strtab;            /* strtab entries */

    elf_strtab_entry *file_strtab_entry;/* .file symbol associated string */
    yasm_symrec *dotdotsym;             /* ..sym symbol */
} yasm_objfmt_elf;

typedef struct {
    yasm_objfmt_elf *objfmt_elf;
    yasm_errwarns *errwarns;
    FILE *f;
    elf_secthead *shead;
    yasm_section *sect;
    yasm_object *object;
    unsigned long sindex;
    yasm_symrec *GOT_sym;
} elf_objfmt_output_info;

typedef struct {
    yasm_object *object;
    yasm_objfmt_elf *objfmt_elf;
    yasm_errwarns *errwarns;
    int local_names;
} build_symtab_info;

yasm_objfmt_module yasm_elf_LTX_objfmt;
yasm_objfmt_module yasm_elf32_LTX_objfmt;
yasm_objfmt_module yasm_elf64_LTX_objfmt;
yasm_objfmt_module yasm_elfx32_LTX_objfmt;


static elf_symtab_entry *
elf_objfmt_symtab_append(yasm_objfmt_elf *objfmt_elf, yasm_symrec *sym,
                         elf_section_index sectidx, elf_symbol_binding bind,
                         elf_symbol_type type, elf_symbol_vis vis,
                         yasm_expr *size, elf_address *value,
                         yasm_object *object)
{
    elf_symtab_entry *entry = yasm_symrec_get_data(sym, &elf_symrec_data);

    if (!entry) {
        /*@only@*/ char *symname = yasm_symrec_get_global_name(sym, object);
        elf_strtab_entry *name =
            elf_strtab_append_str(objfmt_elf->strtab, symname);
        yasm_xfree(symname);
        entry = elf_symtab_entry_create(name, sym);
        yasm_symrec_add_data(sym, &elf_symrec_data, entry);
    }

    /* Only append to table if not already appended */
    if (!elf_sym_in_table(entry))
        elf_symtab_append_entry(objfmt_elf->elf_symtab, entry);

    elf_symtab_set_nonzero(entry, NULL, sectidx, bind, type, size, value);
    elf_sym_set_visibility(entry, vis);

    return entry;
}

static elf_symtab_entry *
build_extern(yasm_objfmt_elf *objfmt_elf, yasm_symrec *sym, yasm_object *object)
{
    yasm_valparamhead *objext_valparams =
        yasm_symrec_get_objext_valparams(sym);

    if (objext_valparams) {
        yasm_valparam *vp = yasm_vps_first(objext_valparams);
        for (; vp; vp = yasm_vps_next(vp)) {
            if (yasm_vp_string(vp))
                yasm_error_set(YASM_ERROR_TYPE,
                               N_("unrecognized symbol type `%s'"),
                               yasm_vp_string(vp));
        }
    }

    return elf_objfmt_symtab_append(objfmt_elf, sym, SHN_UNDEF, STB_GLOBAL, 0,
                                    STV_DEFAULT, NULL, NULL, object);
}

struct elf_build_global_data {
    yasm_expr *size;
    unsigned long type; /* elf_symbol_type */
    elf_symbol_vis vis;
    unsigned int vis_overrides;
};

static int
elf_global_helper_valparam(void *obj, yasm_valparam *vp, unsigned long line,
                           void *d)

{
    struct elf_build_global_data *data = (struct elf_build_global_data *)d;
    const char *s;

    if (!vp->val && (s = yasm_vp_id(vp))) {
        yasm_error_set(YASM_ERROR_TYPE, N_("unrecognized symbol type `%s'"),
                       s);
        return -1;
    } else if (!vp->val && vp->type == YASM_PARAM_EXPR && !data->size) {
        data->size = yasm_expr_copy(vp->param.e);
        return 0;
    } else
        return yasm_dir_helper_valparam_warn(obj, vp, line, d);
}

static int
elf_global_helper_vis(void *obj, yasm_valparam *vp, unsigned long line,
                      void *d, uintptr_t vis)
{
    struct elf_build_global_data *data = (struct elf_build_global_data *)d;
    data->vis = vis;
    data->vis_overrides++;
    return 0;
}


static elf_symtab_entry *
build_global(yasm_objfmt_elf *objfmt_elf, yasm_symrec *sym, yasm_object *object)
{
    yasm_valparamhead *objext_valparams =
        yasm_symrec_get_objext_valparams(sym);

    struct elf_build_global_data data;

    static const yasm_dir_help help[] = {
        { "function", 0, yasm_dir_helper_flag_set,
          offsetof(struct elf_build_global_data, type), STT_FUNC },
        { "data", 0, yasm_dir_helper_flag_set,
          offsetof(struct elf_build_global_data, type), STT_OBJECT },
        { "object", 0, yasm_dir_helper_flag_set,
          offsetof(struct elf_build_global_data, type), STT_OBJECT },
        { "internal", 0, elf_global_helper_vis, 0, STV_INTERNAL },
        { "hidden", 0, elf_global_helper_vis, 0, STV_HIDDEN },
        { "protected", 0, elf_global_helper_vis, 0, STV_PROTECTED },
    };

    data.size = NULL;
    data.type = 0;
    data.vis = STV_DEFAULT;
    data.vis_overrides = 0;

    if (objext_valparams)
        yasm_dir_helper(sym, yasm_vps_first(objext_valparams),
                        yasm_symrec_get_decl_line(sym), help, NELEMS(help),
                        &data, elf_global_helper_valparam);

    if (data.vis_overrides > 1) {
        yasm_warn_set(YASM_WARN_GENERAL,
            N_("More than one symbol visibility provided; using last"));
    }

    return elf_objfmt_symtab_append(objfmt_elf, sym, SHN_UNDEF, STB_GLOBAL,
                                    data.type, data.vis, data.size, NULL,
                                    object);
}

static /*@null@*/ elf_symtab_entry *
build_common(yasm_objfmt_elf *objfmt_elf, yasm_symrec *sym, yasm_object *object)
{
    yasm_expr **size = yasm_symrec_get_common_size(sym);
    yasm_valparamhead *objext_valparams =
        yasm_symrec_get_objext_valparams(sym);
    unsigned long addralign = 0;

    if (objext_valparams) {
        yasm_valparam *vp = yasm_vps_first(objext_valparams);
        for (; vp; vp = yasm_vps_next(vp)) {
            if (!vp->val) {
                /*@only@*/ /*@null@*/ yasm_expr *align_expr;
                /*@dependent@*/ /*@null@*/ const yasm_intnum *align_intn;

                if (!(align_expr = yasm_vp_expr(vp, object->symtab,
                                                yasm_symrec_get_def_line(sym)))
                    || !(align_intn = yasm_expr_get_intnum(&align_expr, 0))) {
                    yasm_error_set(YASM_ERROR_VALUE,
                        N_("alignment constraint is not an integer"));
                    if (align_expr)
                        yasm_expr_destroy(align_expr);
                    return NULL;
                }
                addralign = yasm_intnum_get_uint(align_intn);
                yasm_expr_destroy(align_expr);

                /* Alignments must be a power of two. */
                if (!is_exp2(addralign)) {
                    yasm_error_set(YASM_ERROR_VALUE,
                        N_("alignment constraint is not a power of two"));
                    return NULL;
                }
            } else
                yasm_warn_set(YASM_WARN_GENERAL,
                              N_("Unrecognized qualifier `%s'"), vp->val);
        }
    }

    return elf_objfmt_symtab_append(objfmt_elf, sym, SHN_COMMON, STB_GLOBAL,
                                    0, STV_DEFAULT, *size, &addralign, object);
}

static int
elf_objfmt_build_symtab(yasm_symrec *sym, /*@null@*/ void *d)
{
    build_symtab_info *info = (build_symtab_info *)d;
    yasm_sym_vis vis = yasm_symrec_get_visibility(sym);
    yasm_sym_status status = yasm_symrec_get_status(sym);
    elf_symtab_entry *entry = yasm_symrec_get_data(sym, &elf_symrec_data);
    elf_address value=0;
    yasm_section *sect=NULL;
    yasm_bytecode *precbc=NULL;

    assert(info != NULL);

    if (vis & YASM_SYM_EXTERN) {
        entry = build_extern(info->objfmt_elf, sym, info->object);
        yasm_errwarn_propagate(info->errwarns,
                               yasm_symrec_get_decl_line(sym));
        return 0;
    }

    if (vis & YASM_SYM_COMMON) {
        entry = build_common(info->objfmt_elf, sym, info->object);
        yasm_errwarn_propagate(info->errwarns,
                               yasm_symrec_get_decl_line(sym));
        /* If the COMMON variable was actually defined, fall through. */
        if (!(status & YASM_SYM_DEFINED))
            return 0;
    }

    /* Ignore any undefined at this point. */
    if (!(status & YASM_SYM_DEFINED))
        return 0;

    if (!yasm_symrec_get_label(sym, &precbc)) {
        if (!yasm_symrec_get_equ(sym) && !yasm_symrec_is_abs(sym))
            return 0;
        precbc = NULL;
    }

    if (precbc)
        sect = yasm_bc_get_section(precbc);

    if (entry && elf_sym_in_table(entry))
        ;
    else if (vis & YASM_SYM_GLOBAL) {
        entry = build_global(info->objfmt_elf, sym, info->object);
        yasm_errwarn_propagate(info->errwarns, yasm_symrec_get_decl_line(sym));
    } else {
        int is_sect = 0;

        /* Locals (except when debugging) do not need to be
         * in the symbol table, unless they're a section.
         */
        if (sect &&
            strcmp(yasm_symrec_get_name(sym), yasm_section_get_name(sect))==0)
            is_sect = 1;
#if 0
        /* FIXME: to enable this we must have handling in place for special
         * symbols.
         */
        if (!info->local_names && !is_sect)
            return 0;
#else
        if (yasm_symrec_get_equ(sym) && !yasm_symrec_is_abs(sym))
            return 0;
#endif
        entry = yasm_symrec_get_data(sym, &elf_symrec_data);
        if (!entry) {
            /*@only@*/ char *symname =
                yasm_symrec_get_global_name(sym, info->object);
            elf_strtab_entry *name = !info->local_names || is_sect ? NULL :
                elf_strtab_append_str(info->objfmt_elf->strtab, symname);
            yasm_xfree(symname);
            entry = elf_symtab_entry_create(name, sym);
            yasm_symrec_add_data(sym, &elf_symrec_data, entry);
        }

        if (!elf_sym_in_table(entry))
            elf_symtab_insert_local_sym(info->objfmt_elf->elf_symtab, entry);

        elf_symtab_set_nonzero(entry, sect, 0, STB_LOCAL,
                               is_sect ? STT_SECTION : 0, NULL, 0);

        if (is_sect)
            return 0;
    }

    if (precbc)
        value = yasm_bc_next_offset(precbc);
    elf_symtab_set_nonzero(entry, sect, 0, 0, 0, NULL, &value);

    return 0;
}

static yasm_objfmt *
elf_objfmt_create_common(yasm_object *object, yasm_objfmt_module *module,
                         int bits_pref,
                         const elf_machine_handler **elf_march_out)
{
    yasm_objfmt_elf *objfmt_elf = yasm_xmalloc(sizeof(yasm_objfmt_elf));
    yasm_symrec *filesym;
    elf_symtab_entry *entry;
    const elf_machine_handler *elf_march;

    objfmt_elf->objfmt.module = module;
    elf_march = elf_set_arch(object->arch, object->symtab, bits_pref);
    if (!elf_march) {
        yasm_xfree(objfmt_elf);
        return NULL;
    }
    if (elf_march_out)
        *elf_march_out = elf_march;

    objfmt_elf->shstrtab = elf_strtab_create();
    objfmt_elf->strtab = elf_strtab_create();
    objfmt_elf->elf_symtab = elf_symtab_create();

    /* FIXME: misuse of NULL bytecode here; it works, but only barely. */
    filesym = yasm_symtab_define_label(object->symtab, ".file", NULL, 0, 0);
    /* Put in current input filename; we'll replace it in output() */
    objfmt_elf->file_strtab_entry =
        elf_strtab_append_str(objfmt_elf->strtab, object->src_filename);
    entry = elf_symtab_entry_create(objfmt_elf->file_strtab_entry, filesym);
    yasm_symrec_add_data(filesym, &elf_symrec_data, entry);
    elf_symtab_set_nonzero(entry, NULL, SHN_ABS, STB_LOCAL, STT_FILE, NULL,
                           NULL);
    elf_symtab_append_entry(objfmt_elf->elf_symtab, entry);

    /* FIXME: misuse of NULL bytecode */
    objfmt_elf->dotdotsym =
        yasm_symtab_define_label(object->symtab, "..sym", NULL, 0, 0);

    return (yasm_objfmt *)objfmt_elf;
}

static yasm_objfmt *
elf_objfmt_create(yasm_object *object)
{
    const elf_machine_handler *elf_march;
    yasm_objfmt *objfmt;
    yasm_objfmt_elf *objfmt_elf;

    objfmt = elf_objfmt_create_common(object, &yasm_elf_LTX_objfmt, 0,
                                      &elf_march);
    if (objfmt) {
        objfmt_elf = (yasm_objfmt_elf *)objfmt;
        /* Figure out which bitness of object format to use */
        if (strcmp (elf_march->machine, "x32") == 0)
            objfmt_elf->objfmt.module = &yasm_elfx32_LTX_objfmt;
        else if (elf_march->bits == 32)
            objfmt_elf->objfmt.module = &yasm_elf32_LTX_objfmt;
        else if (elf_march->bits == 64)
            objfmt_elf->objfmt.module = &yasm_elf64_LTX_objfmt;
    }
    return objfmt;
}

static yasm_objfmt *
elf32_objfmt_create(yasm_object *object)
{
    return elf_objfmt_create_common(object, &yasm_elf32_LTX_objfmt, 32, NULL);
}

static yasm_objfmt *
elf64_objfmt_create(yasm_object *object)
{
    return elf_objfmt_create_common(object, &yasm_elf64_LTX_objfmt, 64, NULL);
}

static yasm_objfmt *
elfx32_objfmt_create(yasm_object *object)
{
    return elf_objfmt_create_common(object, &yasm_elfx32_LTX_objfmt, 32, NULL);
}

static long
elf_objfmt_output_align(FILE *f, unsigned int align)
{
    long pos;
    unsigned long delta;
    if (!is_exp2(align))
        yasm_internal_error("requested alignment not a power of two");

    pos = ftell(f);
    if (pos == -1) {
        yasm_error_set(YASM_ERROR_IO,
                       N_("could not get file position on output file"));
        return -1;
    }
    delta = align - (pos & (align-1)); 
    if (delta != align) {
        pos += delta;
        if (fseek(f, pos, SEEK_SET) < 0) {
            yasm_error_set(YASM_ERROR_IO,
                           N_("could not set file position on output file"));
            return -1;
        }
    }
    return pos;
}

static int
elf_objfmt_output_reloc(yasm_symrec *sym, yasm_bytecode *bc,
                        unsigned char *buf, unsigned int destsize,
                        unsigned int valsize, int warn, void *d)
{
    elf_reloc_entry *reloc;
    elf_objfmt_output_info *info = d;
    yasm_intnum *zero;
    int retval;

    reloc = elf_reloc_entry_create(sym, NULL,
        yasm_intnum_create_uint(bc->offset), 0, valsize, 0);
    if (reloc == NULL) {
        yasm_error_set(YASM_ERROR_TYPE, N_("elf: invalid relocation size"));
        return 1;
    }
    /* allocate .rel[a] sections on a need-basis */
    elf_secthead_append_reloc(info->sect, info->shead, reloc);

    zero = yasm_intnum_create_uint(0);
    elf_handle_reloc_addend(zero, reloc, 0);
    retval = yasm_arch_intnum_tobytes(info->object->arch, zero, buf, destsize,
                                      valsize, 0, bc, warn);
    yasm_intnum_destroy(zero);
    return retval;
}

static int
elf_objfmt_output_value(yasm_value *value, unsigned char *buf,
                        unsigned int destsize, unsigned long offset,
                        yasm_bytecode *bc, int warn, /*@null@*/ void *d)
{
    /*@null@*/ elf_objfmt_output_info *info = (elf_objfmt_output_info *)d;
    /*@dependent@*/ /*@null@*/ yasm_intnum *intn;
    unsigned long intn_val;
    /*@null@*/ elf_reloc_entry *reloc = NULL;
    int retval;
    unsigned int valsize = value->size;

    if (info == NULL)
        yasm_internal_error("null info struct");

    if (value->abs)
        value->abs = yasm_expr_simplify(value->abs, 1);

    /* Try to output constant and PC-relative section-local first.
     * Note this does NOT output any value with a SEG, WRT, external,
     * cross-section, or non-PC-relative reference (those are handled below).
     */
    switch (yasm_value_output_basic(value, buf, destsize, bc, warn,
                                    info->object->arch)) {
        case -1:
            return 1;
        case 0:
            break;
        default:
            return 0;
    }

    /* Handle other expressions, with relocation if necessary */
    if (value->seg_of || value->section_rel || value->rshift > 0) {
        yasm_error_set(YASM_ERROR_TOO_COMPLEX,
                       N_("elf: relocation too complex"));
        return 1;
    }

    intn_val = 0;
    if (value->rel) {
        yasm_sym_vis vis = yasm_symrec_get_visibility(value->rel);
        /*@dependent@*/ /*@null@*/ yasm_symrec *sym = value->rel;
        /*@dependent@*/ /*@null@*/ yasm_symrec *wrt = value->wrt;

        if (wrt == info->objfmt_elf->dotdotsym)
            wrt = NULL;
        else if (wrt && elf_is_wrt_sym_relative(wrt))
            ;
        else if (wrt && elf_is_wrt_pos_adjusted(wrt))
            intn_val = offset + bc->offset;
        else if (vis == YASM_SYM_LOCAL) {
            yasm_bytecode *sym_precbc;
            /* Local symbols need relocation to their section's start, and
             * add in the offset of the bytecode (within the target section)
             * into the abs portion.
             *
             * This is only done if the symbol is relocated against the
             * section instead of the symbol itself.
             */
            if (yasm_symrec_get_label(sym, &sym_precbc)) {
                /* Relocate to section start */
                yasm_section *sym_sect = yasm_bc_get_section(sym_precbc);
                /*@null@*/ elf_secthead *sym_shead;
                sym_shead = yasm_section_get_data(sym_sect, &elf_section_data);
                assert(sym_shead != NULL);
                sym = elf_secthead_get_sym(sym_shead);

                intn_val = yasm_bc_next_offset(sym_precbc);
            }
        }
        
        /* For PC-relative, need to add offset of expression within bc. */
        if (value->curpos_rel)
            intn_val += offset;

        /* Check for _GLOBAL_OFFSET_TABLE_ symbol reference */
        reloc = elf_reloc_entry_create(sym, wrt,
            yasm_intnum_create_uint(bc->offset + offset), value->curpos_rel,
            valsize, sym == info->GOT_sym);
        if (reloc == NULL) {
            yasm_error_set(YASM_ERROR_TYPE,
                           N_("elf: invalid relocation (WRT or size)"));
            return 1;
        }
        /* allocate .rel[a] sections on a need-basis */
        elf_secthead_append_reloc(info->sect, info->shead, reloc);
    }

    intn = yasm_intnum_create_uint(intn_val);

    if (value->abs) {
        yasm_intnum *intn2 = yasm_expr_get_intnum(&value->abs, 0);
        if (!intn2) {
            yasm_error_set(YASM_ERROR_TOO_COMPLEX,
                           N_("elf: relocation too complex"));
            yasm_intnum_destroy(intn);
            return 1;
        }
        yasm_intnum_calc(intn, YASM_EXPR_ADD, intn2);
    }

    if (reloc)
        elf_handle_reloc_addend(intn, reloc, offset);
    retval = yasm_arch_intnum_tobytes(info->object->arch, intn, buf, destsize,
                                      valsize, 0, bc, warn);
    yasm_intnum_destroy(intn);
    return retval;
}

static int
elf_objfmt_output_bytecode(yasm_bytecode *bc, /*@null@*/ void *d)
{
    /*@null@*/ elf_objfmt_output_info *info = (elf_objfmt_output_info *)d;
    unsigned char buf[256];
    /*@null@*/ /*@only@*/ unsigned char *bigbuf;
    unsigned long size = 256;
    int gap;

    if (info == NULL)
        yasm_internal_error("null info struct");

    bigbuf = yasm_bc_tobytes(bc, buf, &size, &gap, info,
                             elf_objfmt_output_value, elf_objfmt_output_reloc);

    /* Don't bother doing anything else if size ended up being 0. */
    if (size == 0) {
        if (bigbuf)
            yasm_xfree(bigbuf);
        return 0;
    }
    else {
        yasm_intnum *bcsize = yasm_intnum_create_uint(size);
        elf_secthead_add_size(info->shead, bcsize);
        yasm_intnum_destroy(bcsize);
    }

    /* Warn that gaps are converted to 0 and write out the 0's. */
    if (gap) {
        unsigned long left;
        yasm_warn_set(YASM_WARN_UNINIT_CONTENTS,
            N_("uninitialized space declared in code/data section: zeroing"));
        /* Write out in chunks */
        memset(buf, 0, 256);
        left = size;
        while (left > 256) {
            fwrite(buf, 256, 1, info->f);
            left -= 256;
        }
        fwrite(buf, left, 1, info->f);
    } else {
        /* Output buf (or bigbuf if non-NULL) to file */
        fwrite(bigbuf ? bigbuf : buf, (size_t)size, 1, info->f);
    }

    /* If bigbuf was allocated, free it */
    if (bigbuf)
        yasm_xfree(bigbuf);

    return 0;
}

static int
elf_objfmt_output_section(yasm_section *sect, /*@null@*/ void *d)
{
    /*@null@*/ elf_objfmt_output_info *info = (elf_objfmt_output_info *)d;
    /*@dependent@*/ /*@null@*/ elf_secthead *shead;
    long pos;
    char *relname;
    const char *sectname;

    if (info == NULL)
        yasm_internal_error("null info struct");
    shead = yasm_section_get_data(sect, &elf_section_data);
    if (shead == NULL)
        yasm_internal_error("no associated data");

    if (elf_secthead_get_align(shead) == 0)
        elf_secthead_set_align(shead, yasm_section_get_align(sect));

    /* don't output header-only sections */
    if ((elf_secthead_get_type(shead) & SHT_NOBITS) == SHT_NOBITS)
    {
        yasm_bytecode *last = yasm_section_bcs_last(sect);
        if (last) {
            yasm_intnum *sectsize;
            sectsize = yasm_intnum_create_uint(yasm_bc_next_offset(last));
            elf_secthead_add_size(shead, sectsize);
            yasm_intnum_destroy(sectsize);
        }
        elf_secthead_set_index(shead, ++info->sindex);
        return 0;
    }

    if ((pos = ftell(info->f)) == -1) {
        yasm_error_set(YASM_ERROR_IO,
                       N_("couldn't read position on output stream"));
        yasm_errwarn_propagate(info->errwarns, 0);
    }
    pos = elf_secthead_set_file_offset(shead, pos);
    if (fseek(info->f, pos, SEEK_SET) < 0) {
        yasm_error_set(YASM_ERROR_IO, N_("couldn't seek on output stream"));
        yasm_errwarn_propagate(info->errwarns, 0);
    }

    info->sect = sect;
    info->shead = shead;
    yasm_section_bcs_traverse(sect, info->errwarns, info,
                              elf_objfmt_output_bytecode);

    elf_secthead_set_index(shead, ++info->sindex);

    /* No relocations to output?  Go on to next section */
    if (elf_secthead_write_relocs_to_file(info->f, sect, shead,
                                          info->errwarns) == 0)
        return 0;
    elf_secthead_set_rel_index(shead, ++info->sindex);

    /* name the relocation section .rel[a].foo */
    sectname = yasm_section_get_name(sect);
    relname = elf_secthead_name_reloc_section(sectname);
    elf_secthead_set_rel_name(shead,
        elf_strtab_append_str(info->objfmt_elf->shstrtab, relname));
    yasm_xfree(relname);

    return 0;
}

static int
elf_objfmt_output_secthead(yasm_section *sect, /*@null@*/ void *d)
{
    /*@null@*/ elf_objfmt_output_info *info = (elf_objfmt_output_info *)d;
    /*@dependent@*/ /*@null@*/ elf_secthead *shead;

    if (info == NULL)
        yasm_internal_error("null info struct");
    shead = yasm_section_get_data(sect, &elf_section_data);
    if (shead == NULL)
        yasm_internal_error("no section header attached to section");

    if(elf_secthead_write_to_file(info->f, shead, info->sindex+1))
        info->sindex++;

    /* output strtab headers here? */

    /* relocation entries for .foo are stored in section .rel[a].foo */
    if(elf_secthead_write_rel_to_file(info->f, 3, sect, shead,
                                      info->sindex+1))
        info->sindex++;

    return 0;
}

static void
elf_objfmt_output(yasm_object *object, FILE *f, int all_syms,
                  yasm_errwarns *errwarns)
{
    yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)object->objfmt;
    elf_objfmt_output_info info;
    build_symtab_info buildsym_info;
    long pos;
    unsigned long elf_shead_addr;
    elf_secthead *esdn;
    unsigned long elf_strtab_offset, elf_shstrtab_offset, elf_symtab_offset;
    unsigned long elf_strtab_size, elf_shstrtab_size, elf_symtab_size;
    elf_strtab_entry *elf_strtab_name, *elf_shstrtab_name, *elf_symtab_name;
    unsigned long elf_symtab_nlocal;

    info.object = object;
    info.objfmt_elf = objfmt_elf;
    info.errwarns = errwarns;
    info.f = f;
    info.GOT_sym = yasm_symtab_get(object->symtab, "_GLOBAL_OFFSET_TABLE_");

    /* Update filename strtab */
    elf_strtab_entry_set_str(objfmt_elf->file_strtab_entry,
                             object->src_filename);

    /* Allocate space for Ehdr by seeking forward */
    if (fseek(f, (long)(elf_proghead_get_size()), SEEK_SET) < 0) {
        yasm_error_set(YASM_ERROR_IO, N_("could not seek on output file"));
        yasm_errwarn_propagate(errwarns, 0);
        return;
    }

    /* add all (local) syms to symtab because relocation needs a symtab index
     * if all_syms, register them by name.  if not, use strtab entry 0 */
    buildsym_info.object = object;
    buildsym_info.objfmt_elf = objfmt_elf;
    buildsym_info.errwarns = errwarns;
    buildsym_info.local_names = all_syms;
    yasm_symtab_traverse(object->symtab, &buildsym_info,
                         elf_objfmt_build_symtab);
    elf_symtab_nlocal = elf_symtab_assign_indices(objfmt_elf->elf_symtab);

    /* output known sections - includes reloc sections which aren't in yasm's
     * list.  Assign indices as we go. */
    info.sindex = 3;
    if (yasm_object_sections_traverse(object, &info,
                                      elf_objfmt_output_section))
        return;

    /* add final sections to the shstrtab */
    elf_strtab_name = elf_strtab_append_str(objfmt_elf->shstrtab, ".strtab");
    elf_symtab_name = elf_strtab_append_str(objfmt_elf->shstrtab, ".symtab");
    elf_shstrtab_name = elf_strtab_append_str(objfmt_elf->shstrtab,
                                              ".shstrtab");

    /* output .shstrtab */
    if ((pos = elf_objfmt_output_align(f, 4)) == -1) {
        yasm_errwarn_propagate(errwarns, 0);
        return;
    }
    elf_shstrtab_offset = (unsigned long) pos;
    elf_shstrtab_size = elf_strtab_output_to_file(f, objfmt_elf->shstrtab);

    /* output .strtab */
    if ((pos = elf_objfmt_output_align(f, 4)) == -1) {
        yasm_errwarn_propagate(errwarns, 0);
        return;
    }
    elf_strtab_offset = (unsigned long) pos;
    elf_strtab_size = elf_strtab_output_to_file(f, objfmt_elf->strtab);

    /* output .symtab - last section so all others have indexes */
    if ((pos = elf_objfmt_output_align(f, 4)) == -1) {
        yasm_errwarn_propagate(errwarns, 0);
        return;
    }
    elf_symtab_offset = (unsigned long) pos;
    elf_symtab_size = elf_symtab_write_to_file(f, objfmt_elf->elf_symtab,
                                               errwarns);

    /* output section header table */
    if ((pos = elf_objfmt_output_align(f, 16)) == -1) {
        yasm_errwarn_propagate(errwarns, 0);
        return;
    }
    elf_shead_addr = (unsigned long) pos;

    /* stabs debugging support */
    if (strcmp(yasm_dbgfmt_keyword(object->dbgfmt), "stabs")==0) {
        yasm_section *stabsect = yasm_object_find_general(object, ".stab");
        yasm_section *stabstrsect =
            yasm_object_find_general(object, ".stabstr");
        if (stabsect && stabstrsect) {
            elf_secthead *stab =
                yasm_section_get_data(stabsect, &elf_section_data);
            elf_secthead *stabstr =
                yasm_section_get_data(stabstrsect, &elf_section_data);
            if (stab && stabstr) {
                elf_secthead_set_link(stab, elf_secthead_get_index(stabstr));
            }
            else
                yasm_internal_error(N_("missing .stab or .stabstr section/data"));
        }
    }
    
    /* output dummy section header - 0 */
    info.sindex = 0;

    esdn = elf_secthead_create(NULL, SHT_NULL, 0, 0, 0);
    elf_secthead_set_index(esdn, 0);
    elf_secthead_write_to_file(f, esdn, 0);
    elf_secthead_destroy(esdn);

    esdn = elf_secthead_create(elf_shstrtab_name, SHT_STRTAB, 0,
                               elf_shstrtab_offset, elf_shstrtab_size);
    elf_secthead_set_index(esdn, 1);
    elf_secthead_write_to_file(f, esdn, 1);
    elf_secthead_destroy(esdn);

    esdn = elf_secthead_create(elf_strtab_name, SHT_STRTAB, 0,
                               elf_strtab_offset, elf_strtab_size);
    elf_secthead_set_index(esdn, 2);
    elf_secthead_write_to_file(f, esdn, 2);
    elf_secthead_destroy(esdn);

    esdn = elf_secthead_create(elf_symtab_name, SHT_SYMTAB, 0,
                               elf_symtab_offset, elf_symtab_size);
    elf_secthead_set_index(esdn, 3);
    elf_secthead_set_info(esdn, elf_symtab_nlocal);
    elf_secthead_set_link(esdn, 2);     /* for .strtab, which is index 2 */
    elf_secthead_write_to_file(f, esdn, 3);
    elf_secthead_destroy(esdn);

    info.sindex = 3;
    /* output remaining section headers */
    yasm_object_sections_traverse(object, &info, elf_objfmt_output_secthead);

    /* output Ehdr */
    if (fseek(f, 0, SEEK_SET) < 0) {
        yasm_error_set(YASM_ERROR_IO, N_("could not seek on output file"));
        yasm_errwarn_propagate(errwarns, 0);
        return;
    }

    elf_proghead_write_to_file(f, elf_shead_addr, info.sindex+1, 1);
}

static void
elf_objfmt_destroy(yasm_objfmt *objfmt)
{
    yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)objfmt;
    elf_symtab_destroy(objfmt_elf->elf_symtab);
    elf_strtab_destroy(objfmt_elf->shstrtab);
    elf_strtab_destroy(objfmt_elf->strtab);
    yasm_xfree(objfmt);
}

static void
elf_objfmt_init_new_section(yasm_section *sect, unsigned long line)
{
    yasm_object *object = yasm_section_get_object(sect);
    const char *sectname = yasm_section_get_name(sect);
    yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)object->objfmt;
    elf_secthead *esd;
    yasm_symrec *sym;
    elf_strtab_entry *name = elf_strtab_append_str(objfmt_elf->shstrtab,
                                                   sectname);

    elf_section_type type=SHT_PROGBITS;
    elf_size entsize=0;

    if (yasm__strcasecmp(sectname, ".stab")==0) {
        entsize = 12;
    } else if (yasm__strcasecmp(sectname, ".stabstr")==0) {
        type = SHT_STRTAB;
    }

    esd = elf_secthead_create(name, type, 0, 0, 0);
    elf_secthead_set_entsize(esd, entsize);
    yasm_section_add_data(sect, &elf_section_data, esd);
    sym = yasm_symtab_define_label(object->symtab, sectname,
                                   yasm_section_bcs_first(sect), 1, line);

    elf_secthead_set_sym(esd, sym);
}

static yasm_section *
elf_objfmt_add_default_section(yasm_object *object)
{
    yasm_section *retval;
    int isnew;

    retval = yasm_object_get_general(object, ".text", 16, 1, 0, &isnew, 0);
    if (isnew)
    {
        elf_secthead *esd = yasm_section_get_data(retval, &elf_section_data);
        elf_secthead_set_typeflags(esd, SHT_PROGBITS,
                                   SHF_ALLOC + SHF_EXECINSTR);
        yasm_section_set_default(retval, 1);
    }
    return retval;
}

struct elf_section_switch_data {
    /*@only@*/ /*@null@*/ yasm_intnum *align_intn;
    unsigned long flags;
    unsigned long type;
    int gasflags;
    int stdsect;
};

/* GAS-style flags */
static int
elf_helper_gasflags(void *obj, yasm_valparam *vp, unsigned long line, void *d,
                    /*@unused@*/ uintptr_t arg)
{
    struct elf_section_switch_data *data = (struct elf_section_switch_data *)d;
    const char *s = yasm_vp_string(vp);
    size_t i;

    if (!s) {
        yasm_error_set(YASM_ERROR_VALUE,
                       N_("non-string section attribute"));
        return -1;
    }

    if (data->stdsect && strlen(s) == 0) {
        data->gasflags = 1;
        return 0;
    }

    data->flags = 0;
    for (i=0; i<strlen(s); i++) {
        switch (s[i]) {
            case 'a':
                data->flags |= SHF_ALLOC;
                break;
            case 'w':
                data->flags |= SHF_WRITE;
                break;
            case 'x':
                data->flags |= SHF_EXECINSTR;
                break;
            case 'M':
                data->flags |= SHF_MERGE;
                break;
            case 'S':
                data->flags |= SHF_STRINGS;
                break;
            case 'G':
                data->flags |= SHF_GROUP;
                break;
            case 'T':
                data->flags |= SHF_TLS;
                break;
            default:
                yasm_warn_set(YASM_WARN_GENERAL,
                              N_("unrecognized section attribute: `%c'"),
                              s[i]);
        }
    }

    data->gasflags = 1;
    return 0;
}

static /*@observer@*/ /*@null@*/ yasm_section *
elf_objfmt_section_switch(yasm_object *object, yasm_valparamhead *valparams,
                          /*@null@*/ yasm_valparamhead *objext_valparams,
                          unsigned long line)
{
    yasm_valparam *vp;
    yasm_section *retval;
    int isnew;
    unsigned long align = 4;
    int flags_override = 0;
    const char *sectname;
    int resonly = 0;

    struct elf_section_switch_data data;

    static const yasm_dir_help help[] = {
        { "alloc", 0, yasm_dir_helper_flag_or,
          offsetof(struct elf_section_switch_data, flags), SHF_ALLOC },
        { "exec", 0, yasm_dir_helper_flag_or,
          offsetof(struct elf_section_switch_data, flags), SHF_EXECINSTR },
        { "write", 0, yasm_dir_helper_flag_or,
          offsetof(struct elf_section_switch_data, flags), SHF_WRITE },
        { "tls", 0, yasm_dir_helper_flag_or,
          offsetof(struct elf_section_switch_data, flags), SHF_TLS },
        { "progbits", 0, yasm_dir_helper_flag_set,
          offsetof(struct elf_section_switch_data, type), SHT_PROGBITS },
        { "noalloc", 0, yasm_dir_helper_flag_and,
          offsetof(struct elf_section_switch_data, flags), SHF_ALLOC },
        { "noexec", 0, yasm_dir_helper_flag_and,
          offsetof(struct elf_section_switch_data, flags), SHF_EXECINSTR },
        { "nowrite",  0, yasm_dir_helper_flag_and,
          offsetof(struct elf_section_switch_data, flags), SHF_WRITE },
        { "notls",  0, yasm_dir_helper_flag_and,
          offsetof(struct elf_section_switch_data, flags), SHF_TLS },
        { "noprogbits", 0, yasm_dir_helper_flag_set,
          offsetof(struct elf_section_switch_data, type), SHT_NOBITS },
        { "nobits", 0, yasm_dir_helper_flag_set,
          offsetof(struct elf_section_switch_data, type), SHT_NOBITS },
        { "gasflags", 1, elf_helper_gasflags, 0, 0 },
        { "align", 1, yasm_dir_helper_intn,
          offsetof(struct elf_section_switch_data, align_intn), 0 }
    };
    /*@only@*/ /*@null@*/ yasm_expr *merge_expr = NULL;
    /*@dependent@*/ /*@null@*/ const yasm_intnum *merge_intn = NULL;
    elf_secthead *esd;

    data.align_intn = NULL;
    data.flags = SHF_ALLOC;
    data.type = SHT_PROGBITS;
    data.gasflags = 0;
    data.stdsect = 1;

    vp = yasm_vps_first(valparams);
    sectname = yasm_vp_string(vp);
    if (!sectname)
        return NULL;
    vp = yasm_vps_next(vp);

    if (strcmp(sectname, ".bss") == 0) {
        data.type = SHT_NOBITS;
        data.flags = SHF_ALLOC + SHF_WRITE;
        resonly = 1;
    } else if (strcmp(sectname, ".data") == 0) {
        data.type = SHT_PROGBITS;
        data.flags = SHF_ALLOC + SHF_WRITE;
    } else if (strcmp(sectname, ".tdata") == 0) {
        data.type = SHT_PROGBITS;
        data.flags = SHF_ALLOC + SHF_WRITE + SHF_TLS;
    } else if (strcmp(sectname, ".rodata") == 0) {
        data.type = SHT_PROGBITS;
        data.flags = SHF_ALLOC;
    } else if (strcmp(sectname, ".text") == 0) {
        align = 16;
        data.type = SHT_PROGBITS;
        data.flags = SHF_ALLOC + SHF_EXECINSTR;
    } else if (strcmp(sectname, ".comment") == 0) {
        align = 0;
        data.type = SHT_PROGBITS;
        data.flags = 0;
    } else {
        /* Default to code */
        align = 1;
        data.stdsect = 0;
    }

    flags_override = yasm_dir_helper(object, vp, line, help, NELEMS(help),
                                     &data, yasm_dir_helper_valparam_warn);
    if (flags_override < 0)
        return NULL;    /* error occurred */

    if (data.align_intn) {
        align = yasm_intnum_get_uint(data.align_intn);
        yasm_intnum_destroy(data.align_intn);

        /* Alignments must be a power of two. */
        if (!is_exp2(align)) {
            yasm_error_set(YASM_ERROR_VALUE,
                           N_("argument to `%s' is not a power of two"),
                           "align");
            return NULL;
        }
    }

    /* Handle merge entity size */
    if (data.flags & SHF_MERGE) {
        if (objext_valparams && (vp = yasm_vps_first(objext_valparams))
            && !vp->val) {
            if (!(merge_expr = yasm_vp_expr(vp, object->symtab, line)) ||
                !(merge_intn = yasm_expr_get_intnum(&merge_expr, 0)))
                yasm_warn_set(YASM_WARN_GENERAL,
                              N_("invalid merge entity size"));
        } else {
            yasm_warn_set(YASM_WARN_GENERAL,
                          N_("entity size for SHF_MERGE not specified"));
            data.flags &= ~SHF_MERGE;
        }
    }

    retval = yasm_object_get_general(object, sectname, align,
                                     (data.flags & SHF_EXECINSTR) != 0,
                                     resonly, &isnew, line);

    esd = yasm_section_get_data(retval, &elf_section_data);

    if (isnew || yasm_section_is_default(retval)) {
        yasm_section_set_default(retval, 0);
        elf_secthead_set_typeflags(esd, data.type, data.flags);
        if (merge_intn)
            elf_secthead_set_entsize(esd, yasm_intnum_get_uint(merge_intn));
        yasm_section_set_align(retval, align, line);
    } else if (flags_override && !data.gasflags)
        yasm_warn_set(YASM_WARN_GENERAL,
                      N_("section flags ignored on section redeclaration"));
    if (merge_expr)
        yasm_expr_destroy(merge_expr);
    return retval;
}

static /*@observer@*/ /*@null@*/ yasm_symrec *
elf_objfmt_get_special_sym(yasm_object *object, const char *name,
                           const char *parser)
{
    if (yasm__strcasecmp(name, "sym") == 0) {
        yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)object->objfmt;
        return objfmt_elf->dotdotsym;
    }
    return elf_get_special_sym(name, parser);
}

static void
dir_type(yasm_object *object, yasm_valparamhead *valparams,
         yasm_valparamhead *objext_valparams, unsigned long line)
{
    yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)object->objfmt;
    yasm_valparam *vp = yasm_vps_first(valparams);
    const char *symname = yasm_vp_id(vp);
    /* Get symbol elf data */
    yasm_symrec *sym = yasm_symtab_use(object->symtab, symname, line);
    elf_symtab_entry *entry = yasm_symrec_get_data(sym, &elf_symrec_data);
    /*@null@*/ const char *type;

    /* Create entry if necessary */
    if (!entry) {
        entry = elf_symtab_entry_create(
            elf_strtab_append_str(objfmt_elf->strtab, symname), sym);
        yasm_symrec_add_data(sym, &elf_symrec_data, entry);
    }

    /* Pull new type from param */
    vp = yasm_vps_next(vp);
    if (vp && !vp->val && (type = yasm_vp_id(vp))) {
        if (yasm__strcasecmp(type, "function") == 0)
            elf_sym_set_type(entry, STT_FUNC);
        else if (yasm__strcasecmp(type, "object") == 0)
            elf_sym_set_type(entry, STT_OBJECT);
        else if (yasm__strcasecmp(type, "tls_object") == 0)
            elf_sym_set_type(entry, STT_TLS);
        else if (yasm__strcasecmp(type, "notype") == 0)
            elf_sym_set_type(entry, STT_NOTYPE);
        else
            yasm_warn_set(YASM_WARN_GENERAL,
                          N_("unrecognized symbol type `%s'"), type);
    } else
        yasm_error_set(YASM_ERROR_SYNTAX, N_("no type specified"));
}

static void
dir_size(yasm_object *object, yasm_valparamhead *valparams,
         yasm_valparamhead *objext_valparams, unsigned long line)
{
    yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)object->objfmt;
    yasm_valparam *vp = yasm_vps_first(valparams);
    const char *symname = yasm_vp_id(vp);
    /* Get symbol elf data */
    yasm_symrec *sym = yasm_symtab_use(object->symtab, symname, line);
    elf_symtab_entry *entry = yasm_symrec_get_data(sym, &elf_symrec_data);
    /*@only@*/ /*@null@*/ yasm_expr *size;

    /* Create entry if necessary */
    if (!entry) {
        entry = elf_symtab_entry_create(
            elf_strtab_append_str(objfmt_elf->strtab, symname), sym);
        yasm_symrec_add_data(sym, &elf_symrec_data, entry);
    }

    /* Pull new size from param */
    vp = yasm_vps_next(vp);
    if (vp && !vp->val && (size = yasm_vp_expr(vp, object->symtab, line)))
        elf_sym_set_size(entry, size);
    else
        yasm_error_set(YASM_ERROR_SYNTAX, N_("no size specified"));
}

static void
dir_weak(yasm_object *object, yasm_valparamhead *valparams,
         yasm_valparamhead *objext_valparams, unsigned long line)
{
    yasm_objfmt_elf *objfmt_elf = (yasm_objfmt_elf *)object->objfmt;
    yasm_valparam *vp = yasm_vps_first(valparams);
    const char *symname = yasm_vp_id(vp);
    yasm_symrec *sym = yasm_symtab_declare(object->symtab, symname,
                                           YASM_SYM_GLOBAL, line);
    elf_objfmt_symtab_append(objfmt_elf, sym, SHN_UNDEF, STB_WEAK, 0,
                             STV_DEFAULT, NULL, NULL, object);
}

static void
dir_ident(yasm_object *object, yasm_valparamhead *valparams,
          yasm_valparamhead *objext_valparams, unsigned long line)
{
    yasm_valparamhead sect_vps;
    yasm_datavalhead dvs;
    yasm_section *comment;
    yasm_valparam *vp;
    yasm_valparam *vp2;

    /* Accept, but do nothing with empty ident */
    if (!valparams)
        return;
    vp = yasm_vps_first(valparams);
    if (!vp)
        return;

    /* Put ident data into .comment section */
    yasm_vps_initialize(&sect_vps);
    vp2 = yasm_vp_create_string(NULL, yasm__xstrdup(".comment"));
    yasm_vps_append(&sect_vps, vp2);
    comment = elf_objfmt_section_switch(object, &sect_vps, NULL, line);
    yasm_vps_delete(&sect_vps);

    /* To match GAS output, if the comment section is empty, put an
     * initial 0 byte in the section.
     */
    if (yasm_section_bcs_first(comment) == yasm_section_bcs_last(comment)) {
        yasm_dvs_initialize(&dvs);
        yasm_dvs_append(&dvs, yasm_dv_create_expr(
            yasm_expr_create_ident(
                yasm_expr_int(yasm_intnum_create_uint(0)), line)));
        yasm_section_bcs_append(comment,
            yasm_bc_create_data(&dvs, 1, 0, object->arch, line));
    }

    yasm_dvs_initialize(&dvs);
    do {
        const char *s = yasm_vp_string(vp);
        if (!s) {
            yasm_error_set(YASM_ERROR_VALUE,
                           N_(".comment requires string parameters"));
            yasm_dvs_delete(&dvs);
            return;
        }
        yasm_dvs_append(&dvs,
                        yasm_dv_create_string(yasm__xstrdup(s), strlen(s)));
    } while ((vp = yasm_vps_next(vp)));

    yasm_section_bcs_append(comment,
        yasm_bc_create_data(&dvs, 1, 1, object->arch, line));
}

/* Define valid debug formats to use with this object format */
static const char *elf_objfmt_dbgfmt_keywords[] = {
    "null",
    "stabs",
    "dwarf2",
    NULL
};

static const yasm_directive elf_objfmt_directives[] = {
    { ".type",          "gas",  dir_type,       YASM_DIR_ID_REQUIRED },
    { ".size",          "gas",  dir_size,       YASM_DIR_ID_REQUIRED },
    { ".weak",          "gas",  dir_weak,       YASM_DIR_ID_REQUIRED },
    { ".ident",         "gas",  dir_ident,      YASM_DIR_ANY },
    { "type",           "nasm", dir_type,       YASM_DIR_ID_REQUIRED },
    { "size",           "nasm", dir_size,       YASM_DIR_ID_REQUIRED },
    { "weak",           "nasm", dir_weak,       YASM_DIR_ID_REQUIRED },
    { "ident",          "nasm", dir_ident,      YASM_DIR_ANY },
    { NULL, NULL, NULL, 0 }
};

static const char *elf_nasm_stdmac[] = {
    "%imacro type 1+.nolist",
    "[type %1]",
    "%endmacro",
    "%imacro size 1+.nolist",
    "[size %1]",
    "%endmacro",
    "%imacro weak 1+.nolist",
    "[weak %1]",
    "%endmacro",
    NULL
};

static const yasm_stdmac elf_objfmt_stdmacs[] = {
    { "nasm", "nasm", elf_nasm_stdmac },
    { NULL, NULL, NULL }
};

/* Define objfmt structure -- see objfmt.h for details */
yasm_objfmt_module yasm_elf_LTX_objfmt = {
    "ELF",
    "elf",
    "o",
    32,
    0,
    elf_objfmt_dbgfmt_keywords,
    "null",
    elf_objfmt_directives,
    elf_objfmt_stdmacs,
    elf_objfmt_create,
    elf_objfmt_output,
    elf_objfmt_destroy,
    elf_objfmt_add_default_section,
    elf_objfmt_init_new_section,
    elf_objfmt_section_switch,
    elf_objfmt_get_special_sym
};

yasm_objfmt_module yasm_elf32_LTX_objfmt = {
    "ELF (32-bit)",
    "elf32",
    "o",
    32,
    0,
    elf_objfmt_dbgfmt_keywords,
    "null",
    elf_objfmt_directives,
    elf_objfmt_stdmacs,
    elf32_objfmt_create,
    elf_objfmt_output,
    elf_objfmt_destroy,
    elf_objfmt_add_default_section,
    elf_objfmt_init_new_section,
    elf_objfmt_section_switch,
    elf_objfmt_get_special_sym
};

yasm_objfmt_module yasm_elf64_LTX_objfmt = {
    "ELF (64-bit)",
    "elf64",
    "o",
    64,
    0,
    elf_objfmt_dbgfmt_keywords,
    "null",
    elf_objfmt_directives,
    elf_objfmt_stdmacs,
    elf64_objfmt_create,
    elf_objfmt_output,
    elf_objfmt_destroy,
    elf_objfmt_add_default_section,
    elf_objfmt_init_new_section,
    elf_objfmt_section_switch,
    elf_objfmt_get_special_sym
};

yasm_objfmt_module yasm_elfx32_LTX_objfmt = {
    "ELF (x32)",
    "elfx32",
    "o",
    64,
    0,
    elf_objfmt_dbgfmt_keywords,
    "null",
    elf_objfmt_directives,
    elf_objfmt_stdmacs,
    elfx32_objfmt_create,
    elf_objfmt_output,
    elf_objfmt_destroy,
    elf_objfmt_add_default_section,
    elf_objfmt_init_new_section,
    elf_objfmt_section_switch,
    elf_objfmt_get_special_sym
};
