Hi, Here is an attempt at a patch to load and execute a vmlinux kernel image, only tested with QEMU for x86_64. It is very preliminary, excludes a large amount of planned functionality, and so far only boots the raw vmlinux.bin file found in arch/[x86_64,i386]/boot/vmlinux.bin after compiling Linux with the 'make vmlinux' target (and 'make bzImage' as well). This means that the user must supply the entry point of the kernel on the GRUB command line. This nastiness will be easily removed once vmlinux is used in place of vmlinux.bin.
vmlinux.bin is a derivative of the vmlinux kernel executable that is produced by: objcopy -O binary linux-2.6.xx/vmlinux vmlinux.bin My reasons for doing this are: - eliminating real-mode code from the kernel image (and ultimately 32-bit code, in x86-64) - allowing GRUB to have more control over the boot process - removing the decompression code from the kernel image - instead delegating decompression to the bootloader, as an optional feature - simplying Linux compilation by making all targets aside from vmlinux/modules redundant (indeed, even System.map is redundant with a full vmlinux present). If vmlinux is too large, it is always possible to strip(1). With compression, it becomes very close to the size of a bzImage: $ gzip -9c vmlinux > vmlinux.gz $ bzip2 -9c vmlinux > vmlinux.bz2 $ strip -o vmlinux_str vmlinux $ gzip -9c vmlinux_str > vmlinux_str.gz $ bzip2 -9c vmlinux_str > vmlinux_str.bz2 $ ls -ltcr vmlinux* -rwxrwxr-x 1 maciek maciek 47308143 Jul 30 21:53 vmlinux* -rw-rw-r-- 1 maciek maciek 19453585 Aug 3 00:47 vmlinux.gz -rw-rw-r-- 1 maciek maciek 16795427 Aug 3 00:47 vmlinux.bz2 -rwxrwxr-x 1 maciek maciek 7140752 Aug 3 00:47 vmlinux_str* -rw-rw-r-- 1 maciek maciek 1856381 Aug 3 00:48 vmlinux_str.gz -rw-rw-r-- 1 maciek maciek 1757941 Aug 3 00:48 vmlinux_str.bz2 $ ls -l arch/x86_64/boot/bzImage -rw-rw-r-- 1 maciek maciek 1832285 Jul 9 18:51 arch/x86_64/boot/bzImage $ (bzImage and vmlinux are not representative of the same .config - but it is very close. Admittedly the vmlinux .config defines a slightly smaller kernel image.) Of course I am aware that GRUB does not currently support bzip2 decompression. However, should it gain this feature, the kernel on-disk size would decrease compared to bzImage. Please let me know what you think. I have included an updated version of my run-grub.sh bash script which includes an example invocation of the vmlinux loader (however, it seems that GRUB truncates the command line...? I have not yet investigated this). Maciek
diff -Nur -x autom4te.cache grub2_cvspull.bak/conf/i386-pc.rmk grub2_cvspull/conf/i386-pc.rmk --- grub2_cvspull.bak/conf/i386-pc.rmk 2006-07-31 08:21:35.000000000 -0600 +++ grub2_cvspull/conf/i386-pc.rmk 2006-08-03 00:18:02.000000000 -0600 @@ -112,7 +112,7 @@ pkgdata_MODULES = _chain.mod _linux.mod linux.mod normal.mod \ _multiboot.mod chain.mod multiboot.mod reboot.mod halt.mod \ vbe.mod vbetest.mod vbeinfo.mod video.mod gfxterm.mod \ - videotest.mod play.mod bitmap.mod tga.mod + videotest.mod play.mod bitmap.mod tga.mod vmlinux.mod # For _chain.mod. _chain_mod_SOURCES = loader/i386/pc/chainloader.c @@ -164,6 +164,11 @@ _multiboot_mod_CFLAGS = $(COMMON_CFLAGS) _multiboot_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For vmlinux.mod. +vmlinux_mod_SOURCES = loader/i386/pc/vmlinux.c +vmlinux_mod_CFLAGS = $(COMMON_CFLAGS) +vmlinux_mod_LDFLAGS = $(COMMON_LDFLAGS) + # For multiboot.mod. multiboot_mod_SOURCES = loader/i386/pc/multiboot_normal.c multiboot_mod_CFLAGS = $(COMMON_CFLAGS) diff -Nur -x autom4te.cache grub2_cvspull.bak/include/grub/i386/pc/init.h grub2_cvspull/include/grub/i386/pc/init.h --- grub2_cvspull.bak/include/grub/i386/pc/init.h 2005-01-31 14:40:25.000000000 -0700 +++ grub2_cvspull/include/grub/i386/pc/init.h 2006-08-03 00:21:47.000000000 -0600 @@ -48,8 +48,11 @@ /* Get a memory map entry. Return next continuation value. Zero means the end. */ -grub_uint32_t grub_get_mmap_entry (struct grub_machine_mmap_entry *entry, - grub_uint32_t cont); +/*grub_uint32_t grub_get_mmap_entry (struct grub_machine_mmap_entry *entry, + grub_uint32_t cont);*/ +/* exporting for loader/i386/pc/vmlinux.c */ +grub_uint32_t EXPORT_FUNC (grub_get_mmap_entry) + (struct grub_machine_mmap_entry *entry, grub_uint32_t cont); /* Turn on/off Gate A20. */ void grub_gate_a20 (int on); diff -Nur -x autom4te.cache grub2_cvspull.bak/loader/i386/pc/vmlinux.c grub2_cvspull/loader/i386/pc/vmlinux.c --- grub2_cvspull.bak/loader/i386/pc/vmlinux.c 1969-12-31 17:00:00.000000000 -0700 +++ grub2_cvspull/loader/i386/pc/vmlinux.c 2006-08-03 00:08:10.000000000 -0600 @@ -0,0 +1,416 @@ +/* vmlinux.c - load a Linux i386/x86_64 vmlinux.bin kernel image */ +/* August 3 2006, Maciek Nowacki <[EMAIL PROTECTED]> */ + +/* + * notes: + * + * grub leaks memory. This is apparent even with the stock Linux bzImage + * loader (repeat a command like 'linux /bzImage' about twenty times), and it + * seems to be worse with this loader. I am new to grub, but I could not find + * any obvious leakage here. + */ + +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003 NIIBE Yutaka <[EMAIL PROTECTED]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <grub/types.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/normal.h> + +#include <grub/file.h> +#include <grub/machine/init.h> +#include <grub/machine/memory.h> +#include <grub/i386/linux.h> +#include <grub/loader.h> + +static grub_dl_t this_mod; + +#define STATE_ADDRESS 0 +#define STATE_MAJOR 1 +#define STATE_MINOR 2 +#define STATE_RDONLY 3 + +/* include/asm-x86_64/setup.h:#define COMMAND_LINE_SIZE 256 + */ +#define COMMAND_LINE_SIZE 256 + +/* linux/include/asm-x86_64/bootsetup.h: + * #define BOOT_PARAM_SIZE 4096 + */ +#define BOOT_PARAM_SIZE 4096 + +static const struct grub_arg_option options[] = + { + {"address", 'a', 0, "specify address to load kernel (default: -a 0x200000)", 0, ARG_TYPE_STRING}, + {"major", 'M', 0, "set root device major", 0, ARG_TYPE_INT}, + {"minor", 'm', 0, "set root device minor", 0, ARG_TYPE_INT}, + {"ro", 'r', 0, "set root read-only flag", 0, ARG_TYPE_NONE}, + {0, 0, 0, 0, 0, 0} + }; + +/* + * linux/include/asm-x86_64/bootsetup.h: + * #define PARAM ((unsigned char *)x86_boot_params) + * + * This block constitutes the data that GRUB will pass to Linux. It is safely + * outside of the area defined by grub_os_area_addr and grub_os_area_size, and + * will persist until the 'boot' command is given; however, it is past the + * grub_os_area, and Linux cannot deal with this information being past itself + * in physical memory. This will be taken care of in grub_vmlinux_boot(). + */ +static unsigned char x86_boot_params[COMMAND_LINE_SIZE + BOOT_PARAM_SIZE]; + +static grub_err_t +grub_vmlinux_boot (void) +{ + /* declare another boot block here, where it will be on the stack and + * thus in the first megabyte of memory: + */ + unsigned char x86_boot_params_first_meg[COMMAND_LINE_SIZE + + BOOT_PARAM_SIZE]; + + grub_memmove(&x86_boot_params_first_meg,&x86_boot_params, + COMMAND_LINE_SIZE + BOOT_PARAM_SIZE); + + char *cmdline = (char *)&x86_boot_params_first_meg; + struct linux_kernel_header *lh = + (struct linux_kernel_header *)(COMMAND_LINE_SIZE + cmdline); + + grub_addr_t begin = lh->code32_start; + + if(begin < (grub_addr_t)cmdline) + { + grub_printf("Sorry, I can't boot the kernel: the parameter block occurs past the kernel.\n"); + return GRUB_ERR_OUT_OF_RANGE; + } + + lh->cmd_line_ptr=cmdline; + + grub_printf("cmdline relocated to %08x, lh to %08x\n", + (unsigned int)lh->cmd_line_ptr, (unsigned int)lh); + + asm( "movl %0,%%esi" :: "m" (lh) ); + asm("jmp *%0" :: "m" (begin)); + + /* Not reached. */ + return GRUB_ERR_NONE; +} +static grub_err_t +grub_vmlinux_unload (void) +{ + grub_dl_unref(this_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_vmlinux (struct grub_arg_list *state, int argc, char **args) +{ + grub_dl_ref(this_mod); + if(!argc) + { + grub_printf("Please specify a vmlinux.bin-style kernel image.\n"); + return GRUB_ERR_BAD_ARGUMENT; + } + + int i=argc, cl=0; + while(--i) + cl += 1 + grub_strlen(args[i]); + + if(cl > 1 + COMMAND_LINE_SIZE) // Linux has an implicit '\0' at max length +1 + { + grub_printf("Command line is too long by %u characters\n", + cl - (1 + COMMAND_LINE_SIZE)); + + /* truncation doesn't make much sense - let the user handle it */ + return GRUB_ERR_BAD_ARGUMENT; + } + + /* Handle the cmdline first and as one block along with the + * linux_kernel_header struct (lh), since the cmdline code might scribble + * past its bounds by one byte. + */ + + char *cmdline=(char *)&x86_boot_params; + + // a little bit of poison never hurt anyone (XXX replace with memset()) + for(i=COMMAND_LINE_SIZE; i; cmdline[--i]=0x8f); + + /* A note on cmdline, the commandline buffer: + * Potentially, 257 characters could be copied into the commandline buffer. + * However, the last character is always a space, which the statement after + * the loop would overwrite with '\0'. That character therefore never + * contains user data, as it serves only to delineate the end of the buffer. + * Linux will only use at most 256 characters from the buffer. By using an + * extra byte, the code becomes no more complex despite allowing the entire + * buffer to be used for commandline user data. + */ + + grub_uint32_t cont=argc; + i=cl; + while(i) + { + i -= 1 + grub_strlen(args[--cont]); + *grub_stpcpy(i + cmdline, args[cont])=' '; + } + /* cl is 0 if there isn't a commandline; otherwise, cl is the length of the + * commandline plus 1. + */ + cmdline[ cl - (cl?1:0) ] = '\0'; + + /* deal with lh only once the commandline has been sorted out + * Note that the struct linux_kernel_header only describes part of the data + * defined by BOOT_PARAM_SIZE. It's used as a convenience, and since it is + * smaller than BOOT_PARAM_SIZE, it is not always appropriate. + */ + struct linux_kernel_header *lh = + (struct linux_kernel_header *)(cmdline + COMMAND_LINE_SIZE); + + /* XXX replace with memset() */ + for(i = BOOT_PARAM_SIZE; i; ((char *)lh)[--i] = 0xf8); + + if(state[STATE_ADDRESS].set) + { + for(i = grub_strlen(state[STATE_ADDRESS].arg); + i && 'x' !=state[STATE_ADDRESS].arg[--i] + && 'X'!=state[STATE_ADDRESS].arg[i] + ; + ); + + /* Maybe bail if 0 is returned? Or other preposturous addresses? */ + if(i) + lh->code32_start = (grub_addr_t) grub_strtoul( + 1 + i + state[STATE_ADDRESS].arg, 0, 16); + } + else + { + grub_printf("Please specify a load address (see 'vmlinux -h')\n"); + return GRUB_ERR_BAD_ARGUMENT; + } + + /* here is another struct that overlays the BOOT_PARAM region */ + struct linux_kernel_params *lp = (struct linux_kernel_params *) lh; + + /* Without these two at least, console initialization will get stuck. + * This took me a _long_ time to figure out. + * It would be more correct to obtain this information from GRUB. + */ + lp->video_height = 25; + lp->video_width = 80; + lp->have_vga = 1; + + /* This method is probably going away soon - but it's elegant, in my + * opinion, and it avoids string parsing in the kernel. It allows the + * bootloader to refuse data that doesn't describe a device in acceptable + * notation. + */ + if(state[STATE_MAJOR].set && state[STATE_MINOR].set) + lh->root_dev= (grub_strtoul(state[STATE_MAJOR].arg,NULL,0) << 8) + | grub_strtoul(state[STATE_MINOR].arg,NULL,0); + + /* As above. Regardless, here it is. */ + if(state[STATE_RDONLY].set) + lh->root_flags=1; + + /* Don't set the cmdline pointer, since the boot function will move it. */ + /* lh->cmd_line_ptr=cmdline; */ + + /* these need to be set when initrd support is implemented */ + lh->ramdisk_image=0; + lh->ramdisk_size=0; + + /* useless, but shows up /proc/sys/kernel/bootloader_type */ + lh->type_of_loader=GRUB_LINUX_BOOT_LOADER_TYPE; + + /* I have delayed opening and reading the kernel into memory for as long as + * possible, to allow the code to cleanly abort. While it is still possible + * to bail out here without having touched the grub_os_area, it won't be + * for long. + */ + + grub_file_t file = 0; + + file = grub_file_open(args[0]); + + if(!file) + { + grub_printf("Problem during grub_file_open()... aborting.\n"); + return GRUB_ERR_FILE_READ_ERROR; + } + + // XXX clean this up - obsolete #defines +#define OS_AREA_ADDR grub_os_area_addr +#define OS_AREA_SIZE grub_os_area_size +#define LOAD_ADDRESS lh->code32_start + + if(grub_os_area_addr > lh->code32_start) + { + grub_printf("Error: 0x%08x is too low in memory by 0x%08x bytes\n", + lh->code32_start, grub_os_area_addr - lh->code32_start); + grub_file_close(file); + return GRUB_ERR_OUT_OF_RANGE; + } + + if(grub_os_area_addr+grub_os_area_size < lh->code32_start) + { + grub_printf("Error: 0x%08x exceeds the highest available memory location by 0x%08x bytes\n", + lh->code32_start, lh->code32_start - + (grub_os_area_addr + grub_os_area_size) ); + grub_file_close(file); + return GRUB_ERR_OUT_OF_RANGE; + } + if(grub_os_area_addr + grub_os_area_size < + lh->code32_start + (grub_addr_t) grub_file_size(file)) + { + grub_printf("Error: to load at location 0x%08x, 0x%08x more bytes are needed\n", + lh->code32_start, lh->code32_start + + (grub_addr_t) grub_file_size(file) - + (grub_os_area_addr + grub_os_area_size)); + + grub_file_close(file); + return GRUB_ERR_OUT_OF_MEMORY; + } + + /* Now that the grub_os_area is going to be overwritten (everything else + * should be recoverable with no change in state, to this point): + */ + grub_loader_unset(); + + /* I think it is appropriate to cast the result of grub_file_read() since it + * is a signed value, while grub_file_size() returns grub_off_t (unsigned) + * and thus could represent a value that could be too large to be stored as + * a same-sized signed value. + */ + if(grub_file_size(file) != (grub_off_t) grub_file_read(file, + (char *)lh->code32_start, grub_file_size(file))) + { + grub_printf("Error reading %s into memory.\n", args[0]); + grub_file_close(file); + return GRUB_ERR_READ_ERROR; + } + + grub_file_close(file); + + /* Now that the kernel image has been loaded, let's muck about with some + real-mode BIOS calls to dig out the E820 memory maps. + + Note that I had to export grub_get_mmap_entry() for this. + + I'm putting this as close to the end as possible since I don't know how + the grub_get_mmap_entry() calls might affect everything else. Who can + tell? We are at the mercy of the BIOS! (yes, this is a plea to make the + memory map information available...) */ + + /* from linux/include/asm-x86_64/e820.h - seems to be the same for i386 */ + struct e820entry { + grub_uint64_t addr; /* start of memory segment */ + grub_uint64_t size; /* size of memory segment */ + grub_uint32_t type; /* type of memory segment */ + } __attribute__((packed)); + + /* linux/include/asm-x86_64/e820.h: */ + // #define E820MAP 0x2d0 /* our map */ + + struct e820entry *e820map = ((struct e820entry *) ((char *)lh + 0x2d0)); + + struct grub_machine_mmap_entry entry; + lp->mmap_size=0; /* number of e820 regions */ + cont=0; + do + { + cont = grub_get_mmap_entry (&entry,cont); + e820map[lp->mmap_size].addr = entry.addr; + e820map[lp->mmap_size].size = entry.len; + e820map[lp->mmap_size].type = entry.type; + lp->mmap_size++; + } + while(cont); + + + /* Well, that's about it. Let's make it official. */ + + grub_loader_set (grub_vmlinux_boot, grub_vmlinux_unload, 1); + return GRUB_ERR_NONE; +} + +GRUB_MOD_INIT(vmlinux) +{ + this_mod = mod; /* To stop warning. */ + grub_register_command ("vmlinux", grub_cmd_vmlinux, GRUB_COMMAND_FLAG_BOTH, + "vmlinux", "Load a Linux vmlinux.bin kernel image", options); +} + +GRUB_MOD_FINI(vmlinux) +{ + grub_unregister_command ("vmlinux"); +} + +#if 0 + +Some notes on boot parameter offsets. + + // include/asm-x86_64/bootsetup.h contains a wealth of information about + // what needs to go into real_mode_data. A *wealth*! + // It also explains that the first 0x200 bytes of real_mode_data are + // defined by the contents of boot/bootsect.S, and the next 0xcff - which + // takes us to 0xfff, or 4k (0x1000) - are defined in boot/setup.S. + // + // So the layout goes, roughly: + // 0-0x200: bootsect.S - obviously, many of these are obsolete + // 0 _start + // 8 _start2 + // 0x18 msg_loop + // 0x26 die + // 0x31 bugger_off_msg - I think the first real one follows + // ** everything before this point is part of struct screen_info + // 0x1f1 setup_sects // appears to be unused + // 0x1f2 root_flags + // 0x1f4 syssize + // 0x1f6 swap_dev + // 0x1f8 ram_size + // 0x1fa vid_mode + // 0x1fc root_dev + // 0x1fe boot_flag // AUX_DEVICE_INFO - obsolete PS/2 mouse info + // 0x200-0xfff : hmm, this is not all used. The last 0x100 bytes particularly + // (0xf00-0xfff) appear to just be setup.S code. Oh, add 0x200 to each item. + // 0 begtext + // 8 realmode_switch + // 0xc start_sys_seg + // 0x10 type_of_loader // possibly signal multiboot info here? + // 0x11 loadflags + // 0x12 setup_move_size + // 0x14 code32_start // not used by the kernel + // 0x18 ramdisk_image + // 0x1c ramdisk_size + // 0x20 bootsect_kludge // I wonder what the heck this was + // 0x24 heap_end_ptr + // 0x26 padl + // 0x28 cmd_line_ptr + // 0x2c ramdisk_max // seems obsolete + // 0x30 trampoline + // 0xd00 start_of_setup // ok, the last defined thing is 0x230, + // // 'trampoline'. Since the end of our space + // // is 0x1000, that leaves 0xdd0 for memory + // // maps and such. + // 0x228 : NEW_CL_POINTER, defined in arch/x86_64/kernel/head64.c + // also COMMAND_LINE_SIZE is defined as 256 bytes so... hmm. +#endif
run-grub2.sh
Description: Bourne shell script
_______________________________________________ Grub-devel mailing list Grub-devel@gnu.org http://lists.gnu.org/mailman/listinfo/grub-devel