[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: loadee relocation (Re: loader modules jumping back to kernel)
From: |
Robert Millan |
Subject: |
Re: loadee relocation (Re: loader modules jumping back to kernel) |
Date: |
Sat, 2 Aug 2008 14:11:40 +0200 |
User-agent: |
Mutt/1.5.13 (2006-08-11) |
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 <address@hidden>
>
> * 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
> address@hidden
> 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."