/* * Win64 structured exception handling support * * Copyright (C) 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 /*@unused@*/ RCSID("$Id: win64-except.c 2130 2008-10-07 05:38:11Z peter $"); #include #include "coff-objfmt.h" #define UNW_FLAG_EHANDLER 0x01 #define UNW_FLAG_UHANDLER 0x02 #define UNW_FLAG_CHAININFO 0x04 /* Bytecode callback function prototypes */ static void win64_uwinfo_bc_destroy(void *contents); static void win64_uwinfo_bc_print(const void *contents, FILE *f, int indent_level); static void win64_uwinfo_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc); static int win64_uwinfo_bc_calc_len (yasm_bytecode *bc, yasm_bc_add_span_func add_span, void *add_span_data); static int win64_uwinfo_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val, /*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres); static int win64_uwinfo_bc_tobytes (yasm_bytecode *bc, unsigned char **bufp, void *d, yasm_output_value_func output_value, /*@null@*/ yasm_output_reloc_func output_reloc); static void win64_uwcode_bc_destroy(void *contents); static void win64_uwcode_bc_print(const void *contents, FILE *f, int indent_level); static void win64_uwcode_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc); static int win64_uwcode_bc_calc_len (yasm_bytecode *bc, yasm_bc_add_span_func add_span, void *add_span_data); static int win64_uwcode_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val, /*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres); static int win64_uwcode_bc_tobytes (yasm_bytecode *bc, unsigned char **bufp, void *d, yasm_output_value_func output_value, /*@null@*/ yasm_output_reloc_func output_reloc); /* Bytecode callback structures */ static const yasm_bytecode_callback win64_uwinfo_bc_callback = { win64_uwinfo_bc_destroy, win64_uwinfo_bc_print, win64_uwinfo_bc_finalize, NULL, win64_uwinfo_bc_calc_len, win64_uwinfo_bc_expand, win64_uwinfo_bc_tobytes, 0 }; static const yasm_bytecode_callback win64_uwcode_bc_callback = { win64_uwcode_bc_destroy, win64_uwcode_bc_print, win64_uwcode_bc_finalize, NULL, win64_uwcode_bc_calc_len, win64_uwcode_bc_expand, win64_uwcode_bc_tobytes, 0 }; coff_unwind_info * yasm_win64__uwinfo_create(void) { coff_unwind_info *info = yasm_xmalloc(sizeof(coff_unwind_info)); info->proc = NULL; info->prolog = NULL; info->ehandler = NULL; info->framereg = 0; /* Frameoff is really a 4-bit value, scaled by 16 */ yasm_value_initialize(&info->frameoff, NULL, 8); SLIST_INIT(&info->codes); yasm_value_initialize(&info->prolog_size, NULL, 8); yasm_value_initialize(&info->codes_count, NULL, 8); return info; } void yasm_win64__uwinfo_destroy(coff_unwind_info *info) { coff_unwind_code *code; yasm_value_delete(&info->frameoff); yasm_value_delete(&info->prolog_size); yasm_value_delete(&info->codes_count); while (!SLIST_EMPTY(&info->codes)) { code = SLIST_FIRST(&info->codes); SLIST_REMOVE_HEAD(&info->codes, link); yasm_value_delete(&code->off); yasm_xfree(code); } yasm_xfree(info); } void yasm_win64__unwind_generate(yasm_section *xdata, coff_unwind_info *info, unsigned long line) { yasm_bytecode *infobc, *codebc = NULL; coff_unwind_code *code; /* 4-byte align the start of unwind info */ yasm_section_bcs_append(xdata, yasm_bc_create_align( yasm_expr_create_ident(yasm_expr_int(yasm_intnum_create_uint(4)), line), NULL, NULL, NULL, line)); /* Prolog size = end of prolog - start of procedure */ yasm_value_initialize(&info->prolog_size, yasm_expr_create(YASM_EXPR_SUB, yasm_expr_sym(info->prolog), yasm_expr_sym(info->proc), line), 8); /* Unwind info */ infobc = yasm_bc_create_common(&win64_uwinfo_bc_callback, info, line); yasm_section_bcs_append(xdata, infobc); /* Code array */ SLIST_FOREACH(code, &info->codes, link) { codebc = yasm_bc_create_common(&win64_uwcode_bc_callback, code, yasm_symrec_get_def_line(code->loc)); yasm_section_bcs_append(xdata, codebc); } /* Avoid double-free (by code destroy and uwinfo destroy). */ SLIST_INIT(&info->codes); /* Number of codes = (Last code - end of info) >> 1 */ if (!codebc) { yasm_value_initialize(&info->codes_count, yasm_expr_create_ident(yasm_expr_int(yasm_intnum_create_uint(0)), line), 8); } else { yasm_value_initialize(&info->codes_count, yasm_expr_create(YASM_EXPR_SHR, yasm_expr_expr( yasm_expr_create(YASM_EXPR_SUB, yasm_expr_precbc(codebc), yasm_expr_precbc(infobc), line)), yasm_expr_int(yasm_intnum_create_uint(1)), line), 8); } /* 4-byte align */ yasm_section_bcs_append(xdata, yasm_bc_create_align( yasm_expr_create_ident(yasm_expr_int(yasm_intnum_create_uint(4)), line), NULL, NULL, NULL, line)); /* Exception handler, if present. Use data bytecode. */ if (info->ehandler) { yasm_datavalhead dvs; yasm_dvs_initialize(&dvs); yasm_dvs_append(&dvs, yasm_dv_create_expr( yasm_expr_create_ident(yasm_expr_sym(info->ehandler), line))); yasm_section_bcs_append(xdata, yasm_bc_create_data(&dvs, 4, 0, NULL, line)); } } static void win64_uwinfo_bc_destroy(void *contents) { yasm_win64__uwinfo_destroy((coff_unwind_info *)contents); } static void win64_uwinfo_bc_print(const void *contents, FILE *f, int indent_level) { /* TODO */ } static void win64_uwinfo_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc) { coff_unwind_info *info = (coff_unwind_info *)bc->contents; if (yasm_value_finalize(&info->prolog_size, prev_bc)) yasm_internal_error(N_("prolog size expression too complex")); if (yasm_value_finalize(&info->codes_count, prev_bc)) yasm_internal_error(N_("codes count expression too complex")); if (yasm_value_finalize(&info->frameoff, prev_bc)) yasm_error_set(YASM_ERROR_VALUE, N_("frame offset expression too complex")); } static int win64_uwinfo_bc_calc_len(yasm_bytecode *bc, yasm_bc_add_span_func add_span, void *add_span_data) { coff_unwind_info *info = (coff_unwind_info *)bc->contents; /*@only@*/ /*@null@*/ yasm_intnum *intn; long intv; /* Want to make sure prolog size and codes count doesn't exceed * byte-size, and scaled frame offset doesn't exceed 4 bits. */ add_span(add_span_data, bc, 1, &info->prolog_size, 0, 255); add_span(add_span_data, bc, 2, &info->codes_count, 0, 255); intn = yasm_value_get_intnum(&info->frameoff, bc, 0); if (intn) { intv = yasm_intnum_get_int(intn); if (intv < 0 || intv > 240) yasm_error_set(YASM_ERROR_VALUE, N_("frame offset of %ld bytes, must be between 0 and 240"), intv); else if ((intv & 0xF) != 0) yasm_error_set(YASM_ERROR_VALUE, N_("frame offset of %ld is not a multiple of 16"), intv); yasm_intnum_destroy(intn); } else add_span(add_span_data, bc, 3, &info->frameoff, 0, 240); bc->len += 4; return 0; } static int win64_uwinfo_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val, /*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres) { coff_unwind_info *info = (coff_unwind_info *)bc->contents; switch (span) { case 1: yasm_error_set_xref(yasm_symrec_get_def_line(info->prolog), N_("prologue ended here")); yasm_error_set(YASM_ERROR_VALUE, N_("prologue %ld bytes, must be <256"), new_val); return -1; case 2: yasm_error_set(YASM_ERROR_VALUE, N_("%ld unwind codes, maximum of 255"), new_val); return -1; case 3: yasm_error_set(YASM_ERROR_VALUE, N_("frame offset of %ld bytes, must be between 0 and 240"), new_val); return -1; default: yasm_internal_error(N_("unrecognized span id")); } return 0; } static int win64_uwinfo_bc_tobytes(yasm_bytecode *bc, unsigned char **bufp, void *d, yasm_output_value_func output_value, yasm_output_reloc_func output_reloc) { coff_unwind_info *info = (coff_unwind_info *)bc->contents; unsigned char *buf = *bufp; /*@only@*/ /*@null@*/ yasm_intnum *frameoff; long intv; /* Version and flags */ if (info->ehandler) YASM_WRITE_8(buf, 1 | (UNW_FLAG_EHANDLER << 3)); else YASM_WRITE_8(buf, 1); /* Size of prolog */ output_value(&info->prolog_size, buf, 1, (unsigned long)(buf-*bufp), bc, 1, d); buf += 1; /* Count of codes */ output_value(&info->codes_count, buf, 1, (unsigned long)(buf-*bufp), bc, 1, d); buf += 1; /* Frame register and offset */ frameoff = yasm_value_get_intnum(&info->frameoff, bc, 1); if (!frameoff) { yasm_error_set(YASM_ERROR_VALUE, N_("frame offset expression too complex")); return 1; } intv = yasm_intnum_get_int(frameoff); if (intv < 0 || intv > 240) yasm_error_set(YASM_ERROR_VALUE, N_("frame offset of %ld bytes, must be between 0 and 240"), intv); else if ((intv & 0xF) != 0) yasm_error_set(YASM_ERROR_VALUE, N_("frame offset of %ld is not a multiple of 16"), intv); YASM_WRITE_8(buf, ((unsigned long)intv & 0xF0) | (info->framereg & 0x0F)); yasm_intnum_destroy(frameoff); *bufp = buf; return 0; } static void win64_uwcode_bc_destroy(void *contents) { coff_unwind_code *code = (coff_unwind_code *)contents; yasm_value_delete(&code->off); yasm_xfree(contents); } static void win64_uwcode_bc_print(const void *contents, FILE *f, int indent_level) { /* TODO */ } static void win64_uwcode_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc) { coff_unwind_code *code = (coff_unwind_code *)bc->contents; if (yasm_value_finalize(&code->off, prev_bc)) yasm_error_set(YASM_ERROR_VALUE, N_("offset expression too complex")); } static int win64_uwcode_bc_calc_len(yasm_bytecode *bc, yasm_bc_add_span_func add_span, void *add_span_data) { coff_unwind_code *code = (coff_unwind_code *)bc->contents; int span = 0; /*@only@*/ /*@null@*/ yasm_intnum *intn; long intv; long low, high, mask; bc->len += 2; /* Prolog offset, code, and info */ switch (code->opcode) { case UWOP_PUSH_NONVOL: case UWOP_SET_FPREG: case UWOP_PUSH_MACHFRAME: /* always 1 node */ return 0; case UWOP_ALLOC_SMALL: case UWOP_ALLOC_LARGE: /* Start with smallest, then work our way up as necessary */ code->opcode = UWOP_ALLOC_SMALL; code->info = 0; span = 1; low = 8; high = 128; mask = 0x7; break; case UWOP_SAVE_NONVOL: case UWOP_SAVE_NONVOL_FAR: /* Start with smallest, then work our way up as necessary */ code->opcode = UWOP_SAVE_NONVOL; bc->len += 2; /* Scaled offset */ span = 2; low = 0; high = 8*64*1024-8; /* 16-bit field, *8 scaling */ mask = 0x7; break; case UWOP_SAVE_XMM128: case UWOP_SAVE_XMM128_FAR: /* Start with smallest, then work our way up as necessary */ code->opcode = UWOP_SAVE_XMM128; bc->len += 2; /* Scaled offset */ span = 3; low = 0; high = 16*64*1024-16; /* 16-bit field, *16 scaling */ mask = 0xF; break; default: yasm_internal_error(N_("unrecognied unwind opcode")); /*@unreached@*/ return 0; } intn = yasm_value_get_intnum(&code->off, bc, 0); if (intn) { intv = yasm_intnum_get_int(intn); if (intv > high) { /* Expand it ourselves here if we can and we're already larger */ if (win64_uwcode_bc_expand(bc, span, intv, intv, &low, &high) > 0) add_span(add_span_data, bc, span, &code->off, low, high); } if (intv < low) yasm_error_set(YASM_ERROR_VALUE, N_("negative offset not allowed")); if ((intv & mask) != 0) yasm_error_set(YASM_ERROR_VALUE, N_("offset of %ld is not a multiple of %ld"), intv, mask+1); yasm_intnum_destroy(intn); } else add_span(add_span_data, bc, span, &code->off, low, high); return 0; } static int win64_uwcode_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val, /*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres) { coff_unwind_code *code = (coff_unwind_code *)bc->contents; if (new_val < 0) { yasm_error_set(YASM_ERROR_VALUE, N_("negative offset not allowed")); return -1; } if (span == 1) { /* 3 stages: SMALL, LARGE and info=0, LARGE and info=1 */ if (code->opcode == UWOP_ALLOC_LARGE && code->info == 1) yasm_internal_error(N_("expansion on already largest alloc")); if (code->opcode == UWOP_ALLOC_SMALL && new_val > 128) { /* Overflowed small size */ code->opcode = UWOP_ALLOC_LARGE; bc->len += 2; } if (new_val <= 8*64*1024-8) { /* Still can grow one more size */ *pos_thres = 8*64*1024-8; return 1; } /* We're into the largest size */ code->info = 1; bc->len += 2; } else if (code->opcode == UWOP_SAVE_NONVOL && span == 2) { code->opcode = UWOP_SAVE_NONVOL_FAR; bc->len += 2; } else if (code->opcode == UWOP_SAVE_XMM128 && span == 3) { code->opcode = UWOP_SAVE_XMM128_FAR; bc->len += 2; } return 0; } static int win64_uwcode_bc_tobytes(yasm_bytecode *bc, unsigned char **bufp, void *d, yasm_output_value_func output_value, yasm_output_reloc_func output_reloc) { coff_unwind_code *code = (coff_unwind_code *)bc->contents; unsigned char *buf = *bufp; yasm_value val; unsigned int size; int shift; long intv, low, high, mask; yasm_intnum *intn; /* Offset in prolog */ yasm_value_initialize(&val, yasm_expr_create(YASM_EXPR_SUB, yasm_expr_sym(code->loc), yasm_expr_sym(code->proc), bc->line), 8); output_value(&val, buf, 1, (unsigned long)(buf-*bufp), bc, 1, d); buf += 1; yasm_value_delete(&val); /* Offset value */ switch (code->opcode) { case UWOP_PUSH_NONVOL: case UWOP_SET_FPREG: case UWOP_PUSH_MACHFRAME: /* just 1 node, no offset; write opcode and info and we're done */ YASM_WRITE_8(buf, (code->info << 4) | (code->opcode & 0xF)); *bufp = buf; return 0; case UWOP_ALLOC_SMALL: /* 1 node, but offset stored in info */ size = 0; low = 8; high = 128; shift = 3; mask = 0x7; break; case UWOP_ALLOC_LARGE: if (code->info == 0) { size = 2; low = 136; high = 8*64*1024-8; shift = 3; } else { size = 4; low = high = 0; shift = 0; } mask = 0x7; break; case UWOP_SAVE_NONVOL: size = 2; low = 0; high = 8*64*1024-8; shift = 3; mask = 0x7; break; case UWOP_SAVE_XMM128: size = 2; low = 0; high = 16*64*1024-16; shift = 4; mask = 0xF; break; case UWOP_SAVE_NONVOL_FAR: size = 4; low = high = 0; shift = 0; mask = 0x7; break; case UWOP_SAVE_XMM128_FAR: size = 4; low = high = 0; shift = 0; mask = 0xF; break; default: yasm_internal_error(N_("unrecognied unwind opcode")); /*@unreached@*/ return 1; } /* Check for overflow */ intn = yasm_value_get_intnum(&code->off, bc, 1); if (!intn) { yasm_error_set(YASM_ERROR_VALUE, N_("offset expression too complex")); return 1; } intv = yasm_intnum_get_int(intn); if (size != 4 && (intv < low || intv > high)) { yasm_error_set(YASM_ERROR_VALUE, N_("offset of %ld bytes, must be between %ld and %ld"), intv, low, high); return 1; } if ((intv & mask) != 0) { yasm_error_set(YASM_ERROR_VALUE, N_("offset of %ld is not a multiple of %ld"), intv, mask+1); return 1; } /* Stored value in info instead of extra code space */ if (size == 0) code->info = (yasm_intnum_get_uint(intn) >> shift)-1; /* Opcode and info */ YASM_WRITE_8(buf, (code->info << 4) | (code->opcode & 0xF)); if (size != 0) { yasm_intnum_get_sized(intn, buf, size, size*8, -shift, 0, 1); buf += size; } yasm_intnum_destroy(intn); *bufp = buf; return 0; }