You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
11 KiB
355 lines
11 KiB
/* Native Client support for ELF |
|
Copyright (C) 2012-2014 Free Software Foundation, Inc. |
|
|
|
This file is part of BFD, the Binary File Descriptor library. |
|
|
|
This program 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 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program 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 this program; if not, write to the Free Software |
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
|
MA 02111-1307, USA. */ |
|
|
|
#include "sysdep.h" |
|
#include "bfd.h" |
|
#include "libbfd.h" |
|
#include "elf-bfd.h" |
|
#include "elf-nacl.h" |
|
#include "elf/common.h" |
|
#include "elf/internal.h" |
|
|
|
static bfd_boolean |
|
segment_executable (struct elf_segment_map *seg) |
|
{ |
|
if (seg->p_flags_valid) |
|
return (seg->p_flags & PF_X) != 0; |
|
else |
|
{ |
|
/* The p_flags value has not been computed yet, |
|
so we have to look through the sections. */ |
|
unsigned int i; |
|
for (i = 0; i < seg->count; ++i) |
|
if (seg->sections[i]->flags & SEC_CODE) |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
/* Determine if this segment is eligible to receive the file and program |
|
headers. It must be read-only and non-executable. |
|
Its first section must start far enough past the page boundary to |
|
allow space for the headers. */ |
|
static bfd_boolean |
|
segment_eligible_for_headers (struct elf_segment_map *seg, |
|
bfd_vma minpagesize, bfd_vma sizeof_headers) |
|
{ |
|
unsigned int i; |
|
if (seg->count == 0 || seg->sections[0]->lma % minpagesize < sizeof_headers) |
|
return FALSE; |
|
for (i = 0; i < seg->count; ++i) |
|
{ |
|
if ((seg->sections[i]->flags & (SEC_CODE|SEC_READONLY)) != SEC_READONLY) |
|
return FALSE; |
|
} |
|
return TRUE; |
|
} |
|
|
|
|
|
/* We permute the segment_map to get BFD to do the file layout we want: |
|
The first non-executable PT_LOAD segment appears first in the file |
|
and contains the ELF file header and phdrs. */ |
|
bfd_boolean |
|
nacl_modify_segment_map (bfd *abfd, struct bfd_link_info *info) |
|
{ |
|
const struct elf_backend_data *const bed = get_elf_backend_data (abfd); |
|
struct elf_segment_map **m = &elf_seg_map (abfd); |
|
struct elf_segment_map **first_load = NULL; |
|
struct elf_segment_map **last_load = NULL; |
|
bfd_boolean moved_headers = FALSE; |
|
int sizeof_headers; |
|
|
|
if (info != NULL && info->user_phdrs) |
|
/* The linker script used PHDRS explicitly, so don't change what the |
|
user asked for. */ |
|
return TRUE; |
|
|
|
if (info != NULL) |
|
/* We're doing linking, so evalute SIZEOF_HEADERS as in a linker script. */ |
|
sizeof_headers = bfd_sizeof_headers (abfd, info); |
|
else |
|
{ |
|
/* We're not doing linking, so this is objcopy or suchlike. |
|
We just need to collect the size of the existing headers. */ |
|
struct elf_segment_map *seg; |
|
sizeof_headers = bed->s->sizeof_ehdr; |
|
for (seg = *m; seg != NULL; seg = seg->next) |
|
sizeof_headers += bed->s->sizeof_phdr; |
|
} |
|
|
|
while (*m != NULL) |
|
{ |
|
struct elf_segment_map *seg = *m; |
|
|
|
if (seg->p_type == PT_LOAD) |
|
{ |
|
bfd_boolean executable = segment_executable (seg); |
|
|
|
if (executable |
|
&& seg->count > 0 |
|
&& seg->sections[0]->vma % bed->minpagesize == 0) |
|
{ |
|
asection *lastsec = seg->sections[seg->count - 1]; |
|
bfd_vma end = lastsec->vma + lastsec->size; |
|
if (end % bed->minpagesize != 0) |
|
{ |
|
/* This is an executable segment that starts on a page |
|
boundary but does not end on a page boundary. Fill |
|
it out to a whole page with code fill (the tail of |
|
the segment will not be within any section). Thus |
|
the entire code segment can be mapped from the file |
|
as whole pages and that mapping will contain only |
|
valid instructions. |
|
|
|
To accomplish this, we must fake out the code in |
|
assign_file_positions_for_load_sections (elf.c) so |
|
that it advances past the rest of the final page, |
|
rather than trying to put the next (unaligned, or |
|
unallocated) section. We do this by appending a |
|
dummy section record to this element in the segment |
|
map. No such output section ever actually exists, |
|
but this gets the layout logic to advance the file |
|
positions past this partial page. Since we are |
|
lying to BFD like this, nothing will ever know to |
|
write the section contents. So we do that by hand |
|
after the fact, in nacl_final_write_processing, below. */ |
|
|
|
struct elf_segment_map *newseg; |
|
asection *sec; |
|
struct bfd_elf_section_data *secdata; |
|
|
|
BFD_ASSERT (!seg->p_size_valid); |
|
|
|
secdata = bfd_zalloc (abfd, sizeof *secdata); |
|
if (secdata == NULL) |
|
return FALSE; |
|
|
|
sec = bfd_zalloc (abfd, sizeof *sec); |
|
if (sec == NULL) |
|
return FALSE; |
|
|
|
/* Fill in only the fields that actually affect the logic |
|
in assign_file_positions_for_load_sections. */ |
|
sec->vma = end; |
|
sec->lma = lastsec->lma + lastsec->size; |
|
sec->size = bed->minpagesize - (end % bed->minpagesize); |
|
sec->flags = (SEC_ALLOC | SEC_LOAD |
|
| SEC_READONLY | SEC_CODE | SEC_LINKER_CREATED); |
|
sec->used_by_bfd = secdata; |
|
|
|
secdata->this_hdr.sh_type = SHT_PROGBITS; |
|
secdata->this_hdr.sh_flags = SHF_ALLOC | SHF_EXECINSTR; |
|
secdata->this_hdr.sh_addr = sec->vma; |
|
secdata->this_hdr.sh_size = sec->size; |
|
|
|
newseg = bfd_alloc (abfd, |
|
sizeof *newseg + ((seg->count + 1) |
|
* sizeof (asection *))); |
|
if (newseg == NULL) |
|
return FALSE; |
|
memcpy (newseg, seg, |
|
sizeof *newseg + (seg->count * sizeof (asection *))); |
|
newseg->sections[newseg->count++] = sec; |
|
*m = seg = newseg; |
|
} |
|
} |
|
|
|
/* First, we're just finding the earliest PT_LOAD. |
|
By the normal rules, this will be the lowest-addressed one. |
|
We only have anything interesting to do if it's executable. */ |
|
last_load = m; |
|
if (first_load == NULL) |
|
{ |
|
if (!executable) |
|
goto next; |
|
first_load = m; |
|
} |
|
/* Now that we've noted the first PT_LOAD, we're looking for |
|
the first non-executable PT_LOAD with a nonempty p_filesz. */ |
|
else if (!moved_headers |
|
&& segment_eligible_for_headers (seg, bed->minpagesize, |
|
sizeof_headers)) |
|
{ |
|
/* This is the one we were looking for! |
|
|
|
First, clear the flags on previous segments that |
|
say they include the file header and phdrs. */ |
|
struct elf_segment_map *prevseg; |
|
for (prevseg = *first_load; |
|
prevseg != seg; |
|
prevseg = prevseg->next) |
|
if (prevseg->p_type == PT_LOAD) |
|
{ |
|
prevseg->includes_filehdr = 0; |
|
prevseg->includes_phdrs = 0; |
|
} |
|
|
|
/* This segment will include those headers instead. */ |
|
seg->includes_filehdr = 1; |
|
seg->includes_phdrs = 1; |
|
|
|
moved_headers = TRUE; |
|
} |
|
} |
|
|
|
next: |
|
m = &seg->next; |
|
} |
|
|
|
if (first_load != last_load && moved_headers) |
|
{ |
|
/* Now swap the first and last PT_LOAD segments' |
|
positions in segment_map. */ |
|
struct elf_segment_map *first = *first_load; |
|
struct elf_segment_map *last = *last_load; |
|
*first_load = first->next; |
|
first->next = last->next; |
|
last->next = first; |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
/* After nacl_modify_segment_map has done its work, the file layout has |
|
been done as we wanted. But the PT_LOAD phdrs are no longer in the |
|
proper order for the ELF rule that they must appear in ascending address |
|
order. So find the two segments we swapped before, and swap them back. */ |
|
bfd_boolean |
|
nacl_modify_program_headers (bfd *abfd, struct bfd_link_info *info) |
|
{ |
|
struct elf_segment_map **m = &elf_seg_map (abfd); |
|
Elf_Internal_Phdr *phdr = elf_tdata (abfd)->phdr; |
|
Elf_Internal_Phdr *p = phdr; |
|
|
|
if (info != NULL && info->user_phdrs) |
|
/* The linker script used PHDRS explicitly, so don't change what the |
|
user asked for. */ |
|
return TRUE; |
|
|
|
/* Find the PT_LOAD that contains the headers (should be the first). */ |
|
while (*m != NULL) |
|
{ |
|
if ((*m)->p_type == PT_LOAD && (*m)->includes_filehdr) |
|
break; |
|
|
|
m = &(*m)->next; |
|
++p; |
|
} |
|
|
|
if (*m != NULL) |
|
{ |
|
struct elf_segment_map **first_load_seg = m; |
|
Elf_Internal_Phdr *first_load_phdr = p; |
|
struct elf_segment_map **next_load_seg = NULL; |
|
Elf_Internal_Phdr *next_load_phdr = NULL; |
|
|
|
/* Now move past that first one and find the PT_LOAD that should be |
|
before it by address order. */ |
|
|
|
m = &(*m)->next; |
|
++p; |
|
|
|
while (*m != NULL) |
|
{ |
|
if (p->p_type == PT_LOAD && p->p_vaddr < first_load_phdr->p_vaddr) |
|
{ |
|
next_load_seg = m; |
|
next_load_phdr = p; |
|
break; |
|
} |
|
|
|
m = &(*m)->next; |
|
++p; |
|
} |
|
|
|
/* Swap their positions in the segment_map back to how they used to be. |
|
The phdrs have already been set up by now, so we have to slide up |
|
the earlier ones to insert the one that should be first. */ |
|
if (next_load_seg != NULL) |
|
{ |
|
Elf_Internal_Phdr move_phdr; |
|
struct elf_segment_map *first_seg = *first_load_seg; |
|
struct elf_segment_map *next_seg = *next_load_seg; |
|
struct elf_segment_map *first_next = first_seg->next; |
|
struct elf_segment_map *next_next = next_seg->next; |
|
|
|
if (next_load_seg == &first_seg->next) |
|
{ |
|
*first_load_seg = next_seg; |
|
next_seg->next = first_seg; |
|
first_seg->next = next_next; |
|
} |
|
else |
|
{ |
|
*first_load_seg = first_next; |
|
*next_load_seg = next_next; |
|
|
|
first_seg->next = *next_load_seg; |
|
*next_load_seg = first_seg; |
|
|
|
next_seg->next = *first_load_seg; |
|
*first_load_seg = next_seg; |
|
} |
|
|
|
move_phdr = *next_load_phdr; |
|
memmove (first_load_phdr + 1, first_load_phdr, |
|
(next_load_phdr - first_load_phdr) * sizeof move_phdr); |
|
*first_load_phdr = move_phdr; |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
void |
|
nacl_final_write_processing (bfd *abfd, bfd_boolean linker ATTRIBUTE_UNUSED) |
|
{ |
|
struct elf_segment_map *seg; |
|
for (seg = elf_seg_map (abfd); seg != NULL; seg = seg->next) |
|
if (seg->p_type == PT_LOAD |
|
&& seg->count > 1 |
|
&& seg->sections[seg->count - 1]->owner == NULL) |
|
{ |
|
/* This is a fake section added in nacl_modify_segment_map, above. |
|
It's not a real BFD section, so nothing wrote its contents. |
|
Now write out its contents. */ |
|
|
|
asection *sec = seg->sections[seg->count - 1]; |
|
char *fill; |
|
|
|
BFD_ASSERT (sec->flags & SEC_LINKER_CREATED); |
|
BFD_ASSERT (sec->flags & SEC_CODE); |
|
BFD_ASSERT (sec->size > 0); |
|
|
|
fill = abfd->arch_info->fill (sec->size, bfd_big_endian (abfd), TRUE); |
|
|
|
if (fill == NULL |
|
|| bfd_seek (abfd, sec->filepos, SEEK_SET) != 0 |
|
|| bfd_bwrite (fill, sec->size, abfd) != sec->size) |
|
{ |
|
/* We don't have a proper way to report an error here. So |
|
instead fudge things so that elf_write_shdrs_and_ehdr will |
|
fail. */ |
|
elf_elfheader (abfd)->e_shoff = (file_ptr) -1; |
|
} |
|
|
|
free (fill); |
|
} |
|
}
|
|
|