1/*
2 * Win64 structured exception handling support
3 *
4 *  Copyright (C) 2007  Peter Johnson
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27#include <util.h>
28
29#include <libyasm.h>
30
31#include "coff-objfmt.h"
32
33
34#define UNW_FLAG_EHANDLER   0x01
35#define UNW_FLAG_UHANDLER   0x02
36#define UNW_FLAG_CHAININFO  0x04
37
38/* Bytecode callback function prototypes */
39static void win64_uwinfo_bc_destroy(void *contents);
40static void win64_uwinfo_bc_print(const void *contents, FILE *f,
41                                  int indent_level);
42static void win64_uwinfo_bc_finalize(yasm_bytecode *bc,
43                                     yasm_bytecode *prev_bc);
44static int win64_uwinfo_bc_calc_len
45    (yasm_bytecode *bc, yasm_bc_add_span_func add_span, void *add_span_data);
46static int win64_uwinfo_bc_expand(yasm_bytecode *bc, int span, long old_val,
47                                  long new_val, /*@out@*/ long *neg_thres,
48                                  /*@out@*/ long *pos_thres);
49static int win64_uwinfo_bc_tobytes
50    (yasm_bytecode *bc, unsigned char **bufp, unsigned char *bufstart, void *d,
51     yasm_output_value_func output_value,
52     /*@null@*/ yasm_output_reloc_func output_reloc);
53
54static void win64_uwcode_bc_destroy(void *contents);
55static void win64_uwcode_bc_print(const void *contents, FILE *f,
56                                  int indent_level);
57static void win64_uwcode_bc_finalize(yasm_bytecode *bc,
58                                     yasm_bytecode *prev_bc);
59static int win64_uwcode_bc_calc_len
60    (yasm_bytecode *bc, yasm_bc_add_span_func add_span, void *add_span_data);
61static int win64_uwcode_bc_expand(yasm_bytecode *bc, int span, long old_val,
62                                  long new_val, /*@out@*/ long *neg_thres,
63                                  /*@out@*/ long *pos_thres);
64static int win64_uwcode_bc_tobytes
65    (yasm_bytecode *bc, unsigned char **bufp, unsigned char *bufstart, void *d,
66     yasm_output_value_func output_value,
67     /*@null@*/ yasm_output_reloc_func output_reloc);
68
69/* Bytecode callback structures */
70static const yasm_bytecode_callback win64_uwinfo_bc_callback = {
71    win64_uwinfo_bc_destroy,
72    win64_uwinfo_bc_print,
73    win64_uwinfo_bc_finalize,
74    NULL,
75    win64_uwinfo_bc_calc_len,
76    win64_uwinfo_bc_expand,
77    win64_uwinfo_bc_tobytes,
78    0
79};
80
81static const yasm_bytecode_callback win64_uwcode_bc_callback = {
82    win64_uwcode_bc_destroy,
83    win64_uwcode_bc_print,
84    win64_uwcode_bc_finalize,
85    NULL,
86    win64_uwcode_bc_calc_len,
87    win64_uwcode_bc_expand,
88    win64_uwcode_bc_tobytes,
89    0
90};
91
92
93coff_unwind_info *
94yasm_win64__uwinfo_create(void)
95{
96    coff_unwind_info *info = yasm_xmalloc(sizeof(coff_unwind_info));
97    info->proc = NULL;
98    info->prolog = NULL;
99    info->ehandler = NULL;
100    info->framereg = 0;
101    /* Frameoff is really a 4-bit value, scaled by 16 */
102    yasm_value_initialize(&info->frameoff, NULL, 8);
103    SLIST_INIT(&info->codes);
104    yasm_value_initialize(&info->prolog_size, NULL, 8);
105    yasm_value_initialize(&info->codes_count, NULL, 8);
106    return info;
107}
108
109void
110yasm_win64__uwinfo_destroy(coff_unwind_info *info)
111{
112    coff_unwind_code *code;
113
114    yasm_value_delete(&info->frameoff);
115    yasm_value_delete(&info->prolog_size);
116    yasm_value_delete(&info->codes_count);
117
118    while (!SLIST_EMPTY(&info->codes)) {
119        code = SLIST_FIRST(&info->codes);
120        SLIST_REMOVE_HEAD(&info->codes, link);
121        yasm_value_delete(&code->off);
122        yasm_xfree(code);
123    }
124    yasm_xfree(info);
125}
126
127void
128yasm_win64__unwind_generate(yasm_section *xdata, coff_unwind_info *info,
129                            unsigned long line)
130{
131    yasm_bytecode *infobc, *codebc = NULL;
132    coff_unwind_code *code;
133
134    /* 4-byte align the start of unwind info */
135    yasm_section_bcs_append(xdata, yasm_bc_create_align(
136        yasm_expr_create_ident(yasm_expr_int(yasm_intnum_create_uint(4)),
137                               line),
138        NULL, NULL, NULL, line));
139
140    /* Prolog size = end of prolog - start of procedure */
141    yasm_value_initialize(&info->prolog_size,
142        yasm_expr_create(YASM_EXPR_SUB, yasm_expr_sym(info->prolog),
143                         yasm_expr_sym(info->proc), line),
144        8);
145
146    /* Unwind info */
147    infobc = yasm_bc_create_common(&win64_uwinfo_bc_callback, info, line);
148    yasm_section_bcs_append(xdata, infobc);
149
150    /* Code array */
151    SLIST_FOREACH(code, &info->codes, link) {
152        codebc = yasm_bc_create_common(&win64_uwcode_bc_callback, code,
153                                       yasm_symrec_get_def_line(code->loc));
154        yasm_section_bcs_append(xdata, codebc);
155    }
156
157    /* Avoid double-free (by code destroy and uwinfo destroy). */
158    SLIST_INIT(&info->codes);
159
160    /* Number of codes = (Last code - end of info) >> 1 */
161    if (!codebc) {
162        yasm_value_initialize(&info->codes_count,
163            yasm_expr_create_ident(yasm_expr_int(yasm_intnum_create_uint(0)),
164                                   line),
165            8);
166    } else {
167        yasm_value_initialize(&info->codes_count,
168            yasm_expr_create(YASM_EXPR_SHR, yasm_expr_expr(
169                yasm_expr_create(YASM_EXPR_SUB, yasm_expr_precbc(codebc),
170                                 yasm_expr_precbc(infobc), line)),
171                yasm_expr_int(yasm_intnum_create_uint(1)), line),
172            8);
173    }
174
175    /* 4-byte align */
176    yasm_section_bcs_append(xdata, yasm_bc_create_align(
177        yasm_expr_create_ident(yasm_expr_int(yasm_intnum_create_uint(4)),
178                               line),
179        NULL, NULL, NULL, line));
180
181    /* Exception handler, if present.  Use data bytecode. */
182    if (info->ehandler) {
183        yasm_datavalhead dvs;
184
185        yasm_dvs_initialize(&dvs);
186        yasm_dvs_append(&dvs, yasm_dv_create_expr(
187            yasm_expr_create_ident(yasm_expr_sym(info->ehandler), line)));
188        yasm_section_bcs_append(xdata,
189                                yasm_bc_create_data(&dvs, 4, 0, NULL, line));
190    }
191}
192
193static void
194win64_uwinfo_bc_destroy(void *contents)
195{
196    yasm_win64__uwinfo_destroy((coff_unwind_info *)contents);
197}
198
199static void
200win64_uwinfo_bc_print(const void *contents, FILE *f, int indent_level)
201{
202    /* TODO */
203}
204
205static void
206win64_uwinfo_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc)
207{
208    coff_unwind_info *info = (coff_unwind_info *)bc->contents;
209
210    if (yasm_value_finalize(&info->prolog_size, prev_bc))
211        yasm_internal_error(N_("prolog size expression too complex"));
212
213    if (yasm_value_finalize(&info->codes_count, prev_bc))
214        yasm_internal_error(N_("codes count expression too complex"));
215
216    if (yasm_value_finalize(&info->frameoff, prev_bc))
217        yasm_error_set(YASM_ERROR_VALUE,
218                       N_("frame offset expression too complex"));
219}
220
221static int
222win64_uwinfo_bc_calc_len(yasm_bytecode *bc, yasm_bc_add_span_func add_span,
223                         void *add_span_data)
224{
225    coff_unwind_info *info = (coff_unwind_info *)bc->contents;
226    /*@only@*/ /*@null@*/ yasm_intnum *intn;
227    long intv;
228
229    /* Want to make sure prolog size and codes count doesn't exceed
230     * byte-size, and scaled frame offset doesn't exceed 4 bits.
231     */
232    add_span(add_span_data, bc, 1, &info->prolog_size, 0, 255);
233    add_span(add_span_data, bc, 2, &info->codes_count, 0, 255);
234
235    intn = yasm_value_get_intnum(&info->frameoff, bc, 0);
236    if (intn) {
237        intv = yasm_intnum_get_int(intn);
238        if (intv < 0 || intv > 240)
239            yasm_error_set(YASM_ERROR_VALUE,
240                N_("frame offset of %ld bytes, must be between 0 and 240"),
241                intv);
242        else if ((intv & 0xF) != 0)
243            yasm_error_set(YASM_ERROR_VALUE,
244                N_("frame offset of %ld is not a multiple of 16"), intv);
245        yasm_intnum_destroy(intn);
246    } else
247        add_span(add_span_data, bc, 3, &info->frameoff, 0, 240);
248
249    bc->len += 4;
250    return 0;
251}
252
253static int
254win64_uwinfo_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val,
255                       /*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres)
256{
257    coff_unwind_info *info = (coff_unwind_info *)bc->contents;
258    switch (span) {
259        case 1:
260            yasm_error_set_xref(yasm_symrec_get_def_line(info->prolog),
261                                N_("prologue ended here"));
262            yasm_error_set(YASM_ERROR_VALUE,
263                           N_("prologue %ld bytes, must be <256"), new_val);
264            return -1;
265        case 2:
266            yasm_error_set(YASM_ERROR_VALUE,
267                           N_("%ld unwind codes, maximum of 255"), new_val);
268            return -1;
269        case 3:
270            yasm_error_set(YASM_ERROR_VALUE,
271                N_("frame offset of %ld bytes, must be between 0 and 240"),
272                new_val);
273            return -1;
274        default:
275            yasm_internal_error(N_("unrecognized span id"));
276    }
277    return 0;
278}
279
280static int
281win64_uwinfo_bc_tobytes(yasm_bytecode *bc, unsigned char **bufp,
282                        unsigned char *bufstart, void *d,
283                        yasm_output_value_func output_value,
284                        yasm_output_reloc_func output_reloc)
285{
286    coff_unwind_info *info = (coff_unwind_info *)bc->contents;
287    unsigned char *buf = *bufp;
288    /*@only@*/ /*@null@*/ yasm_intnum *frameoff;
289    long intv;
290
291    /* Version and flags */
292    if (info->ehandler)
293        YASM_WRITE_8(buf, 1 | (UNW_FLAG_EHANDLER << 3));
294    else
295        YASM_WRITE_8(buf, 1);
296
297    /* Size of prolog */
298    output_value(&info->prolog_size, buf, 1, (unsigned long)(buf-bufstart),
299                 bc, 1, d);
300    buf += 1;
301
302    /* Count of codes */
303    output_value(&info->codes_count, buf, 1, (unsigned long)(buf-bufstart),
304                 bc, 1, d);
305    buf += 1;
306
307    /* Frame register and offset */
308    frameoff = yasm_value_get_intnum(&info->frameoff, bc, 1);
309    if (!frameoff) {
310        yasm_error_set(YASM_ERROR_VALUE,
311                       N_("frame offset expression too complex"));
312        return 1;
313    }
314    intv = yasm_intnum_get_int(frameoff);
315    if (intv < 0 || intv > 240)
316        yasm_error_set(YASM_ERROR_VALUE,
317            N_("frame offset of %ld bytes, must be between 0 and 240"), intv);
318    else if ((intv & 0xF) != 0)
319        yasm_error_set(YASM_ERROR_VALUE,
320            N_("frame offset of %ld is not a multiple of 16"), intv);
321
322    YASM_WRITE_8(buf, ((unsigned long)intv & 0xF0) | (info->framereg & 0x0F));
323    yasm_intnum_destroy(frameoff);
324
325    *bufp = buf;
326    return 0;
327}
328
329static void
330win64_uwcode_bc_destroy(void *contents)
331{
332    coff_unwind_code *code = (coff_unwind_code *)contents;
333    yasm_value_delete(&code->off);
334    yasm_xfree(contents);
335}
336
337static void
338win64_uwcode_bc_print(const void *contents, FILE *f, int indent_level)
339{
340    /* TODO */
341}
342
343static void
344win64_uwcode_bc_finalize(yasm_bytecode *bc, yasm_bytecode *prev_bc)
345{
346    coff_unwind_code *code = (coff_unwind_code *)bc->contents;
347    if (yasm_value_finalize(&code->off, prev_bc))
348        yasm_error_set(YASM_ERROR_VALUE, N_("offset expression too complex"));
349}
350
351static int
352win64_uwcode_bc_calc_len(yasm_bytecode *bc, yasm_bc_add_span_func add_span,
353                         void *add_span_data)
354{
355    coff_unwind_code *code = (coff_unwind_code *)bc->contents;
356    int span = 0;
357    /*@only@*/ /*@null@*/ yasm_intnum *intn;
358    long intv;
359    long low, high, mask;
360
361    bc->len += 2;   /* Prolog offset, code, and info */
362
363    switch (code->opcode) {
364        case UWOP_PUSH_NONVOL:
365        case UWOP_SET_FPREG:
366        case UWOP_PUSH_MACHFRAME:
367            /* always 1 node */
368            return 0;
369        case UWOP_ALLOC_SMALL:
370        case UWOP_ALLOC_LARGE:
371            /* Start with smallest, then work our way up as necessary */
372            code->opcode = UWOP_ALLOC_SMALL;
373            code->info = 0;
374            span = 1; low = 8; high = 128; mask = 0x7;
375            break;
376        case UWOP_SAVE_NONVOL:
377        case UWOP_SAVE_NONVOL_FAR:
378            /* Start with smallest, then work our way up as necessary */
379            code->opcode = UWOP_SAVE_NONVOL;
380            bc->len += 2;   /* Scaled offset */
381            span = 2;
382            low = 0;
383            high = 8*64*1024-8;         /* 16-bit field, *8 scaling */
384            mask = 0x7;
385            break;
386        case UWOP_SAVE_XMM128:
387        case UWOP_SAVE_XMM128_FAR:
388            /* Start with smallest, then work our way up as necessary */
389            code->opcode = UWOP_SAVE_XMM128;
390            bc->len += 2;   /* Scaled offset */
391            span = 3;
392            low = 0;
393            high = 16*64*1024-16;       /* 16-bit field, *16 scaling */
394            mask = 0xF;
395            break;
396        default:
397            yasm_internal_error(N_("unrecognied unwind opcode"));
398            /*@unreached@*/
399            return 0;
400    }
401
402    intn = yasm_value_get_intnum(&code->off, bc, 0);
403    if (intn) {
404        intv = yasm_intnum_get_int(intn);
405        if (intv > high) {
406            /* Expand it ourselves here if we can and we're already larger */
407            if (win64_uwcode_bc_expand(bc, span, intv, intv, &low, &high) > 0)
408                add_span(add_span_data, bc, span, &code->off, low, high);
409        }
410        if (intv < low)
411            yasm_error_set(YASM_ERROR_VALUE,
412                           N_("negative offset not allowed"));
413        if ((intv & mask) != 0)
414            yasm_error_set(YASM_ERROR_VALUE,
415                N_("offset of %ld is not a multiple of %ld"), intv, mask+1);
416        yasm_intnum_destroy(intn);
417    } else
418        add_span(add_span_data, bc, span, &code->off, low, high);
419    return 0;
420}
421
422static int
423win64_uwcode_bc_expand(yasm_bytecode *bc, int span, long old_val, long new_val,
424                       /*@out@*/ long *neg_thres, /*@out@*/ long *pos_thres)
425{
426    coff_unwind_code *code = (coff_unwind_code *)bc->contents;
427
428    if (new_val < 0) {
429        yasm_error_set(YASM_ERROR_VALUE, N_("negative offset not allowed"));
430        return -1;
431    }
432
433    if (span == 1) {
434        /* 3 stages: SMALL, LARGE and info=0, LARGE and info=1 */
435        if (code->opcode == UWOP_ALLOC_LARGE && code->info == 1)
436            yasm_internal_error(N_("expansion on already largest alloc"));
437
438        if (code->opcode == UWOP_ALLOC_SMALL && new_val > 128) {
439            /* Overflowed small size */
440            code->opcode = UWOP_ALLOC_LARGE;
441            bc->len += 2;
442        }
443        if (new_val <= 8*64*1024-8) {
444            /* Still can grow one more size */
445            *pos_thres = 8*64*1024-8;
446            return 1;
447        }
448        /* We're into the largest size */
449        code->info = 1;
450        bc->len += 2;
451    } else if (code->opcode == UWOP_SAVE_NONVOL && span == 2) {
452        code->opcode = UWOP_SAVE_NONVOL_FAR;
453        bc->len += 2;
454    } else if (code->opcode == UWOP_SAVE_XMM128 && span == 3) {
455        code->opcode = UWOP_SAVE_XMM128_FAR;
456        bc->len += 2;
457    }
458    return 0;
459}
460
461static int
462win64_uwcode_bc_tobytes(yasm_bytecode *bc, unsigned char **bufp,
463                        unsigned char *bufstart, void *d,
464                        yasm_output_value_func output_value,
465                        yasm_output_reloc_func output_reloc)
466{
467    coff_unwind_code *code = (coff_unwind_code *)bc->contents;
468    unsigned char *buf = *bufp;
469    yasm_value val;
470    unsigned int size;
471    int shift;
472    long intv, low, high, mask;
473    yasm_intnum *intn;
474
475    /* Offset in prolog */
476    yasm_value_initialize(&val,
477        yasm_expr_create(YASM_EXPR_SUB, yasm_expr_sym(code->loc),
478                         yasm_expr_sym(code->proc), bc->line),
479        8);
480    output_value(&val, buf, 1, (unsigned long)(buf-bufstart), bc, 1, d);
481    buf += 1;
482    yasm_value_delete(&val);
483
484    /* Offset value */
485    switch (code->opcode) {
486        case UWOP_PUSH_NONVOL:
487        case UWOP_SET_FPREG:
488        case UWOP_PUSH_MACHFRAME:
489            /* just 1 node, no offset; write opcode and info and we're done */
490            YASM_WRITE_8(buf, (code->info << 4) | (code->opcode & 0xF));
491            *bufp = buf;
492            return 0;
493        case UWOP_ALLOC_SMALL:
494            /* 1 node, but offset stored in info */
495            size = 0; low = 8; high = 128; shift = 3; mask = 0x7;
496            break;
497        case UWOP_ALLOC_LARGE:
498            if (code->info == 0) {
499                size = 2; low = 136; high = 8*64*1024-8; shift = 3;
500            } else {
501                size = 4; low = high = 0; shift = 0;
502            }
503            mask = 0x7;
504            break;
505        case UWOP_SAVE_NONVOL:
506            size = 2; low = 0; high = 8*64*1024-8; shift = 3; mask = 0x7;
507            break;
508        case UWOP_SAVE_XMM128:
509            size = 2; low = 0; high = 16*64*1024-16; shift = 4; mask = 0xF;
510            break;
511        case UWOP_SAVE_NONVOL_FAR:
512            size = 4; low = high = 0; shift = 0; mask = 0x7;
513            break;
514        case UWOP_SAVE_XMM128_FAR:
515            size = 4; low = high = 0; shift = 0; mask = 0xF;
516            break;
517        default:
518            yasm_internal_error(N_("unrecognied unwind opcode"));
519            /*@unreached@*/
520            return 1;
521    }
522
523    /* Check for overflow */
524    intn = yasm_value_get_intnum(&code->off, bc, 1);
525    if (!intn) {
526        yasm_error_set(YASM_ERROR_VALUE, N_("offset expression too complex"));
527        return 1;
528    }
529    intv = yasm_intnum_get_int(intn);
530    if (size != 4 && (intv < low || intv > high)) {
531        yasm_error_set(YASM_ERROR_VALUE,
532            N_("offset of %ld bytes, must be between %ld and %ld"),
533            intv, low, high);
534        return 1;
535    }
536    if ((intv & mask) != 0) {
537        yasm_error_set(YASM_ERROR_VALUE,
538                       N_("offset of %ld is not a multiple of %ld"),
539                       intv, mask+1);
540        return 1;
541    }
542
543    /* Stored value in info instead of extra code space */
544    if (size == 0)
545        code->info = (yasm_intnum_get_uint(intn) >> shift)-1;
546
547    /* Opcode and info */
548    YASM_WRITE_8(buf, (code->info << 4) | (code->opcode & 0xF));
549
550    if (size != 0) {
551        yasm_intnum_get_sized(intn, buf, size, size*8, -shift, 0, 1);
552        buf += size;
553    }
554
555    yasm_intnum_destroy(intn);
556
557    *bufp = buf;
558    return 0;
559}
560