aboutsummaryrefslogtreecommitdiff
path: root/gas
diff options
context:
space:
mode:
Diffstat (limited to 'gas')
-rw-r--r--gas/ChangeLog21
-rw-r--r--gas/NEWS2
-rw-r--r--gas/config/tc-arm.c1429
-rw-r--r--gas/doc/c-arm.texi98
-rw-r--r--gas/testsuite/gas/arm/arm.exp3
-rw-r--r--gas/testsuite/gas/arm/unwind.d33
-rw-r--r--gas/testsuite/gas/arm/unwind.s46
7 files changed, 1457 insertions, 175 deletions
diff --git a/gas/ChangeLog b/gas/ChangeLog
index 5fdaa9b..dbd128a 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,24 @@
+2004-10-05 Paul Brook <paul@codesourcery.com>
+
+ * config/tc-arm.c (unwind): New variable.
+ (vfp_sp_encode_reg): New function.
+ (vfp_sp_reg_required_here): Use it.
+ (vfp_sp_reg_list, vfp_dp_reg_list): Remove.
+ (vfp_parse_reg_list): New function.
+ (s_arm_unwind_fnstart, s_arm_unwind_fnend, s_arm_unwind_cantunwind,
+ s_arm_unwind_personality, s_arm_unwind_personalityindex,
+ s_arm_unwind_handlerdata, s_arm_unwind_save, s_arm_unwind_movsp,
+ s_arm_unwind_pad, s_arm_unwind_setfp, s_arm_unwind_raw): New
+ functions.
+ (md_pseudo_table): Add them.
+ (do_vfp_reg2_from_sp2): Use vfp_parse_reg_list and vfp_sp_encode_reg.
+ (do_vfp_sp2_from_reg2, vfp_sp_ldstm, vfp_dp_ldstm): Ditto.
+ (set_section, add_unwind_adjustsp, flush_pending_unwind,
+ finish_unwind_opcodes, start_unwind_section, create_unwind_entry,
+ require_hashconst, add_unwind_opcode): New functions.
+ * doc/tc-arm.text: Document unwinding opcodes.
+ * NEWS: Mention the new feature.
+
2004-10-04 Eric Christopher <echristo@redhat.com>
* config/tc-mips.c (md_apply_fix3): Remove erroneous assert.
diff --git a/gas/NEWS b/gas/NEWS
index 5a6b42b..fb487c2 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -1,5 +1,7 @@
-*- text -*-
+* Added support for generating unwind tables for ARM ELF targets.
+
* Add a -g command line option to generate debug information in the target's
preferred debug format.
diff --git a/gas/config/tc-arm.c b/gas/config/tc-arm.c
index 9770ccd..e17b037 100644
--- a/gas/config/tc-arm.c
+++ b/gas/config/tc-arm.c
@@ -43,9 +43,46 @@
#include "dwarf2dbg.h"
#endif
-/* XXX Set this to 1 after the next binutils release */
+/* XXX Set this to 1 after the next binutils release. */
#define WARN_DEPRECATED 0
+#ifdef OBJ_ELF
+/* Must be at least the size of the largest unwind opcode (currently two). */
+#define ARM_OPCODE_CHUNK_SIZE 8
+
+/* This structure holds the unwinding state. */
+
+static struct
+{
+ symbolS * proc_start;
+ symbolS * table_entry;
+ symbolS * personality_routine;
+ int personality_index;
+ /* The segment containing the function. */
+ segT saved_seg;
+ subsegT saved_subseg;
+ /* Opcodes generated from this function. */
+ unsigned char * opcodes;
+ int opcode_count;
+ int opcode_alloc;
+ /* The number of bytes pushed to the stack. */
+ offsetT frame_size;
+ /* We don't add stack adjustment opcodes immediately so that we can merge
+ multiple adjustments. We can also omit the final adjustment
+ when using a frame pointer. */
+ offsetT pending_offset;
+ /* These two fields are set by both unwind_movsp and unwind_setfp. They
+ hold the reg+offset to use when restoring sp from a frame pointer. */
+ offsetT fp_offset;
+ int fp_reg;
+ /* Nonzero if an unwind_setfp directive has been seen. */
+ unsigned fp_used:1;
+ /* Nonzero if the last opcode restores sp from fp_reg. */
+ unsigned sp_restored:1;
+} unwind;
+
+#endif /* OBJ_ELF */
+
enum arm_float_abi
{
ARM_FLOAT_ABI_HARD,
@@ -7270,32 +7307,40 @@ do_fpa_to_reg (char * str)
end_of_line (str);
}
+/* Encode a VFP SP register number. */
+
+static void
+vfp_sp_encode_reg (int reg, enum vfp_sp_reg_pos pos)
+{
+ switch (pos)
+ {
+ case VFP_REG_Sd:
+ inst.instruction |= ((reg >> 1) << 12) | ((reg & 1) << 22);
+ break;
+
+ case VFP_REG_Sn:
+ inst.instruction |= ((reg >> 1) << 16) | ((reg & 1) << 7);
+ break;
+
+ case VFP_REG_Sm:
+ inst.instruction |= ((reg >> 1) << 0) | ((reg & 1) << 5);
+ break;
+
+ default:
+ abort ();
+ }
+}
+
static int
vfp_sp_reg_required_here (char ** str,
enum vfp_sp_reg_pos pos)
{
int reg;
- char *start = *str;
+ char * start = *str;
if ((reg = arm_reg_parse (str, all_reg_maps[REG_TYPE_SN].htab)) != FAIL)
{
- switch (pos)
- {
- case VFP_REG_Sd:
- inst.instruction |= ((reg >> 1) << 12) | ((reg & 1) << 22);
- break;
-
- case VFP_REG_Sn:
- inst.instruction |= ((reg >> 1) << 16) | ((reg & 1) << 7);
- break;
-
- case VFP_REG_Sm:
- inst.instruction |= ((reg >> 1) << 0) | ((reg & 1) << 5);
- break;
-
- default:
- abort ();
- }
+ vfp_sp_encode_reg (reg, pos);
return reg;
}
@@ -7445,21 +7490,21 @@ do_vfp_reg_from_sp (char * str)
end_of_line (str);
}
-/* Parse and encode a VFP SP register list, storing the initial
- register in position POS and returning the range as the result. If
- the string is invalid return FAIL (an invalid range). */
+/* Parse a VFP register list. If the string is invalid return FAIL.
+ Otherwise return the number of registers, and set PBASE to the first
+ register. Double precision registers are matched if DP is nonzero. */
-static long
-vfp_sp_reg_list (char ** str, enum vfp_sp_reg_pos pos)
+static int
+vfp_parse_reg_list (char **str, int *pbase, int dp)
{
- long range = 0;
- int base_reg = 0;
+ int base_reg;
int new_base;
- long base_bits = 0;
+ int regtype;
+ int max_regs;
int count = 0;
- long tempinst;
- unsigned long mask = 0;
int warned = 0;
+ unsigned long mask = 0;
+ int i;
if (**str != '{')
return FAIL;
@@ -7467,21 +7512,31 @@ vfp_sp_reg_list (char ** str, enum vfp_sp_reg_pos pos)
(*str)++;
skip_whitespace (*str);
- tempinst = inst.instruction;
-
- do
+ if (dp)
{
- inst.instruction = 0;
+ regtype = REG_TYPE_DN;
+ max_regs = 16;
+ }
+ else
+ {
+ regtype = REG_TYPE_SN;
+ max_regs = 32;
+ }
- if ((new_base = vfp_sp_reg_required_here (str, pos)) == FAIL)
- return FAIL;
+ base_reg = max_regs;
- if (count == 0 || base_reg > new_base)
+ do
+ {
+ new_base = arm_reg_parse (str, all_reg_maps[regtype].htab);
+ if (new_base == FAIL)
{
- base_reg = new_base;
- base_bits = inst.instruction;
+ inst.error = _(all_reg_maps[regtype].expected);
+ return FAIL;
}
+ if (new_base < base_reg)
+ base_reg = new_base;
+
if (mask & (1 << new_base))
{
inst.error = _("invalid register list");
@@ -7506,10 +7561,10 @@ vfp_sp_reg_list (char ** str, enum vfp_sp_reg_pos pos)
(*str)++;
if ((high_range
- = arm_reg_parse (str, all_reg_maps[REG_TYPE_SN].htab))
+ = arm_reg_parse (str, all_reg_maps[regtype].htab))
== FAIL)
{
- inst.error = _(all_reg_maps[REG_TYPE_SN].expected);
+ inst.error = _(all_reg_maps[regtype].expected);
return FAIL;
}
@@ -7534,37 +7589,33 @@ vfp_sp_reg_list (char ** str, enum vfp_sp_reg_pos pos)
}
while (skip_past_comma (str) != FAIL);
- if (**str != '}')
- {
- inst.error = _("invalid register list");
- return FAIL;
- }
-
(*str)++;
- range = count;
-
/* Sanity check -- should have raised a parse error above. */
- if (count == 0 || count > 32)
+ if (count == 0 || count > max_regs)
abort ();
+ *pbase = base_reg;
+
/* Final test -- the registers must be consecutive. */
- while (count--)
+ mask >>= base_reg;
+ for (i = 0; i < count; i++)
{
- if ((mask & (1 << base_reg++)) == 0)
+ if ((mask & (1u << i)) == 0)
{
inst.error = _("non-contiguous register range");
return FAIL;
}
}
- inst.instruction = tempinst | base_bits;
- return range;
+ return count;
}
static void
do_vfp_reg2_from_sp2 (char * str)
{
+ int reg;
+
skip_whitespace (str);
if (reg_required_here (&str, 12) == FAIL
@@ -7578,11 +7629,12 @@ do_vfp_reg2_from_sp2 (char * str)
}
/* We require exactly two consecutive SP registers. */
- if (vfp_sp_reg_list (&str, VFP_REG_Sm) != 2)
+ if (vfp_parse_reg_list (&str, &reg, 0) != 2)
{
if (! inst.error)
inst.error = _("only two consecutive VFP SP registers allowed here");
}
+ vfp_sp_encode_reg (reg, VFP_REG_Sm);
end_of_line (str);
}
@@ -7609,14 +7661,17 @@ do_vfp_sp_from_reg (char * str)
static void
do_vfp_sp2_from_reg2 (char * str)
{
+ int reg;
+
skip_whitespace (str);
/* We require exactly two consecutive SP registers. */
- if (vfp_sp_reg_list (&str, VFP_REG_Sm) != 2)
+ if (vfp_parse_reg_list (&str, &reg, 0) != 2)
{
if (! inst.error)
inst.error = _("only two consecutive VFP SP registers allowed here");
}
+ vfp_sp_encode_reg (reg, VFP_REG_Sm);
if (skip_past_comma (&str) == FAIL
|| reg_required_here (&str, 12) == FAIL
@@ -7851,122 +7906,12 @@ do_vfp_dp_ldst (char * str)
end_of_line (str);
}
-static long
-vfp_dp_reg_list (char ** str)
-{
- long range = 0;
- int base_reg = 0;
- int new_base;
- int count = 0;
- long tempinst;
- unsigned long mask = 0;
- int warned = 0;
-
- if (**str != '{')
- return FAIL;
-
- (*str)++;
- skip_whitespace (*str);
-
- tempinst = inst.instruction;
-
- do
- {
- inst.instruction = 0;
-
- if ((new_base = vfp_dp_reg_required_here (str, VFP_REG_Dd)) == FAIL)
- return FAIL;
-
- if (count == 0 || base_reg > new_base)
- {
- base_reg = new_base;
- range = inst.instruction;
- }
-
- if (mask & (1 << new_base))
- {
- inst.error = _("invalid register list");
- return FAIL;
- }
-
- if ((mask >> new_base) != 0 && ! warned)
- {
- as_tsktsk (_("register list not in ascending order"));
- warned = 1;
- }
-
- mask |= 1 << new_base;
- count++;
-
- skip_whitespace (*str);
-
- if (**str == '-') /* We have the start of a range expression */
- {
- int high_range;
-
- (*str)++;
-
- if ((high_range
- = arm_reg_parse (str, all_reg_maps[REG_TYPE_DN].htab))
- == FAIL)
- {
- inst.error = _(all_reg_maps[REG_TYPE_DN].expected);
- return FAIL;
- }
-
- if (high_range <= new_base)
- {
- inst.error = _("register range not in ascending order");
- return FAIL;
- }
-
- for (new_base++; new_base <= high_range; new_base++)
- {
- if (mask & (1 << new_base))
- {
- inst.error = _("invalid register list");
- return FAIL;
- }
-
- mask |= 1 << new_base;
- count++;
- }
- }
- }
- while (skip_past_comma (str) != FAIL);
-
- if (**str != '}')
- {
- inst.error = _("invalid register list");
- return FAIL;
- }
-
- (*str)++;
-
- range |= 2 * count;
-
- /* Sanity check -- should have raised a parse error above. */
- if (count == 0 || count > 16)
- abort ();
-
- /* Final test -- the registers must be consecutive. */
- while (count--)
- {
- if ((mask & (1 << base_reg++)) == 0)
- {
- inst.error = _("non-contiguous register range");
- return FAIL;
- }
- }
-
- inst.instruction = tempinst;
- return range;
-}
static void
vfp_sp_ldstm (char * str, enum vfp_ldstm_type ldstm_type)
{
- long range;
+ int count;
+ int reg;
skip_whitespace (str);
@@ -7987,21 +7932,23 @@ vfp_sp_ldstm (char * str, enum vfp_ldstm_type ldstm_type)
}
if (skip_past_comma (&str) == FAIL
- || (range = vfp_sp_reg_list (&str, VFP_REG_Sd)) == FAIL)
+ || (count = vfp_parse_reg_list (&str, &reg, 0)) == FAIL)
{
if (!inst.error)
inst.error = BAD_ARGS;
return;
}
+ vfp_sp_encode_reg (reg, VFP_REG_Sd);
- inst.instruction |= range;
+ inst.instruction |= count;
end_of_line (str);
}
static void
vfp_dp_ldstm (char * str, enum vfp_ldstm_type ldstm_type)
{
- long range;
+ int count;
+ int reg;
skip_whitespace (str);
@@ -8022,17 +7969,18 @@ vfp_dp_ldstm (char * str, enum vfp_ldstm_type ldstm_type)
}
if (skip_past_comma (&str) == FAIL
- || (range = vfp_dp_reg_list (&str)) == FAIL)
+ || (count = vfp_parse_reg_list (&str, &reg, 1)) == FAIL)
{
if (!inst.error)
inst.error = BAD_ARGS;
return;
}
+ count <<= 1;
if (ldstm_type == VFP_LDSTMIAX || ldstm_type == VFP_LDSTMDBX)
- range += 1;
+ count += 1;
- inst.instruction |= range;
+ inst.instruction |= (reg << 12) | count;
end_of_line (str);
}
@@ -10071,7 +10019,7 @@ static const struct asm_opcode insns[] =
{ "wfe", 0xe320f002, 3, ARM_EXT_V6K, do_empty},
{ "wfi", 0xe320f003, 3, ARM_EXT_V6K, do_empty},
{ "yield", 0xe320f001, 5, ARM_EXT_V6K, do_empty},
-
+
/* ARM V6Z. */
{ "smi", 0xe1600070, 3, ARM_EXT_V6Z, do_smi},
@@ -13692,6 +13640,1128 @@ s_arm_rel31 (int ignored ATTRIBUTE_UNUSED)
demand_empty_rest_of_line ();
}
+
+/* Code to deal with unwinding tables. */
+
+static void add_unwind_adjustsp (offsetT);
+
+/* Switch to section NAME and create section if necessary. It's
+ rather ugly that we have to manipulate input_line_pointer but I
+ don't see any other way to accomplish the same thing without
+ changing obj-elf.c (which may be the Right Thing, in the end).
+ Copied from tc-ia64.c. */
+
+static void
+set_section (char *name)
+{
+ char *saved_input_line_pointer;
+
+ saved_input_line_pointer = input_line_pointer;
+ input_line_pointer = name;
+ obj_elf_section (0);
+ input_line_pointer = saved_input_line_pointer;
+}
+
+/* Cenerate and deferred unwind frame offset. */
+
+static void
+flush_pending_unwind (void)
+{
+ offsetT offset;
+
+ offset = unwind.pending_offset;
+ unwind.pending_offset = 0;
+ if (offset != 0)
+ add_unwind_adjustsp (offset);
+}
+
+/* Add an opcode to this list for this function. Two-byte opcodes should
+ be passed as op[0] << 8 | op[1]. The list of opcodes is built in reverse
+ order. */
+
+static void
+add_unwind_opcode (valueT op, int length)
+{
+ /* Add any deferred stack adjustment. */
+ if (unwind.pending_offset)
+ flush_pending_unwind ();
+
+ unwind.sp_restored = 0;
+
+ if (unwind.opcode_count + length > unwind.opcode_alloc)
+ {
+ unwind.opcode_alloc += ARM_OPCODE_CHUNK_SIZE;
+ if (unwind.opcodes)
+ unwind.opcodes = xrealloc (unwind.opcodes,
+ unwind.opcode_alloc);
+ else
+ unwind.opcodes = xmalloc (unwind.opcode_alloc);
+ }
+ while (length > 0)
+ {
+ length--;
+ unwind.opcodes[unwind.opcode_count] = op & 0xff;
+ op >>= 8;
+ unwind.opcode_count++;
+ }
+}
+
+/* Add unwind opcodes to adjust the stack pointer. */
+
+static void
+add_unwind_adjustsp (offsetT offset)
+{
+ valueT op;
+
+ if (offset > 0x200)
+ {
+ /* We need at most 5 bytes to hold a 32-bit value in a uleb128. */
+ char bytes[5];
+ int n;
+ valueT o;
+
+ /* Long form: 0xb2, uleb128. */
+ /* This might not fit in a word so add the individual bytes,
+ remembering the list is built in reverse order. */
+ o = (valueT) ((offset - 0x204) >> 2);
+ if (o == 0)
+ add_unwind_opcode (0, 1);
+
+ /* Calculate the uleb128 encoding of the offset. */
+ n = 0;
+ while (o)
+ {
+ bytes[n] = o & 0x7f;
+ o >>= 7;
+ if (o)
+ bytes[n] |= 0x80;
+ n++;
+ }
+ /* Add the insn. */
+ for (; n; n--)
+ add_unwind_opcode (bytes[n - 1], 1);
+ add_unwind_opcode (0xb2, 1);
+ }
+ else if (offset > 0x100)
+ {
+ /* Two short opcodes. */
+ add_unwind_opcode (0x3f, 1);
+ op = (offset - 0x104) >> 2;
+ add_unwind_opcode (op, 1);
+ }
+ else if (offset > 0)
+ {
+ /* Short opcode. */
+ op = (offset - 4) >> 2;
+ add_unwind_opcode (op, 1);
+ }
+ else if (offset < 0)
+ {
+ offset = -offset;
+ while (offset > 0x100)
+ {
+ add_unwind_opcode (0x7f, 1);
+ offset -= 0x100;
+ }
+ op = ((offset - 4) >> 2) | 0x40;
+ add_unwind_opcode (op, 1);
+ }
+}
+
+/* Finish the list of unwind opcodes for this function. */
+static void
+finish_unwind_opcodes (void)
+{
+ valueT op;
+
+ if (unwind.fp_used)
+ {
+ /* Adjust sp as neccessary. */
+ unwind.pending_offset += unwind.fp_offset - unwind.frame_size;
+ flush_pending_unwind ();
+
+ /* After restoring sp from the frame pointer. */
+ op = 0x90 | unwind.fp_reg;
+ add_unwind_opcode (op, 1);
+ }
+ else
+ flush_pending_unwind ();
+}
+
+
+/* Start an exception table entry. If idx is nonzero this is an index table
+ entry. */
+
+static void
+start_unwind_section (const segT text_seg, int idx)
+{
+ const char * text_name;
+ const char * prefix;
+ const char * prefix_once;
+ size_t prefix_len;
+ size_t text_len;
+ char * sec_name;
+ size_t sec_name_len;
+
+ if (idx)
+ {
+ prefix = ELF_STRING_ARM_unwind;
+ prefix_once = ELF_STRING_ARM_unwind_once;
+ }
+ else
+ {
+ prefix = ELF_STRING_ARM_unwind_info;
+ prefix_once = ELF_STRING_ARM_unwind_info_once;
+ }
+
+ text_name = segment_name (text_seg);
+ if (streq (text_name, ".text"))
+ text_name = "";
+
+ if (strncmp (text_name, ".gnu.linkonce.t.",
+ strlen (".gnu.linkonce.t.")) == 0)
+ {
+ prefix = prefix_once;
+ text_name += strlen (".gnu.linkonce.t.");
+ }
+
+ prefix_len = strlen (prefix);
+ text_len = strlen (text_name);
+ sec_name_len = prefix_len + text_len;
+ sec_name = alloca (sec_name_len + 1);
+ memcpy (sec_name, prefix, prefix_len);
+ memcpy (sec_name + prefix_len, text_name, text_len);
+ sec_name[prefix_len + text_len] = '\0';
+
+ /* Handle COMDAT group. */
+ if (prefix != prefix_once && (text_seg->flags & SEC_LINK_ONCE) != 0)
+ {
+ char *section;
+ size_t len, group_name_len;
+ const char *group_name = elf_group_name (text_seg);
+
+ if (group_name == NULL)
+ {
+ as_bad ("Group section `%s' has no group signature",
+ segment_name (text_seg));
+ ignore_rest_of_line ();
+ return;
+ }
+ /* We have to construct a fake section directive. */
+ group_name_len = strlen (group_name);
+ if (idx)
+ prefix_len = 13;
+ else
+ prefix_len = 16;
+
+ len = (sec_name_len
+ + prefix_len /* ,"aG",%sectiontype, */
+ + group_name_len /* ,group_name */
+ + 7); /* ,comdat */
+
+ section = alloca (len + 1);
+ memcpy (section, sec_name, sec_name_len);
+ if (idx)
+ memcpy (section + sec_name_len, ",\"aG\",%exidx,", 13);
+ else
+ memcpy (section + sec_name_len, ",\"aG\",%progbits,", 16);
+ memcpy (section + sec_name_len + prefix_len, group_name, group_name_len);
+ memcpy (section + len - 7, ",comdat", 7);
+ section [len] = '\0';
+ set_section (section);
+ }
+ else
+ {
+ set_section (sec_name);
+ bfd_set_section_flags (stdoutput, now_seg,
+ SEC_LOAD | SEC_ALLOC | SEC_READONLY);
+ }
+
+ /* Set the setion link for index tables. */
+ if (idx)
+ elf_linked_to_section (now_seg) = text_seg;
+}
+
+
+/* Start an unwind table entry. HAVE_DATA is nonzero if we have additional
+ personality routine data. Returns zero, or the index table value for
+ and inline entry. */
+
+static valueT
+create_unwind_entry (int have_data)
+{
+ int size;
+ addressT where;
+ unsigned char *ptr;
+ /* The current word of data. */
+ valueT data;
+ /* The number of bytes left in this word. */
+ int n;
+
+ finish_unwind_opcodes ();
+
+ /* Remember the current text section. */
+ unwind.saved_seg = now_seg;
+ unwind.saved_subseg = now_subseg;
+
+ start_unwind_section (now_seg, 0);
+
+ if (unwind.personality_routine == NULL)
+ {
+ if (unwind.personality_index == -2)
+ {
+ if (have_data)
+ as_bad (_("handerdata in cantunwind frame"));
+ return 1; /* EXIDX_CANTUNWIND. */
+ }
+
+ /* Use a default personality routine if none is specified. */
+ if (unwind.personality_index == -1)
+ {
+ if (unwind.opcode_count > 3)
+ unwind.personality_index = 1;
+ else
+ unwind.personality_index = 0;
+ }
+
+ /* Space for the personality routine entry. */
+ if (unwind.personality_index == 0)
+ {
+ if (unwind.opcode_count > 3)
+ as_bad (_("too many unwind opcodes for personality routine 0"));
+
+ if (!have_data)
+ {
+ /* All the data is inline in the index table. */
+ data = 0x80;
+ n = 3;
+ while (unwind.opcode_count > 0)
+ {
+ unwind.opcode_count--;
+ data = (data << 8) | unwind.opcodes[unwind.opcode_count];
+ n--;
+ }
+
+ /* Pad with "finish" opcodes. */
+ while (n--)
+ data = (data << 8) | 0xb0;
+
+ return data;
+ }
+ size = 0;
+ }
+ else
+ /* We get two opcodes "free" in the first word. */
+ size = unwind.opcode_count - 2;
+ }
+ else
+ /* An extra byte is required for the opcode count. */
+ size = unwind.opcode_count + 1;
+
+ size = (size + 3) >> 2;
+ if (size > 0xff)
+ as_bad (_("too many unwind opcodes"));
+
+ frag_align (2, 0, 0);
+ record_alignment (now_seg, 2);
+ unwind.table_entry = expr_build_dot ();
+
+ /* Allocate the table entry. */
+ ptr = frag_more ((size << 2) + 4);
+ where = frag_now_fix () - ((size << 2) + 4);
+
+ switch (unwind.personality_index)
+ {
+ case -1:
+ /* ??? Should this be a PLT generating relocation? */
+ /* Custom personality routine. */
+ fix_new (frag_now, where, 4, unwind.personality_routine, 0, 1,
+ BFD_RELOC_ARM_PREL31);
+ where += 4;
+ ptr += 4;
+
+ /* Set the first byte to the number of additional words. */
+ data = size - 1;
+ n = 3;
+ break;
+
+ /* ABI defined personality routines. */
+ /* TODO: Emit R_ARM_NONE to the personality routine. */
+ case 0:
+ /* Three opcodes bytes are packed into the first word. */
+ data = 0x80;
+ n = 3;
+ break;
+
+ case 1:
+ case 2:
+ /* The size and first two opcode bytes go in the first word. */
+ data = ((0x80 + unwind.personality_index) << 8) | size;
+ n = 2;
+ break;
+
+ default:
+ /* Should never happen. */
+ abort ();
+ }
+
+ /* Pack the opcodes into words (MSB first), reversing the list at the same
+ time. */
+ while (unwind.opcode_count > 0)
+ {
+ if (n == 0)
+ {
+ md_number_to_chars (ptr, data, 4);
+ ptr += 4;
+ n = 4;
+ data = 0;
+ }
+ unwind.opcode_count--;
+ n--;
+ data = (data << 8) | unwind.opcodes[unwind.opcode_count];
+ }
+
+ /* Finish off the last word. */
+ if (n < 4)
+ {
+ /* Pad with "finish" opcodes. */
+ while (n--)
+ data = (data << 8) | 0xb0;
+
+ md_number_to_chars (ptr, data, 4);
+ }
+
+ if (!have_data)
+ {
+ /* Add an empty descriptor if there is no user-specified data. */
+ ptr = frag_more (4);
+ md_number_to_chars (ptr, 0, 4);
+ }
+
+ return 0;
+}
+
+
+/* Parse an unwind_fnstart directive. Simply records the current location. */
+
+static void
+s_arm_unwind_fnstart (int ignored ATTRIBUTE_UNUSED)
+{
+ demand_empty_rest_of_line ();
+ /* Mark the start of the function. */
+ unwind.proc_start = expr_build_dot ();
+
+ /* Reset the rest of the unwind info. */
+ unwind.opcode_count = 0;
+ unwind.table_entry = NULL;
+ unwind.personality_routine = NULL;
+ unwind.personality_index = -1;
+ unwind.frame_size = 0;
+ unwind.fp_offset = 0;
+ unwind.fp_reg = 13;
+ unwind.fp_used = 0;
+ unwind.sp_restored = 0;
+}
+
+
+/* Parse a handlerdata directive. Creates the exception handling table entry
+ for the function. */
+
+static void
+s_arm_unwind_handlerdata (int ignored ATTRIBUTE_UNUSED)
+{
+ demand_empty_rest_of_line ();
+ if (unwind.table_entry)
+ as_bad (_("dupicate .handlerdata directive"));
+
+ create_unwind_entry (1);
+}
+
+/* Parse an unwind_fnend directive. Generates the index table entry. */
+
+static void
+s_arm_unwind_fnend (int ignored ATTRIBUTE_UNUSED)
+{
+ long where;
+ unsigned char *ptr;
+ valueT val;
+
+ demand_empty_rest_of_line ();
+
+ /* Add eh table entry. */
+ if (unwind.table_entry == NULL)
+ val = create_unwind_entry (0);
+ else
+ val = 0;
+
+ /* Add index table entry. This is two words. */
+ start_unwind_section (unwind.saved_seg, 1);
+ frag_align (2, 0, 0);
+ record_alignment (now_seg, 2);
+
+ ptr = frag_more (8);
+ where = frag_now_fix () - 8;
+
+ /* Self relative offset of the function start. */
+ fix_new (frag_now, where, 4, unwind.proc_start, 0, 1,
+ BFD_RELOC_32);
+
+ if (val)
+ /* Inline exception table entry. */
+ md_number_to_chars (ptr + 4, val, 4);
+ else
+ /* Self relative offset of the table entry. */
+ fix_new (frag_now, where + 4, 4, unwind.table_entry, 0, 1,
+ BFD_RELOC_ARM_PREL31);
+
+ /* Restore the original section. */
+ subseg_set (unwind.saved_seg, unwind.saved_subseg);
+}
+
+
+/* Parse an unwind_cantunwind directive. */
+
+static void
+s_arm_unwind_cantunwind (int ignored ATTRIBUTE_UNUSED)
+{
+ demand_empty_rest_of_line ();
+ if (unwind.personality_routine || unwind.personality_index != -1)
+ as_bad (_("personality routine specified for cantunwind frame"));
+
+ unwind.personality_index = -2;
+}
+
+
+/* Parse a personalityindex directive. */
+
+static void
+s_arm_unwind_personalityindex (int ignored ATTRIBUTE_UNUSED)
+{
+ expressionS exp;
+
+ if (unwind.personality_routine || unwind.personality_index != -1)
+ as_bad (_("duplicate .personalityindex directive"));
+
+ SKIP_WHITESPACE ();
+
+ expression (&exp);
+
+ if (exp.X_op != O_constant
+ || exp.X_add_number < 0 || exp.X_add_number > 15)
+ {
+ as_bad (_("bad personality routine number"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ unwind.personality_index = exp.X_add_number;
+
+ demand_empty_rest_of_line ();
+}
+
+
+/* Parse a personality directive. */
+
+static void
+s_arm_unwind_personality (int ignored ATTRIBUTE_UNUSED)
+{
+ char *name, *p, c;
+
+ if (unwind.personality_routine || unwind.personality_index != -1)
+ as_bad (_("duplicate .personality directive"));
+
+ SKIP_WHITESPACE ();
+ name = input_line_pointer;
+ c = get_symbol_end ();
+ p = input_line_pointer;
+ unwind.personality_routine = symbol_find_or_make (name);
+ *p = c;
+ SKIP_WHITESPACE ();
+ demand_empty_rest_of_line ();
+}
+
+
+/* Parse a directive saving core registers. */
+
+static void
+s_arm_unwind_save_core (void)
+{
+ valueT op;
+ long range;
+ int n;
+
+ SKIP_WHITESPACE ();
+ range = reg_list (&input_line_pointer);
+ if (range == FAIL)
+ {
+ as_bad (_("expected register list"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ demand_empty_rest_of_line ();
+
+ /* Turn .unwind_movsp ip followed by .unwind_save {..., ip, ...}
+ into .unwind_save {..., sp...}. We aren't bothered about the value of
+ ip because it is clobbered by calls. */
+ if (unwind.sp_restored && unwind.fp_reg == 12
+ && (range & 0x3000) == 0x1000)
+ {
+ unwind.opcode_count--;
+ unwind.sp_restored = 0;
+ range = (range | 0x2000) & ~0x1000;
+ unwind.pending_offset = 0;
+ }
+
+ /* See if we can use the short opcodes. These pop a block of upto 8
+ registers starting with r4, plus maybe r14. */
+ for (n = 0; n < 8; n++)
+ {
+ /* Break at the first non-saved register. */
+ if ((range & (1 << (n + 4))) == 0)
+ break;
+ }
+ /* See if there are any other bits set. */
+ if (n == 0 || (range & (0xfff0 << n) & 0xbff0) != 0)
+ {
+ /* Use the long form. */
+ op = 0x8000 | ((range >> 4) & 0xfff);
+ add_unwind_opcode (op, 2);
+ }
+ else
+ {
+ /* Use the short form. */
+ if (range & 0x4000)
+ op = 0xa8; /* Pop r14. */
+ else
+ op = 0xa0; /* Do not pop r14. */
+ op |= (n - 1);
+ add_unwind_opcode (op, 1);
+ }
+
+ /* Pop r0-r3. */
+ if (range & 0xf)
+ {
+ op = 0xb100 | (range & 0xf);
+ add_unwind_opcode (op, 2);
+ }
+
+ /* Record the number of bytes pushed. */
+ for (n = 0; n < 16; n++)
+ {
+ if (range & (1 << n))
+ unwind.frame_size += 4;
+ }
+}
+
+
+/* Parse a directive saving FPA registers. */
+
+static void
+s_arm_unwind_save_fpa (int reg)
+{
+ expressionS exp;
+ int num_regs;
+ valueT op;
+
+ /* Get Number of registers to transfer. */
+ if (skip_past_comma (&input_line_pointer) != FAIL)
+ expression (&exp);
+ else
+ exp.X_op = O_illegal;
+
+ if (exp.X_op != O_constant)
+ {
+ as_bad (_("expected , <constant>"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ num_regs = exp.X_add_number;
+
+ if (num_regs < 1 || num_regs > 4)
+ {
+ as_bad (_("number of registers must be in the range [1:4]"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ demand_empty_rest_of_line ();
+
+ if (reg == 4)
+ {
+ /* Short form. */
+ op = 0xb4 | (num_regs - 1);
+ add_unwind_opcode (op, 1);
+ }
+ else
+ {
+ /* Long form. */
+ op = 0xc800 | (reg << 4) | (num_regs - 1);
+ add_unwind_opcode (op, 2);
+ }
+ unwind.frame_size += num_regs * 12;
+}
+
+
+/* Parse a directive saving VFP registers. */
+
+static void
+s_arm_unwind_save_vfp (void)
+{
+ int count;
+ int reg;
+ valueT op;
+
+ count = vfp_parse_reg_list (&input_line_pointer, &reg, 1);
+ if (count == FAIL)
+ {
+ as_bad (_("expected register list"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ demand_empty_rest_of_line ();
+
+ if (reg == 8)
+ {
+ /* Short form. */
+ op = 0xb8 | (count - 1);
+ add_unwind_opcode (op, 1);
+ }
+ else
+ {
+ /* Long form. */
+ op = 0xb300 | (reg << 4) | (count - 1);
+ add_unwind_opcode (op, 2);
+ }
+ unwind.frame_size += count * 8 + 4;
+}
+
+
+/* Parse a directive saving iWMMXt registers. */
+
+static void
+s_arm_unwind_save_wmmx (void)
+{
+ int reg;
+ int hi_reg;
+ int i;
+ unsigned wcg_mask;
+ unsigned wr_mask;
+ valueT op;
+
+ if (*input_line_pointer == '{')
+ input_line_pointer++;
+
+ wcg_mask = 0;
+ wr_mask = 0;
+ do
+ {
+ reg = arm_reg_parse (&input_line_pointer,
+ all_reg_maps[REG_TYPE_IWMMXT].htab);
+
+ if (wr_register (reg))
+ {
+ i = reg & ~WR_PREFIX;
+ if (wr_mask >> i)
+ as_tsktsk (_("register list not in ascending order"));
+ wr_mask |= 1 << i;
+ }
+ else if (wcg_register (reg))
+ {
+ i = (reg & ~WC_PREFIX) - 8;
+ if (wcg_mask >> i)
+ as_tsktsk (_("register list not in ascending order"));
+ wcg_mask |= 1 << i;
+ }
+ else
+ {
+ as_bad (_("expected wr or wcgr"));
+ goto error;
+ }
+
+ SKIP_WHITESPACE ();
+ if (*input_line_pointer == '-')
+ {
+ hi_reg = arm_reg_parse (&input_line_pointer,
+ all_reg_maps[REG_TYPE_IWMMXT].htab);
+ if (wr_register (reg) && wr_register (hi_reg))
+ {
+ for (; reg < hi_reg; reg++)
+ wr_mask |= 1 << (reg & ~WR_PREFIX);
+ }
+ else if (wcg_register (reg) && wcg_register (hi_reg))
+ {
+ for (; reg < hi_reg; reg++)
+ wcg_mask |= 1 << ((reg & ~WC_PREFIX) - 8);
+ }
+ else
+ {
+ as_bad (_("bad register range"));
+ goto error;
+ }
+ }
+ }
+ while (skip_past_comma (&input_line_pointer) != FAIL);
+
+ SKIP_WHITESPACE ();
+ if (*input_line_pointer == '}')
+ input_line_pointer++;
+
+ demand_empty_rest_of_line ();
+
+ if (wr_mask && wcg_mask)
+ {
+ as_bad (_("inconsistent register types"));
+ goto error;
+ }
+
+ /* Generate any deferred opcodes becuuse we're going to be looking at
+ the list. */
+ flush_pending_unwind ();
+
+ if (wcg_mask)
+ {
+ for (i = 0; i < 16; i++)
+ {
+ if (wcg_mask & (1 << i))
+ unwind.frame_size += 4;
+ }
+ op = 0xc700 | wcg_mask;
+ add_unwind_opcode (op, 2);
+ }
+ else
+ {
+ for (i = 0; i < 16; i++)
+ {
+ if (wr_mask & (1 << i))
+ unwind.frame_size += 8;
+ }
+ /* Attempt to combine with a previous opcode. We do this because gcc
+ likes to output separate unwind directives for a single block of
+ registers. */
+ if (unwind.opcode_count > 0)
+ {
+ i = unwind.opcodes[unwind.opcode_count - 1];
+ if ((i & 0xf8) == 0xc0)
+ {
+ i &= 7;
+ /* Only merge if the blocks are contiguous. */
+ if (i < 6)
+ {
+ if ((wr_mask & 0xfe00) == (1 << 9))
+ {
+ wr_mask |= ((1 << (i + 11)) - 1) & 0xfc00;
+ unwind.opcode_count--;
+ }
+ }
+ else if (i == 6 && unwind.opcode_count >= 2)
+ {
+ i = unwind.opcodes[unwind.opcode_count - 2];
+ reg = i >> 4;
+ i &= 0xf;
+
+ op = 0xffff << (reg - 1);
+ if (reg > 0
+ || ((wr_mask & op) == (1u << (reg - 1))))
+ {
+ op = (1 << (reg + i + 1)) - 1;
+ op &= ~((1 << reg) - 1);
+ wr_mask |= op;
+ unwind.opcode_count -= 2;
+ }
+ }
+ }
+ }
+
+ hi_reg = 15;
+ /* We want to generate opcodes in the order the registers have been
+ saved, ie. descending order. */
+ for (reg = 15; reg >= -1; reg--)
+ {
+ /* Save registers in blocks. */
+ if (reg < 0
+ || !(wr_mask & (1 << reg)))
+ {
+ /* We found an unsaved reg. Generate opcodes to save the
+ preceeding block. */
+ if (reg != hi_reg)
+ {
+ if (reg == 9)
+ {
+ /* Short form. */
+ op = 0xc0 | (hi_reg - 10);
+ add_unwind_opcode (op, 1);
+ }
+ else
+ {
+ /* Long form. */
+ op = 0xc600 | ((reg + 1) << 4) | ((hi_reg - reg) - 1);
+ add_unwind_opcode (op, 2);
+ }
+ }
+ hi_reg = reg - 1;
+ }
+ }
+ }
+ return;
+error:
+ ignore_rest_of_line ();
+}
+
+
+/* Parse an unwind_save directive. */
+
+static void
+s_arm_unwind_save (int ignored ATTRIBUTE_UNUSED)
+{
+ char *saved_ptr;
+ int reg;
+
+ /* Figure out what sort of save we have. */
+ SKIP_WHITESPACE ();
+ saved_ptr = input_line_pointer;
+
+ reg = arm_reg_parse (&input_line_pointer, all_reg_maps[REG_TYPE_FN].htab);
+ if (reg != FAIL)
+ {
+ s_arm_unwind_save_fpa (reg);
+ return;
+ }
+
+ if (*input_line_pointer == '{')
+ input_line_pointer++;
+
+ SKIP_WHITESPACE ();
+
+ reg = arm_reg_parse (&input_line_pointer, all_reg_maps[REG_TYPE_RN].htab);
+ if (reg != FAIL)
+ {
+ input_line_pointer = saved_ptr;
+ s_arm_unwind_save_core ();
+ return;
+ }
+
+ reg = arm_reg_parse (&input_line_pointer, all_reg_maps[REG_TYPE_DN].htab);
+ if (reg != FAIL)
+ {
+ input_line_pointer = saved_ptr;
+ s_arm_unwind_save_vfp ();
+ return;
+ }
+
+ reg = arm_reg_parse (&input_line_pointer,
+ all_reg_maps[REG_TYPE_IWMMXT].htab);
+ if (reg != FAIL)
+ {
+ input_line_pointer = saved_ptr;
+ s_arm_unwind_save_wmmx ();
+ return;
+ }
+
+ /* TODO: Maverick registers. */
+ as_bad (_("unrecognised register"));
+}
+
+
+/* Parse an unwind_movsp directive. */
+
+static void
+s_arm_unwind_movsp (int ignored ATTRIBUTE_UNUSED)
+{
+ int reg;
+ valueT op;
+
+ SKIP_WHITESPACE ();
+ reg = reg_required_here (&input_line_pointer, -1);
+ if (reg == FAIL)
+ {
+ as_bad (_("ARM register expected"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ if (reg == 13 || reg == 15)
+ {
+ as_bad (_("r%d not permitted in .unwind_movsp directive"), reg);
+ ignore_rest_of_line ();
+ return;
+ }
+
+ if (unwind.fp_reg != 13)
+ as_bad (_("unexpected .unwind_movsp directive"));
+
+ /* Generate opcode to restore the value. */
+ op = 0x90 | reg;
+ add_unwind_opcode (op, 1);
+
+ /* Record the information for later. */
+ unwind.fp_reg = reg;
+ unwind.fp_offset = unwind.frame_size;
+ unwind.sp_restored = 1;
+ demand_empty_rest_of_line ();
+}
+
+
+/* Parse #<number>. */
+
+static int
+require_hashconst (int * val)
+{
+ expressionS exp;
+
+ SKIP_WHITESPACE ();
+ if (*input_line_pointer == '#')
+ {
+ input_line_pointer++;
+ expression (&exp);
+ }
+ else
+ exp.X_op = O_illegal;
+
+ if (exp.X_op != O_constant)
+ {
+ as_bad (_("expected #constant"));
+ ignore_rest_of_line ();
+ return FAIL;
+ }
+ *val = exp.X_add_number;
+ return SUCCESS;
+}
+
+/* Parse an unwind_pad directive. */
+
+static void
+s_arm_unwind_pad (int ignored ATTRIBUTE_UNUSED)
+{
+ int offset;
+
+ if (require_hashconst (&offset) == FAIL)
+ return;
+
+ if (offset & 3)
+ {
+ as_bad (_("stack increment must be multiple of 4"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ /* Don't generate any opcodes, just record the details for later. */
+ unwind.frame_size += offset;
+ unwind.pending_offset += offset;
+
+ demand_empty_rest_of_line ();
+}
+
+/* Parse an unwind_setfp directive. */
+
+static void
+s_arm_unwind_setfp (int ignored ATTRIBUTE_UNUSED)
+{
+ int sp_reg;
+ int fp_reg;
+ int offset;
+
+ fp_reg = reg_required_here (&input_line_pointer, -1);
+ if (skip_past_comma (&input_line_pointer) == FAIL)
+ sp_reg = FAIL;
+ else
+ sp_reg = reg_required_here (&input_line_pointer, -1);
+
+ if (fp_reg == FAIL || sp_reg == FAIL)
+ {
+ as_bad (_("expected <reg>, <reg>"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ /* Optonal constant. */
+ if (skip_past_comma (&input_line_pointer) != FAIL)
+ {
+ if (require_hashconst (&offset) == FAIL)
+ return;
+ }
+ else
+ offset = 0;
+
+ demand_empty_rest_of_line ();
+
+ if (sp_reg != 13 && sp_reg != unwind.fp_reg)
+ {
+ as_bad (_("register must be either sp or set by a previous"
+ "unwind_movsp directive"));
+ return;
+ }
+
+ /* Don't generate any opcodes, just record the information for later. */
+ unwind.fp_reg = fp_reg;
+ unwind.fp_used = 1;
+ if (sp_reg == 13)
+ unwind.fp_offset = unwind.frame_size - offset;
+ else
+ unwind.fp_offset -= offset;
+}
+
+/* Parse an unwind_raw directive. */
+
+static void
+s_arm_unwind_raw (int ignored ATTRIBUTE_UNUSED)
+{
+ expressionS exp;
+ /* This is an arbitary limit. */
+ unsigned char op[16];
+ int count;
+
+ SKIP_WHITESPACE ();
+ expression (&exp);
+ if (exp.X_op == O_constant
+ && skip_past_comma (&input_line_pointer) != FAIL)
+ {
+ unwind.frame_size += exp.X_add_number;
+ expression (&exp);
+ }
+ else
+ exp.X_op = O_illegal;
+
+ if (exp.X_op != O_constant)
+ {
+ as_bad (_("expected <offset>, <opcode>"));
+ ignore_rest_of_line ();
+ return;
+ }
+
+ count = 0;
+
+ /* Parse the opcode. */
+ for (;;)
+ {
+ if (count >= 16)
+ {
+ as_bad (_("unwind opcode too long"));
+ ignore_rest_of_line ();
+ }
+ if (exp.X_op != O_constant || exp.X_add_number & ~0xff)
+ {
+ as_bad (_("invalid unwind opcode"));
+ ignore_rest_of_line ();
+ return;
+ }
+ op[count++] = exp.X_add_number;
+
+ /* Parse the next byte. */
+ if (skip_past_comma (&input_line_pointer) == FAIL)
+ break;
+
+ expression (&exp);
+ }
+
+ /* Add the opcode bytes in reverse order. */
+ while (count--)
+ add_unwind_opcode (op[count], 1);
+
+ demand_empty_rest_of_line ();
+}
#endif /* OBJ_ELF */
@@ -13815,6 +14885,17 @@ const pseudo_typeS md_pseudo_table[] =
{ "word", s_arm_elf_cons, 4 },
{ "long", s_arm_elf_cons, 4 },
{ "rel31", s_arm_rel31, 0 },
+ { "fnstart", s_arm_unwind_fnstart, 0 },
+ { "fnend", s_arm_unwind_fnend, 0 },
+ { "cantunwind", s_arm_unwind_cantunwind, 0 },
+ { "personality", s_arm_unwind_personality, 0 },
+ { "personalityindex", s_arm_unwind_personalityindex, 0 },
+ { "handlerdata", s_arm_unwind_handlerdata, 0 },
+ { "save", s_arm_unwind_save, 0 },
+ { "movsp", s_arm_unwind_movsp, 0 },
+ { "pad", s_arm_unwind_pad, 0 },
+ { "setfp", s_arm_unwind_setfp, 0 },
+ { "unwind_raw", s_arm_unwind_raw, 0 },
#else
{ "word", cons, 4},
#endif
diff --git a/gas/doc/c-arm.texi b/gas/doc/c-arm.texi
index cb4569b..edd233b 100644
--- a/gas/doc/c-arm.texi
+++ b/gas/doc/c-arm.texi
@@ -407,6 +407,104 @@ it prevents accurate control of the placement of literal pools.
@item .pool
This is a synonym for .ltorg.
+@cindex @code{.fnstart} directive, ARM
+@item .unwind_fnstart
+Marks the start of a function with an unwind table entry.
+
+@cindex @code{.fnend} directive, ARM
+@item .unwind_fnend
+Marks the end of a function with an unwind table entry. The unwind index
+table entry is created when this directive is processed.
+
+If no personality routine has been specified then standard personality
+routine 0 or 1 will be used, depending on the number of unwind opcodes
+required.
+
+@cindex @code{.cantunwind} directive, ARM
+@item .cantunwind
+Prevents unwinding through the current function. No personality routine
+or exception table data is required or permitted.
+
+@cindex @code{.personality} directive, ARM
+@item .personality @var{name}
+Sets the personality routine for the current function to @var{name}.
+
+@cindex @code{.personalityindex} directive, ARM
+@item .personalityindex @var{index}
+Sets the personality routine for the current function to the EABI standard
+routine number @var{index}
+
+@cindex @code{.handlerdata} directive, ARM
+@item .handlerdata
+Marks the end of the current function, and the start of the exception table
+entry for that function. Anything between this directive and the
+@code{.fnend} directive will be added to the exception table entry.
+
+Must be preceded by a @code{.personality} or @code{.personalityindex}
+directive.
+
+@cindex @code{.save} directive, ARM
+@item .save @var{reglist}
+Generate unwinder annotations to restore the registers in @var{reglist}.
+The format of @var{reglist} is the same as the corresponding store-multiple
+instruction.
+
+@smallexample
+@exdent @emph{core registers}
+ .save @{r4, r5, r6, lr@}
+ stmfd sp!, @{r4, r5, r6, lr@}
+@exdent @emph{FPA registers}
+ .save f4, 2
+ sfmfd f4, 2, [sp]!
+@exdent @emph{VFP registers}
+ .save @{d8, d9, d10@}
+ fstmdf sp!, @{d8, d9, d10@}
+@exdent @emph{iWMMXt registers}
+ .save @{wr10, wr11@}
+ wstrd wr11, [sp, #-8]!
+ wstrd wr10, [sp, #-8]!
+or
+ .save wr11
+ wstrd wr11, [sp, #-8]!
+ .save wr10
+ wstrd wr10, [sp, #-8]!
+@end smallexample
+
+@cindex @code{.pad} directive, ARM
+@item .pad #@var{count}
+Generate unwinder annotations for a stack adjustment of @var{count} bytes.
+A positive value indicates the function prologue allocated stack space by
+decrementing the stack pointer.
+
+@cindex @code{.movsp} directive, ARM
+@item .movsp @var{reg}
+Tell the unwinder that @var{reg} contains the current stack pointer.
+
+@cindex @code{.setfp} directive, ARM
+@item .setfp @var{fpreg}, @var{spreg} [, #@var{offset}]
+Make all unwinder annotations relaive to a frame pointer. Without this
+the unwinder will use offsets from the stack pointer.
+
+The syntax of this directive is the same as the @code{sub} or @code{mov}
+instruction used to set the frame pointer. @var{spreg} must be either
+@code{sp} or mentioned in a previous @code{.movsp} directive.
+
+@smallexample
+.movsp ip
+mov ip, sp
+@dots{}
+.setfp fp, ip, #4
+sub fp, ip, #4
+@end smallexample
+
+@cindex @code{.unwind_raw} directive, ARM
+@item .raw @var{offset}, @var{byte1}, @dots{}
+Insert one of more arbitary unwind opcode bytes, which are known to adjust
+the stack pointer by @var{offset} bytes.
+
+For example @code{.unwind_raw 4, 0xb1, 0x01} is equivalent to
+@code{.save @{r0@}}
+
@end table
@node ARM Opcodes
diff --git a/gas/testsuite/gas/arm/arm.exp b/gas/testsuite/gas/arm/arm.exp
index 732468a..d1578ec 100644
--- a/gas/testsuite/gas/arm/arm.exp
+++ b/gas/testsuite/gas/arm/arm.exp
@@ -58,10 +58,11 @@ if {[istarget *arm*-*-*] || [istarget "xscale-*-*"]} then {
run_errors_test "r15-bad" "" "Invalid use of r15 errors"
run_errors_test "undefined" "" "Undefined local label error"
- if {[istarget *-*-elf*] || [istarget *-*-linux*]} then {
+ if {[istarget *-*-*elf*] || [istarget *-*-linux*] || [istarget *-*-*eabi]} then {
run_dump_test "pic"
run_dump_test "mapping"
gas_test "bignum1.s" "" $stdoptlist "bignums"
+ run_dump_test "unwind"
}
if {! [istarget arm*-*-aout] && ![istarget arm-*-pe]} then {
diff --git a/gas/testsuite/gas/arm/unwind.d b/gas/testsuite/gas/arm/unwind.d
new file mode 100644
index 0000000..63f9fdb
--- /dev/null
+++ b/gas/testsuite/gas/arm/unwind.d
@@ -0,0 +1,33 @@
+#objdump: -sr
+#name: Unwind table generation
+
+.*: file format.*
+
+RELOCATION RECORDS FOR \[.ARM.extab\]:
+OFFSET TYPE VALUE
+0000000c R_ARM_PREL31 .text
+
+
+RELOCATION RECORDS FOR \[.ARM.exidx\]:
+OFFSET TYPE VALUE
+00000000 R_ARM_REL32 .text
+00000008 R_ARM_REL32 .text
+0000000c R_ARM_PREL31 .ARM.extab
+00000010 R_ARM_REL32 .text
+00000014 R_ARM_PREL31 .ARM.extab
+00000018 R_ARM_REL32 .text
+0000001c R_ARM_PREL31 .ARM.extab
+00000020 R_ARM_REL32 .text
+
+
+Contents of section .text:
+ 0000 (0000a0e3 0100a0e3 0200a0e3 0300a0e3|e3a00000 a0e30001 e3a00002 e3a00003) .*
+ 0010 (0420|2004) .*
+Contents of section .ARM.extab:
+ 0000 (449b0181 b0b08086|81019b44 8680b0b0) 00000000 00000000 .*
+ 0010 (8402b101 b0b0b005 2a000000 00c60181|01b10284 05b0b0b0 000000a2 8101c600) .*
+ 0020 (b0b0c1c1|c1c1b0b0) 00000000 .*
+Contents of section .ARM.exidx:
+ 0000 00000000 (b0b0a880 04000000|80a8b0b0 00000004) 00000000 .*
+ 0010 (08000000 0c000000 0c000000 1c000000|00000008 0000000c 0000000c 0000001c) .*
+ 0020 (10000000 08849780|00000010 80978480) .*
diff --git a/gas/testsuite/gas/arm/unwind.s b/gas/testsuite/gas/arm/unwind.s
new file mode 100644
index 0000000..43f01e6
--- /dev/null
+++ b/gas/testsuite/gas/arm/unwind.s
@@ -0,0 +1,46 @@
+# Test generation of unwind tables
+ .text
+foo: @ Simple function
+ .fnstart
+ .save {r4, lr}
+ mov r0, #0
+ .fnend
+foo1: @ Typical frame pointer prologue
+ .fnstart
+ .movsp ip
+ @mov ip, sp
+ .pad #4
+ .save {fp, ip, lr}
+ @stmfd sp!, {fp, ip, lr, pc}
+ .setfp fp, ip, #4
+ @sub fp, ip, #4
+ mov r0, #1
+ .fnend
+foo2: @ Custom personality routine
+ .fnstart
+ .save {r1, r4, r6, lr}
+ @stmfd {r1, r4, r6, lr}
+ mov r0, #2
+ .personality foo
+ .handlerdata
+ .word 42
+ .fnend
+foo3: @ Saving iwmmxt registers
+ .fnstart
+ .save {wr11}
+ .save {wr10}
+ .save {wr10, wr11}
+ .save {wr0}
+ mov r0, #3
+ .fnend
+ .code 16
+foo4: @ Thumb frame pointer
+ .fnstart
+ .save {r7, lr}
+ @push {r7, lr}
+ .setfp r7, sp
+ @mov r7, sp
+ .pad #8
+ @sub sp, sp, #8
+ mov r0, #4
+ .fnend