diff options
Diffstat (limited to 'gas/dw2gencfi.c')
-rw-r--r-- | gas/dw2gencfi.c | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/gas/dw2gencfi.c b/gas/dw2gencfi.c new file mode 100644 index 00000000000..f83610b3765 --- /dev/null +++ b/gas/dw2gencfi.c @@ -0,0 +1,721 @@ +/* dw2gencfi.c - Support for generating Dwarf2 CFI information. + Copyright 2003 Free Software Foundation, Inc. + Contributed by Michal Ludvig <mludvig@suse.cz> + + This file is part of GAS, the GNU Assembler. + + GAS is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + GAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GAS; see the file COPYING. If not, write to the Free + Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#include <errno.h> +#include "as.h" +#include "dw2gencfi.h" + +/* Current target config. */ +static struct cfi_config current_config; + +/* This is the main entry point to the CFI machinery. */ +static void dot_cfi (int arg); + +const pseudo_typeS cfi_pseudo_table[] = + { + { "cfi_verbose", dot_cfi, CFI_verbose }, + { "cfi_startproc", dot_cfi, CFI_startproc }, + { "cfi_endproc", dot_cfi, CFI_endproc }, + { "cfi_def_cfa", dot_cfi, CFA_def_cfa }, + { "cfi_def_cfa_register", dot_cfi, CFA_def_cfa_register }, + { "cfi_def_cfa_offset", dot_cfi, CFA_def_cfa_offset }, + { "cfi_adjust_cfa_offset", dot_cfi, CFI_adjust_cfa_offset }, + { "cfi_offset", dot_cfi, CFA_offset }, + { NULL, NULL, 0 } + }; + +static const char * +cfi_insn_str (enum cfi_insn insn) +{ + switch (insn) + { + case CFA_nop: + return "CFA_nop"; + case CFA_set_loc: + return "CFA_set_loc"; + case CFA_advance_loc1: + return "CFA_advance_loc1"; + case CFA_advance_loc2: + return "CFA_advance_loc2"; + case CFA_advance_loc4: + return "CFA_advance_loc4"; + case CFA_offset_extended: + return "CFA_offset_extended"; + case CFA_resotre_extended: + return "CFA_resotre_extended"; + case CFA_undefined: + return "CFA_undefined"; + case CFA_same_value: + return "CFA_same_value"; + case CFA_register: + return "CFA_register"; + case CFA_remember_state: + return "CFA_remember_state"; + case CFA_restore_state: + return "CFA_restore_state"; + case CFA_def_cfa: + return "CFA_def_cfa"; + case CFA_def_cfa_register: + return "CFA_def_cfa_register"; + case CFA_def_cfa_offset: + return "CFA_def_cfa_offset"; + case CFA_advance_loc: + return "CFA_advance_loc"; + case CFA_offset: + return "CFA_offset"; + case CFA_restore: + return "CFA_restore"; + default: + break; + } + + return "CFA_unknown"; +} + +struct cfi_data +{ + enum cfi_insn insn; + long param[2]; + struct cfi_data *next; +}; + +struct cfi_info +{ + addressT start_address; + addressT end_address; + addressT last_address; + const char *labelname; + struct cfi_data *data; + struct cfi_info *next; +}; + +static struct cfi_info *cfi_info; + +static struct cfi_data * +alloc_cfi_data (void) +{ + return (struct cfi_data *) xcalloc (sizeof (struct cfi_info), 1); +} + +static struct cfi_info * +alloc_cfi_info (void) +{ + return (struct cfi_info *) xcalloc (sizeof (struct cfi_info), 1); +} + +/* Parse arguments. */ +static int +cfi_parse_arg (long *param, int resolvereg) +{ + char *name, c, *p; + long value; + int retval = -1; + int nchars; + + assert (param != NULL); + SKIP_WHITESPACE (); + + if (sscanf (input_line_pointer, "%li%n", &value, &nchars) >= 1) + { + input_line_pointer += nchars; + retval = 1; + } + else if (resolvereg && (is_name_beginner (*input_line_pointer))) + { + name = input_line_pointer; + c = get_symbol_end (); + p = input_line_pointer; + + if ((value = tc_regname_to_dw2regnum (name)) >= 0) + retval = 1; + + *p = c; + } + else + as_bad (resolvereg ? + _("can't convert argument to a register number") : + _("can't convert argument to an integer")); + + if (retval > 0) + *param = value; + + SKIP_WHITESPACE (); + if (*input_line_pointer == ',') + { + input_line_pointer++; + SKIP_WHITESPACE (); + } + + return retval; +} + +static int +cfi_parse_reg (long *param) +{ + return cfi_parse_arg (param, 1); +} + +static int +cfi_parse_const (long *param) +{ + return cfi_parse_arg (param, 0); +} + +void +cfi_add_insn (enum cfi_insn insn, long param0, long param1) +{ + struct cfi_data *data_ptr; + + if (!cfi_info->data) + { + cfi_info->data = alloc_cfi_data (); + data_ptr = cfi_info->data; + } + else + { + data_ptr = cfi_info->data; + + while (data_ptr && data_ptr->next) + data_ptr = data_ptr->next; + + data_ptr->next = alloc_cfi_data (); + + data_ptr = data_ptr->next; + } + + data_ptr->insn = insn; + data_ptr->param[0] = param0; + data_ptr->param[1] = param1; +} + +static void +cfi_advance_loc (void) +{ + addressT curr_address = frag_now_fix (); + if (cfi_info->last_address == curr_address) + return; + cfi_add_insn (CFA_advance_loc, + (long) (curr_address - cfi_info->last_address), 0); + cfi_info->last_address = curr_address; +} + +static long +get_current_offset (struct cfi_info *info) +{ + long current_offset = 0; + struct cfi_data *data = info->data; + + current_offset = 0; + while (data) + { + if (data->insn == CFA_def_cfa) + current_offset = data->param[1]; + else if (data->insn == CFA_def_cfa_offset) + current_offset = data->param[0]; + data = data->next; + } + + return current_offset; +} + +static void +cfi_make_insn (int arg) +{ + long param[2] = { 0, 0 }; + + if (!cfi_info) + { + as_bad (_("CFI instruction used without previous .cfi_startproc")); + return; + } + + cfi_advance_loc (); + + switch (arg) + { + /* Instructions that take two arguments (register, integer). */ + case CFA_offset: + case CFA_def_cfa: + if (cfi_parse_reg (¶m[0]) < 0) + { + as_bad (_("first argument to %s is not a register"), + cfi_insn_str (arg)); + return; + } + if (cfi_parse_const (¶m[1]) < 0) + { + as_bad (_("second argument to %s is not a number"), + cfi_insn_str (arg)); + return; + } + break; + + /* Instructions that take one register argument. */ + case CFA_def_cfa_register: + if (cfi_parse_reg (¶m[0]) < 0) + { + as_bad (_("argument to %s is not a register"), cfi_insn_str (arg)); + return; + } + break; + + /* Instructions that take one integer argument. */ + case CFA_def_cfa_offset: + if (cfi_parse_const (¶m[0]) < 0) + { + as_bad (_("argument to %s is not a number"), cfi_insn_str (arg)); + return; + } + break; + + /* Special handling for pseudo-instruction. */ + case CFI_adjust_cfa_offset: + if (cfi_parse_const (¶m[0]) < 0) + { + as_bad (_("argument to %s is not a number"), + ".cfi_adjust_cfa_offset"); + return; + } + param[0] += get_current_offset (cfi_info); + arg = CFA_def_cfa_offset; + break; + + default: + as_bad (_("unknown CFI instruction %d (%s)"), arg, cfi_insn_str (arg)); + return; + } + cfi_add_insn (arg, param[0], param[1]); +} + +static symbolS * +cfi_get_label (void) +{ + char symname[40], *symbase=".Llbl_cfi"; + symbolS *symbolP; + unsigned int i = 0; + + snprintf (symname, sizeof (symname), "%s_0x%lx", + symbase, (long) frag_now_fix ()); + while ((symbolP = symbol_find (symname))) + { + if ((S_GET_VALUE (symbolP) == frag_now_fix ()) + && (S_GET_SEGMENT (symbolP) == now_seg)) + { + return symbolP; + } + snprintf (symname, sizeof (symname), "%s_0x%lx_%u", + symbase, (long) frag_now_fix (), i++); + } + symbolP = (symbolS *) local_symbol_make (symname, now_seg, + (valueT) frag_now_fix (), + frag_now); + return symbolP; +} + +static void +dot_cfi_startproc (void) +{ + if (cfi_info) + { + as_bad (_("previous CFI entry not closed (missing .cfi_endproc)")); + return; + } + + cfi_info = alloc_cfi_info (); + + cfi_info->start_address = frag_now_fix (); + cfi_info->last_address = cfi_info->start_address; + cfi_info->labelname = S_GET_NAME (cfi_get_label ()); + +#ifdef tc_cfi_frame_initial_instructions + tc_cfi_frame_initial_instructions (); +#endif +} + +#define cfi_is_advance_insn(insn) \ + ((insn >= CFA_set_loc && insn <= CFA_advance_loc4) \ + || insn == CFA_advance_loc) + +enum data_types + { + t_ascii = 0, + t_byte = 1, + t_half = 2, + t_long = 4, + t_quad = 8, + t_uleb128 = 0x10, + t_sleb128 = 0x11 + }; + +/* Output CFI instructions to the file. */ + +static int +output_data (char **p, unsigned long *size, enum data_types type, long value) +{ + char *ptr = *p; + unsigned int ret_size; + + switch (type) + { + case t_byte: + ret_size = 1; + break; + case t_half: + ret_size = 2; + break; + case t_long: + ret_size = 4; + break; + case t_quad: + case t_uleb128: + case t_sleb128: + ret_size = 8; + break; + default: + as_warn (_("unknown type %d"), type); + return 0; + } + + if (*size < ret_size) + { + as_bad (_("output_data buffer is too small")); + return 0; + } + + switch (type) + { + case t_byte: + *ptr = (char) value; + if (verbose) + printf ("\t.byte\t0x%x\n", (unsigned char) *ptr); + break; + case t_half: + *(short *) ptr = (short) value & 0xFFFF; + if (verbose) + printf ("\t.half\t0x%x\n", (unsigned short) *ptr); + break; + case t_long: + *(int *) ptr = (int) value & 0xFFFFFFFF; + if (verbose) + printf ("\t.long\t0x%x\n", (unsigned int) *ptr); + break; + case t_quad: + *(long long *) ptr = (long long) value & 0xFFFFFFFF; + if (verbose) + printf ("\t.quad\t0x%x\n", (unsigned int) *ptr); + break; + case t_uleb128: + case t_sleb128: + ret_size = output_leb128 (ptr, value, type == t_sleb128); + if (verbose) + printf ("\t.%s\t0x%lx\n", + type == t_sleb128 ? "sleb128" : "uleb128", + value); + break; + default: + as_warn ("unknown type %d", type); + return 0; + } + + *size -= ret_size; + *p += ret_size; + + return ret_size; +} + +static int +cfi_output_insn (struct cfi_data *data, char **buf, unsigned long *buf_size) +{ + char **pbuf = buf, *orig_buf = *buf; + unsigned long size; + + if (!data || !buf) + as_fatal (_("cfi_output_insn called with NULL pointer")); + + switch (data->insn) + { + case CFA_advance_loc: + if (verbose) + printf ("\t# %s(%ld)\n", cfi_insn_str (data->insn), + data->param[0]); + if (data->param[0] <= 0x3F) + { + output_data (pbuf, buf_size, t_byte, CFA_advance_loc + + (data->param[0] / current_config.code_align)); + } + else if (data->param[0] <= 0xFF) + { + output_data (pbuf, buf_size, t_byte, CFA_advance_loc1); + output_data (pbuf, buf_size, t_byte, + data->param[0] / current_config.code_align); + } + else if (data->param[0] <= 0xFFFF) + { + output_data (pbuf, buf_size, t_byte, CFA_advance_loc2); + output_data (pbuf, buf_size, t_half, + data->param[0] / current_config.code_align); + } + else + { + output_data (pbuf, buf_size, t_byte, CFA_advance_loc4); + output_data (pbuf, buf_size, t_long, + data->param[0] / current_config.code_align); + } + break; + + case CFA_def_cfa: + if (verbose) + printf ("\t# CFA_def_cfa(%ld,%ld)\n", data->param[0], data->param[1]); + output_data (pbuf, buf_size, t_byte, CFA_def_cfa); + output_data (pbuf, buf_size, t_uleb128, data->param[0]); + output_data (pbuf, buf_size, t_uleb128, data->param[1]); + break; + + case CFA_def_cfa_register: + case CFA_def_cfa_offset: + if (verbose) + printf ("\t# %s(%ld)\n", cfi_insn_str (data->insn), + data->param[0]); + output_data (pbuf, buf_size, t_byte, data->insn); + output_data (pbuf, buf_size, t_uleb128, data->param[0]); + break; + + case CFA_offset: + if (verbose) + printf ("\t# %s(%ld,%ld)\n", cfi_insn_str (data->insn), + data->param[0], data->param[1]); + + /* Check whether to use CFA_offset or CFA_offset_extended. */ + if (data->param[0] <= 0x3F) + output_data (pbuf, buf_size, t_byte, CFA_offset + data->param[0]); + else + { + output_data (pbuf, buf_size, t_byte, CFA_offset_extended); + output_data (pbuf, buf_size, t_uleb128, data->param[0]); + } + output_data (pbuf, buf_size, t_uleb128, + data->param[1] / current_config.data_align); + break; + + case CFA_nop: + if (verbose) + printf ("\t# CFA_nop\n"); + output_data (pbuf, buf_size, t_byte, CFA_nop); + break; + + default: + as_warn ("CFA_unknown[%d](%ld,%ld)", data->insn, + data->param[0], data->param[1]); + } + size = *pbuf - orig_buf; + *buf = *pbuf; + *buf_size -= size; + return size; +} + +static void +dot_cfi_endproc (void) +{ + struct cfi_data *data_ptr; + char *cie_buf, *fde_buf, *pbuf, *where; + unsigned long buf_size, cie_size, fde_size, last_cie_offset; + unsigned long fde_initloc_offset, fde_len_offset; + void *saved_seg, *cfi_seg; + + if (! cfi_info) + { + as_bad (_(".cfi_endproc without corresponding .cfi_startproc")); + return; + } + cfi_info->end_address = frag_now_fix (); + + /* Open .eh_frame section. */ + saved_seg = now_seg; + cfi_seg = subseg_new (".eh_frame", 0); + bfd_set_section_flags (stdoutput, cfi_seg, + SEC_ALLOC | SEC_LOAD | SEC_RELOC | SEC_DATA); + subseg_set (cfi_seg, 0); + + /* Build CIE. */ + cie_buf = xcalloc (1024, 1); + /* Skip space for CIE length. */ + pbuf = cie_buf + 4; + buf_size = 1020; + + if (verbose) + printf ("# CIE *****\n"); + + /* CIE id. */ + output_data (&pbuf, &buf_size, t_long, 0x0); + /* Version. */ + output_data (&pbuf, &buf_size, t_byte, 1); + /* Augmentation. */ + output_data (&pbuf, &buf_size, t_byte, 0); + /* Code alignment. */ + output_data (&pbuf, &buf_size, t_uleb128, current_config.code_align); + /* Data alignment. */ + output_data (&pbuf, &buf_size, t_sleb128, current_config.data_align); + /* Return address column. */ + output_data (&pbuf, &buf_size, t_byte, current_config.ra_column); + + /* Build CFI instructions. */ + data_ptr = cfi_info->data; + while (data_ptr && !cfi_is_advance_insn (data_ptr->insn)) + { + cfi_output_insn (data_ptr, &pbuf, &buf_size); + data_ptr = data_ptr->next; + } + + /* Align the whole data to current_config.eh_align. */ + cie_size = pbuf - cie_buf; + cie_size += current_config.eh_align - cie_size % current_config.eh_align; + + /* CIE length. */ + pbuf = cie_buf; + output_data (&pbuf, &buf_size, t_long, cie_size - 4); + + /* OK, we built the CIE. Let's write it to the file... */ + last_cie_offset = frag_now_fix (); + where = (unsigned char *) frag_more (cie_size); + memcpy (where, cie_buf, cie_size); + + /* Clean up. */ + free (cie_buf); + + /* Build the FDE... */ + fde_buf = xcalloc (1024, 1); + pbuf = fde_buf; + buf_size = 1024; + + if (verbose) + { + printf ("# FDE: start=0x%lx, end=0x%lx, delta=%d\n", + (long) cfi_info->start_address, + (long) cfi_info->end_address, + (int) (cfi_info->end_address - cfi_info->start_address)); + } + + /* FDE length (t_long, 4 bytes) - will be set later. */ + fde_len_offset = pbuf - fde_buf; + pbuf += 4; + buf_size -= 4; + + /* CIE pointer - offset from here. */ + output_data (&pbuf, &buf_size, t_long, cie_size + 4); + + /* FDE initial location - this must be set relocatable! */ + fde_initloc_offset = pbuf - fde_buf; + output_data (&pbuf, &buf_size, current_config.addr_length, + cfi_info->start_address); + + /* FDE address range. */ + output_data (&pbuf, &buf_size, current_config.addr_length, + cfi_info->end_address - cfi_info->start_address); + + while (data_ptr) + { + cfi_output_insn (data_ptr, &pbuf, &buf_size); + data_ptr = data_ptr->next; + } + + fde_size = pbuf - fde_buf; + fde_size += current_config.eh_align - fde_size % current_config.eh_align; + + /* Now we can set FDE length. */ + pbuf = fde_buf + fde_len_offset; + buf_size = 4; + output_data (&pbuf, &buf_size, t_long, fde_size - 4); + + /* Adjust initloc offset. */ + fde_initloc_offset += frag_now_fix (); + + /* Copy FDE to objfile. */ + where = (unsigned char *) frag_more (fde_size); + memcpy (where, fde_buf, fde_size); + + /* Set relocation for initial address. */ + buf_size = current_config.addr_length; + expressionS exp; + memset (&exp, 0, sizeof (exp)); + exp.X_op = O_symbol; + exp.X_add_symbol = symbol_find (cfi_info->labelname); + fix_new_exp (frag_now, fde_initloc_offset, + current_config.addr_length, + &exp, 0, current_config.reloc_type); + + /* Clean up. */ + free (fde_buf); + + free (cfi_info); + cfi_info = NULL; + + /* Restore previous segment. */ + subseg_set (saved_seg, 0); +} + +void +dot_cfi (int arg) +{ + long param; + + switch (arg) + { + case CFI_startproc: + dot_cfi_startproc (); + break; + case CFI_endproc: + dot_cfi_endproc (); + break; + case CFA_def_cfa: + case CFA_def_cfa_register: + case CFA_def_cfa_offset: + case CFA_offset: + case CFI_adjust_cfa_offset: + cfi_make_insn (arg); + break; + case CFI_verbose: + if (cfi_parse_const (¶m) >= 0) + verbose = (int) param; + else + verbose = 1; + break; + default: + as_bad (_("unknown CFI code 0x%x (%s)"), arg, cfi_insn_str (arg)); + break; + } + ignore_rest_of_line (); +} + +void +cfi_set_config (struct cfi_config *cfg) +{ + assert (cfg != NULL); + assert (cfg->addr_length > 0); + + current_config = *cfg; +} + +void +cfi_finish (void) +{ + if (cfi_info) + as_bad (_("open CFI at the end of file; missing .cfi_endproc directive")); +} |