summaryrefslogtreecommitdiff
path: root/bfd/elf32-avr.c
diff options
context:
space:
mode:
authorDenis Chertykov <chertykov@gmail.com>2014-04-10 19:50:33 +0400
committerDenis Chertykov <chertykov@gmail.com>2014-04-10 19:50:33 +0400
commite4ef1b6c3fee295ab41138bf77a82dff16f1d7c3 (patch)
tree4f6ad10afb271882fe8624a082ecb088a1db0feb /bfd/elf32-avr.c
parent9d497a19eabe25b3af1cdd50a957592ed22658e5 (diff)
downloadbinutils-gdb-e4ef1b6c3fee295ab41138bf77a82dff16f1d7c3.tar.gz
bfd/ChangeLog
* elf32-avr.c: Add DIFF relocations for AVR. (avr_final_link_relocate): Handle the DIFF relocs. (bfd_elf_avr_diff_reloc): New. (elf32_avr_is_diff_reloc): New. (elf32_avr_adjust_diff_reloc_value): Reduce difference value. (elf32_avr_relax_delete_bytes): Recompute difference after deleting bytes. * reloc.c: Add BFD_RELOC_AVR_DIFF8/16/32 relocations gas/ChangeLog * config/tc-avr.c: Add new flag mlink-relax. (md_show_usage): Add flag and help text. (md_parse_option): Record whether link relax is turned on. (relaxable_section): New. (avr_validate_fix_sub): New. (avr_force_relocation): New. (md_apply_fix): Generate DIFF reloc. (avr_allow_local_subtract): New. * config/tc-avr.h (TC_LINKRELAX_FIXUP): Define to 0. (TC_FORCE_RELOCATION): Define. (TC_FORCE_RELOCATION_SUB_SAME): Define. (TC_VALIDATE_FIX_SUB): Define. (avr_force_relocation): Declare. (avr_validate_fix_sub): Declare. (md_allow_local_subtract): Define. (avr_allow_local_subtract): Declare. gas/testsuite/ChangeLog * gas/avr/diffreloc_withrelax.d: New testcase. * gas/avr/noreloc_withoutrelax.d: Likewise. * gas/avr/relax.s: Likewise. include/ChangeLog * elf/avr.h: Add new DIFF relocs. ld/testsuite/ChangeLog * ld-avr/norelax_diff.d: New testcase. * ld-avr/relax_diff.d: Likewise. * ld-avr/relax.s: Likewise.
Diffstat (limited to 'bfd/elf32-avr.c')
-rw-r--r--bfd/elf32-avr.c182
1 files changed, 181 insertions, 1 deletions
diff --git a/bfd/elf32-avr.c b/bfd/elf32-avr.c
index 3fd43c9d12f..425e2d1c1ed 100644
--- a/bfd/elf32-avr.c
+++ b/bfd/elf32-avr.c
@@ -32,6 +32,15 @@ static bfd_boolean debug_relax = FALSE;
/* Enable debugging printout at stdout with this variable. */
static bfd_boolean debug_stubs = FALSE;
+static bfd_reloc_status_type
+bfd_elf_avr_diff_reloc (bfd *abfd,
+ arelent *reloc_entry,
+ asymbol *symbol,
+ void *data,
+ asection *input_section,
+ bfd *output_bfd,
+ char **error_message);
+
/* Hash table initialization and handling. Code is taken from the hppa port
and adapted to the needs of AVR. */
@@ -557,6 +566,45 @@ static reloc_howto_type elf_avr_howto_table[] =
0xffffff, /* src_mask */
0xffffff, /* dst_mask */
FALSE), /* pcrel_offset */
+ HOWTO (R_AVR_DIFF8, /* type */
+ 0, /* rightshift */
+ 0, /* size (0 = byte, 1 = short, 2 = long) */
+ 8, /* bitsize */
+ FALSE, /* pc_relative */
+ 0, /* bitpos */
+ complain_overflow_bitfield, /* complain_on_overflow */
+ bfd_elf_avr_diff_reloc, /* special_function */
+ "R_AVR_DIFF8", /* name */
+ FALSE, /* partial_inplace */
+ 0, /* src_mask */
+ 0xff, /* dst_mask */
+ FALSE), /* pcrel_offset */
+ HOWTO (R_AVR_DIFF16, /* type */
+ 0, /* rightshift */
+ 1, /* size (0 = byte, 1 = short, 2 = long) */
+ 16, /* bitsize */
+ FALSE, /* pc_relative */
+ 0, /* bitpos */
+ complain_overflow_bitfield, /* complain_on_overflow */
+ bfd_elf_avr_diff_reloc, /* special_function */
+ "R_AVR_DIFF16", /* name */
+ FALSE, /* partial_inplace */
+ 0, /* src_mask */
+ 0xffff, /* dst_mask */
+ FALSE), /* pcrel_offset */
+ HOWTO (R_AVR_DIFF32, /* type */
+ 0, /* rightshift */
+ 2, /* size (0 = byte, 1 = short, 2 = long) */
+ 32, /* bitsize */
+ FALSE, /* pc_relative */
+ 0, /* bitpos */
+ complain_overflow_bitfield, /* complain_on_overflow */
+ bfd_elf_avr_diff_reloc, /* special_function */
+ "R_AVR_DIFF32", /* name */
+ FALSE, /* partial_inplace */
+ 0, /* src_mask */
+ 0xffffffff, /* dst_mask */
+ FALSE) /* pcrel_offset */
};
/* Map BFD reloc types to AVR ELF reloc types. */
@@ -598,7 +646,10 @@ static const struct avr_reloc_map avr_reloc_map[] =
{ BFD_RELOC_8, R_AVR_8 },
{ BFD_RELOC_AVR_8_LO, R_AVR_8_LO8 },
{ BFD_RELOC_AVR_8_HI, R_AVR_8_HI8 },
- { BFD_RELOC_AVR_8_HLO, R_AVR_8_HLO8 }
+ { BFD_RELOC_AVR_8_HLO, R_AVR_8_HLO8 },
+ { BFD_RELOC_AVR_DIFF8, R_AVR_DIFF8 },
+ { BFD_RELOC_AVR_DIFF16, R_AVR_DIFF16 },
+ { BFD_RELOC_AVR_DIFF32, R_AVR_DIFF32 }
};
/* Meant to be filled one day with the wrap around address for the
@@ -797,6 +848,22 @@ avr_get_stub_addr (bfd_vma srel,
return 0x020000;
}
+/* Perform a diff relocation. Nothing to do, as the difference value is already
+ written into the section's contents. */
+
+static bfd_reloc_status_type
+bfd_elf_avr_diff_reloc (bfd *abfd ATTRIBUTE_UNUSED,
+ arelent *reloc_entry ATTRIBUTE_UNUSED,
+ asymbol *symbol ATTRIBUTE_UNUSED,
+ void *data ATTRIBUTE_UNUSED,
+ asection *input_section ATTRIBUTE_UNUSED,
+ bfd *output_bfd ATTRIBUTE_UNUSED,
+ char **error_message ATTRIBUTE_UNUSED)
+{
+ return bfd_reloc_ok;
+}
+
+
/* Perform a single relocation. By default we use the standard BFD
routines, but a few relocs, we have to do them ourselves. */
@@ -1149,6 +1216,13 @@ avr_final_link_relocate (reloc_howto_type * howto,
bfd_put_16 (input_bfd, (bfd_vma) srel &0x00ffff, contents);
break;
+ case R_AVR_DIFF8:
+ case R_AVR_DIFF16:
+ case R_AVR_DIFF32:
+ /* Nothing to do here, as contents already contains the diff value. */
+ r = bfd_reloc_ok;
+ break;
+
default:
r = _bfd_final_link_relocate (howto, input_bfd, input_section,
contents, rel->r_offset,
@@ -1457,6 +1531,104 @@ elf32_avr_object_p (bfd *abfd)
e_set);
}
+/* Returns whether the relocation type passed is a diff reloc. */
+
+static bfd_boolean
+elf32_avr_is_diff_reloc (Elf_Internal_Rela *irel)
+{
+ return (ELF32_R_TYPE (irel->r_info) == R_AVR_DIFF8
+ ||ELF32_R_TYPE (irel->r_info) == R_AVR_DIFF16
+ || ELF32_R_TYPE (irel->r_info) == R_AVR_DIFF32);
+}
+
+/* Reduce the diff value written in the section by count if the shrinked
+ insn address happens to fall between the two symbols for which this
+ diff reloc was emitted. */
+
+static void
+elf32_avr_adjust_diff_reloc_value (bfd *abfd,
+ struct bfd_section *isec,
+ Elf_Internal_Rela *irel,
+ bfd_vma symval,
+ bfd_vma shrinked_insn_address,
+ int count)
+{
+ unsigned char *reloc_contents = NULL;
+ unsigned char *isec_contents = elf_section_data (isec)->this_hdr.contents;
+ if (isec_contents == NULL)
+ {
+ if (! bfd_malloc_and_get_section (abfd, isec, &isec_contents))
+ return;
+
+ elf_section_data (isec)->this_hdr.contents = isec_contents;
+ }
+
+ reloc_contents = isec_contents + irel->r_offset;
+
+ /* Read value written in object file. */
+ bfd_vma x = 0;
+ switch (ELF32_R_TYPE (irel->r_info))
+ {
+ case R_AVR_DIFF8:
+ {
+ x = *reloc_contents;
+ break;
+ }
+ case R_AVR_DIFF16:
+ {
+ x = bfd_get_16 (abfd, reloc_contents);
+ break;
+ }
+ case R_AVR_DIFF32:
+ {
+ x = bfd_get_32 (abfd, reloc_contents);
+ break;
+ }
+ default:
+ {
+ BFD_FAIL();
+ }
+ }
+
+ /* For a diff reloc sym1 - sym2 the diff at assembly time (x) is written
+ into the object file at the reloc offset. sym2's logical value is
+ symval (<start_of_section>) + reloc addend. Compute the start and end
+ addresses and check if the shrinked insn falls between sym1 and sym2. */
+
+ bfd_vma end_address = symval + irel->r_addend;
+ bfd_vma start_address = end_address - x;
+
+ /* Reduce the diff value by count bytes and write it back into section
+ contents. */
+
+ if (shrinked_insn_address >= start_address &&
+ shrinked_insn_address <= end_address)
+ {
+ switch (ELF32_R_TYPE (irel->r_info))
+ {
+ case R_AVR_DIFF8:
+ {
+ *reloc_contents = (x - count);
+ break;
+ }
+ case R_AVR_DIFF16:
+ {
+ bfd_put_16 (abfd, (x - count) & 0xFFFF, reloc_contents);
+ break;
+ }
+ case R_AVR_DIFF32:
+ {
+ bfd_put_32 (abfd, (x - count) & 0xFFFFFFFF, reloc_contents);
+ break;
+ }
+ default:
+ {
+ BFD_FAIL();
+ }
+ }
+
+ }
+}
/* Delete some bytes from a section while changing the size of an instruction.
The parameter "addr" denotes the section-relative offset pointing just
@@ -1595,6 +1767,14 @@ elf32_avr_relax_delete_bytes (bfd *abfd,
if (symval <= shrinked_insn_address
&& (symval + irel->r_addend) > shrinked_insn_address)
{
+ if (elf32_avr_is_diff_reloc (irel))
+ {
+ elf32_avr_adjust_diff_reloc_value (abfd, isec, irel,
+ symval,
+ shrinked_insn_address,
+ count);
+ }
+
irel->r_addend -= count;
if (debug_relax)