| /* Native Client support for ELF |
| Copyright (C) 2012-2024 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, see <http://www.gnu.org/licenses/>. */ |
| |
| #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 bool |
| 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 bool |
| 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. */ |
| bool |
| 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 **headers = NULL; |
| 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) |
| { |
| bool 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 * sizeof (asection *))); |
| if (newseg == NULL) |
| return false; |
| memcpy (newseg, seg, (sizeof (*newseg) - sizeof (asection *) |
| + 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. */ |
| if (first_load == NULL) |
| 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 (headers == NULL |
| && segment_eligible_for_headers (seg, bed->minpagesize, |
| sizeof_headers)) |
| headers = m; |
| } |
| m = &seg->next; |
| } |
| |
| if (headers != NULL) |
| { |
| struct elf_segment_map **last_load = NULL; |
| struct elf_segment_map *seg; |
| |
| m = first_load; |
| while ((seg = *m) != NULL) |
| { |
| if (seg->p_type == PT_LOAD) |
| { |
| /* Clear the flags on any previous segment that |
| included the file header and phdrs. */ |
| seg->includes_filehdr = 0; |
| seg->includes_phdrs = 0; |
| seg->no_sort_lma = 1; |
| /* Also strip out empty segments. */ |
| if (seg->count == 0) |
| { |
| if (headers == &seg->next) |
| headers = m; |
| *m = seg->next; |
| continue; |
| } |
| last_load = m; |
| } |
| m = &seg->next; |
| } |
| |
| /* This segment will include those headers instead. */ |
| seg = *headers; |
| seg->includes_filehdr = 1; |
| seg->includes_phdrs = 1; |
| |
| if (last_load != NULL && first_load != last_load && first_load != headers) |
| { |
| /* Put the first PT_LOAD header last. */ |
| 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. */ |
| bool |
| nacl_modify_headers (bfd *abfd, struct bfd_link_info *info) |
| { |
| if (info != NULL && info->user_phdrs) |
| /* The linker script used PHDRS explicitly, so don't change what the |
| user asked for. */ |
| ; |
| else |
| { |
| struct elf_segment_map **m = &elf_seg_map (abfd); |
| Elf_Internal_Phdr *phdr = elf_tdata (abfd)->phdr; |
| Elf_Internal_Phdr *p = phdr; |
| |
| /* 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 _bfd_elf_modify_headers (abfd, info); |
| } |
| |
| bool |
| nacl_final_write_processing (bfd *abfd) |
| { |
| 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_write (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); |
| } |
| return _bfd_elf_final_write_processing (abfd); |
| } |