* loader modules jumping back to kernel @ 2008-07-19 23:06 Robert Millan 2008-07-20 3:26 ` Bean 2008-07-20 9:09 ` Yoshinori K. Okuji 0 siblings, 2 replies; 9+ messages in thread From: Robert Millan @ 2008-07-19 23:06 UTC (permalink / raw) To: grub-devel Anyone recalls the reason our loaders had to jump back to kernel (startup.S) to do the final part of the load? IIRC this causes trouble when the loadee chose an address that precisely overwrites the loader, which is garanteed to happen when GRUB is loading itself, AFAICT. -- Robert Millan <GPLv2> I know my rights; I want my phone call! <DRM> What good is a phone call… if you are unable to speak? (as seen on /.) ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loader modules jumping back to kernel 2008-07-19 23:06 loader modules jumping back to kernel Robert Millan @ 2008-07-20 3:26 ` Bean 2008-07-20 9:09 ` Yoshinori K. Okuji 1 sibling, 0 replies; 9+ messages in thread From: Bean @ 2008-07-20 3:26 UTC (permalink / raw) To: The development of GRUB 2 On Sun, Jul 20, 2008 at 7:06 AM, Robert Millan <rmh@aybabtu.com> wrote: > > Anyone recalls the reason our loaders had to jump back to kernel (startup.S) to > do the final part of the load? > > IIRC this causes trouble when the loadee chose an address that precisely > overwrites the loader, which is garanteed to happen when GRUB is loading > itself, AFAICT. Hi, I think there is no appear reason for this other than to keep assembly code in a single place. IMO, a lot of stuff should be moved out of startup.S. For example, vbe functions are only used by vbe module, they should be placed alongside other vbe files. -- Bean ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loader modules jumping back to kernel 2008-07-19 23:06 loader modules jumping back to kernel Robert Millan 2008-07-20 3:26 ` Bean @ 2008-07-20 9:09 ` Yoshinori K. Okuji 2008-07-27 21:39 ` Robert Millan 1 sibling, 1 reply; 9+ messages in thread From: Yoshinori K. Okuji @ 2008-07-20 9:09 UTC (permalink / raw) To: The development of GRUB 2 On Sunday 20 July 2008 01:06:22 Robert Millan wrote: > Anyone recalls the reason our loaders had to jump back to kernel > (startup.S) to do the final part of the load? Not all of them should do that, but it might be more convenient. I look at one by one: - The chainloader needs to get back the original state (e.g. A20 disabled), so the final code must be located at below 1MB. Since the address of the startup code is well known, it is easier to use. - The linux loader does not have to overwrite the startup code, but other regions can be. So it is easier to use. - The multiboot loader had, historically speaking, a limitation that it may not load an OS image below 1MB. So it was easier to use. But I don't remember if this limitation is still present in the current implementation. > IIRC this causes trouble when the loadee chose an address that precisely > overwrites the loader, which is garanteed to happen when GRUB is loading > itself, AFAICT. Sure. My recommendation is, in case where you might overwrite that part, that you should write relocatable code (which is rather easy for simple code on i386) at anywhere (it could be in the startup), find out a safe region when loading an OS image, copy the code to the safe region, and finalize the bootstrap in that code (e.g. relocating the OS image, initializing registers, and jumping to it). On i386, we have a reserved region to temporarily load an OS image for the very reason, so this is not difficult. Regards, Okuji ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loader modules jumping back to kernel 2008-07-20 9:09 ` Yoshinori K. Okuji @ 2008-07-27 21:39 ` Robert Millan 2008-07-30 19:15 ` loadee relocation (Re: loader modules jumping back to kernel) Robert Millan 0 siblings, 1 reply; 9+ messages in thread From: Robert Millan @ 2008-07-27 21:39 UTC (permalink / raw) To: The development of GRUB 2; +Cc: Yoshinori K. Okuji On Sun, Jul 20, 2008 at 11:09:02AM +0200, Yoshinori K. Okuji wrote: > > IIRC this causes trouble when the loadee chose an address that precisely > > overwrites the loader, which is garanteed to happen when GRUB is loading > > itself, AFAICT. > > Sure. My recommendation is, in case where you might overwrite that part, that > you should write relocatable code (which is rather easy for simple code on > i386) at anywhere (it could be in the startup), find out a safe region when > loading an OS image, copy the code to the safe region, and finalize the > bootstrap in that code (e.g. relocating the OS image, initializing registers, > and jumping to it). On i386, we have a reserved region to temporarily load an > OS image for the very reason, so this is not difficult. Ok. I've been looking at grub_multiboot_load_elf32() which contains the bound checks that make loading abort in first place; It seems that bounds are checked for every segment in the ELF image, in: /* Load every loadable segment in memory. */ for (i = 0; i < ehdr->e_phnum; i++) so I'm wondering if it is safe to assume the segments are going to occupy a single block of memory (which can be relocated in one run) or it is allowed for them to be scattered. As for the safe region, AFAICT the OS load area is our only choice, or maybe the heap, but in both cases overlaps are a problem, as we don't want the relocator code to overwrite itself. In case of the OS load area, we could abort on situations where payload requested region overlaps with our area, and in case of the heap, we could play some ugly tricks in order to obtain a non-overlapped region from malloc. TBH I don't like either of the options. Do you have any other suggestions? -- Robert Millan <GPLv2> I know my rights; I want my phone call! <DRM> What good is a phone call… if you are unable to speak? (as seen on /.) ^ permalink raw reply [flat|nested] 9+ messages in thread
* loadee relocation (Re: loader modules jumping back to kernel) 2008-07-27 21:39 ` Robert Millan @ 2008-07-30 19:15 ` Robert Millan 2008-07-31 23:45 ` Robert Millan 0 siblings, 1 reply; 9+ messages in thread From: Robert Millan @ 2008-07-30 19:15 UTC (permalink / raw) To: The development of GRUB 2; +Cc: Yoshinori K. Okuji [-- Attachment #1: Type: text/plain, Size: 2899 bytes --] On Sun, Jul 27, 2008 at 11:39:49PM +0200, Robert Millan wrote: > On Sun, Jul 20, 2008 at 11:09:02AM +0200, Yoshinori K. Okuji wrote: > > > IIRC this causes trouble when the loadee chose an address that precisely > > > overwrites the loader, which is garanteed to happen when GRUB is loading > > > itself, AFAICT. > > > > Sure. My recommendation is, in case where you might overwrite that part, that > > you should write relocatable code (which is rather easy for simple code on > > i386) at anywhere (it could be in the startup), find out a safe region when > > loading an OS image, copy the code to the safe region, and finalize the > > bootstrap in that code (e.g. relocating the OS image, initializing registers, > > and jumping to it). On i386, we have a reserved region to temporarily load an > > OS image for the very reason, so this is not difficult. > > Ok. I've been looking at grub_multiboot_load_elf32() which contains the > bound checks that make loading abort in first place; It seems that bounds > are checked for every segment in the ELF image, in: > > /* Load every loadable segment in memory. */ > for (i = 0; i < ehdr->e_phnum; i++) > > so I'm wondering if it is safe to assume the segments are going to occupy > a single block of memory (which can be relocated in one run) or it is allowed > for them to be scattered. > > As for the safe region, AFAICT the OS load area is our only choice, or maybe > the heap, but in both cases overlaps are a problem, as we don't want the > relocator code to overwrite itself. In case of the OS load area, we could > abort on situations where payload requested region overlaps with our area, > and in case of the heap, we could play some ugly tricks in order to obtain > a non-overlapped region from malloc. > > TBH I don't like either of the options. Do you have any other suggestions? Let's try to revive this discussion. Here's a patch I made a while ago that implements support for loading at any address. It works by having a "special" version of malloc() that is told to allocate a chunk of memory that does _not_ overlap with a specific region. It does so by iteratively reserving memory (and keeping track of what was reserved, of course). Then we use this function to allocate the asm relocator code in heap, asking it to garantee that it won't overlap with our final destination. Finally, our loadee can be put anywhere (e.g. in heap), and we just jump to the relocator which will jump to the loadee. But I really find the approach really ugly. What do you think? If we agree that this is the way to go, I can do some cleanup & update it to current svn for a merge. -- Robert Millan The DRM opt-in fallacy: "Your data belongs to us. We will decide when (and how) you may access your data; but nobody's threatening your freedom: we still allow you to remove your data and not access it at all." [-- Attachment #2: loader_relocation.diff --] [-- Type: text/x-diff, Size: 8131 bytes --] diff -ur grub2/include/grub/i386/loader.h grub2.self/include/grub/i386/loader.h --- grub2/include/grub/i386/loader.h 2007-10-17 11:38:55.000000000 +0200 +++ grub2.self/include/grub/i386/loader.h 2007-10-17 12:36:16.000000000 +0200 @@ -29,13 +29,23 @@ extern grub_addr_t EXPORT_VAR(grub_os_area_addr); extern grub_size_t EXPORT_VAR(grub_os_area_size); +extern grub_addr_t EXPORT_VAR(playground); +extern grub_addr_t EXPORT_VAR(requested_addr); +extern int EXPORT_VAR(playground_size); + void EXPORT_FUNC(grub_linux_boot_zimage) (void) __attribute__ ((noreturn)); void EXPORT_FUNC(grub_linux_boot_bzimage) (void) __attribute__ ((noreturn)); /* The asm part of the multiboot loader. */ +void EXPORT_FUNC(grub_multiboot_real_boot_1st) (grub_addr_t entry, + struct grub_multiboot_info *mbi, + void *next) + __attribute__ ((noreturn)); void EXPORT_FUNC(grub_multiboot_real_boot) (grub_addr_t entry, struct grub_multiboot_info *mbi) __attribute__ ((noreturn)); +extern int EXPORT_VAR(end_of_grub_multiboot_real_boot); + void EXPORT_FUNC(grub_multiboot2_real_boot) (grub_addr_t entry, struct grub_multiboot_info *mbi) __attribute__ ((noreturn)); diff -ur grub2/kern/i386/pc/startup.S grub2.self/kern/i386/pc/startup.S --- grub2/kern/i386/pc/startup.S 2007-07-25 21:29:24.000000000 +0200 +++ grub2.self/kern/i386/pc/startup.S 2007-10-17 12:36:16.000000000 +0200 @@ -800,23 +800,59 @@ * This starts the multiboot kernel. */ -FUNCTION(grub_multiboot_real_boot) +VARIABLE(playground) + .long 0 +VARIABLE(playground_size) + .long 0 +VARIABLE(requested_addr) + .long 0 + +/* entry, mbi, next */ +FUNCTION(grub_multiboot_real_boot_1st) /* Push the entry address on the stack. */ pushl %eax /* Move the address of the multiboot information structure to ebx. */ movl %edx,%ebx + /* Push the grub_multiboot_real_boot address on the stack. */ + pushl %ecx /* Unload all modules and stop the floppy driver. */ call EXT_C(grub_dl_unload_all) call EXT_C(grub_stop_floppy) + movl EXT_C(playground_size), %ecx + movl EXT_C(playground), %esi + movl EXT_C(requested_addr), %edi + /* Interrupts should be disabled. */ cli - - /* Move the magic value into eax and jump to the kernel. */ + + /* Jump to relocated grub_multiboot_real_boot */ + popl %eax + jmp *%eax + + /* External references stop working at this point */ + +FUNCTION(grub_multiboot_real_boot) + /* Get this from stack now that we still can */ + popl %edx + + /* Relocate the payload. Overlaps are garanteed NOT to happen, so + we don't need to check for them (like in grub_memmove) */ + + /* %ecx, %esi and %edi were set in the previous function */ + cld + rep + movsb + + /* Stack may have stopped working at this point */ + + /* Move the magic value into eax, and jump to the kernel */ movl $MULTIBOOT_MAGIC2,%eax - popl %ecx - jmp *%ecx + jmp *%edx + +/* Nothing here, just used to relocate the above function */ +VARIABLE(end_of_grub_multiboot_real_boot) /* * This starts the multiboot 2 kernel. diff -ur grub2/loader/i386/pc/multiboot.c grub2.self/loader/i386/pc/multiboot.c --- grub2/loader/i386/pc/multiboot.c 2007-07-25 21:29:24.000000000 +0200 +++ grub2.self/loader/i386/pc/multiboot.c 2007-10-17 12:36:16.000000000 +0200 @@ -47,10 +47,48 @@ static struct grub_multiboot_info *mbi; static grub_addr_t entry; +extern grub_addr_t playground; +extern int playground_size; +extern grub_addr_t requested_addr; + +void * +grub_protected_malloc (grub_size_t size, grub_addr_t protected_region_addr, grub_size_t protected_region_size) +{ + void **old, **tmp, **ret; + tmp = grub_malloc (size); + *tmp = NULL; + + while ((tmp + size >= protected_region_addr) && (tmp <= protected_region_addr + protected_region_size)) + { + /* Get a new region */ + old = tmp; + tmp = grub_malloc (size); + /* Store the address of our previous region */ + *tmp = old; + } + + /* Save result */ + ret = tmp; + + /* Cleanup */ + tmp = *tmp; + while (tmp) + { + old = *tmp; + grub_free (tmp); + tmp = old; + } + + return ret; +} + static grub_err_t grub_multiboot_boot (void) { - grub_multiboot_real_boot (entry, mbi); + void *tmp = grub_protected_malloc ((long) &end_of_grub_multiboot_real_boot - (long) grub_multiboot_real_boot + 32, + requested_addr, playground_size); + grub_memcpy (tmp, grub_multiboot_real_boot, (long) &end_of_grub_multiboot_real_boot - (long) grub_multiboot_real_boot + 32); + grub_multiboot_real_boot_1st (entry, mbi, tmp); /* Not reached. */ return GRUB_ERR_NONE; @@ -94,7 +132,7 @@ grub_multiboot_load_elf32 (grub_file_t file, void *buffer) { Elf32_Ehdr *ehdr = (Elf32_Ehdr *) buffer; - Elf32_Phdr *phdr; + void *phdr_base; int i; if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) @@ -112,35 +150,37 @@ entry = ehdr->e_entry; + phdr_base = (void *) buffer + ehdr->e_phoff; +#define phdr(i) ((Elf32_Phdr *) (phdr_base + (i) * ehdr->e_phentsize)) + + requested_addr = phdr(0)->p_paddr; + playground_size = (phdr(ehdr->e_phnum - 1)->p_paddr + phdr(ehdr->e_phnum - 1)->p_memsz) - phdr(0)->p_paddr; + playground = grub_protected_malloc (playground_size, requested_addr, playground_size); /* FIXME: memleak */ + /* Load every loadable segment in memory. */ for (i = 0; i < ehdr->e_phnum; i++) { - phdr = (Elf32_Phdr *) ((char *) buffer + ehdr->e_phoff - + i * ehdr->e_phentsize); - if (phdr->p_type == PT_LOAD) + if (phdr(i)->p_type == PT_LOAD) { - /* The segment should fit in the area reserved for the OS. */ - if ((phdr->p_paddr < grub_os_area_addr) - || (phdr->p_paddr + phdr->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"); + void *playground_addr = playground + (phdr(i)->p_paddr - phdr(0)->p_paddr); - if (grub_file_seek (file, (grub_off_t) phdr->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->p_paddr, phdr->p_filesz) - != (grub_ssize_t) phdr->p_filesz) + if (grub_file_read (file, playground_addr, 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->p_filesz < phdr->p_memsz) - grub_memset ((char *) phdr->p_paddr + phdr->p_filesz, 0, - phdr->p_memsz - phdr->p_filesz); + if (phdr(i)->p_filesz < phdr(i)->p_memsz) + grub_memset ((char *) playground_addr + phdr(i)->p_filesz, 0, + phdr(i)->p_memsz - phdr(i)->p_filesz); } } + +#undef phdr return grub_errno; } @@ -293,7 +333,7 @@ if (grub_multiboot_load_elf (file, buffer) != GRUB_ERR_NONE) goto fail; - mbi = grub_malloc (sizeof (struct grub_multiboot_info)); + mbi = grub_protected_malloc (sizeof (struct grub_multiboot_info), requested_addr, playground_size); if (! mbi) goto fail; @@ -306,7 +346,7 @@ for (i = 0, len = 0; i < argc; i++) len += grub_strlen (argv[i]) + 1; - cmdline = p = grub_malloc (len); + cmdline = p = grub_protected_malloc (len, requested_addr, playground_size); if (! cmdline) goto fail; @@ -379,7 +419,7 @@ for (i = 0; i < argc; i++) len += grub_strlen (argv[i]) + 1; - cmdline = p = grub_malloc (len); + cmdline = p = grub_protected_malloc (len, requested_addr, playground_size); if (! cmdline) goto fail; @@ -410,7 +450,7 @@ } else { - struct grub_mod_list *modlist = grub_malloc (sizeof (struct grub_mod_list)); + struct grub_mod_list *modlist = grub_protected_malloc (sizeof (struct grub_mod_list), requested_addr, playground_size); if (! modlist) goto fail; modlist->mod_start = (grub_uint32_t) module; ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loadee relocation (Re: loader modules jumping back to kernel) 2008-07-30 19:15 ` loadee relocation (Re: loader modules jumping back to kernel) Robert Millan @ 2008-07-31 23:45 ` Robert Millan 2008-08-01 16:16 ` Robert Millan 0 siblings, 1 reply; 9+ messages in thread From: Robert Millan @ 2008-07-31 23:45 UTC (permalink / raw) To: The development of GRUB 2; +Cc: Yoshinori K. Okuji [-- Attachment #1: Type: text/plain, Size: 1813 bytes --] On Wed, Jul 30, 2008 at 09:15:10PM +0200, Robert Millan wrote: > > Let's try to revive this discussion. Here's a patch I made a while ago that > implements support for loading at any address. It works by having a "special" > version of malloc() that is told to allocate a chunk of memory that does > _not_ overlap with a specific region. It does so by iteratively reserving > memory (and keeping track of what was reserved, of course). > > Then we use this function to allocate the asm relocator code in heap, asking > it to garantee that it won't overlap with our final destination. Finally, > our loadee can be put anywhere (e.g. in heap), and we just jump to the > relocator which will jump to the loadee. > > But I really find the approach really ugly. What do you think? If we agree > that this is the way to go, I can do some cleanup & update it to current svn > for a merge. Here's a new patch, with the following approach. We put this in a single heap area: <forward_relocator> <payload> <backward_relocator> we pick the relocator we want depending on wether we want to copy to a lower or higher address. This garantees that the relocator itself isn't overwritten. Then we set cpu context appropiately, and jump. I like this much better. Also, it doesn't depend on the static OS load area. Some doubts: - What to do about physical_entry_addr now? My patch currently discards it, which I suppose is not what we want. - Should we do the same for the elf64 loader? And how? I don't know of any example ELF64 images I could test with. -- Robert Millan The DRM opt-in fallacy: "Your data belongs to us. We will decide when (and how) you may access your data; but nobody's threatening your freedom: we still allow you to remove your data and not access it at all." [-- Attachment #2: relocate_payload.diff --] [-- Type: text/x-diff, Size: 5529 bytes --] Index: kern/i386/loader.S =================================================================== --- kern/i386/loader.S (revision 1755) +++ kern/i386/loader.S (working copy) @@ -123,6 +123,13 @@ * 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 + FUNCTION(grub_multiboot_real_boot) /* Push the entry address on the stack. */ pushl %eax @@ -138,9 +145,16 @@ /* 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 + + /* Jump to the relocator. */ + popl %edx + jmp *%edx + /* * This starts the multiboot 2 kernel. */ Index: include/grub/i386/pc/kernel.h =================================================================== --- include/grub/i386/pc/kernel.h (revision 1755) +++ include/grub/i386/pc/kernel.h (working copy) @@ -86,6 +86,10 @@ extern grub_addr_t EXPORT_FUNC(grub_arch_memdisk_addr) (void); extern grub_off_t EXPORT_FUNC(grub_arch_memdisk_size) (void); +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); + #endif /* ! ASM_FILE */ #endif /* ! KERNEL_MACHINE_HEADER */ Index: loader/i386/pc/multiboot.c =================================================================== --- loader/i386/pc/multiboot.c (revision 1756) +++ loader/i386/pc/multiboot.c (working copy) @@ -50,6 +50,28 @@ static struct grub_multiboot_info *mbi; static grub_addr_t entry; +static char *playground = NULL; +extern char *grub_multiboot_payload_orig, *grub_multiboot_payload_dest; +extern grub_size_t grub_multiboot_payload_size; + +static grub_uint8_t forward_relocator[] = +{ + 0xfc, /* cld */ + 0x89, 0xf2, /* movl %esi, %edx */ + 0xf3, 0xa4, /* rep movsb */ + 0xff, 0xe2, /* jmp *%edx */ +}; + +static grub_uint8_t backward_relocator[] = +{ + 0xfd, /* std */ + 0x01, 0xce, /* addl %ecx, %esi */ + 0x01, 0xcf, /* addl %ecx, %edi */ + 0x41, /* incl %ecx */ + 0xf3, 0xa4, /* rep movsb */ + 0xff, 0xe7, /* jmp *%edi */ +}; + static grub_err_t grub_multiboot_boot (void) { @@ -118,35 +140,50 @@ phdr_base = (char *) buffer + ehdr->e_phoff; #define phdr(i) ((Elf32_Phdr *) (phdr_base + (i) * ehdr->e_phentsize)) + + { + /* Find the highest address claimed by our payload. */ + char *end_addr = NULL; + for (i = 0; i < ehdr->e_phnum; i++) + { + grub_addr_t addr = (phdr(i)->p_paddr + phdr(i)->p_memsz); + if (addr > end_addr) + end_addr = addr; + } + grub_multiboot_payload_size = end_addr - phdr(0)->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_multiboot_payload_dest = (char *) phdr(0)->p_paddr; + + 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) && @@ -159,6 +196,13 @@ if (physical_entry_addr) entry = physical_entry_addr; + /* FIXME: how do we handle physical_entry_addr now? */ + + 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; } ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loadee relocation (Re: loader modules jumping back to kernel) 2008-07-31 23:45 ` Robert Millan @ 2008-08-01 16:16 ` Robert Millan 2008-08-01 22:45 ` Robert Millan 0 siblings, 1 reply; 9+ messages in thread From: Robert Millan @ 2008-08-01 16:16 UTC (permalink / raw) To: The development of GRUB 2; +Cc: Yoshinori K. Okuji [-- Attachment #1: Type: text/plain, Size: 541 bytes --] On Fri, Aug 01, 2008 at 01:45:30AM +0200, Robert Millan wrote: > > - What to do about physical_entry_addr now? My patch currently discards > it, which I suppose is not what we want. Fixed after some discussion with Bean on IRC. This version of the patch should handle physical_entry_addr fine. -- Robert Millan The DRM opt-in fallacy: "Your data belongs to us. We will decide when (and how) you may access your data; but nobody's threatening your freedom: we still allow you to remove your data and not access it at all." [-- Attachment #2: relocate_payload.diff --] [-- Type: text/x-diff, Size: 6654 bytes --] 2008-08-01 Robert Millan <rmh@aybabtu.com> * 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): 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/pc/loader.h (grub_multiboot_payload_size) (grub_multiboot_payload_orig, grub_multiboot_payload_dest): Export. Index: kern/i386/loader.S =================================================================== --- kern/i386/loader.S (revision 1758) +++ kern/i386/loader.S (working copy) @@ -123,6 +123,13 @@ * 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 + FUNCTION(grub_multiboot_real_boot) /* Push the entry address on the stack. */ pushl %eax @@ -138,9 +145,16 @@ /* 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 + + /* Jump to the relocator. */ + popl %edx + jmp *%edx + /* * This starts the multiboot 2 kernel. */ Index: include/grub/i386/pc/loader.h =================================================================== --- include/grub/i386/pc/loader.h (revision 1758) +++ include/grub/i386/pc/loader.h (working copy) @@ -25,4 +25,8 @@ /* This is an asm part of the chainloader. */ void EXPORT_FUNC(grub_chainloader_real_boot) (int drive, void *part_addr) __attribute__ ((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); + #endif /* ! GRUB_LOADER_MACHINE_HEADER */ Index: loader/i386/pc/multiboot.c =================================================================== --- loader/i386/pc/multiboot.c (revision 1758) +++ loader/i386/pc/multiboot.c (working copy) @@ -50,6 +50,26 @@ 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 */ + 0xff, 0xe2, /* jmp *%edx */ +}; + +static grub_uint8_t backward_relocator[] = +{ + 0xfd, /* std */ + 0x01, 0xce, /* addl %ecx, %esi */ + 0x01, 0xcf, /* addl %ecx, %edi */ + 0x41, /* incl %ecx */ + 0xf3, 0xa4, /* rep movsb */ + 0xff, 0xe7, /* jmp *%edi */ +}; + static grub_err_t grub_multiboot_boot (void) { @@ -98,7 +118,7 @@ { Elf32_Ehdr *ehdr = (Elf32_Ehdr *) buffer; char *phdr_base; - grub_addr_t physical_entry_addr = 0; + grub_off_t physical_entry_offset = 0; int i; if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) @@ -118,47 +138,66 @@ phdr_base = (char *) buffer + ehdr->e_phoff; #define phdr(i) ((Elf32_Phdr *) (phdr_base + (i) * ehdr->e_phentsize)) + + { + /* Find the highest address claimed by our payload. */ + char *end_addr = NULL; + for (i = 0; i < ehdr->e_phnum; i++) + { + grub_addr_t addr = (phdr(i)->p_paddr + phdr(i)->p_memsz); + if (addr > end_addr) + end_addr = addr; + } + grub_multiboot_payload_size = end_addr - phdr(0)->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_multiboot_payload_dest = (char *) phdr(0)->p_paddr; + + 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; + physical_entry_offset = phdr(i)->p_paddr - phdr(i)->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; + + entry += physical_entry_offset; + return grub_errno; } ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loadee relocation (Re: loader modules jumping back to kernel) 2008-08-01 16:16 ` Robert Millan @ 2008-08-01 22:45 ` Robert Millan 2008-08-02 12:11 ` Robert Millan 0 siblings, 1 reply; 9+ messages in thread From: Robert Millan @ 2008-08-01 22:45 UTC (permalink / raw) To: The development of GRUB 2; +Cc: Yoshinori K. Okuji [-- Attachment #1: Type: text/plain, Size: 1326 bytes --] On Fri, Aug 01, 2008 at 06:16:06PM +0200, Robert Millan wrote: > On Fri, Aug 01, 2008 at 01:45:30AM +0200, Robert Millan wrote: > > > > - What to do about physical_entry_addr now? My patch currently discards > > it, which I suppose is not what we want. > > Fixed after some discussion with Bean on IRC. This version of the patch > should handle physical_entry_addr fine. Then again, I still got spurious crashes when trying my code with: ftp://ftp.netbsd.org/pub/NetBSD-daily/netbsd-4/200807310002Z/i386/binary/kernel/netbsd-GENERIC.gz In case someone is curious, the problems that made me spend all day debugging are: grub_multiboot_payload_entry_offset was defined with a 64-bit type but allocated with ".long 0" in loader.S, resulting in the first 4 bytes of grub_multiboot_real_boot being fucked up occasionally. %edi was off-by-one in the backward relocator, which was not usually a problem for invaders (what harm can one byte do?) but broke netbsd. Lessons learned: gdb is your friend, and is definitely worth the hassle of setting up for use in QEMU/GRUB. -- Robert Millan The DRM opt-in fallacy: "Your data belongs to us. We will decide when (and how) you may access your data; but nobody's threatening your freedom: we still allow you to remove your data and not access it at all." [-- Attachment #2: relocate_payload.diff --] [-- Type: text/x-diff, Size: 7555 bytes --] 2008-08-01 Robert Millan <rmh@aybabtu.com> * 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/pc/loader.h (grub_multiboot_payload_size) (grub_multiboot_payload_orig, grub_multiboot_payload_dest) (grub_multiboot_payload_entry_offset): Export. Index: kern/i386/loader.S =================================================================== --- kern/i386/loader.S (revision 1758) +++ kern/i386/loader.S (working copy) @@ -123,6 +123,15 @@ * 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 @@ /* 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. */ Index: include/grub/i386/pc/loader.h =================================================================== --- include/grub/i386/pc/loader.h (revision 1758) +++ include/grub/i386/pc/loader.h (working copy) @@ -25,4 +25,9 @@ /* This is an asm part of the chainloader. */ void EXPORT_FUNC(grub_chainloader_real_boot) (int drive, void *part_addr) __attribute__ ((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); + #endif /* ! GRUB_LOADER_MACHINE_HEADER */ Index: loader/i386/pc/multiboot.c =================================================================== --- loader/i386/pc/multiboot.c (revision 1758) +++ loader/i386/pc/multiboot.c (working copy) @@ -50,6 +50,33 @@ 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 @@ 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 @@ 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; } ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: loadee relocation (Re: loader modules jumping back to kernel) 2008-08-01 22:45 ` Robert Millan @ 2008-08-02 12:11 ` Robert Millan 0 siblings, 0 replies; 9+ messages in thread From: Robert Millan @ 2008-08-02 12:11 UTC (permalink / raw) To: The development of GRUB 2; +Cc: Yoshinori K. Okuji Committed. On Sat, Aug 02, 2008 at 12:45:20AM +0200, Robert Millan wrote: > On Fri, Aug 01, 2008 at 06:16:06PM +0200, Robert Millan wrote: > > On Fri, Aug 01, 2008 at 01:45:30AM +0200, Robert Millan wrote: > > > > > > - What to do about physical_entry_addr now? My patch currently discards > > > it, which I suppose is not what we want. > > > > Fixed after some discussion with Bean on IRC. This version of the patch > > should handle physical_entry_addr fine. > > Then again, I still got spurious crashes when trying my code with: > ftp://ftp.netbsd.org/pub/NetBSD-daily/netbsd-4/200807310002Z/i386/binary/kernel/netbsd-GENERIC.gz > > In case someone is curious, the problems that made me spend all day debugging > are: > > grub_multiboot_payload_entry_offset was defined with a 64-bit type but > allocated with ".long 0" in loader.S, resulting in the first 4 bytes of > grub_multiboot_real_boot being fucked up occasionally. > > %edi was off-by-one in the backward relocator, which was not usually a > problem for invaders (what harm can one byte do?) but broke netbsd. > > Lessons learned: gdb is your friend, and is definitely worth the hassle > of setting up for use in QEMU/GRUB. > > -- > Robert Millan > > The DRM opt-in fallacy: "Your data belongs to us. We will decide when (and > how) you may access your data; but nobody's threatening your freedom: we > still allow you to remove your data and not access it at all." > 2008-08-01 Robert Millan <rmh@aybabtu.com> > > * 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/pc/loader.h (grub_multiboot_payload_size) > (grub_multiboot_payload_orig, grub_multiboot_payload_dest) > (grub_multiboot_payload_entry_offset): Export. > > Index: kern/i386/loader.S > =================================================================== > --- kern/i386/loader.S (revision 1758) > +++ kern/i386/loader.S (working copy) > @@ -123,6 +123,15 @@ > * 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 @@ > /* 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. > */ > Index: include/grub/i386/pc/loader.h > =================================================================== > --- include/grub/i386/pc/loader.h (revision 1758) > +++ include/grub/i386/pc/loader.h (working copy) > @@ -25,4 +25,9 @@ > /* This is an asm part of the chainloader. */ > void EXPORT_FUNC(grub_chainloader_real_boot) (int drive, void *part_addr) __attribute__ ((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); > + > #endif /* ! GRUB_LOADER_MACHINE_HEADER */ > Index: loader/i386/pc/multiboot.c > =================================================================== > --- loader/i386/pc/multiboot.c (revision 1758) > +++ loader/i386/pc/multiboot.c (working copy) > @@ -50,6 +50,33 @@ > 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 @@ > 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 @@ > 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; > } > _______________________________________________ > Grub-devel mailing list > Grub-devel@gnu.org > http://lists.gnu.org/mailman/listinfo/grub-devel -- Robert Millan The DRM opt-in fallacy: "Your data belongs to us. We will decide when (and how) you may access your data; but nobody's threatening your freedom: we still allow you to remove your data and not access it at all." ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2008-08-02 12:12 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2008-07-19 23:06 loader modules jumping back to kernel Robert Millan 2008-07-20 3:26 ` Bean 2008-07-20 9:09 ` Yoshinori K. Okuji 2008-07-27 21:39 ` Robert Millan 2008-07-30 19:15 ` loadee relocation (Re: loader modules jumping back to kernel) Robert Millan 2008-07-31 23:45 ` Robert Millan 2008-08-01 16:16 ` Robert Millan 2008-08-01 22:45 ` Robert Millan 2008-08-02 12:11 ` Robert Millan
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.