Subject: Re: Adding Multiboot support (or not)
To: Julio M. Merino Vidal <jmmv84@gmail.com>
From: Pavel Cahyna <pavel.cahyna@st.mff.cuni.cz>
List: port-i386
Date: 02/11/2006 01:51:38
--0F1p//8PRICkK4MW
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Wed, Dec 28, 2005 at 05:25:34PM +0100, Julio M. Merino Vidal wrote:
> Hi all,
> 
> [ I'm not sure if this is the correct list to send this message or if it
>   should have gone to port-i386... ]
> 
> The GRUB [1] developers came up with the idea of designing a
> "standard" protocol between boot loaders and OS kernels, known
> as the Multiboot Specification [2] (I'll use MB for simplicity).  The
> idea behind this protocol is to enable any MB-compliant boot loader
> to execute a MB-compliant kernel without knowing any of its
> internal details.  (This way, you'd install your boot loader of choice
> and have it load any OS without "dirty tricks".)
> 
> I think the idea is quite interesting and it could simplify the boot
> process on machines with multiple OSes installed in them (think of
> chainloading several bootloaders, one after the other).  This is
> why I've spent the last two days trying to adapt the NetBSD kernel
> to support MB.
> 
> Basically, the following is needed:
> 
> - Add a little header within the first 8K of the kernel.
> - Add some new code to process the MB information structure passed
>   by the loader and generate the appropriate information required by
>   the NetBSD kernel (aka, bootinfo structures and such).
> - Fix a bug in GRUB, heh.

I think this bug
(https://savannah.gnu.org/bugs/?func=detailitem&item_id=15590) is more a
deficiency in NetBSD. The ELF format has the ability to tell the boot
loader where the program should be loaded (the p_paddr field, see PhysAddr
in the output of readelf). But the NetBSD kernel has this field set to the
virtual address, which is incorrect. Instead of using a not very well
defined feature of the multiboot specification, why not use a standard
way? If the paddr fields are set correctly, unpatched grub (from Debian
3.1) can load a multiboot NetBSD kernel. 

(elfsh is an useful tool for playing with this, just do
set netbsd.pht[0].paddr 0x00100000
set netbsd.pht[1].paddr ... )

As doing this with a linker script seems unfortunately impossible, I have
written a small program to change physical adresses of an ELF32 binary.
Beware, it currently would not work on a big-endian machine.

It would be good to have such solution, because by having to patch Grub
the multiboot support loses most of its charm. (That is, the possibility
of trying NetBSD as easily as possible if you have Linux with Grub
already installed.)

Bye	Pavel

--0F1p//8PRICkK4MW
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="fixpaddr.c"

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <elf.h>

static void usage(void);

int
main(int argc, char *argv[])
{
	int f, ret, off, i;
	Elf32_Ehdr header;
	Elf32_Phdr *phents, *phent;
	
	int ehs = sizeof(Elf32_Ehdr);
	int phs = sizeof(Elf32_Phdr);

	setprogname(argv[0]);
       
	if (argc != 2) {
		usage();
		errx(1, "too %s arguments", argc > 2 ? "many" : "few");
	}

	f = open(argv[1], O_RDWR, 0);
	if (f == -1)
		err(1, argv[1]);

	if ((ret=read(f, &header, sizeof(Elf32_Ehdr))) != sizeof(Elf32_Ehdr))
		if (ret == -1)
			err(1, "error reading ELF header");
		else
			errx(1, "file %s too short", argv[1]);
	
	if ((phents = malloc(phs * header.e_phnum)) == NULL)
		err(1, NULL);
	
	for (i = 0; i < header.e_phnum; i++) {
		off = i * header.e_phentsize + header.e_phoff;
		phent = phents + i;
		if ((ret=pread(f, phent, phs, off))
		    != phs)
			if (ret == -1)
				err(1, "error reading segment header");
			else
				errx(1, "file %s too short", argv[1]);
		
		if (phent->p_type == PT_LOAD)
			phent->p_paddr &= 0xFFFFFF;
	}

	for (i = 0; i < header.e_phnum; i++) {
		off = i * header.e_phentsize + header.e_phoff;
		phent = phents + i;
		if ((ret=pwrite(f, phent, phs, off))
		    != phs)
			if (ret == -1)
				err(1, "error writing segment header");
			else
				errx(1, "%s: wrote %d bytes instead of %d",
				     argv[1], ret, phs);
       	}
}

static void
usage(void)
{
	(void)fprintf(stderr, "usage: %s kernel-image\n", getprogname());
}

--0F1p//8PRICkK4MW--