| /* |
| * 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 <util.h> |
| /*@unused@*/ RCSID("$Id: win64-except.c 2130 2008-10-07 05:38:11Z peter $"); |
| |
| #include <libyasm.h> |
| |
| #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; |
| } |