ld: better handling of lma region for orphan sections

When picking an lma_region for an orphan section we currently create a
new lang_output_section_statement_type and then populate this with the
orphan section.

The problem is that the lang_output_section_statement_type has a prev
pointer that links back to the previous output section.  For non-orphan
output sections, that are created in linker script order, the prev
pointer will point to the output section that appears previous in linker
script order, as you'd probably expect.

The problem is that orphan sections are placed after processing the
linker script, and so, in the case of an output section created for an
orphan input section, the prev pointer actually points to the last
output section created.

This causes some unexpected behaviour when the orphan section is not
placed after the last non-orphan section that was created.

For example, consider this linker script:

  MEMORY {
    TEXT   : ORIGIN = 0x200,  LENGTH = 0x10
    RODATA : ORIGIN = 0x400,  LENGTH = 0x10
  }

  SECTIONS {
    .text   :           {*(.text)    } AT>TEXT
    .data   : AT(0x300) { *(.data)   }
    .rodata :           { *(.rodata) } AT>RODATA
  }

If we are processing an orphan section '.data.1' and decide to place
this after '.data', then the output section created will have a prev
pointer that references the '.rodata' output section.  The result of
this is that '.data.1' will actually be assigned to the RODATA lma
region, which is probably not the expected behaviour.

The reason why '.data.1' is placed into the lma region of the '.rodata'
section is that lma region propagation is done at the time we create the
output section, based on the previous output section pointer, which is
really just a last-output-section-created pointer at that point in time,
though the prev point is fixed up later to reflect the true order of the
output sections.

The solution I propose in this commit is to move the propagation of lma
regions into a separate pass of the linker, rather than performing this
as part of the enter/exit of output sections during linker script
parsing.

During this later phase we have all of the output sections to hand, and
the prev/next points have been fixed up by this point to reflect the
actual placement ordering.

There's a new test to cover this issue that passes on a range of
targets, however, some targets generate additional sections, or have
stricter memory region size requirements that make it harder to come
up with a generic pass pattern, that still tests the required
features.  For now I've set the test to ignore these targets.

ld/ChangeLog:

	* ldlang.c (lang_leave_output_section_statement): Move lma_region
	logic to...
	(lang_propagate_lma_regions): ...this new function.
	(lang_process): Call new function.
	* testsuite/ld-elf/orphan-9.d: New file.
	* testsuite/ld-elf/orphan-9.ld: New file.
	* testsuite/ld-elf/orphan-9.s: New file.
	* NEWS: Mention change in behaviour.
This commit is contained in:
Andrew Burgess
2017-01-17 19:12:54 +00:00
parent a87ded7b88
commit 77f5e65ecf
6 changed files with 89 additions and 10 deletions

View File

@ -6844,6 +6844,27 @@ lang_check_relocs (void)
}
}
/* Look through all output sections looking for places where we can
propagate forward the lma region. */
static void
lang_propagate_lma_regions (void)
{
lang_output_section_statement_type *os;
for (os = &lang_output_section_statement.head->output_section_statement;
os != NULL;
os = os->next)
{
if (os->prev != NULL
&& os->lma_region == NULL
&& os->load_base == NULL
&& os->addr_tree == NULL
&& os->region == os->prev->region)
os->lma_region = os->prev->lma_region;
}
}
void
lang_process (void)
{
@ -7022,6 +7043,9 @@ lang_process (void)
}
}
/* Copy forward lma regions for output sections in same lma region. */
lang_propagate_lma_regions ();
/* Do anything special before sizing sections. This is where ELF
and other back-ends size dynamic sections. */
ldemul_before_allocation ();
@ -7302,16 +7326,6 @@ lang_leave_output_section_statement (fill_type *fill, const char *memspec,
current_section->load_base != NULL,
current_section->addr_tree != NULL);
/* If this section has no load region or base, but uses the same
region as the previous section, then propagate the previous
section's load region. */
if (current_section->lma_region == NULL
&& current_section->load_base == NULL
&& current_section->addr_tree == NULL
&& current_section->region == current_section->prev->region)
current_section->lma_region = current_section->prev->lma_region;
current_section->fill = fill;
current_section->phdrs = phdrs;
pop_stat_ptr ();