1 /* ----------------------------------------------------------------------- * 2 * 3 * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved 4 * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin 5 * 6 * Permission is hereby granted, free of charge, to any person 7 * obtaining a copy of this software and associated documentation 8 * files (the "Software"), to deal in the Software without 9 * restriction, including without limitation the rights to use, 10 * copy, modify, merge, publish, distribute, sublicense, and/or 11 * sell copies of the Software, and to permit persons to whom 12 * the Software is furnished to do so, subject to the following 13 * conditions: 14 * 15 * The above copyright notice and this permission notice shall 16 * be included in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 * OTHER DEALINGS IN THE SOFTWARE. 26 * 27 * ----------------------------------------------------------------------- */ 28 29 /* 30 * map.c 31 * 32 * Functions that deal with the memory map of various objects 33 */ 34 35 #include "mboot.h" 36 37 static struct syslinux_movelist *ml = NULL; 38 static struct syslinux_memmap *mmap = NULL, *amap = NULL; 39 static addr_t mboot_high_water_mark = 0x100000; 40 41 /* 42 * Note: although there is no such thing in the spec, at least Xen makes 43 * assumptions as to where in the memory space Grub would have loaded 44 * certain things. To support that, if "high" is set, then allocate this 45 * at an address strictly above any previous allocations. 46 * 47 * As a precaution, this also pads the data with zero up to the next 48 * alignment datum. 49 */ 50 addr_t map_data(const void *data, size_t len, size_t align, int flags) 51 { 52 addr_t start = (flags & MAP_HIGH) ? mboot_high_water_mark : 0x2000; 53 addr_t pad = (flags & MAP_NOPAD) ? 0 : -len & (align - 1); 54 addr_t xlen = len + pad; 55 56 if (syslinux_memmap_find_type(amap, SMT_FREE, &start, &xlen, align) || 57 syslinux_add_memmap(&amap, start, len + pad, SMT_ALLOC) || 58 syslinux_add_movelist(&ml, start, (addr_t) data, len) || 59 (pad && syslinux_add_memmap(&mmap, start + len, pad, SMT_ZERO))) { 60 printf("Cannot map %zu bytes\n", len + pad); 61 return 0; 62 } 63 64 dprintf("Mapping 0x%08x bytes (%#x pad) at 0x%08x\n", len, pad, start); 65 66 if (start + len + pad > mboot_high_water_mark) 67 mboot_high_water_mark = start + len + pad; 68 69 return start; 70 } 71 72 addr_t map_string(const char *string) 73 { 74 if (!string) 75 return 0; 76 else 77 return map_data(string, strlen(string) + 1, 1, 0); 78 } 79 80 int init_map(void) 81 { 82 /* 83 * Note: mmap is the memory map (containing free and zeroed regions) 84 * needed by syslinux_shuffle_boot_pm(); amap is a map where we keep 85 * track ourselves which target memory ranges have already been 86 * allocated. 87 */ 88 mmap = syslinux_memory_map(); 89 amap = syslinux_dup_memmap(mmap); 90 if (!mmap || !amap) { 91 error("Failed to allocate initial memory map!\n"); 92 return -1; 93 } 94 95 dprintf("Initial memory map:\n"); 96 syslinux_dump_memmap(mmap); 97 98 return 0; 99 } 100 101 struct multiboot_header *map_image(void *ptr, size_t len) 102 { 103 struct multiboot_header *mbh; 104 int mbh_len; 105 char *cptr = ptr; 106 Elf32_Ehdr *eh = ptr; 107 Elf32_Phdr *ph; 108 Elf32_Shdr *sh; 109 unsigned int i, mbh_offset; 110 uint32_t bad_flags; 111 112 /* 113 * Search for the multiboot header... 114 */ 115 mbh_len = 0; 116 for (mbh_offset = 0; mbh_offset < MULTIBOOT_SEARCH; mbh_offset += 4) { 117 mbh = (struct multiboot_header *)((char *)ptr + mbh_offset); 118 if (mbh->magic != MULTIBOOT_MAGIC) 119 continue; 120 if (mbh->magic + mbh->flags + mbh->checksum) 121 continue; 122 if (mbh->flags & MULTIBOOT_VIDEO_MODE) 123 mbh_len = 48; 124 else if (mbh->flags & MULTIBOOT_AOUT_KLUDGE) 125 mbh_len = 32; 126 else 127 mbh_len = 12; 128 129 if (mbh_offset + mbh_len > len) 130 mbh_len = 0; /* Invalid... */ 131 else 132 break; /* Found something... */ 133 } 134 135 if (mbh_len) { 136 bad_flags = mbh->flags & MULTIBOOT_UNSUPPORTED; 137 if (bad_flags) { 138 printf("Unsupported Multiboot flags set: %#x\n", bad_flags); 139 return NULL; 140 } 141 } 142 143 if (len < sizeof(Elf32_Ehdr) || 144 memcmp(eh->e_ident, "\x7f" "ELF\1\1\1", 6) || 145 (eh->e_machine != EM_386 && eh->e_machine != EM_486 && 146 eh->e_machine != EM_X86_64) || 147 eh->e_version != EV_CURRENT || 148 eh->e_ehsize < sizeof(Elf32_Ehdr) || eh->e_ehsize >= len || 149 eh->e_phentsize < sizeof(Elf32_Phdr) || 150 !eh->e_phnum || eh->e_phoff + eh->e_phentsize * eh->e_phnum > len) 151 eh = NULL; /* No valid ELF header found */ 152 153 /* Is this a Solaris kernel? */ 154 if (!set.solaris && eh && kernel_is_solaris(eh)) 155 opt.solaris = true; 156 157 /* 158 * Note: the Multiboot Specification implies that AOUT_KLUDGE should 159 * have precedence over the ELF header. However, Grub disagrees, and 160 * Grub is "the reference bootloader" for the Multiboot Specification. 161 * This is insane, since it makes the AOUT_KLUDGE bit functionally 162 * useless, but at least Solaris apparently depends on this behavior. 163 */ 164 if (eh && !(opt.aout && mbh_len && (mbh->flags & MULTIBOOT_AOUT_KLUDGE))) { 165 regs.eip = eh->e_entry; /* Can be overridden further down... */ 166 167 ph = (Elf32_Phdr *) (cptr + eh->e_phoff); 168 169 for (i = 0; i < eh->e_phnum; i++) { 170 if (ph->p_type == PT_LOAD || ph->p_type == PT_PHDR) { 171 /* 172 * This loads at p_paddr, which matches Grub. However, if 173 * e_entry falls within the p_vaddr range of this PHDR, then 174 * adjust it to match the p_paddr range... this is how Grub 175 * behaves, so it's by definition correct (it doesn't have to 176 * make sense...) 177 */ 178 addr_t addr = ph->p_paddr; 179 addr_t msize = ph->p_memsz; 180 addr_t dsize = min(msize, ph->p_filesz); 181 182 if (eh->e_entry >= ph->p_vaddr 183 && eh->e_entry < ph->p_vaddr + msize) 184 regs.eip = eh->e_entry + (ph->p_paddr - ph->p_vaddr); 185 186 dprintf("Segment at 0x%08x data 0x%08x len 0x%08x\n", 187 addr, dsize, msize); 188 189 if (syslinux_memmap_type(amap, addr, msize) != SMT_FREE) { 190 printf 191 ("Memory segment at 0x%08x (len 0x%08x) is unavailable\n", 192 addr, msize); 193 return NULL; /* Memory region unavailable */ 194 } 195 196 /* Mark this region as allocated in the available map */ 197 if (syslinux_add_memmap(&amap, addr, msize, SMT_ALLOC)) { 198 error("Overlapping segments found in ELF header\n"); 199 return NULL; 200 } 201 202 if (ph->p_filesz) { 203 /* Data present region. Create a move entry for it. */ 204 if (syslinux_add_movelist 205 (&ml, addr, (addr_t) cptr + ph->p_offset, dsize)) { 206 error("Failed to map PHDR data\n"); 207 return NULL; 208 } 209 } 210 if (msize > dsize) { 211 /* Zero-filled region. Mark as a zero region in the memory map. */ 212 if (syslinux_add_memmap 213 (&mmap, addr + dsize, msize - dsize, SMT_ZERO)) { 214 error("Failed to map PHDR zero region\n"); 215 return NULL; 216 } 217 } 218 if (addr + msize > mboot_high_water_mark) 219 mboot_high_water_mark = addr + msize; 220 } else { 221 /* Ignore this program header */ 222 } 223 224 ph = (Elf32_Phdr *) ((char *)ph + eh->e_phentsize); 225 } 226 227 /* Load the ELF symbol table */ 228 if (eh->e_shoff) { 229 addr_t addr, len; 230 231 sh = (Elf32_Shdr *) ((char *)eh + eh->e_shoff); 232 233 len = eh->e_shentsize * eh->e_shnum; 234 /* 235 * Align this, but don't pad -- in general this means a bunch of 236 * smaller sections gets packed into a single page. 237 */ 238 addr = map_data(sh, len, 4096, MAP_HIGH | MAP_NOPAD); 239 if (!addr) { 240 error("Failed to map symbol table\n"); 241 return NULL; 242 } 243 244 mbinfo.flags |= MB_INFO_ELF_SHDR; 245 mbinfo.syms.e.addr = addr; 246 mbinfo.syms.e.num = eh->e_shnum; 247 mbinfo.syms.e.size = eh->e_shentsize; 248 mbinfo.syms.e.shndx = eh->e_shstrndx; 249 250 for (i = 0; i < eh->e_shnum; i++) { 251 addr_t align; 252 253 if (!sh[i].sh_size) 254 continue; /* Empty section */ 255 if (sh[i].sh_flags & SHF_ALLOC) 256 continue; /* SHF_ALLOC sections should have PHDRs */ 257 258 align = sh[i].sh_addralign ? sh[i].sh_addralign : 0; 259 addr = map_data((char *)ptr + sh[i].sh_offset, sh[i].sh_size, 260 align, MAP_HIGH); 261 if (!addr) { 262 error("Failed to map symbol section\n"); 263 return NULL; 264 } 265 sh[i].sh_addr = addr; 266 } 267 } 268 } else if (mbh_len && (mbh->flags & MULTIBOOT_AOUT_KLUDGE)) { 269 /* 270 * a.out kludge thing... 271 */ 272 char *data_ptr; 273 addr_t data_len, bss_len; 274 addr_t bss_addr; 275 276 regs.eip = mbh->entry_addr; 277 278 data_ptr = (char *)mbh - (mbh->header_addr - mbh->load_addr); 279 280 if (mbh->load_end_addr) 281 data_len = mbh->load_end_addr - mbh->load_addr; 282 else 283 data_len = len - mbh_offset + (mbh->header_addr - mbh->load_addr); 284 285 bss_addr = mbh->load_addr + data_len; 286 287 if (mbh->bss_end_addr) 288 bss_len = mbh->bss_end_addr - mbh->load_end_addr; 289 else 290 bss_len = 0; 291 292 if (syslinux_memmap_type(amap, mbh->load_addr, data_len + bss_len) 293 != SMT_FREE) { 294 printf("Memory segment at 0x%08x (len 0x%08x) is unavailable\n", 295 mbh->load_addr, data_len + bss_len); 296 return NULL; /* Memory region unavailable */ 297 } 298 if (syslinux_add_memmap(&amap, mbh->load_addr, 299 data_len + bss_len, SMT_ALLOC)) { 300 error("Failed to claim a.out address space!\n"); 301 return NULL; 302 } 303 if (data_len) 304 if (syslinux_add_movelist(&ml, mbh->load_addr, (addr_t) data_ptr, 305 data_len)) { 306 error("Failed to map a.out data\n"); 307 return NULL; 308 } 309 if (bss_len) 310 if (syslinux_add_memmap 311 (&mmap, bss_addr, bss_len, SMT_ZERO)) { 312 error("Failed to map a.out bss\n"); 313 return NULL; 314 } 315 if (bss_addr + bss_len > mboot_high_water_mark) 316 mboot_high_water_mark = bss_addr + bss_len; 317 } else { 318 error 319 ("Invalid Multiboot image: neither ELF header nor a.out kludge found\n"); 320 return NULL; 321 } 322 323 return mbh; 324 } 325 326 /* 327 * Set up a stack. This isn't actually required by the spec, but it seems 328 * like a prudent thing to do. Also, put enough zeros at the top of the 329 * stack that something that looks for an ELF invocation record will know 330 * there isn't one. 331 */ 332 static void mboot_map_stack(void) 333 { 334 addr_t start, len; 335 336 if (syslinux_memmap_largest(amap, SMT_FREE, &start, &len) || len < 64) 337 return; /* Not much we can do, here... */ 338 339 regs.esp = (start + len - 32) & ~15; 340 dprintf("Mapping stack at 0x%08x\n", regs.esp); 341 syslinux_add_memmap(&mmap, regs.esp, 32, SMT_ZERO); 342 } 343 344 void mboot_run(int bootflags) 345 { 346 mboot_map_stack(); 347 348 dprintf("Running, eip = 0x%08x, ebx = 0x%08x\n", regs.eip, regs.ebx); 349 350 regs.eax = MULTIBOOT_VALID; 351 syslinux_shuffle_boot_pm(ml, mmap, bootflags, ®s); 352 } 353