[e16e8f2] | 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 | } |
---|