]> git.proxmox.com Git - grub2.git/blobdiff - grub-core/loader/multiboot_elfxx.c
multiboot2: Add support for relocatable images
[grub2.git] / grub-core / loader / multiboot_elfxx.c
index 024b4474741ae8bccb447502539f14d2f831a6f9..5e649ed2545bee3fbe9602c9d97079160dc6420a 100644 (file)
 # define ELFCLASSXX    ELFCLASS32
 # define Elf_Ehdr      Elf32_Ehdr
 # define Elf_Phdr      Elf32_Phdr
+# define Elf_Shdr      Elf32_Shdr
 #elif defined(MULTIBOOT_LOAD_ELF64)
 # define XX            64
 # define E_MACHINE     MULTIBOOT_ELF64_MACHINE
 # define ELFCLASSXX    ELFCLASS64
 # define Elf_Ehdr      Elf64_Ehdr
 # define Elf_Phdr      Elf64_Phdr
+# define Elf_Shdr      Elf64_Shdr
 #else
 #error "I'm confused"
 #endif
@@ -37,6 +39,8 @@
 #define CONCAT(a,b)    CONCAT_(a, b)
 #define CONCAT_(a,b)   a ## b
 
+#pragma GCC diagnostic ignored "-Wcast-align"
+
 /* Check if BUFFER contains ELF32 (or ELF64).  */
 static int
 CONCAT(grub_multiboot_is_elf, XX) (void *buffer)
@@ -47,83 +51,114 @@ CONCAT(grub_multiboot_is_elf, XX) (void *buffer)
 }
 
 static grub_err_t
-CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, void *buffer)
+CONCAT(grub_multiboot_load_elf, XX) (mbi_load_data_t *mld)
 {
-  Elf_Ehdr *ehdr = (Elf_Ehdr *) buffer;
+  Elf_Ehdr *ehdr = (Elf_Ehdr *) mld->buffer;
   char *phdr_base;
+  grub_err_t err;
+  grub_relocator_chunk_t ch;
+  grub_uint32_t load_offset, load_size;
   int i;
-
-  if (ehdr->e_ident[EI_CLASS] != ELFCLASSXX)
-    return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF class");
+  void *source;
 
   if (ehdr->e_ident[EI_MAG0] != ELFMAG0
       || ehdr->e_ident[EI_MAG1] != ELFMAG1
       || ehdr->e_ident[EI_MAG2] != ELFMAG2
       || ehdr->e_ident[EI_MAG3] != ELFMAG3
-      || ehdr->e_version != EV_CURRENT
-      || ehdr->e_ident[EI_DATA] != ELFDATA2LSB
-      || ehdr->e_machine != E_MACHINE)
-    return grub_error(GRUB_ERR_UNKNOWN_OS, "no valid ELF header found");
+      || ehdr->e_ident[EI_DATA] != ELFDATA2LSB)
+    return grub_error(GRUB_ERR_UNKNOWN_OS, N_("invalid arch-independent ELF magic"));
+
+  if (ehdr->e_ident[EI_CLASS] != ELFCLASSXX || ehdr->e_machine != E_MACHINE
+      || ehdr->e_version != EV_CURRENT)
+    return grub_error (GRUB_ERR_UNKNOWN_OS, N_("invalid arch-dependent ELF magic"));
 
   if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
-    return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF file type");
+    return grub_error (GRUB_ERR_UNKNOWN_OS, N_("this ELF file is not of the right type"));
 
   /* FIXME: Should we support program headers at strange locations?  */
-  if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > MULTIBOOT_SEARCH)
+  if (ehdr->e_phoff + (grub_uint32_t) ehdr->e_phnum * ehdr->e_phentsize > MULTIBOOT_SEARCH)
     return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset");
 
-#if defined (MULTIBOOT_LOAD_ELF64) && defined (__mips)
-  /* We still in 32-bit mode.  */
-  if (ehdr->e_entry < 0xffffffff80000000ULL)
-    return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64");
-#else
-  /* We still in 32-bit mode.  */
-  if (ehdr->e_entry > 0xffffffff)
-    return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64");
+  phdr_base = (char *) mld->buffer + ehdr->e_phoff;
+#define phdr(i)                        ((Elf_Phdr *) (phdr_base + (i) * ehdr->e_phentsize))
+
+  mld->link_base_addr = ~0;
+
+  /* Calculate lowest and highest load address.  */
+  for (i = 0; i < ehdr->e_phnum; i++)
+    if (phdr(i)->p_type == PT_LOAD)
+      {
+       mld->link_base_addr = grub_min (mld->link_base_addr, phdr(i)->p_paddr);
+       highest_load = grub_max (highest_load, phdr(i)->p_paddr + phdr(i)->p_memsz);
+      }
+
+#ifdef MULTIBOOT_LOAD_ELF64
+  if (highest_load >= 0x100000000)
+    return grub_error (GRUB_ERR_BAD_OS, "segment crosses 4 GiB border");
 #endif
 
-  phdr_base = (char *) buffer + ehdr->e_phoff;
-#define phdr(i)                        ((Elf_Phdr *) (phdr_base + (i) * ehdr->e_phentsize))
+  load_size = highest_load - mld->link_base_addr;
+
+  if (mld->relocatable)
+    {
+      if (load_size > mld->max_addr || mld->min_addr > mld->max_addr - load_size)
+       return grub_error (GRUB_ERR_BAD_OS, "invalid min/max address and/or load size");
+
+      err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch,
+                                             mld->min_addr, mld->max_addr - load_size,
+                                             load_size, mld->align ? mld->align : 1,
+                                             mld->preference, mld->avoid_efi_boot_services);
+    }
+  else
+    err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator, &ch,
+                                          mld->link_base_addr, load_size);
+
+  if (err)
+    {
+      grub_dprintf ("multiboot_loader", "Cannot allocate memory for OS image\n");
+      return err;
+    }
+
+  mld->load_base_addr = get_physical_target_address (ch);
+  source = get_virtual_current_address (ch);
+
+  grub_dprintf ("multiboot_loader", "link_base_addr=0x%x, load_base_addr=0x%x, "
+               "load_size=0x%x, relocatable=%d\n", mld->link_base_addr,
+               mld->load_base_addr, load_size, mld->relocatable);
+
+  if (mld->relocatable)
+    grub_dprintf ("multiboot_loader", "align=0x%lx, preference=0x%x, avoid_efi_boot_services=%d\n",
+                 (long) mld->align, mld->preference, mld->avoid_efi_boot_services);
 
   /* Load every loadable segment in memory.  */
   for (i = 0; i < ehdr->e_phnum; i++)
     {
       if (phdr(i)->p_type == PT_LOAD)
         {
-         grub_err_t err;
-         void *source;
 
          grub_dprintf ("multiboot_loader", "segment %d: paddr=0x%lx, memsz=0x%lx, vaddr=0x%lx\n",
                        i, (long) phdr(i)->p_paddr, (long) phdr(i)->p_memsz, (long) phdr(i)->p_vaddr);
 
-         {
-           grub_relocator_chunk_t ch;
-           err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator, 
-                                                  &ch, phdr(i)->p_paddr,
-                                                  phdr(i)->p_memsz);
-           if (err)
-             {
-               grub_dprintf ("multiboot_loader", "Error loading phdr %d\n", i);
-               return err;
-             }
-           source = get_virtual_current_address (ch);
-         }
+         load_offset = phdr(i)->p_paddr - mld->link_base_addr;
 
          if (phdr(i)->p_filesz != 0)
            {
-             if (grub_file_seek (file, (grub_off_t) phdr(i)->p_offset)
+             if (grub_file_seek (mld->file, (grub_off_t) phdr(i)->p_offset)
                  == (grub_off_t) -1)
-               return grub_error (GRUB_ERR_BAD_OS,
-                                  "invalid offset in program header");
+               return grub_errno;
 
-             if (grub_file_read (file, source, phdr(i)->p_filesz)
+             if (grub_file_read (mld->file, (grub_uint8_t *) source + load_offset, phdr(i)->p_filesz)
                  != (grub_ssize_t) phdr(i)->p_filesz)
-               return grub_error (GRUB_ERR_BAD_OS,
-                                  "couldn't read segment from file");
+               {
+                 if (!grub_errno)
+                   grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
+                               mld->filename);
+                 return grub_errno;
+               }
            }
 
           if (phdr(i)->p_filesz < phdr(i)->p_memsz)
-            grub_memset ((grub_uint8_t *) source + phdr(i)->p_filesz, 0,
+            grub_memset ((grub_uint8_t *) source + load_offset + phdr(i)->p_filesz, 0,
                         phdr(i)->p_memsz - phdr(i)->p_filesz);
         }
     }
@@ -134,28 +169,55 @@ CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, void *buffer)
       {
        grub_multiboot_payload_eip = (ehdr->e_entry - phdr(i)->p_vaddr)
          + phdr(i)->p_paddr;
+#ifdef MULTIBOOT_LOAD_ELF64
+# ifdef __mips
+  /* We still in 32-bit mode.  */
+  if ((ehdr->e_entry - phdr(i)->p_vaddr)
+      + phdr(i)->p_paddr < 0xffffffff80000000ULL)
+    return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64");
+# else
+  /* We still in 32-bit mode.  */
+  if ((ehdr->e_entry - phdr(i)->p_vaddr)
+      + phdr(i)->p_paddr > 0xffffffff)
+    return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64");
+# endif
+#endif
        break;
       }
 
   if (i == ehdr->e_phnum)
     return grub_error (GRUB_ERR_BAD_OS, "entry point isn't in a segment");
 
+#if defined (__i386__) || defined (__x86_64__)
+  
+#elif defined (__mips)
+  grub_multiboot_payload_eip |= 0x80000000;
+#else
+#error Please complete this
+#endif
+
   if (ehdr->e_shnum)
     {
       grub_uint8_t *shdr, *shdrptr;
 
-      shdr = grub_malloc (ehdr->e_shnum * ehdr->e_shentsize);
+      shdr = grub_malloc ((grub_uint32_t) ehdr->e_shnum * ehdr->e_shentsize);
       if (!shdr)
        return grub_errno;
       
-      if (grub_file_seek (file, ehdr->e_shoff) == (grub_off_t) -1)
-       return grub_error (GRUB_ERR_BAD_OS,
-                          "invalid offset to section headers");
+      if (grub_file_seek (mld->file, ehdr->e_shoff) == (grub_off_t) -1)
+       {
+         grub_free (shdr);
+         return grub_errno;
+       }
 
-      if (grub_file_read (file, shdr, ehdr->e_shnum * ehdr->e_shentsize)
+      if (grub_file_read (mld->file, shdr, (grub_uint32_t) ehdr->e_shnum * ehdr->e_shentsize)
               != (grub_ssize_t) ehdr->e_shnum * ehdr->e_shentsize)
-       return grub_error (GRUB_ERR_BAD_OS,
-                          "couldn't read sections headers from file");
+       {
+         if (!grub_errno)
+           grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
+                       mld->filename);
+         return grub_errno;
+       }
       
       for (shdrptr = shdr, i = 0; i < ehdr->e_shnum;
           shdrptr += ehdr->e_shentsize, i++)
@@ -163,7 +225,9 @@ CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, void *buffer)
          Elf_Shdr *sh = (Elf_Shdr *) shdrptr;
          void *src;
          grub_addr_t target;
-         grub_err_t err;
+
+         if (mld->mbi_ver >= 2 && (sh->sh_type == SHT_REL || sh->sh_type == SHT_RELA))
+           return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "ELF files with relocs are not supported yet");
 
          /* This section is a loaded section,
             so we don't care.  */
@@ -174,31 +238,30 @@ CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, void *buffer)
          if (sh->sh_size == 0)
            continue;
 
-         {
-           grub_relocator_chunk_t ch;
-           err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator,
-                                                   &ch, 0,
-                                                   (0xffffffff - sh->sh_size)
-                                                   + 1, sh->sh_size,
-                                                   sh->sh_addralign,
-                                                   GRUB_RELOCATOR_PREFERENCE_NONE);
-           if (err)
-             {
-               grub_dprintf ("multiboot_loader", "Error loading shdr %d\n", i);
-               return err;
-             }
-           src = get_virtual_current_address (ch);
-           target = get_physical_target_address (ch);
-         }
-
-         if (grub_file_seek (file, sh->sh_offset) == (grub_off_t) -1)
-           return grub_error (GRUB_ERR_BAD_OS,
-                              "invalid offset in section header");
-
-          if (grub_file_read (file, src, sh->sh_size)
+         err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch, 0,
+                                                 (0xffffffff - sh->sh_size) + 1,
+                                                 sh->sh_size, sh->sh_addralign,
+                                                 GRUB_RELOCATOR_PREFERENCE_NONE,
+                                                 mld->avoid_efi_boot_services);
+         if (err)
+           {
+             grub_dprintf ("multiboot_loader", "Error loading shdr %d\n", i);
+             return err;
+           }
+         src = get_virtual_current_address (ch);
+         target = get_physical_target_address (ch);
+
+         if (grub_file_seek (mld->file, sh->sh_offset) == (grub_off_t) -1)
+           return grub_errno;
+
+          if (grub_file_read (mld->file, src, sh->sh_size)
               != (grub_ssize_t) sh->sh_size)
-           return grub_error (GRUB_ERR_BAD_OS,
-                              "couldn't read segment from file");
+           {
+             if (!grub_errno)
+               grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
+                           mld->filename);
+             return grub_errno;
+           }
          sh->sh_addr = target;
        }
       grub_multiboot_add_elfsyms (ehdr->e_shnum, ehdr->e_shentsize,
@@ -215,3 +278,4 @@ CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, void *buffer)
 #undef ELFCLASSXX
 #undef Elf_Ehdr
 #undef Elf_Phdr
+#undef Elf_Shdr