From a927cc738309279c868ea9feeea535adc74b4c64 Mon Sep 17 00:00:00 2001 From: robertmh Date: Sat, 2 Aug 2008 12:12:14 +0000 Subject: [PATCH] 2008-08-02 Robert Millan * loader/i386/pc/multiboot.c (playground, forward_relocator) (backward_relocator): New variables. Used to allocate and relocate the payload, respectively. (grub_multiboot_load_elf32): Load into heap instead of requested address, install the appropiate relocator code in each bound of the payload, and set the entry point such that grub_multiboot_real_boot() will jump to one of them. * kern/i386/loader.S (grub_multiboot_payload_size) (grub_multiboot_payload_orig, grub_multiboot_payload_dest) (grub_multiboot_payload_entry_offset): New variables. (grub_multiboot_real_boot): Set cpu context to what the relocator expects, and jump to the relocator instead of the payload. * include/grub/i386/loader.h (grub_multiboot_payload_size) (grub_multiboot_payload_orig, grub_multiboot_payload_dest) (grub_multiboot_payload_entry_offset): Export. --- ChangeLog | 20 ++++++++++ include/grub/i386/loader.h | 4 ++ kern/i386/loader.S | 24 ++++++++--- loader/i386/pc/multiboot.c | 82 ++++++++++++++++++++++++++++---------- 4 files changed, 104 insertions(+), 26 deletions(-) diff --git a/ChangeLog b/ChangeLog index b4478f76f..e8e55a957 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2008-08-02 Robert Millan + + * loader/i386/pc/multiboot.c (playground, forward_relocator) + (backward_relocator): New variables. Used to allocate and relocate + the payload, respectively. + (grub_multiboot_load_elf32): Load into heap instead of requested + address, install the appropiate relocator code in each bound of + the payload, and set the entry point such that + grub_multiboot_real_boot() will jump to one of them. + + * kern/i386/loader.S (grub_multiboot_payload_size) + (grub_multiboot_payload_orig, grub_multiboot_payload_dest) + (grub_multiboot_payload_entry_offset): New variables. + (grub_multiboot_real_boot): Set cpu context to what the relocator + expects, and jump to the relocator instead of the payload. + + * include/grub/i386/loader.h (grub_multiboot_payload_size) + (grub_multiboot_payload_orig, grub_multiboot_payload_dest) + (grub_multiboot_payload_entry_offset): Export. + 2008-08-01 Bean * normal/menu_entry.c (editor_getline): Don't return the original diff --git a/include/grub/i386/loader.h b/include/grub/i386/loader.h index b120207a9..2a2325c93 100644 --- a/include/grub/i386/loader.h +++ b/include/grub/i386/loader.h @@ -43,6 +43,10 @@ void EXPORT_FUNC(grub_multiboot2_real_boot) (grub_addr_t entry, void EXPORT_FUNC(grub_unix_real_boot) (grub_addr_t entry, ...) __attribute__ ((cdecl,noreturn)); +extern grub_addr_t EXPORT_VAR(grub_multiboot_payload_orig); +extern grub_addr_t EXPORT_VAR(grub_multiboot_payload_dest); +extern grub_size_t EXPORT_VAR(grub_multiboot_payload_size); +extern grub_uint32_t EXPORT_VAR(grub_multiboot_payload_entry_offset); /* It is necessary to export these functions, because normal mode commands reuse rescue mode commands. */ diff --git a/kern/i386/loader.S b/kern/i386/loader.S index 39cf6a001..8a0f45c4b 100644 --- a/kern/i386/loader.S +++ b/kern/i386/loader.S @@ -123,6 +123,15 @@ linux_setup_seg: * This starts the multiboot kernel. */ +VARIABLE(grub_multiboot_payload_size) + .long 0 +VARIABLE(grub_multiboot_payload_orig) + .long 0 +VARIABLE(grub_multiboot_payload_dest) + .long 0 +VARIABLE(grub_multiboot_payload_entry_offset) + .long 0 + FUNCTION(grub_multiboot_real_boot) /* Push the entry address on the stack. */ pushl %eax @@ -136,11 +145,16 @@ FUNCTION(grub_multiboot_real_boot) /* Interrupts should be disabled. */ cli - /* Move the magic value into eax and jump to the kernel. */ - movl $MULTIBOOT_MAGIC2,%eax - popl %ecx - jmp *%ecx - + /* Where do we copy what from. */ + movl EXT_C(grub_multiboot_payload_size), %ecx + movl EXT_C(grub_multiboot_payload_orig), %esi + movl EXT_C(grub_multiboot_payload_dest), %edi + movl EXT_C(grub_multiboot_payload_entry_offset), %eax + + /* Jump to the relocator. */ + popl %edx + jmp *%edx + /* * This starts the multiboot 2 kernel. */ diff --git a/loader/i386/pc/multiboot.c b/loader/i386/pc/multiboot.c index 0e336cbf9..d39dea857 100644 --- a/loader/i386/pc/multiboot.c +++ b/loader/i386/pc/multiboot.c @@ -50,6 +50,33 @@ extern grub_dl_t my_mod; static struct grub_multiboot_info *mbi; static grub_addr_t entry; +static char *playground = NULL; + +static grub_uint8_t forward_relocator[] = +{ + 0xfc, /* cld */ + 0x89, 0xf2, /* movl %esi, %edx */ + 0xf3, 0xa4, /* rep movsb */ + 0x01, 0xc2, /* addl %eax, %edx */ + 0xb8, 0x02, 0xb0, 0xad, 0x2b, /* movl $MULTIBOOT_MAGIC2, %eax */ + 0xff, 0xe2, /* jmp *%edx */ +}; + +static grub_uint8_t backward_relocator[] = +{ + 0xfd, /* std */ + 0x01, 0xce, /* addl %ecx, %esi */ + 0x01, 0xcf, /* addl %ecx, %edi */ + /* backward movsb is implicitly off-by-one. compensate that. */ + 0x41, /* incl %ecx */ + 0xf3, 0xa4, /* rep movsb */ + /* same problem again. */ + 0x47, /* incl %edi */ + 0x01, 0xc7, /* addl %eax, %edi */ + 0xb8, 0x02, 0xb0, 0xad, 0x2b, /* movl $MULTIBOOT_MAGIC2, %eax */ + 0xff, 0xe7, /* jmp *%edi */ +}; + static grub_err_t grub_multiboot_boot (void) { @@ -99,6 +126,7 @@ grub_multiboot_load_elf32 (grub_file_t file, void *buffer) Elf32_Ehdr *ehdr = (Elf32_Ehdr *) buffer; char *phdr_base; grub_addr_t physical_entry_addr = 0; + int lowest_segment = 0, highest_segment = 0; int i; if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) @@ -114,50 +142,62 @@ grub_multiboot_load_elf32 (grub_file_t file, void *buffer) if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > MULTIBOOT_SEARCH) return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset"); - entry = ehdr->e_entry; - phdr_base = (char *) buffer + ehdr->e_phoff; #define phdr(i) ((Elf32_Phdr *) (phdr_base + (i) * ehdr->e_phentsize)) + for (i = 0; i < ehdr->e_phnum; i++) + if (phdr(i)->p_type == PT_LOAD) + { + if (phdr(i)->p_paddr < phdr(lowest_segment)->p_paddr) + lowest_segment = i; + if (phdr(i)->p_paddr > phdr(highest_segment)->p_paddr) + highest_segment = i; + } + grub_multiboot_payload_size = (phdr(highest_segment)->p_paddr + phdr(highest_segment)->p_memsz) - phdr(lowest_segment)->p_paddr; + grub_multiboot_payload_dest = phdr(lowest_segment)->p_paddr; + + if (playground) + grub_free (playground); + playground = grub_malloc (sizeof (forward_relocator) + grub_multiboot_payload_size + sizeof (backward_relocator)); + if (! playground) + return grub_errno; + + grub_multiboot_payload_orig = playground + sizeof (forward_relocator); + + grub_memmove (playground, forward_relocator, sizeof (forward_relocator)); + grub_memmove (grub_multiboot_payload_orig + grub_multiboot_payload_size, backward_relocator, sizeof (backward_relocator)); + /* Load every loadable segment in memory. */ for (i = 0; i < ehdr->e_phnum; i++) { if (phdr(i)->p_type == PT_LOAD) { - /* The segment should fit in the area reserved for the OS. */ - if (phdr(i)->p_paddr < grub_os_area_addr) - return grub_error (GRUB_ERR_BAD_OS, - "segment doesn't fit in memory reserved for the OS (0x%lx < 0x%lx)", - phdr(i)->p_paddr, grub_os_area_addr); - if (phdr(i)->p_paddr + phdr(i)->p_memsz > grub_os_area_addr + grub_os_area_size) - return grub_error (GRUB_ERR_BAD_OS, - "segment doesn't fit in memory reserved for the OS (0x%lx > 0x%lx)", - phdr(i)->p_paddr + phdr(i)->p_memsz, - grub_os_area_addr + grub_os_area_size); + char *load_this_module_at = grub_multiboot_payload_orig + (phdr(i)->p_paddr - phdr(0)->p_paddr); - if (grub_file_seek (file, (grub_off_t) phdr(i)->p_offset) + if (grub_file_seek (file, (grub_off_t) phdr(i)->p_offset) == (grub_off_t) -1) return grub_error (GRUB_ERR_BAD_OS, "invalid offset in program header"); - if (grub_file_read (file, (void *) phdr(i)->p_paddr, phdr(i)->p_filesz) + if (grub_file_read (file, load_this_module_at, 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 (phdr(i)->p_filesz < phdr(i)->p_memsz) - grub_memset ((char *) phdr(i)->p_paddr + phdr(i)->p_filesz, 0, + grub_memset (load_this_module_at + phdr(i)->p_filesz, 0, phdr(i)->p_memsz - phdr(i)->p_filesz); - - if ((entry >= phdr(i)->p_vaddr) && - (entry < phdr(i)->p_vaddr + phdr(i)->p_memsz)) - physical_entry_addr = entry + phdr(i)->p_paddr - phdr(i)->p_vaddr; } } + + grub_multiboot_payload_entry_offset = ehdr->e_entry - phdr(lowest_segment)->p_vaddr; + #undef phdr - if (physical_entry_addr) - entry = physical_entry_addr; + if (grub_multiboot_payload_dest >= grub_multiboot_payload_orig) + entry = (grub_addr_t) playground; + else + entry = (grub_addr_t) grub_multiboot_payload_orig + grub_multiboot_payload_size; return grub_errno; }