| /* |
| * Flat-format binary object format |
| * |
| * Copyright (C) 2002-2007 Peter Johnson |
| * |
| * 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> |
| /*@unused@*/ RCSID("$Id$"); |
| |
| #define YASM_LIB_INTERNAL |
| #define YASM_BC_INTERNAL |
| #define YASM_EXPR_INTERNAL |
| #include <libyasm.h> |
| |
| |
| #define REGULAR_OUTBUF_SIZE 1024 |
| |
| typedef struct yasm_objfmt_bin { |
| yasm_objfmt_base objfmt; /* base structure */ |
| } yasm_objfmt_bin; |
| |
| yasm_objfmt_module yasm_bin_LTX_objfmt; |
| |
| |
| static yasm_objfmt * |
| bin_objfmt_create(yasm_object *object) |
| { |
| yasm_objfmt_bin *objfmt_bin = yasm_xmalloc(sizeof(yasm_objfmt_bin)); |
| objfmt_bin->objfmt.module = &yasm_bin_LTX_objfmt; |
| return (yasm_objfmt *)objfmt_bin; |
| } |
| |
| /* Aligns sect to either its specified alignment. Uses prevsect and base to |
| * both determine the new starting address (returned) and the total length of |
| * prevsect after sect has been aligned. |
| */ |
| static unsigned long |
| bin_objfmt_align_section(yasm_section *sect, yasm_section *prevsect, |
| unsigned long base, |
| /*@out@*/ unsigned long *prevsectlen, |
| /*@out@*/ unsigned long *padamt) |
| { |
| unsigned long start; |
| unsigned long align; |
| |
| /* Figure out the size of .text by looking at the last bytecode's offset |
| * plus its length. Add the start and size together to get the new start. |
| */ |
| *prevsectlen = yasm_bc_next_offset(yasm_section_bcs_last(prevsect)); |
| start = base + *prevsectlen; |
| |
| /* Round new start up to alignment of .data section, and adjust textlen to |
| * indicate padded size. Because aignment is always a power of two, we |
| * can use some bit trickery to do this easily. |
| */ |
| align = yasm_section_get_align(sect); |
| |
| if (start & (align-1)) |
| start = (start & ~(align-1)) + align; |
| |
| *padamt = start - (base + *prevsectlen); |
| |
| return start; |
| } |
| |
| typedef struct bin_objfmt_output_info { |
| yasm_object *object; |
| yasm_errwarns *errwarns; |
| /*@dependent@*/ FILE *f; |
| /*@only@*/ unsigned char *buf; |
| /*@observer@*/ const yasm_section *sect; |
| unsigned long start; /* what normal variables go against */ |
| unsigned long abs_start; /* what absolutes go against */ |
| } bin_objfmt_output_info; |
| |
| static /*@only@*/ yasm_expr * |
| bin_objfmt_expr_xform(/*@returned@*/ /*@only@*/ yasm_expr *e, |
| /*@unused@*/ /*@null@*/ void *d) |
| { |
| int i; |
| /*@dependent@*/ yasm_section *sect; |
| /*@dependent@*/ /*@null@*/ yasm_bytecode *precbc; |
| /*@null@*/ yasm_intnum *dist; |
| |
| for (i=0; i<e->numterms; i++) { |
| /* Transform symrecs that reference sections into |
| * start expr + intnum(dist). |
| */ |
| if (e->terms[i].type == YASM_EXPR_SYM && |
| yasm_symrec_get_label(e->terms[i].data.sym, &precbc) && |
| (sect = yasm_bc_get_section(precbc)) && |
| (dist = yasm_calc_bc_dist(yasm_section_bcs_first(sect), precbc))) { |
| const yasm_expr *start = yasm_section_get_start(sect); |
| e->terms[i].type = YASM_EXPR_EXPR; |
| e->terms[i].data.expn = |
| yasm_expr_create(YASM_EXPR_ADD, |
| yasm_expr_expr(yasm_expr_copy(start)), |
| yasm_expr_int(dist), e->line); |
| } |
| } |
| |
| return e; |
| } |
| |
| static int |
| bin_objfmt_output_value(yasm_value *value, unsigned char *buf, |
| unsigned int destsize, |
| /*@unused@*/ unsigned long offset, yasm_bytecode *bc, |
| int warn, /*@null@*/ void *d) |
| { |
| /*@null@*/ bin_objfmt_output_info *info = (bin_objfmt_output_info *)d; |
| /*@dependent@*/ /*@null@*/ yasm_bytecode *precbc; |
| /*@dependent@*/ yasm_section *sect; |
| |
| assert(info != NULL); |
| |
| /* Binary objects we need to resolve against object, not against section. */ |
| if (value->rel && !value->curpos_rel |
| && yasm_symrec_get_label(value->rel, &precbc) |
| && (sect = yasm_bc_get_section(precbc))) { |
| unsigned int rshift = (unsigned int)value->rshift; |
| yasm_expr *syme; |
| if (value->rshift > 0) |
| syme = yasm_expr_create(YASM_EXPR_SHR, yasm_expr_sym(value->rel), |
| yasm_expr_int(yasm_intnum_create_uint(rshift)), bc->line); |
| else |
| syme = yasm_expr_create_ident(yasm_expr_sym(value->rel), bc->line); |
| |
| if (!value->abs) |
| value->abs = syme; |
| else |
| value->abs = |
| yasm_expr_create(YASM_EXPR_ADD, yasm_expr_expr(value->abs), |
| yasm_expr_expr(syme), bc->line); |
| value->rel = NULL; |
| value->rshift = 0; |
| } |
| |
| /* Simplify absolute portion of value, transforming symrecs */ |
| if (value->abs) |
| value->abs = yasm_expr__level_tree |
| (value->abs, 1, 1, 1, 0, bin_objfmt_expr_xform, NULL); |
| |
| /* Output */ |
| switch (yasm_value_output_basic(value, buf, destsize, bc, warn, |
| info->object->arch)) { |
| case -1: |
| return 1; |
| case 0: |
| break; |
| default: |
| return 0; |
| } |
| |
| /* Absolute value; handle it here as output_basic won't understand it */ |
| if (value->rel && yasm_symrec_is_abs(value->rel)) { |
| if (value->curpos_rel) { |
| /* Calculate value relative to current assembly position */ |
| /*@only@*/ yasm_intnum *outval; |
| unsigned int valsize = value->size; |
| int retval = 0; |
| |
| outval = yasm_intnum_create_uint(bc->offset + info->abs_start); |
| yasm_intnum_calc(outval, YASM_EXPR_NEG, NULL); |
| |
| if (value->rshift > 0) { |
| /*@only@*/ yasm_intnum *shamt = |
| yasm_intnum_create_uint((unsigned long)value->rshift); |
| yasm_intnum_calc(outval, YASM_EXPR_SHR, shamt); |
| yasm_intnum_destroy(shamt); |
| } |
| /* Add in absolute portion */ |
| if (value->abs) |
| yasm_intnum_calc(outval, YASM_EXPR_ADD, |
| yasm_expr_get_intnum(&value->abs, 1)); |
| /* Output! */ |
| if (yasm_arch_intnum_tobytes(info->object->arch, outval, buf, |
| destsize, valsize, 0, bc, warn)) |
| retval = 1; |
| yasm_intnum_destroy(outval); |
| return retval; |
| } |
| } |
| |
| /* Couldn't output, assume it contains an external reference. */ |
| yasm_error_set(YASM_ERROR_GENERAL, |
| N_("binary object format does not support external references")); |
| return 1; |
| } |
| |
| static int |
| bin_objfmt_output_bytecode(yasm_bytecode *bc, /*@null@*/ void *d) |
| { |
| /*@null@*/ bin_objfmt_output_info *info = (bin_objfmt_output_info *)d; |
| /*@null@*/ /*@only@*/ unsigned char *bigbuf; |
| unsigned long size = REGULAR_OUTBUF_SIZE; |
| int gap; |
| |
| assert(info != NULL); |
| |
| bigbuf = yasm_bc_tobytes(bc, info->buf, &size, &gap, info, |
| bin_objfmt_output_value, NULL); |
| |
| /* Don't bother doing anything else if size ended up being 0. */ |
| if (size == 0) { |
| if (bigbuf) |
| yasm_xfree(bigbuf); |
| return 0; |
| } |
| |
| /* 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(info->buf, 0, REGULAR_OUTBUF_SIZE); |
| left = size; |
| while (left > REGULAR_OUTBUF_SIZE) { |
| fwrite(info->buf, REGULAR_OUTBUF_SIZE, 1, info->f); |
| left -= REGULAR_OUTBUF_SIZE; |
| } |
| fwrite(info->buf, left, 1, info->f); |
| } else { |
| /* Output buf (or bigbuf if non-NULL) to file */ |
| fwrite(bigbuf ? bigbuf : info->buf, (size_t)size, 1, info->f); |
| } |
| |
| /* If bigbuf was allocated, free it */ |
| if (bigbuf) |
| yasm_xfree(bigbuf); |
| |
| return 0; |
| } |
| |
| static int |
| bin_objfmt_check_sym(yasm_symrec *sym, /*@null@*/ void *d) |
| { |
| /*@null@*/ bin_objfmt_output_info *info = (bin_objfmt_output_info *)d; |
| yasm_sym_vis vis = yasm_symrec_get_visibility(sym); |
| assert(info != NULL); |
| |
| if (vis & YASM_SYM_EXTERN) { |
| yasm_warn_set(YASM_WARN_GENERAL, |
| N_("binary object format does not support extern variables")); |
| yasm_errwarn_propagate(info->errwarns, yasm_symrec_get_decl_line(sym)); |
| } else if (vis & YASM_SYM_GLOBAL) { |
| yasm_warn_set(YASM_WARN_GENERAL, |
| N_("binary object format does not support global variables")); |
| yasm_errwarn_propagate(info->errwarns, yasm_symrec_get_decl_line(sym)); |
| } else if (vis & YASM_SYM_COMMON) { |
| yasm_error_set(YASM_ERROR_TYPE, |
| N_("binary object format does not support common variables")); |
| yasm_errwarn_propagate(info->errwarns, yasm_symrec_get_decl_line(sym)); |
| } |
| return 0; |
| } |
| |
| static void |
| bin_objfmt_output(yasm_object *object, FILE *f, /*@unused@*/ int all_syms, |
| yasm_errwarns *errwarns) |
| { |
| /*@observer@*/ /*@null@*/ yasm_section *text, *data, *bss, *prevsect; |
| /*@null@*/ yasm_expr *startexpr; |
| /*@dependent@*/ /*@null@*/ const yasm_intnum *startnum; |
| unsigned long start = 0, textstart = 0, datastart = 0; |
| unsigned long textlen = 0, textpad = 0, datalen = 0, datapad = 0; |
| unsigned long *prevsectlenptr, *prevsectpadptr; |
| unsigned long i; |
| bin_objfmt_output_info info; |
| |
| info.object = object; |
| info.errwarns = errwarns; |
| info.f = f; |
| info.buf = yasm_xmalloc(REGULAR_OUTBUF_SIZE); |
| |
| /* Check symbol table */ |
| yasm_symtab_traverse(object->symtab, &info, bin_objfmt_check_sym); |
| |
| text = yasm_object_find_general(object, ".text"); |
| data = yasm_object_find_general(object, ".data"); |
| bss = yasm_object_find_general(object, ".bss"); |
| |
| if (!text) |
| yasm_internal_error(N_("No `.text' section in bin objfmt output")); |
| |
| /* First determine the actual starting offsets for .data and .bss. |
| * As the order in the file is .text -> .data -> .bss (not present), |
| * use the last bytecode in .text (and the .text section start) to |
| * determine the starting offset in .data, and likewise for .bss. |
| * Also compensate properly for alignment. |
| */ |
| |
| /* Find out the start of .text */ |
| startexpr = yasm_expr_copy(yasm_section_get_start(text)); |
| assert(startexpr != NULL); |
| startnum = yasm_expr_get_intnum(&startexpr, 0); |
| if (!startnum) { |
| yasm_error_set(YASM_ERROR_TOO_COMPLEX, |
| N_("ORG expression too complex")); |
| yasm_errwarn_propagate(errwarns, startexpr->line); |
| return; |
| } |
| start = yasm_intnum_get_uint(startnum); |
| yasm_expr_destroy(startexpr); |
| info.abs_start = start; |
| textstart = start; |
| |
| /* Align .data and .bss (if present) by adjusting their starts. */ |
| prevsect = text; |
| prevsectlenptr = &textlen; |
| prevsectpadptr = &textpad; |
| if (data) { |
| start = bin_objfmt_align_section(data, prevsect, start, |
| prevsectlenptr, prevsectpadptr); |
| yasm_section_set_start(data, yasm_expr_create_ident( |
| yasm_expr_int(yasm_intnum_create_uint(start)), 0), 0); |
| datastart = start; |
| prevsect = data; |
| prevsectlenptr = &datalen; |
| prevsectpadptr = &datapad; |
| } |
| if (bss) { |
| start = bin_objfmt_align_section(bss, prevsect, start, |
| prevsectlenptr, prevsectpadptr); |
| yasm_section_set_start(bss, yasm_expr_create_ident( |
| yasm_expr_int(yasm_intnum_create_uint(start)), 0), 0); |
| } |
| |
| /* Output .text first. */ |
| info.sect = text; |
| info.start = textstart; |
| yasm_section_bcs_traverse(text, errwarns, &info, |
| bin_objfmt_output_bytecode); |
| |
| /* If .data is present, output it */ |
| if (data) { |
| /* Add padding to align .data. Just use a for loop, as this will |
| * seldom be very many bytes. |
| */ |
| for (i=0; i<textpad; i++) |
| fputc(0, f); |
| |
| /* Output .data bytecodes */ |
| info.sect = data; |
| info.start = datastart; |
| yasm_section_bcs_traverse(data, errwarns, |
| &info, bin_objfmt_output_bytecode); |
| } |
| |
| /* If .bss is present, check it for non-reserve bytecodes */ |
| |
| |
| yasm_xfree(info.buf); |
| } |
| |
| static void |
| bin_objfmt_destroy(yasm_objfmt *objfmt) |
| { |
| yasm_xfree(objfmt); |
| } |
| |
| static void |
| bin_objfmt_init_new_section(yasm_object *object, yasm_section *sect, |
| const char *sectname, unsigned long line) |
| { |
| yasm_symtab_define_label(object->symtab, sectname, |
| yasm_section_bcs_first(sect), 1, line); |
| } |
| |
| static yasm_section * |
| bin_objfmt_add_default_section(yasm_object *object) |
| { |
| yasm_section *retval; |
| int isnew; |
| |
| retval = yasm_object_get_general(object, ".text", 0, 16, 1, 0, &isnew, 0); |
| if (isnew) { |
| bin_objfmt_init_new_section(object, retval, ".text", 0); |
| yasm_section_set_default(retval, 1); |
| } |
| return retval; |
| } |
| |
| static /*@observer@*/ /*@null@*/ yasm_section * |
| bin_objfmt_section_switch(yasm_object *object, yasm_valparamhead *valparams, |
| /*@unused@*/ /*@null@*/ |
| yasm_valparamhead *objext_valparams, |
| unsigned long line) |
| { |
| yasm_valparam *vp; |
| yasm_section *retval; |
| int isnew; |
| unsigned long start; |
| const char *sectname; |
| int resonly = 0; |
| /*@only@*/ /*@null@*/ yasm_intnum *align_intn = NULL; |
| unsigned long align = 4; |
| int have_align = 0; |
| |
| static const yasm_dir_help help[] = { |
| { "align", 1, yasm_dir_helper_intn, 0, 0 } |
| }; |
| |
| vp = yasm_vps_first(valparams); |
| sectname = yasm_vp_string(vp); |
| if (!sectname) |
| return NULL; |
| vp = yasm_vps_next(vp); |
| |
| /* If it's the first section output (.text) start at 0, otherwise |
| * make sure the start is > 128. |
| */ |
| if (strcmp(sectname, ".text") == 0) |
| start = 0; |
| else if (strcmp(sectname, ".data") == 0) |
| start = 200; |
| else if (strcmp(sectname, ".bss") == 0) { |
| start = 200; |
| resonly = 1; |
| } else { |
| /* other section names not recognized. */ |
| yasm_error_set(YASM_ERROR_GENERAL, |
| N_("segment name `%s' not recognized"), sectname); |
| return NULL; |
| } |
| |
| have_align = yasm_dir_helper(object, vp, line, help, NELEMS(help), |
| &align_intn, yasm_dir_helper_valparam_warn); |
| if (have_align < 0) |
| return NULL; /* error occurred */ |
| |
| if (align_intn) { |
| align = yasm_intnum_get_uint(align_intn); |
| yasm_intnum_destroy(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; |
| } |
| } |
| |
| retval = yasm_object_get_general(object, sectname, |
| yasm_expr_create_ident( |
| yasm_expr_int(yasm_intnum_create_uint(start)), line), align, |
| strcmp(sectname, ".text") == 0, resonly, &isnew, line); |
| |
| if (isnew) |
| bin_objfmt_init_new_section(object, retval, sectname, line); |
| |
| if (isnew || yasm_section_is_default(retval)) { |
| yasm_section_set_default(retval, 0); |
| yasm_section_set_align(retval, align, line); |
| } else if (have_align) |
| yasm_warn_set(YASM_WARN_GENERAL, |
| N_("alignment value ignored on section redeclaration")); |
| |
| return retval; |
| } |
| |
| static void |
| bin_objfmt_dir_org(yasm_object *object, |
| /*@null@*/ yasm_valparamhead *valparams, |
| /*@unused@*/ /*@null@*/ |
| yasm_valparamhead *objext_valparams, unsigned long line) |
| { |
| yasm_section *sect; |
| yasm_valparam *vp; |
| /*@only@*/ /*@null@*/ yasm_expr *start; |
| |
| /* ORG takes just a simple integer as param */ |
| vp = yasm_vps_first(valparams); |
| start = yasm_vp_expr(vp, object->symtab, line); |
| if (!start) { |
| yasm_error_set(YASM_ERROR_SYNTAX, |
| N_("argument to ORG must be expression")); |
| return; |
| } |
| |
| /* ORG changes the start of the .text section */ |
| sect = yasm_object_find_general(object, ".text"); |
| if (!sect) |
| yasm_internal_error( |
| N_("bin objfmt: .text section does not exist before ORG is called?")); |
| yasm_section_set_start(sect, start, line); |
| } |
| |
| |
| /* Define valid debug formats to use with this object format */ |
| static const char *bin_objfmt_dbgfmt_keywords[] = { |
| "null", |
| NULL |
| }; |
| |
| static const yasm_directive bin_objfmt_directives[] = { |
| { "org", "nasm", bin_objfmt_dir_org, YASM_DIR_ARG_REQUIRED }, |
| { NULL, NULL, NULL, 0 } |
| }; |
| |
| /* Define objfmt structure -- see objfmt.h for details */ |
| yasm_objfmt_module yasm_bin_LTX_objfmt = { |
| "Flat format binary", |
| "bin", |
| NULL, |
| 16, |
| bin_objfmt_dbgfmt_keywords, |
| "null", |
| bin_objfmt_directives, |
| bin_objfmt_create, |
| bin_objfmt_output, |
| bin_objfmt_destroy, |
| bin_objfmt_add_default_section, |
| bin_objfmt_section_switch |
| }; |