1 /* multiboot.c - boot a multiboot OS image. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2003,2004,2005,2007,2008 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
21 * FIXME: The following features from the Multiboot specification still
22 * need to be implemented:
29 * - ROM configuration table
33 #include <grub/loader.h>
34 #include <grub/machine/loader.h>
35 #include <grub/multiboot.h>
36 #include <grub/machine/init.h>
37 #include <grub/machine/memory.h>
39 #include <grub/aout.h>
40 #include <grub/file.h>
42 #include <grub/rescue.h>
45 #include <grub/misc.h>
46 #include <grub/gzio.h>
49 extern grub_dl_t my_mod
;
50 static struct grub_multiboot_info
*mbi
;
51 static grub_addr_t entry
;
54 grub_multiboot_boot (void)
56 grub_multiboot_real_boot (entry
, mbi
);
63 grub_multiboot_unload (void)
68 for (i
= 0; i
< mbi
->mods_count
; i
++)
71 ((struct grub_mod_list
*) mbi
->mods_addr
)[i
].mod_start
);
73 ((struct grub_mod_list
*) mbi
->mods_addr
)[i
].cmdline
);
75 grub_free ((void *) mbi
->mods_addr
);
76 grub_free ((void *) mbi
->cmdline
);
82 grub_dl_unref (my_mod
);
87 /* Check if BUFFER contains ELF32. */
89 grub_multiboot_is_elf32 (void *buffer
)
91 Elf32_Ehdr
*ehdr
= (Elf32_Ehdr
*) buffer
;
93 return ehdr
->e_ident
[EI_CLASS
] == ELFCLASS32
;
97 grub_multiboot_load_elf32 (grub_file_t file
, void *buffer
)
99 Elf32_Ehdr
*ehdr
= (Elf32_Ehdr
*) buffer
;
101 grub_addr_t physical_entry_addr
= 0;
104 if (ehdr
->e_ident
[EI_CLASS
] != ELFCLASS32
)
105 return grub_error (GRUB_ERR_UNKNOWN_OS
, "invalid ELF class");
107 if (grub_dl_check_header (ehdr
, sizeof(Elf32_Ehdr
)))
108 return grub_error (GRUB_ERR_UNKNOWN_OS
, "no valid ELF header found");
110 if (ehdr
->e_type
!= ET_EXEC
)
111 return grub_error (GRUB_ERR_UNKNOWN_OS
, "invalid ELF file type");
113 /* FIXME: Should we support program headers at strange locations? */
114 if (ehdr
->e_phoff
+ ehdr
->e_phnum
* ehdr
->e_phentsize
> MULTIBOOT_SEARCH
)
115 return grub_error (GRUB_ERR_BAD_OS
, "program header at a too high offset");
117 entry
= ehdr
->e_entry
;
119 /* Load every loadable segment in memory. */
120 for (i
= 0; i
< ehdr
->e_phnum
; i
++)
122 phdr
= (Elf32_Phdr
*) ((char *) buffer
+ ehdr
->e_phoff
123 + i
* ehdr
->e_phentsize
);
124 if (phdr
->p_type
== PT_LOAD
)
126 /* The segment should fit in the area reserved for the OS. */
127 if (phdr
->p_paddr
< grub_os_area_addr
)
128 return grub_error (GRUB_ERR_BAD_OS
,
129 "segment doesn't fit in memory reserved for the OS (0x%lx < 0x%lx)",
130 phdr
->p_paddr
, grub_os_area_addr
);
131 if (phdr
->p_paddr
+ phdr
->p_memsz
> grub_os_area_addr
+ grub_os_area_size
)
132 return grub_error (GRUB_ERR_BAD_OS
,
133 "segment doesn't fit in memory reserved for the OS (0x%lx > 0x%lx)",
134 phdr
->p_paddr
+ phdr
->p_memsz
,
135 grub_os_area_addr
+ grub_os_area_size
);
137 if (grub_file_seek (file
, (grub_off_t
) phdr
->p_offset
)
139 return grub_error (GRUB_ERR_BAD_OS
,
140 "invalid offset in program header");
142 if ((phdr
->p_filesz
> 0) && (grub_file_read (file
, (void *) phdr
->p_paddr
, phdr
->p_filesz
)
143 != (grub_ssize_t
) phdr
->p_filesz
))
144 return grub_error (GRUB_ERR_BAD_OS
,
145 "couldn't read segment from file");
147 if (phdr
->p_filesz
< phdr
->p_memsz
)
148 grub_memset ((char *) phdr
->p_paddr
+ phdr
->p_filesz
, 0,
149 phdr
->p_memsz
- phdr
->p_filesz
);
151 if ((entry
>= phdr
->p_vaddr
) &&
152 (entry
< phdr
->p_vaddr
+ phdr
->p_memsz
))
153 physical_entry_addr
= entry
+ phdr
->p_paddr
- phdr
->p_vaddr
;
157 if (physical_entry_addr
)
158 entry
= physical_entry_addr
;
163 /* Check if BUFFER contains ELF64. */
165 grub_multiboot_is_elf64 (void *buffer
)
167 Elf64_Ehdr
*ehdr
= (Elf64_Ehdr
*) buffer
;
169 return ehdr
->e_ident
[EI_CLASS
] == ELFCLASS64
;
173 grub_multiboot_load_elf64 (grub_file_t file
, void *buffer
)
175 Elf64_Ehdr
*ehdr
= (Elf64_Ehdr
*) buffer
;
177 grub_addr_t physical_entry_addr
= 0;
180 if (ehdr
->e_ident
[EI_CLASS
] != ELFCLASS64
)
181 return grub_error (GRUB_ERR_UNKNOWN_OS
, "invalid ELF class");
183 if (ehdr
->e_ident
[EI_MAG0
] != ELFMAG0
184 || ehdr
->e_ident
[EI_MAG1
] != ELFMAG1
185 || ehdr
->e_ident
[EI_MAG2
] != ELFMAG2
186 || ehdr
->e_ident
[EI_MAG3
] != ELFMAG3
187 || ehdr
->e_version
!= EV_CURRENT
188 || ehdr
->e_ident
[EI_DATA
] != ELFDATA2LSB
189 || ehdr
->e_machine
!= EM_X86_64
)
190 return grub_error(GRUB_ERR_UNKNOWN_OS
, "no valid ELF header found");
192 if (ehdr
->e_type
!= ET_EXEC
)
193 return grub_error (GRUB_ERR_UNKNOWN_OS
, "invalid ELF file type");
195 /* FIXME: Should we support program headers at strange locations? */
196 if (ehdr
->e_phoff
+ ehdr
->e_phnum
* ehdr
->e_phentsize
> MULTIBOOT_SEARCH
)
197 return grub_error (GRUB_ERR_BAD_OS
, "program header at a too high offset");
199 /* We still in 32-bit mode */
200 if (ehdr
->e_entry
> 0xffffffff)
201 return grub_error (GRUB_ERR_BAD_OS
, "invalid entry point for ELF64");
203 entry
= ehdr
->e_entry
;
205 /* Load every loadable segment in memory. */
206 for (i
= 0; i
< ehdr
->e_phnum
; i
++)
208 phdr
= (Elf64_Phdr
*) ((char *) buffer
+ ehdr
->e_phoff
209 + i
* ehdr
->e_phentsize
);
210 if (phdr
->p_type
== PT_LOAD
)
212 /* The segment should fit in the area reserved for the OS. */
213 if (phdr
->p_paddr
< (grub_uint64_t
) grub_os_area_addr
)
214 return grub_error (GRUB_ERR_BAD_OS
,
215 "segment doesn't fit in memory reserved for the OS (0x%lx < 0x%lx)",
216 phdr
->p_paddr
, (grub_uint64_t
) grub_os_area_addr
);
217 if (phdr
->p_paddr
+ phdr
->p_memsz
218 > (grub_uint64_t
) grub_os_area_addr
+ (grub_uint64_t
) grub_os_area_size
)
219 return grub_error (GRUB_ERR_BAD_OS
,
220 "segment doesn't fit in memory reserved for the OS (0x%lx > 0x%lx)",
221 phdr
->p_paddr
+ phdr
->p_memsz
,
222 (grub_uint64_t
) grub_os_area_addr
+ (grub_uint64_t
) grub_os_area_size
);
224 if (grub_file_seek (file
, (grub_off_t
) phdr
->p_offset
)
226 return grub_error (GRUB_ERR_BAD_OS
,
227 "invalid offset in program header");
229 if ((phdr
->p_filesz
> 0) && (grub_file_read (file
, (void *) ((grub_uint32_t
) phdr
->p_paddr
),
231 != (grub_ssize_t
) phdr
->p_filesz
)
232 return grub_error (GRUB_ERR_BAD_OS
,
233 "couldn't read segment from file");
235 if (phdr
->p_filesz
< phdr
->p_memsz
)
236 grub_memset (((char *) ((grub_uint32_t
) phdr
->p_paddr
)
239 phdr
->p_memsz
- phdr
->p_filesz
);
241 if ((entry
>= phdr
->p_vaddr
) &&
242 (entry
< phdr
->p_vaddr
+ phdr
->p_memsz
))
243 physical_entry_addr
= entry
+ phdr
->p_paddr
- phdr
->p_vaddr
;
247 if (physical_entry_addr
)
248 entry
= physical_entry_addr
;
253 /* Load ELF32 or ELF64. */
255 grub_multiboot_load_elf (grub_file_t file
, void *buffer
)
257 if (grub_multiboot_is_elf32 (buffer
))
258 return grub_multiboot_load_elf32 (file
, buffer
);
259 else if (grub_multiboot_is_elf64 (buffer
))
260 return grub_multiboot_load_elf64 (file
, buffer
);
262 return grub_error (GRUB_ERR_UNKNOWN_OS
, "unknown ELF class");
266 grub_multiboot_get_bootdev (grub_uint32_t
*bootdev
)
270 p
= grub_env_get ("root");
271 if ((p
) && ((p
[0] == 'h') || (p
[0] == 'f')) && (p
[1] == 'd') &&
272 (p
[2] >= '0') && (p
[2] <= '9'))
276 bd
= (p
[0] == 'h') ? 0x80 : 0;
277 bd
+= grub_strtoul (p
+ 2, &p
, 0);
280 if ((p
) && (p
[0] == ','))
282 if ((p
[1] >= '0') && (p
[1] <= '9'))
285 bd
+= ((grub_strtoul (p
+ 1, &p
, 0) - 1) & 0xFF) << 16;
287 if ((p
) && (p
[0] == ','))
293 if ((p
[0] >= 'a') && (p
[0] <= 'z'))
294 bd
+= (p
[0] - 'a') << 8;
311 grub_multiboot (int argc
, char *argv
[])
313 grub_file_t file
= 0;
314 char buffer
[MULTIBOOT_SEARCH
], *cmdline
= 0, *p
;
315 struct grub_multiboot_header
*header
;
319 grub_loader_unset ();
323 grub_error (GRUB_ERR_BAD_ARGUMENT
, "No kernel specified");
327 file
= grub_gzfile_open (argv
[0], 1);
330 grub_error (GRUB_ERR_BAD_ARGUMENT
, "Couldn't open file");
334 len
= grub_file_read (file
, buffer
, MULTIBOOT_SEARCH
);
337 grub_error (GRUB_ERR_BAD_OS
, "File too small");
341 /* Look for the multiboot header in the buffer. The header should
342 be at least 12 bytes and aligned on a 4-byte boundary. */
343 for (header
= (struct grub_multiboot_header
*) buffer
;
344 ((char *) header
<= buffer
+ len
- 12) || (header
= 0);
345 header
= (struct grub_multiboot_header
*) ((char *) header
+ 4))
347 if (header
->magic
== MULTIBOOT_MAGIC
348 && !(header
->magic
+ header
->flags
+ header
->checksum
))
354 grub_error (GRUB_ERR_BAD_ARGUMENT
, "No multiboot header found");
358 if (header
->flags
& MULTIBOOT_UNSUPPORTED
)
360 grub_error (GRUB_ERR_UNKNOWN_OS
,
361 "Unsupported flag: 0x%x", header
->flags
);
365 if (header
->flags
& MULTIBOOT_AOUT_KLUDGE
)
369 ofs
= (char *) header
- buffer
-
370 (header
->header_addr
- header
->load_addr
);
371 if ((grub_aout_load (file
, ofs
, header
->load_addr
,
372 ((header
->load_end_addr
== 0) ? 0 :
373 header
->load_end_addr
- header
->load_addr
),
374 header
->bss_end_addr
))
378 entry
= header
->entry_addr
;
380 else if (grub_multiboot_load_elf (file
, buffer
) != GRUB_ERR_NONE
)
383 mbi
= grub_malloc (sizeof (struct grub_multiboot_info
));
387 grub_memset (mbi
, 0, sizeof (struct grub_multiboot_info
));
389 mbi
->flags
= MULTIBOOT_INFO_MEMORY
;
391 /* Convert from bytes to kilobytes. */
392 mbi
->mem_lower
= grub_lower_mem
/ 1024;
393 mbi
->mem_upper
= grub_upper_mem
/ 1024;
395 for (i
= 0, len
= 0; i
< argc
; i
++)
396 len
+= grub_strlen (argv
[i
]) + 1;
398 cmdline
= p
= grub_malloc (len
);
402 for (i
= 0; i
< argc
; i
++)
404 p
= grub_stpcpy (p
, argv
[i
]);
408 /* Remove the space after the last word. */
411 mbi
->flags
|= MULTIBOOT_INFO_CMDLINE
;
412 mbi
->cmdline
= (grub_uint32_t
) cmdline
;
414 mbi
->flags
|= MULTIBOOT_INFO_BOOT_LOADER_NAME
;
415 mbi
->boot_loader_name
= (grub_uint32_t
) grub_strdup (PACKAGE_STRING
);
417 if (grub_multiboot_get_bootdev (&mbi
->boot_device
))
418 mbi
->flags
|= MULTIBOOT_INFO_BOOTDEV
;
420 grub_loader_set (grub_multiboot_boot
, grub_multiboot_unload
, 1);
424 grub_file_close (file
);
426 if (grub_errno
!= GRUB_ERR_NONE
)
430 grub_dl_unref (my_mod
);
436 grub_module (int argc
, char *argv
[])
438 grub_file_t file
= 0;
439 grub_ssize_t size
, len
= 0;
440 char *module
= 0, *cmdline
= 0, *p
;
445 grub_error (GRUB_ERR_BAD_ARGUMENT
, "No module specified");
451 grub_error (GRUB_ERR_BAD_ARGUMENT
,
452 "You need to load the multiboot kernel first");
456 file
= grub_gzfile_open (argv
[0], 1);
460 size
= grub_file_size (file
);
461 module
= grub_memalign (MULTIBOOT_MOD_ALIGN
, size
);
465 if (grub_file_read (file
, module
, size
) != size
)
467 grub_error (GRUB_ERR_FILE_READ_ERROR
, "Couldn't read file");
471 for (i
= 0; i
< argc
; i
++)
472 len
+= grub_strlen (argv
[i
]) + 1;
474 cmdline
= p
= grub_malloc (len
);
478 for (i
= 0; i
< argc
; i
++)
480 p
= grub_stpcpy (p
, argv
[i
]);
484 /* Remove the space after the last word. */
487 if (mbi
->flags
& MULTIBOOT_INFO_MODS
)
489 struct grub_mod_list
*modlist
= (struct grub_mod_list
*) mbi
->mods_addr
;
491 modlist
= grub_realloc (modlist
, (mbi
->mods_count
+ 1)
492 * sizeof (struct grub_mod_list
));
495 mbi
->mods_addr
= (grub_uint32_t
) modlist
;
496 modlist
+= mbi
->mods_count
;
497 modlist
->mod_start
= (grub_uint32_t
) module
;
498 modlist
->mod_end
= (grub_uint32_t
) module
+ size
;
499 modlist
->cmdline
= (grub_uint32_t
) cmdline
;
505 struct grub_mod_list
*modlist
= grub_malloc (sizeof (struct grub_mod_list
));
508 modlist
->mod_start
= (grub_uint32_t
) module
;
509 modlist
->mod_end
= (grub_uint32_t
) module
+ size
;
510 modlist
->cmdline
= (grub_uint32_t
) cmdline
;
513 mbi
->mods_addr
= (grub_uint32_t
) modlist
;
514 mbi
->flags
|= MULTIBOOT_INFO_MODS
;
519 grub_file_close (file
);
521 if (grub_errno
!= GRUB_ERR_NONE
)