123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- /* elf.c - load ELF files */
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2003,2004,2005,2006,2007,2008,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/elf.h>
- #include <grub/elfload.h>
- #include <grub/file.h>
- #include <grub/gzio.h>
- #include <grub/misc.h>
- #include <grub/mm.h>
- GRUB_EXPORT(grub_elf32_load);
- GRUB_EXPORT(grub_elf64_load);
- GRUB_EXPORT(grub_elf_open);
- GRUB_EXPORT(grub_elf_close);
- GRUB_EXPORT(grub_elf_file);
- GRUB_EXPORT(grub_elf_is_elf32);
- GRUB_EXPORT(grub_elf_is_elf64);
- GRUB_EXPORT(grub_elf32_size);
- GRUB_EXPORT(grub_elf64_size);
- /* Check if EHDR is a valid ELF header. */
- static grub_err_t
- grub_elf_check_header (grub_elf_t elf)
- {
- Elf32_Ehdr *e = &elf->ehdr.ehdr32;
- if (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 grub_error (GRUB_ERR_BAD_OS, "invalid arch independent ELF magic");
- return GRUB_ERR_NONE;
- }
- grub_err_t
- grub_elf_close (grub_elf_t elf)
- {
- grub_file_t file = elf->file;
- grub_free (elf->phdrs);
- grub_free (elf);
- if (file)
- grub_file_close (file);
- return grub_errno;
- }
- grub_elf_t
- grub_elf_file (grub_file_t file)
- {
- grub_elf_t elf;
- elf = grub_zalloc (sizeof (*elf));
- if (! elf)
- return 0;
- elf->file = file;
- if (grub_file_seek (elf->file, 0) == (grub_off_t) -1)
- goto fail;
- if (grub_file_read (elf->file, &elf->ehdr, sizeof (elf->ehdr))
- != sizeof (elf->ehdr))
- {
- grub_error_push ();
- grub_error (GRUB_ERR_READ_ERROR, "cannot read ELF header");
- goto fail;
- }
- if (grub_elf_check_header (elf))
- goto fail;
- return elf;
- fail:
- grub_free (elf->phdrs);
- grub_free (elf);
- return 0;
- }
- grub_elf_t
- grub_elf_open (const char *name)
- {
- grub_file_t file;
- grub_elf_t elf;
- file = grub_gzfile_open (name, 1);
- if (! file)
- return 0;
- elf = grub_elf_file (file);
- if (! elf)
- grub_file_close (file);
- return elf;
- }
- /* 32-bit */
- int
- grub_elf_is_elf32 (grub_elf_t elf)
- {
- return elf->ehdr.ehdr32.e_ident[EI_CLASS] == ELFCLASS32;
- }
- static grub_err_t
- grub_elf32_load_phdrs (grub_elf_t elf)
- {
- grub_ssize_t phdrs_size;
- phdrs_size = elf->ehdr.ehdr32.e_phnum * elf->ehdr.ehdr32.e_phentsize;
- grub_dprintf ("elf", "Loading program headers at 0x%llx, size 0x%lx.\n",
- (unsigned long long) elf->ehdr.ehdr32.e_phoff,
- (unsigned long) phdrs_size);
- elf->phdrs = grub_malloc (phdrs_size);
- if (! elf->phdrs)
- return grub_errno;
- if ((grub_file_seek (elf->file, elf->ehdr.ehdr32.e_phoff) == (grub_off_t) -1)
- || (grub_file_read (elf->file, elf->phdrs, phdrs_size) != phdrs_size))
- {
- grub_error_push ();
- return grub_error (GRUB_ERR_READ_ERROR, "cannot read program headers");
- }
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- grub_elf32_phdr_iterate (grub_elf_t elf,
- int (*hook) (grub_elf_t, Elf32_Phdr *, void *closure),
- void *closure)
- {
- Elf32_Phdr *phdrs;
- unsigned int i;
- if (! elf->phdrs)
- if (grub_elf32_load_phdrs (elf))
- return grub_errno;
- phdrs = elf->phdrs;
- for (i = 0; i < elf->ehdr.ehdr32.e_phnum; i++)
- {
- Elf32_Phdr *phdr = phdrs + i;
- grub_dprintf ("elf",
- "Segment %u: type 0x%x paddr 0x%lx memsz 0x%lx "
- "filesz %lx\n",
- i, phdr->p_type,
- (unsigned long) phdr->p_paddr,
- (unsigned long) phdr->p_memsz,
- (unsigned long) phdr->p_filesz);
- if (hook (elf, phdr, closure))
- break;
- }
- return grub_errno;
- }
- struct grub_elf32_size_closure
- {
- Elf32_Addr segments_start;
- Elf32_Addr segments_end;
- int nr_phdrs;
- };
- /* Run through the program headers to calculate the total memory size we
- * should claim. */
- static int
- grub_elf32_calcsize (grub_elf_t _elf __attribute__ ((unused)),
- Elf32_Phdr *phdr, void *closure)
- {
- struct grub_elf32_size_closure *c = closure;
- /* Only consider loadable segments. */
- if (phdr->p_type != PT_LOAD)
- return 0;
- c->nr_phdrs++;
- if (phdr->p_paddr < c->segments_start)
- c->segments_start = phdr->p_paddr;
- if (phdr->p_paddr + phdr->p_memsz > c->segments_end)
- c->segments_end = phdr->p_paddr + phdr->p_memsz;
- return 0;
- }
- /* Calculate the amount of memory spanned by the segments. */
- grub_size_t
- grub_elf32_size (grub_elf_t elf, Elf32_Addr *base)
- {
- struct grub_elf32_size_closure c;
- c.segments_start = (Elf32_Addr) -1;
- c.segments_end = 0;
- c.nr_phdrs = 0;
- grub_elf32_phdr_iterate (elf, grub_elf32_calcsize, &c);
- if (base)
- *base = 0;
- if (c.nr_phdrs == 0)
- {
- grub_error (GRUB_ERR_BAD_OS, "no program headers present");
- return 0;
- }
- if (c.segments_end < c.segments_start)
- {
- /* Very bad addresses. */
- grub_error (GRUB_ERR_BAD_OS, "bad program header load addresses");
- return 0;
- }
- if (base)
- *base = c.segments_start;
- return c.segments_end - c.segments_start;
- }
- struct grub_elf32_load_closure
- {
- grub_addr_t load_base;
- grub_size_t load_size;
- grub_elf32_load_hook_t hook;
- void *closure;
- };
- static int
- grub_elf32_load_segment (grub_elf_t elf, Elf32_Phdr *phdr, void *closure)
- {
- struct grub_elf32_load_closure *c = closure;
- grub_addr_t load_addr;
- int do_load = 1;
- load_addr = phdr->p_paddr;
- if (c->hook && c->hook (phdr, &load_addr, &do_load, c->closure))
- return 1;
- if (! do_load)
- return 0;
- if (load_addr < c->load_base)
- c->load_base = load_addr;
- grub_dprintf ("elf", "Loading segment at 0x%llx, size 0x%llx\n",
- (unsigned long long) load_addr,
- (unsigned long long) phdr->p_memsz);
- if (grub_file_seek (elf->file, phdr->p_offset) == (grub_off_t) -1)
- {
- grub_error_push ();
- return grub_error (GRUB_ERR_BAD_OS,
- "invalid offset in program header.");
- }
- if (phdr->p_filesz)
- {
- grub_ssize_t read;
- read = grub_file_read (elf->file, (void *) load_addr, phdr->p_filesz);
- if (read != (grub_ssize_t) phdr->p_filesz)
- {
- /* XXX How can we free memory from `load_hook'? */
- grub_error_push ();
- return grub_error (GRUB_ERR_BAD_OS,
- "couldn't read segment from file: "
- "wanted 0x%lx bytes; read 0x%lx bytes.",
- phdr->p_filesz, read);
- }
- }
- if (phdr->p_filesz < phdr->p_memsz)
- grub_memset ((void *) (long) (load_addr + phdr->p_filesz),
- 0, phdr->p_memsz - phdr->p_filesz);
- c->load_size += phdr->p_memsz;
- return 0;
- }
- /* Load every loadable segment into memory specified by `_load_hook'. */
- grub_err_t
- grub_elf32_load (grub_elf_t _elf, grub_elf32_load_hook_t hook, void *closure,
- grub_addr_t *base, grub_size_t *size)
- {
- grub_err_t err;
- struct grub_elf32_load_closure c;
- c.hook = hook;
- c.closure = closure;
- c.load_base = (grub_addr_t) -1ULL;
- c.load_size = 0;
- err = grub_elf32_phdr_iterate (_elf, grub_elf32_load_segment, &c);
- if (base)
- *base = c.load_base;
- if (size)
- *size = c.load_size;
- return err;
- }
- /* 64-bit */
- int
- grub_elf_is_elf64 (grub_elf_t elf)
- {
- return elf->ehdr.ehdr64.e_ident[EI_CLASS] == ELFCLASS64;
- }
- static grub_err_t
- grub_elf64_load_phdrs (grub_elf_t elf)
- {
- grub_ssize_t phdrs_size;
- phdrs_size = elf->ehdr.ehdr64.e_phnum * elf->ehdr.ehdr64.e_phentsize;
- grub_dprintf ("elf", "Loading program headers at 0x%llx, size 0x%lx.\n",
- (unsigned long long) elf->ehdr.ehdr64.e_phoff,
- (unsigned long) phdrs_size);
- elf->phdrs = grub_malloc (phdrs_size);
- if (! elf->phdrs)
- return grub_errno;
- if ((grub_file_seek (elf->file, elf->ehdr.ehdr64.e_phoff) == (grub_off_t) -1)
- || (grub_file_read (elf->file, elf->phdrs, phdrs_size) != phdrs_size))
- {
- grub_error_push ();
- return grub_error (GRUB_ERR_READ_ERROR, "cannot read program headers");
- }
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- grub_elf64_phdr_iterate (grub_elf_t elf,
- int (*hook) (grub_elf_t, Elf64_Phdr *, void *closure),
- void *closure)
- {
- Elf64_Phdr *phdrs;
- unsigned int i;
- if (! elf->phdrs)
- if (grub_elf64_load_phdrs (elf))
- return grub_errno;
- phdrs = elf->phdrs;
- for (i = 0; i < elf->ehdr.ehdr64.e_phnum; i++)
- {
- Elf64_Phdr *phdr = phdrs + i;
- grub_dprintf ("elf",
- "Segment %u: type 0x%x paddr 0x%lx memsz 0x%lx "
- "filesz %lx\n",
- i, phdr->p_type,
- (unsigned long) phdr->p_paddr,
- (unsigned long) phdr->p_memsz,
- (unsigned long) phdr->p_filesz);
- if (hook (elf, phdr, closure))
- break;
- }
- return grub_errno;
- }
- struct grub_elf64_size_closure
- {
- Elf64_Addr segments_start;
- Elf64_Addr segments_end;
- int nr_phdrs;
- };
- /* Run through the program headers to calculate the total memory size we
- * should claim. */
- static int
- grub_elf64_calcsize (grub_elf_t _elf __attribute__ ((unused)),
- Elf64_Phdr *phdr, void *closure)
- {
- struct grub_elf64_size_closure *c = closure;
- /* Only consider loadable segments. */
- if (phdr->p_type != PT_LOAD)
- return 0;
- c->nr_phdrs++;
- if (phdr->p_paddr < c->segments_start)
- c->segments_start = phdr->p_paddr;
- if (phdr->p_paddr + phdr->p_memsz > c->segments_end)
- c->segments_end = phdr->p_paddr + phdr->p_memsz;
- return 0;
- }
- /* Calculate the amount of memory spanned by the segments. */
- grub_size_t
- grub_elf64_size (grub_elf_t elf, Elf64_Addr *base)
- {
- struct grub_elf64_size_closure c;
- c.segments_start = (Elf64_Addr) -1;
- c.segments_end = 0;
- c.nr_phdrs = 0;
- grub_elf64_phdr_iterate (elf, grub_elf64_calcsize, &c);
- if (base)
- *base = 0;
- if (c.nr_phdrs == 0)
- {
- grub_error (GRUB_ERR_BAD_OS, "no program headers present");
- return 0;
- }
- if (c.segments_end < c.segments_start)
- {
- /* Very bad addresses. */
- grub_error (GRUB_ERR_BAD_OS, "bad program header load addresses");
- return 0;
- }
- if (base)
- *base = c.segments_start;
- return c.segments_end - c.segments_start;
- }
- struct grub_elf64_load_closure
- {
- grub_addr_t load_base;
- grub_size_t load_size;
- grub_elf64_load_hook_t hook;
- void *closure;
- };
- static int
- grub_elf64_load_segment (grub_elf_t elf, Elf64_Phdr *phdr, void *closure)
- {
- struct grub_elf64_load_closure *c = closure;
- grub_addr_t load_addr;
- int do_load = 1;
- load_addr = phdr->p_paddr;
- if (c->hook && c->hook (phdr, &load_addr, &do_load, c->closure))
- return 1;
- if (! do_load)
- return 0;
- if (load_addr < c->load_base)
- c->load_base = load_addr;
- grub_dprintf ("elf", "Loading segment at 0x%llx, size 0x%llx\n",
- (unsigned long long) load_addr,
- (unsigned long long) phdr->p_memsz);
- if (grub_file_seek (elf->file, phdr->p_offset) == (grub_off_t) -1)
- {
- grub_error_push ();
- return grub_error (GRUB_ERR_BAD_OS,
- "invalid offset in program header.");
- }
- if (phdr->p_filesz)
- {
- grub_ssize_t read;
- read = grub_file_read (elf->file, (void *) load_addr, phdr->p_filesz);
- if (read != (grub_ssize_t) phdr->p_filesz)
- {
- /* XXX How can we free memory from `load_hook'? */
- grub_error_push ();
- return grub_error (GRUB_ERR_BAD_OS,
- "couldn't read segment from file: "
- "wanted 0x%lx bytes; read 0x%lx bytes.",
- phdr->p_filesz, read);
- }
- }
- if (phdr->p_filesz < phdr->p_memsz)
- grub_memset ((void *) (long) (load_addr + phdr->p_filesz),
- 0, phdr->p_memsz - phdr->p_filesz);
- c->load_size += phdr->p_memsz;
- return 0;
- }
- /* Load every loadable segment into memory specified by `_load_hook'. */
- grub_err_t
- grub_elf64_load (grub_elf_t _elf, grub_elf64_load_hook_t hook, void *closure,
- grub_addr_t *base, grub_size_t *size)
- {
- grub_err_t err;
- struct grub_elf64_load_closure c;
- c.hook = hook;
- c.closure = closure;
- c.load_base = (grub_addr_t) -1ULL;
- c.load_size = 0;
- err = grub_elf64_phdr_iterate (_elf, grub_elf64_load_segment, &c);
- if (base)
- *base = c.load_base;
- if (size)
- *size = c.load_size;
- return err;
- }
|