char *buffer, struct multiboot_header *header)
{
grub_err_t err;
+ mbi_load_data_t mld;
+
+ mld.file = file;
+ mld.filename = filename;
+ mld.buffer = buffer;
+ mld.mbi_ver = 1;
+ mld.relocatable = 0;
+ mld.avoid_efi_boot_services = 0;
+
if (grub_multiboot_quirks & GRUB_MULTIBOOT_QUIRK_BAD_KLUDGE)
{
- err = grub_multiboot_load_elf (file, filename, buffer);
+ err = grub_multiboot_load_elf (&mld);
if (err == GRUB_ERR_NONE) {
return GRUB_ERR_NONE;
}
return GRUB_ERR_NONE;
}
- return grub_multiboot_load_elf (file, filename, buffer);
+ return grub_multiboot_load_elf (&mld);
}
static struct multiboot_header *
/* Load ELF32 or ELF64. */
grub_err_t
-grub_multiboot_load_elf (grub_file_t file, const char *filename,
- void *buffer)
+grub_multiboot_load_elf (mbi_load_data_t *mld)
{
- if (grub_multiboot_is_elf32 (buffer))
- return grub_multiboot_load_elf32 (file, filename, buffer);
- else if (grub_multiboot_is_elf64 (buffer))
- return grub_multiboot_load_elf64 (file, filename, buffer);
+ if (grub_multiboot_is_elf32 (mld->buffer))
+ return grub_multiboot_load_elf32 (mld);
+ else if (grub_multiboot_is_elf64 (mld->buffer))
+ return grub_multiboot_load_elf64 (mld);
return grub_error (GRUB_ERR_UNKNOWN_OS, N_("invalid arch-dependent ELF magic"));
}
}
static grub_err_t
-CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, const char *filename, 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;
+ void *source;
if (ehdr->e_ident[EI_MAG0] != ELFMAG0
|| ehdr->e_ident[EI_MAG1] != ELFMAG1
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");
- phdr_base = (char *) buffer + ehdr->e_phoff;
+ 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
+
+ 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;
-
- if (phdr(i)->p_paddr + phdr(i)->p_memsz > highest_load)
- highest_load = phdr(i)->p_paddr + phdr(i)->p_memsz;
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_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)
{
if (!grub_errno)
grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
- filename);
+ 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);
}
}
if (!shdr)
return grub_errno;
- if (grub_file_seek (file, ehdr->e_shoff) == (grub_off_t) -1)
+ 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, (grub_uint32_t) 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)
{
if (!grub_errno)
grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
- filename);
+ mld->filename);
return grub_errno;
}
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. */
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,
- 0);
- 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)
+ 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 (file, src, sh->sh_size)
+ if (grub_file_read (mld->file, src, sh->sh_size)
!= (grub_ssize_t) sh->sh_size)
{
if (!grub_errno)
grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"),
- filename);
+ mld->filename);
return grub_errno;
}
sh->sh_addr = target;
static unsigned elf_sec_shstrndx;
static void *elf_sections;
static int keep_bs = 0;
+static grub_uint32_t load_base_addr;
void
grub_multiboot_add_elfsyms (grub_size_t num, grub_size_t entsize,
grub_err_t
grub_multiboot_load (grub_file_t file, const char *filename)
{
- grub_properly_aligned_t *buffer;
grub_ssize_t len;
struct multiboot_header *header;
grub_err_t err;
struct multiboot_header_tag *tag;
struct multiboot_header_tag_address *addr_tag = NULL;
+ struct multiboot_header_tag_relocatable *rel_tag;
int entry_specified = 0, efi_entry_specified = 0;
grub_addr_t entry = 0, efi_entry = 0;
grub_uint32_t console_required = 0;
struct multiboot_header_tag_framebuffer *fbtag = NULL;
int accepted_consoles = GRUB_MULTIBOOT_CONSOLE_EGA_TEXT;
+ mbi_load_data_t mld;
- buffer = grub_malloc (MULTIBOOT_SEARCH);
- if (!buffer)
+ mld.mbi_ver = 2;
+ mld.relocatable = 0;
+
+ mld.buffer = grub_malloc (MULTIBOOT_SEARCH);
+ if (!mld.buffer)
return grub_errno;
- len = grub_file_read (file, buffer, MULTIBOOT_SEARCH);
+ len = grub_file_read (file, mld.buffer, MULTIBOOT_SEARCH);
if (len < 32)
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), filename);
}
COMPILE_TIME_ASSERT (MULTIBOOT_HEADER_ALIGN % 4 == 0);
- header = find_header (buffer, len);
+ header = find_header (mld.buffer, len);
if (header == 0)
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_error (GRUB_ERR_BAD_ARGUMENT, "no multiboot header found");
}
case MULTIBOOT_TAG_TYPE_EFI_BS:
case MULTIBOOT_TAG_TYPE_EFI32_IH:
case MULTIBOOT_TAG_TYPE_EFI64_IH:
+ case MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR:
break;
default:
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_error (GRUB_ERR_UNKNOWN_OS,
"unsupported information tag: 0x%x",
request_tag->requests[i]);
accepted_consoles |= GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER;
break;
+ case MULTIBOOT_HEADER_TAG_RELOCATABLE:
+ mld.relocatable = 1;
+ rel_tag = (struct multiboot_header_tag_relocatable *) tag;
+ mld.min_addr = rel_tag->min_addr;
+ mld.max_addr = rel_tag->max_addr;
+ mld.align = rel_tag->align;
+ switch (rel_tag->preference)
+ {
+ case MULTIBOOT_LOAD_PREFERENCE_LOW:
+ mld.preference = GRUB_RELOCATOR_PREFERENCE_LOW;
+ break;
+
+ case MULTIBOOT_LOAD_PREFERENCE_HIGH:
+ mld.preference = GRUB_RELOCATOR_PREFERENCE_HIGH;
+ break;
+
+ default:
+ mld.preference = GRUB_RELOCATOR_PREFERENCE_NONE;
+ }
+ break;
+
/* GRUB always page-aligns modules. */
case MULTIBOOT_HEADER_TAG_MODULE_ALIGN:
break;
default:
if (! (tag->flags & MULTIBOOT_HEADER_TAG_OPTIONAL))
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_error (GRUB_ERR_UNKNOWN_OS,
"unsupported tag: 0x%x", tag->type);
}
if (addr_tag && !entry_specified && !(keep_bs && efi_entry_specified))
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_error (GRUB_ERR_UNKNOWN_OS,
"load address tag without entry address tag");
}
{
grub_uint64_t load_addr = (addr_tag->load_addr + 1)
? addr_tag->load_addr : (addr_tag->header_addr
- - ((char *) header - (char *) buffer));
- int offset = ((char *) header - (char *) buffer -
+ - ((char *) header - (char *) mld.buffer));
+ int offset = ((char *) header - (char *) mld.buffer -
(addr_tag->header_addr - load_addr));
int load_size = ((addr_tag->load_end_addr == 0) ? file->size - offset :
addr_tag->load_end_addr - addr_tag->load_addr);
else
code_size = load_size;
- err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator,
- &ch, load_addr,
- code_size);
+ if (mld.relocatable)
+ {
+ if (code_size > mld.max_addr || mld.min_addr > mld.max_addr - code_size)
+ {
+ grub_free (mld.buffer);
+ 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 - code_size,
+ code_size, mld.align ? mld.align : 1,
+ mld.preference, keep_bs);
+ }
+ else
+ err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator,
+ &ch, load_addr, code_size);
if (err)
{
grub_dprintf ("multiboot_loader", "Error loading aout kludge\n");
- grub_free (buffer);
+ grub_free (mld.buffer);
return err;
}
+ mld.link_base_addr = load_addr;
+ 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%lx, relocatable=%d\n", mld.link_base_addr,
+ mld.load_base_addr, (long) code_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, keep_bs);
+
if ((grub_file_seek (file, offset)) == (grub_off_t) -1)
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_errno;
}
grub_file_read (file, source, load_size);
if (grub_errno)
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return grub_errno;
}
}
else
{
- err = grub_multiboot_load_elf (file, filename, buffer);
+ mld.file = file;
+ mld.filename = filename;
+ mld.avoid_efi_boot_services = keep_bs;
+ err = grub_multiboot_load_elf (&mld);
if (err)
{
- grub_free (buffer);
+ grub_free (mld.buffer);
return err;
}
}
+ load_base_addr = mld.load_base_addr;
+
if (keep_bs && efi_entry_specified)
grub_multiboot_payload_eip = efi_entry;
else if (entry_specified)
grub_multiboot_payload_eip = entry;
+ if (mld.relocatable)
+ {
+ /*
+ * Both branches are mathematically equivalent. However, it looks
+ * that real life (C?) is more complicated. I am trying to avoid
+ * wrap around here if mld.load_base_addr < mld.link_base_addr.
+ * If you look at C operator precedence then everything should work.
+ * However, I am not 100% sure that a given compiler will not
+ * optimize/break this stuff. So, maybe we should use signed
+ * 64-bit int here.
+ */
+ if (mld.load_base_addr >= mld.link_base_addr)
+ grub_multiboot_payload_eip += mld.load_base_addr - mld.link_base_addr;
+ else
+ grub_multiboot_payload_eip -= mld.link_base_addr - mld.load_base_addr;
+ }
+
if (fbtag)
err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER,
accepted_consoles,
+ ALIGN_UP (sizeof (struct multiboot_tag_framebuffer), MULTIBOOT_TAG_ALIGN)
+ ALIGN_UP (sizeof (struct multiboot_tag_old_acpi)
+ sizeof (struct grub_acpi_rsdp_v10), MULTIBOOT_TAG_ALIGN)
+ + ALIGN_UP (sizeof (struct multiboot_tag_load_base_addr), MULTIBOOT_TAG_ALIGN)
+ acpiv2_size ()
+ net_size ()
#ifdef GRUB_MACHINE_EFI
% sizeof (grub_properly_aligned_t) == 0);
ptrorig += (2 * sizeof (grub_uint32_t)) / sizeof (grub_properly_aligned_t);
+ {
+ struct multiboot_tag_load_base_addr *tag = (struct multiboot_tag_load_base_addr *) ptrorig;
+ tag->type = MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR;
+ tag->size = sizeof (struct multiboot_tag_load_base_addr);
+ tag->load_base_addr = load_base_addr;
+ ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN)
+ / sizeof (grub_properly_aligned_t);
+ }
+
{
struct multiboot_tag_string *tag = (struct multiboot_tag_string *) ptrorig;
tag->type = MULTIBOOT_TAG_TYPE_CMDLINE;
int console_required);
grub_err_t
grub_multiboot_load (grub_file_t file, const char *filename);
+
+struct mbi_load_data
+{
+ grub_file_t file;
+ const char *filename;
+ void *buffer;
+ unsigned int mbi_ver;
+ int relocatable;
+ grub_uint32_t min_addr;
+ grub_uint32_t max_addr;
+ grub_size_t align;
+ grub_uint32_t preference;
+ grub_uint32_t link_base_addr;
+ grub_uint32_t load_base_addr;
+ int avoid_efi_boot_services;
+};
+typedef struct mbi_load_data mbi_load_data_t;
+
/* Load ELF32 or ELF64. */
grub_err_t
-grub_multiboot_load_elf (grub_file_t file, const char *filename,
- void *buffer);
+grub_multiboot_load_elf (mbi_load_data_t *mld);
+
extern grub_size_t grub_multiboot_pure_size;
extern grub_size_t grub_multiboot_alloc_mbi;
extern grub_uint32_t grub_multiboot_payload_eip;
#define MULTIBOOT_TAG_TYPE_EFI_BS 18
#define MULTIBOOT_TAG_TYPE_EFI32_IH 19
#define MULTIBOOT_TAG_TYPE_EFI64_IH 20
+#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21
#define MULTIBOOT_HEADER_TAG_END 0
#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1
#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6
#define MULTIBOOT_HEADER_TAG_EFI_BS 7
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9
+#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10
#define MULTIBOOT_ARCHITECTURE_I386 0
#define MULTIBOOT_ARCHITECTURE_MIPS32 4
#define MULTIBOOT_HEADER_TAG_OPTIONAL 1
+#define MULTIBOOT_LOAD_PREFERENCE_NONE 0
+#define MULTIBOOT_LOAD_PREFERENCE_LOW 1
+#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2
+
#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1
#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2
multiboot_uint32_t size;
};
+struct multiboot_header_tag_relocatable
+{
+ multiboot_uint16_t type;
+ multiboot_uint16_t flags;
+ multiboot_uint32_t size;
+ multiboot_uint32_t min_addr;
+ multiboot_uint32_t max_addr;
+ multiboot_uint32_t align;
+ multiboot_uint32_t preference;
+};
+
struct multiboot_color
{
multiboot_uint8_t red;
multiboot_uint64_t pointer;
};
+struct multiboot_tag_load_base_addr
+{
+ multiboot_uint32_t type;
+ multiboot_uint32_t size;
+ multiboot_uint32_t load_base_addr;
+};
+
#endif /* ! ASM_FILE */
#endif /* ! MULTIBOOT_HEADER */