| /* Load runtime image of EFIemu. Functions specific to 32/64-bit mode */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2009 Free Software Foundation, Inc. |
| * |
| * GRUB 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. |
| * |
| * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/err.h> |
| #include <grub/mm.h> |
| #include <grub/misc.h> |
| #include <grub/efiemu/efiemu.h> |
| #include <grub/cpu/efiemu.h> |
| #include <grub/elf.h> |
| #include <grub/i18n.h> |
| |
| /* ELF symbols and their values */ |
| static struct grub_efiemu_elf_sym *grub_efiemu_elfsyms = 0; |
| static int grub_efiemu_nelfsyms = 0; |
| |
| /* Return the address of a section whose index is N. */ |
| static grub_err_t |
| grub_efiemu_get_section_addr (grub_efiemu_segment_t segs, unsigned n, |
| int *handle, grub_off_t *off) |
| { |
| grub_efiemu_segment_t seg; |
| |
| for (seg = segs; seg; seg = seg->next) |
| if (seg->section == n) |
| { |
| *handle = seg->handle; |
| *off = seg->off; |
| return GRUB_ERR_NONE; |
| } |
| |
| return grub_error (GRUB_ERR_BAD_OS, "section %d not found", n); |
| } |
| |
| grub_err_t |
| SUFFIX (grub_efiemu_loadcore_unload) (void) |
| { |
| grub_free (grub_efiemu_elfsyms); |
| grub_efiemu_elfsyms = 0; |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Check if EHDR is a valid ELF header. */ |
| int |
| SUFFIX (grub_efiemu_check_header) (void *ehdr, grub_size_t size) |
| { |
| Elf_Ehdr *e = ehdr; |
| |
| /* Check the header size. */ |
| if (size < sizeof (Elf_Ehdr)) |
| return 0; |
| |
| /* Check the magic numbers. */ |
| if (!SUFFIX (grub_arch_efiemu_check_header) (ehdr) |
| || e->e_ident[EI_MAG0] != ELFMAG0 |
| || e->e_ident[EI_MAG1] != ELFMAG1 |
| || e->e_ident[EI_MAG2] != ELFMAG2 |
| || e->e_ident[EI_MAG3] != ELFMAG3 |
| || e->e_ident[EI_VERSION] != EV_CURRENT |
| || e->e_version != EV_CURRENT) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Load all segments from memory specified by E. */ |
| static grub_err_t |
| grub_efiemu_load_segments (grub_efiemu_segment_t segs, const Elf_Ehdr *e) |
| { |
| Elf_Shdr *s; |
| grub_efiemu_segment_t cur; |
| |
| grub_dprintf ("efiemu", "loading segments\n"); |
| |
| for (cur=segs; cur; cur = cur->next) |
| { |
| s = (Elf_Shdr *)cur->srcptr; |
| |
| if ((s->sh_flags & SHF_ALLOC) && s->sh_size) |
| { |
| void *addr; |
| |
| addr = (grub_uint8_t *) grub_efiemu_mm_obtain_request (cur->handle) |
| + cur->off; |
| |
| switch (s->sh_type) |
| { |
| case SHT_PROGBITS: |
| grub_memcpy (addr, (char *) e + s->sh_offset, s->sh_size); |
| break; |
| case SHT_NOBITS: |
| grub_memset (addr, 0, s->sh_size); |
| break; |
| } |
| } |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Get a string at offset OFFSET from strtab */ |
| static char * |
| grub_efiemu_get_string (unsigned offset, const Elf_Ehdr *e) |
| { |
| unsigned i; |
| Elf_Shdr *s; |
| |
| for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); |
| i < e->e_shnum; |
| i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) |
| if (s->sh_type == SHT_STRTAB && offset < s->sh_size) |
| return (char *) e + s->sh_offset + offset; |
| return 0; |
| } |
| |
| /* Request memory for segments and fill segments info */ |
| static grub_err_t |
| grub_efiemu_init_segments (grub_efiemu_segment_t *segs, const Elf_Ehdr *e) |
| { |
| unsigned i; |
| Elf_Shdr *s; |
| |
| for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); |
| i < e->e_shnum; |
| i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) |
| { |
| if (s->sh_flags & SHF_ALLOC) |
| { |
| grub_efiemu_segment_t seg; |
| seg = (grub_efiemu_segment_t) grub_malloc (sizeof (*seg)); |
| if (! seg) |
| return grub_errno; |
| |
| if (s->sh_size) |
| { |
| seg->handle |
| = grub_efiemu_request_memalign |
| (s->sh_addralign, s->sh_size, |
| s->sh_flags & SHF_EXECINSTR ? GRUB_EFI_RUNTIME_SERVICES_CODE |
| : GRUB_EFI_RUNTIME_SERVICES_DATA); |
| if (seg->handle < 0) |
| { |
| grub_free (seg); |
| return grub_errno; |
| } |
| seg->off = 0; |
| } |
| |
| /* |
| .text-physical doesn't need to be relocated when switching to |
| virtual mode |
| */ |
| if (!grub_strcmp (grub_efiemu_get_string (s->sh_name, e), |
| ".text-physical")) |
| seg->ptv_rel_needed = 0; |
| else |
| seg->ptv_rel_needed = 1; |
| seg->size = s->sh_size; |
| seg->section = i; |
| seg->next = *segs; |
| seg->srcptr = s; |
| *segs = seg; |
| } |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Count symbols and relocators and allocate/request memory for them */ |
| static grub_err_t |
| grub_efiemu_count_symbols (const Elf_Ehdr *e) |
| { |
| unsigned i; |
| Elf_Shdr *s; |
| int num = 0; |
| |
| /* Symbols */ |
| for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); |
| i < e->e_shnum; |
| i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) |
| if (s->sh_type == SHT_SYMTAB) |
| break; |
| |
| if (i == e->e_shnum) |
| return grub_error (GRUB_ERR_BAD_OS, N_("no symbol table")); |
| |
| grub_efiemu_nelfsyms = (unsigned) s->sh_size / (unsigned) s->sh_entsize; |
| grub_efiemu_elfsyms = (struct grub_efiemu_elf_sym *) |
| grub_malloc (sizeof (struct grub_efiemu_elf_sym) * grub_efiemu_nelfsyms); |
| |
| /* Relocators */ |
| for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); |
| i < e->e_shnum; |
| i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) |
| if (s->sh_type == SHT_REL || s->sh_type == SHT_RELA) |
| num += ((unsigned) s->sh_size) / ((unsigned) s->sh_entsize); |
| |
| grub_efiemu_request_symbols (num); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Fill grub_efiemu_elfsyms with symbol values */ |
| static grub_err_t |
| grub_efiemu_resolve_symbols (grub_efiemu_segment_t segs, Elf_Ehdr *e) |
| { |
| unsigned i; |
| Elf_Shdr *s; |
| Elf_Sym *sym; |
| const char *str; |
| Elf_Word size, entsize; |
| |
| grub_dprintf ("efiemu", "resolving symbols\n"); |
| |
| for (i = 0, s = (Elf_Shdr *) ((char *) e + e->e_shoff); |
| i < e->e_shnum; |
| i++, s = (Elf_Shdr *) ((char *) s + e->e_shentsize)) |
| if (s->sh_type == SHT_SYMTAB) |
| break; |
| |
| if (i == e->e_shnum) |
| return grub_error (GRUB_ERR_BAD_OS, N_("no symbol table")); |
| |
| sym = (Elf_Sym *) ((char *) e + s->sh_offset); |
| size = s->sh_size; |
| entsize = s->sh_entsize; |
| |
| s = (Elf_Shdr *) ((char *) e + e->e_shoff + e->e_shentsize * s->sh_link); |
| str = (char *) e + s->sh_offset; |
| |
| for (i = 0; |
| i < size / entsize; |
| i++, sym = (Elf_Sym *) ((char *) sym + entsize)) |
| { |
| unsigned char type = ELF_ST_TYPE (sym->st_info); |
| unsigned char bind = ELF_ST_BIND (sym->st_info); |
| int handle; |
| grub_off_t off; |
| grub_err_t err; |
| const char *name = str + sym->st_name; |
| grub_efiemu_elfsyms[i].section = sym->st_shndx; |
| switch (type) |
| { |
| case STT_NOTYPE: |
| /* Resolve a global symbol. */ |
| if (sym->st_name != 0 && sym->st_shndx == 0) |
| { |
| err = grub_efiemu_resolve_symbol (name, &handle, &off); |
| if (err) |
| return err; |
| grub_efiemu_elfsyms[i].handle = handle; |
| grub_efiemu_elfsyms[i].off = off; |
| } |
| else |
| sym->st_value = 0; |
| break; |
| |
| case STT_OBJECT: |
| err = grub_efiemu_get_section_addr (segs, sym->st_shndx, |
| &handle, &off); |
| if (err) |
| return err; |
| |
| off += sym->st_value; |
| if (bind != STB_LOCAL) |
| { |
| err = grub_efiemu_register_symbol (name, handle, off); |
| if (err) |
| return err; |
| } |
| grub_efiemu_elfsyms[i].handle = handle; |
| grub_efiemu_elfsyms[i].off = off; |
| break; |
| |
| case STT_FUNC: |
| err = grub_efiemu_get_section_addr (segs, sym->st_shndx, |
| &handle, &off); |
| if (err) |
| return err; |
| |
| off += sym->st_value; |
| if (bind != STB_LOCAL) |
| { |
| err = grub_efiemu_register_symbol (name, handle, off); |
| if (err) |
| return err; |
| } |
| grub_efiemu_elfsyms[i].handle = handle; |
| grub_efiemu_elfsyms[i].off = off; |
| break; |
| |
| case STT_SECTION: |
| err = grub_efiemu_get_section_addr (segs, sym->st_shndx, |
| &handle, &off); |
| if (err) |
| { |
| grub_efiemu_elfsyms[i].handle = 0; |
| grub_efiemu_elfsyms[i].off = 0; |
| grub_errno = GRUB_ERR_NONE; |
| break; |
| } |
| |
| grub_efiemu_elfsyms[i].handle = handle; |
| grub_efiemu_elfsyms[i].off = off; |
| break; |
| |
| case STT_FILE: |
| grub_efiemu_elfsyms[i].handle = 0; |
| grub_efiemu_elfsyms[i].off = 0; |
| break; |
| |
| default: |
| return grub_error (GRUB_ERR_BAD_MODULE, |
| "unknown symbol type `%d'", (int) type); |
| } |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Load runtime to the memory and request memory for definitive location*/ |
| grub_err_t |
| SUFFIX (grub_efiemu_loadcore_init) (void *core, const char *filename, |
| grub_size_t core_size, |
| grub_efiemu_segment_t *segments) |
| { |
| Elf_Ehdr *e = (Elf_Ehdr *) core; |
| grub_err_t err; |
| |
| if (e->e_type != ET_REL) |
| return grub_error (GRUB_ERR_BAD_MODULE, N_("this ELF file is not of the right type")); |
| |
| /* Make sure that every section is within the core. */ |
| if ((grub_size_t) core_size < e->e_shoff + (grub_uint32_t) e->e_shentsize * e->e_shnum) |
| return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), |
| filename); |
| |
| err = grub_efiemu_init_segments (segments, core); |
| if (err) |
| return err; |
| err = grub_efiemu_count_symbols (core); |
| if (err) |
| return err; |
| |
| grub_efiemu_request_symbols (1); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Load runtime definitively */ |
| grub_err_t |
| SUFFIX (grub_efiemu_loadcore_load) (void *core, |
| grub_size_t core_size |
| __attribute__ ((unused)), |
| grub_efiemu_segment_t segments) |
| { |
| grub_err_t err; |
| err = grub_efiemu_load_segments (segments, core); |
| if (err) |
| return err; |
| |
| err = grub_efiemu_resolve_symbols (segments, core); |
| if (err) |
| return err; |
| |
| err = SUFFIX (grub_arch_efiemu_relocate_symbols) (segments, |
| grub_efiemu_elfsyms, |
| core); |
| if (err) |
| return err; |
| |
| return GRUB_ERR_NONE; |
| } |