/* xnu.c - load xnu kernel. Thanks to Florian Idelberger for all the time he spent testing this */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2009 Free Software Foundation, Inc. * * VAS_EBOOT 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 3 of the License, or * (at your option) any later version. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); #if defined (__i386) && !defined (VAS_EBOOT_MACHINE_EFI) #include #endif struct VasEBoot_xnu_devtree_key *VasEBoot_xnu_devtree_root = 0; static int driverspackagenum = 0; static int driversnum = 0; int VasEBoot_xnu_is_64bit = 0; int VasEBoot_xnu_darwin_version = 0; VasEBoot_addr_t VasEBoot_xnu_heap_target_start = 0; VasEBoot_size_t VasEBoot_xnu_heap_size = 0; struct VasEBoot_relocator *VasEBoot_xnu_relocator; static VasEBoot_err_t VasEBoot_xnu_register_memory (const char *prefix, int *suffix, VasEBoot_addr_t addr, VasEBoot_size_t size); VasEBoot_err_t VasEBoot_xnu_heap_malloc (int size, void **src, VasEBoot_addr_t *target) { VasEBoot_err_t err; VasEBoot_relocator_chunk_t ch; VasEBoot_addr_t tgt; if (VasEBoot_add (VasEBoot_xnu_heap_target_start, VasEBoot_xnu_heap_size, &tgt)) return VAS_EBOOT_ERR_OUT_OF_RANGE; err = VasEBoot_relocator_alloc_chunk_addr (VasEBoot_xnu_relocator, &ch, tgt, size); if (err) return err; *src = get_virtual_current_address (ch); *target = tgt; VasEBoot_xnu_heap_size += size; VasEBoot_dprintf ("xnu", "val=%p\n", *src); return VAS_EBOOT_ERR_NONE; } /* Make sure next block of the heap will be aligned. Please notice: aligned are pointers AFTER relocation and not the current ones. */ VasEBoot_err_t VasEBoot_xnu_align_heap (int align) { VasEBoot_xnu_heap_size = ALIGN_UP (VasEBoot_xnu_heap_target_start+ VasEBoot_xnu_heap_size, align) - VasEBoot_xnu_heap_target_start; return VAS_EBOOT_ERR_NONE; } /* Free subtree pointed by CUR. */ void VasEBoot_xnu_free_devtree (struct VasEBoot_xnu_devtree_key *cur) { struct VasEBoot_xnu_devtree_key *d; while (cur) { VasEBoot_free (cur->name); if (cur->datasize == -1) VasEBoot_xnu_free_devtree (cur->first_child); else if (cur->data) VasEBoot_free (cur->data); d = cur->next; VasEBoot_free (cur); cur = d; } } /* Compute the size of device tree in xnu format. */ static VasEBoot_size_t VasEBoot_xnu_writetree_get_size (struct VasEBoot_xnu_devtree_key *start, const char *name) { VasEBoot_size_t ret; struct VasEBoot_xnu_devtree_key *cur; /* Key header. */ ret = 2 * sizeof (VasEBoot_uint32_t); /* "name" value. */ ret += 32 + sizeof (VasEBoot_uint32_t) + VasEBoot_strlen (name) + 4 - (VasEBoot_strlen (name) % 4); for (cur = start; cur; cur = cur->next) if (cur->datasize != -1) { int align_overhead; align_overhead = 4 - (cur->datasize % 4); if (align_overhead == 4) align_overhead = 0; ret += 32 + sizeof (VasEBoot_uint32_t) + cur->datasize + align_overhead; } else ret += VasEBoot_xnu_writetree_get_size (cur->first_child, cur->name); return ret; } /* Write devtree in XNU format at curptr assuming the head is named NAME.*/ static void * VasEBoot_xnu_writetree_toheap_real (void *curptr, struct VasEBoot_xnu_devtree_key *start, const char *name) { struct VasEBoot_xnu_devtree_key *cur; int nkeys = 0, nvals = 0; for (cur = start; cur; cur = cur->next) { if (cur->datasize == -1) nkeys++; else nvals++; } /* For the name. */ nvals++; *((VasEBoot_uint32_t *) curptr) = nvals; curptr = ((VasEBoot_uint32_t *) curptr) + 1; *((VasEBoot_uint32_t *) curptr) = nkeys; curptr = ((VasEBoot_uint32_t *) curptr) + 1; /* First comes "name" value. */ VasEBoot_memset (curptr, 0, 32); VasEBoot_memcpy (curptr, "name", 4); curptr = ((VasEBoot_uint8_t *) curptr) + 32; *((VasEBoot_uint32_t *)curptr) = VasEBoot_strlen (name) + 1; curptr = ((VasEBoot_uint32_t *) curptr) + 1; VasEBoot_memcpy (curptr, name, VasEBoot_strlen (name)); curptr = ((VasEBoot_uint8_t *) curptr) + VasEBoot_strlen (name); VasEBoot_memset (curptr, 0, 4 - (VasEBoot_strlen (name) % 4)); curptr = ((VasEBoot_uint8_t *) curptr) + (4 - (VasEBoot_strlen (name) % 4)); /* Then the other values. */ for (cur = start; cur; cur = cur->next) if (cur->datasize != -1) { int align_overhead; align_overhead = 4 - (cur->datasize % 4); if (align_overhead == 4) align_overhead = 0; VasEBoot_memset (curptr, 0, 32); VasEBoot_strncpy (curptr, cur->name, 31); curptr = ((VasEBoot_uint8_t *) curptr) + 32; *((VasEBoot_uint32_t *) curptr) = cur->datasize; curptr = ((VasEBoot_uint32_t *) curptr) + 1; VasEBoot_memcpy (curptr, cur->data, cur->datasize); curptr = ((VasEBoot_uint8_t *) curptr) + cur->datasize; VasEBoot_memset (curptr, 0, align_overhead); curptr = ((VasEBoot_uint8_t *) curptr) + align_overhead; } /* And then the keys. Recursively use this function. */ for (cur = start; cur; cur = cur->next) if (cur->datasize == -1) { curptr = VasEBoot_xnu_writetree_toheap_real (curptr, cur->first_child, cur->name); if (!curptr) return 0; } return curptr; } VasEBoot_err_t VasEBoot_xnu_writetree_toheap (VasEBoot_addr_t *target, VasEBoot_size_t *size) { struct VasEBoot_xnu_devtree_key *chosen; struct VasEBoot_xnu_devtree_key *memorymap; struct VasEBoot_xnu_devtree_key *driverkey; struct VasEBoot_xnu_extdesc *extdesc; VasEBoot_err_t err; void *src; err = VasEBoot_xnu_align_heap (VAS_EBOOT_XNU_PAGESIZE); if (err) return err; /* Device tree itself is in the memory map of device tree. */ /* Create a dummy value in memory-map. */ chosen = VasEBoot_xnu_create_key (&VasEBoot_xnu_devtree_root, "chosen"); if (! chosen) return VasEBoot_errno; memorymap = VasEBoot_xnu_create_key (&(chosen->first_child), "memory-map"); if (! memorymap) return VasEBoot_errno; driverkey = (struct VasEBoot_xnu_devtree_key *) VasEBoot_zalloc (sizeof (*driverkey)); if (! driverkey) return VasEBoot_errno; driverkey->name = VasEBoot_strdup ("DeviceTree"); if (! driverkey->name) { err = VasEBoot_errno; goto fail; } driverkey->datasize = sizeof (*extdesc); driverkey->next = memorymap->first_child; memorymap->first_child = driverkey; driverkey->data = extdesc = (struct VasEBoot_xnu_extdesc *) VasEBoot_malloc (sizeof (*extdesc)); if (! driverkey->data) { err = VasEBoot_errno; goto fail; } /* Allocate the space based on the size with dummy value. */ *size = VasEBoot_xnu_writetree_get_size (VasEBoot_xnu_devtree_root, "/"); err = VasEBoot_xnu_heap_malloc (ALIGN_UP (*size + 1, VAS_EBOOT_XNU_PAGESIZE), &src, target); if (err) goto fail; /* Put real data in the dummy. */ extdesc->addr = *target; extdesc->size = (VasEBoot_uint32_t) *size; /* Write the tree to heap. */ VasEBoot_xnu_writetree_toheap_real (src, VasEBoot_xnu_devtree_root, "/"); return VAS_EBOOT_ERR_NONE; fail: memorymap->first_child = NULL; VasEBoot_free (driverkey->data); VasEBoot_free (driverkey->name); VasEBoot_free (driverkey); return err; } /* Find a key or value in parent key. */ struct VasEBoot_xnu_devtree_key * VasEBoot_xnu_find_key (struct VasEBoot_xnu_devtree_key *parent, const char *name) { struct VasEBoot_xnu_devtree_key *cur; for (cur = parent; cur; cur = cur->next) if (VasEBoot_strcmp (cur->name, name) == 0) return cur; return 0; } struct VasEBoot_xnu_devtree_key * VasEBoot_xnu_create_key (struct VasEBoot_xnu_devtree_key **parent, const char *name) { struct VasEBoot_xnu_devtree_key *ret; ret = VasEBoot_xnu_find_key (*parent, name); if (ret) return ret; ret = (struct VasEBoot_xnu_devtree_key *) VasEBoot_zalloc (sizeof (*ret)); if (! ret) return 0; ret->name = VasEBoot_strdup (name); if (! ret->name) { VasEBoot_free (ret); return 0; } ret->datasize = -1; ret->next = *parent; *parent = ret; return ret; } struct VasEBoot_xnu_devtree_key * VasEBoot_xnu_create_value (struct VasEBoot_xnu_devtree_key **parent, const char *name) { struct VasEBoot_xnu_devtree_key *ret; ret = VasEBoot_xnu_find_key (*parent, name); if (ret) { if (ret->datasize == -1) VasEBoot_xnu_free_devtree (ret->first_child); else if (ret->datasize) VasEBoot_free (ret->data); ret->datasize = 0; ret->data = 0; return ret; } ret = (struct VasEBoot_xnu_devtree_key *) VasEBoot_zalloc (sizeof (*ret)); if (! ret) return 0; ret->name = VasEBoot_strdup (name); if (! ret->name) { VasEBoot_free (ret); return 0; } ret->next = *parent; *parent = ret; return ret; } static VasEBoot_err_t VasEBoot_xnu_unload (void) { VasEBoot_cpu_xnu_unload (); VasEBoot_xnu_free_devtree (VasEBoot_xnu_devtree_root); VasEBoot_xnu_devtree_root = 0; /* Free loaded image. */ driversnum = 0; driverspackagenum = 0; VasEBoot_relocator_unload (VasEBoot_xnu_relocator); VasEBoot_xnu_relocator = NULL; VasEBoot_xnu_heap_target_start = 0; VasEBoot_xnu_heap_size = 0; VasEBoot_xnu_unlock (); return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_cmd_xnu_kernel (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { VasEBoot_err_t err; VasEBoot_macho_t macho; VasEBoot_uint32_t startcode, endcode; int i; char *ptr; void *loadaddr; VasEBoot_addr_t loadaddr_target; if (argc < 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); VasEBoot_xnu_unload (); macho = VasEBoot_macho_open (args[0], VAS_EBOOT_FILE_TYPE_XNU_KERNEL, 0); if (! macho) return VasEBoot_errno; err = VasEBoot_macho_size32 (macho, &startcode, &endcode, VAS_EBOOT_MACHO_NOBSS, args[0]); if (err) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return err; } VasEBoot_dprintf ("xnu", "endcode = %lx, startcode = %lx\n", (unsigned long) endcode, (unsigned long) startcode); VasEBoot_xnu_relocator = VasEBoot_relocator_new (); if (!VasEBoot_xnu_relocator) return VasEBoot_errno; VasEBoot_xnu_heap_target_start = startcode; err = VasEBoot_xnu_heap_malloc (endcode - startcode, &loadaddr, &loadaddr_target); if (err) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return err; } /* Load kernel. */ err = VasEBoot_macho_load32 (macho, args[0], (char *) loadaddr - startcode, VAS_EBOOT_MACHO_NOBSS, &VasEBoot_xnu_darwin_version); if (err) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return err; } VasEBoot_xnu_entry_point = VasEBoot_macho_get_entry_point32 (macho, args[0]); if (! VasEBoot_xnu_entry_point) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, "couldn't find entry point"); } VasEBoot_macho_close (macho); err = VasEBoot_xnu_align_heap (VAS_EBOOT_XNU_PAGESIZE); if (err) { VasEBoot_xnu_unload (); return err; } /* Copy parameters to kernel command line. */ ptr = VasEBoot_xnu_cmdline; for (i = 1; i < argc; i++) { if (ptr + VasEBoot_strlen (args[i]) + 1 >= VasEBoot_xnu_cmdline + sizeof (VasEBoot_xnu_cmdline)) break; VasEBoot_memcpy (ptr, args[i], VasEBoot_strlen (args[i])); ptr += VasEBoot_strlen (args[i]); *ptr = ' '; ptr++; } /* Replace last space by '\0'. */ if (ptr != VasEBoot_xnu_cmdline) *(ptr - 1) = 0; err = VasEBoot_verify_string (VasEBoot_xnu_cmdline, VAS_EBOOT_VERIFY_KERNEL_CMDLINE); if (err) return err; #if defined (__i386) && !defined (VAS_EBOOT_MACHINE_EFI) err = VasEBoot_efiemu_autocore (); if (err) return err; #endif VasEBoot_loader_set (VasEBoot_xnu_boot, VasEBoot_xnu_unload, 0); VasEBoot_xnu_lock (); VasEBoot_xnu_is_64bit = 0; return 0; } static VasEBoot_err_t VasEBoot_cmd_xnu_kernel64 (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { VasEBoot_err_t err; VasEBoot_macho_t macho; VasEBoot_uint64_t startcode, endcode; int i; char *ptr; void *loadaddr; VasEBoot_addr_t loadaddr_target; if (argc < 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); VasEBoot_xnu_unload (); macho = VasEBoot_macho_open (args[0], VAS_EBOOT_FILE_TYPE_XNU_KERNEL, 1); if (! macho) return VasEBoot_errno; err = VasEBoot_macho_size64 (macho, &startcode, &endcode, VAS_EBOOT_MACHO_NOBSS, args[0]); if (err) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return err; } startcode &= 0x0fffffff; endcode &= 0x0fffffff; VasEBoot_dprintf ("xnu", "endcode = %lx, startcode = %lx\n", (unsigned long) endcode, (unsigned long) startcode); VasEBoot_xnu_relocator = VasEBoot_relocator_new (); if (!VasEBoot_xnu_relocator) return VasEBoot_errno; VasEBoot_xnu_heap_target_start = startcode; err = VasEBoot_xnu_heap_malloc (endcode - startcode, &loadaddr, &loadaddr_target); if (err) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return err; } /* Load kernel. */ err = VasEBoot_macho_load64 (macho, args[0], (char *) loadaddr - startcode, VAS_EBOOT_MACHO_NOBSS, &VasEBoot_xnu_darwin_version); if (err) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return err; } VasEBoot_xnu_entry_point = VasEBoot_macho_get_entry_point64 (macho, args[0]) & 0x0fffffff; if (! VasEBoot_xnu_entry_point) { VasEBoot_macho_close (macho); VasEBoot_xnu_unload (); return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, "couldn't find entry point"); } VasEBoot_macho_close (macho); err = VasEBoot_xnu_align_heap (VAS_EBOOT_XNU_PAGESIZE); if (err) { VasEBoot_xnu_unload (); return err; } /* Copy parameters to kernel command line. */ ptr = VasEBoot_xnu_cmdline; for (i = 1; i < argc; i++) { if (ptr + VasEBoot_strlen (args[i]) + 1 >= VasEBoot_xnu_cmdline + sizeof (VasEBoot_xnu_cmdline)) break; VasEBoot_memcpy (ptr, args[i], VasEBoot_strlen (args[i])); ptr += VasEBoot_strlen (args[i]); *ptr = ' '; ptr++; } /* Replace last space by '\0'. */ if (ptr != VasEBoot_xnu_cmdline) *(ptr - 1) = 0; err = VasEBoot_verify_string (VasEBoot_xnu_cmdline, VAS_EBOOT_VERIFY_KERNEL_CMDLINE); if (err) return err; #if defined (__i386) && !defined (VAS_EBOOT_MACHINE_EFI) err = VasEBoot_efiemu_autocore (); if (err) return err; #endif VasEBoot_loader_set (VasEBoot_xnu_boot, VasEBoot_xnu_unload, 0); VasEBoot_xnu_lock (); VasEBoot_xnu_is_64bit = 1; return 0; } /* Register a memory in a memory map under name PREFIXSUFFIX and increment SUFFIX. */ static VasEBoot_err_t VasEBoot_xnu_register_memory (const char *prefix, int *suffix, VasEBoot_addr_t addr, VasEBoot_size_t size) { struct VasEBoot_xnu_devtree_key *chosen; struct VasEBoot_xnu_devtree_key *memorymap; struct VasEBoot_xnu_devtree_key *driverkey; struct VasEBoot_xnu_extdesc *extdesc; if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); chosen = VasEBoot_xnu_create_key (&VasEBoot_xnu_devtree_root, "chosen"); if (! chosen) return VasEBoot_errno; memorymap = VasEBoot_xnu_create_key (&(chosen->first_child), "memory-map"); if (! memorymap) return VasEBoot_errno; driverkey = (struct VasEBoot_xnu_devtree_key *) VasEBoot_malloc (sizeof (*driverkey)); if (! driverkey) return VasEBoot_errno; if (suffix) driverkey->name = VasEBoot_xasprintf ("%s%d", prefix, (*suffix)++); else driverkey->name = VasEBoot_strdup (prefix); if (!driverkey->name) { VasEBoot_free (driverkey); return VasEBoot_errno; } driverkey->datasize = sizeof (*extdesc); driverkey->next = memorymap->first_child; driverkey->data = extdesc = (struct VasEBoot_xnu_extdesc *) VasEBoot_malloc (sizeof (*extdesc)); if (! driverkey->data) { VasEBoot_free (driverkey->name); VasEBoot_free (driverkey); return VasEBoot_errno; } memorymap->first_child = driverkey; extdesc->addr = addr; extdesc->size = (VasEBoot_uint32_t) size; return VAS_EBOOT_ERR_NONE; } static inline char * get_name_ptr (char *name) { char *p = name, *p2; /* Skip Info.plist. */ p2 = VasEBoot_strrchr (p, '/'); if (!p2) return name; if (p2 == name) return name + 1; p = p2 - 1; p2 = VasEBoot_strrchr (p, '/'); if (!p2) return name; if (p2 == name) return name + 1; if (VasEBoot_memcmp (p2, "/Contents/", sizeof ("/Contents/") - 1) != 0) return p2 + 1; p = p2 - 1; p2 = VasEBoot_strrchr (p, '/'); if (!p2) return name; return p2 + 1; } /* Load .kext. */ static VasEBoot_err_t VasEBoot_xnu_load_driver (char *infoplistname, VasEBoot_file_t binaryfile, const char *filename) { VasEBoot_macho_t macho; VasEBoot_err_t err; VasEBoot_file_t infoplist; struct VasEBoot_xnu_extheader *exthead; int neededspace = sizeof (*exthead); VasEBoot_uint8_t *buf; void *buf0; VasEBoot_addr_t buf_target; VasEBoot_size_t infoplistsize = 0, machosize = 0; char *name, *nameend; int namelen; if (infoplistname == NULL) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FILENAME, N_("missing p-list filename")); name = get_name_ptr (infoplistname); nameend = VasEBoot_strchr (name, '/'); if (nameend) namelen = nameend - name; else namelen = VasEBoot_strlen (name); neededspace += namelen + 1; if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); /* Compute the needed space. */ if (binaryfile) { macho = VasEBoot_macho_file (binaryfile, filename, VasEBoot_xnu_is_64bit); if (!macho) VasEBoot_file_close (binaryfile); else { if (VasEBoot_xnu_is_64bit) machosize = VasEBoot_macho_filesize64 (macho); else machosize = VasEBoot_macho_filesize32 (macho); } neededspace += machosize; } else macho = 0; infoplist = VasEBoot_file_open (infoplistname, VAS_EBOOT_FILE_TYPE_XNU_INFO_PLIST); VasEBoot_errno = VAS_EBOOT_ERR_NONE; if (infoplist) { infoplistsize = VasEBoot_file_size (infoplist); neededspace += infoplistsize + 1; } else infoplistsize = 0; /* Allocate the space. */ err = VasEBoot_xnu_align_heap (VAS_EBOOT_XNU_PAGESIZE); if (err) goto fail; err = VasEBoot_xnu_heap_malloc (neededspace, &buf0, &buf_target); if (err) goto fail; buf = buf0; exthead = (struct VasEBoot_xnu_extheader *) buf; VasEBoot_memset (exthead, 0, sizeof (*exthead)); buf += sizeof (*exthead); /* Load the binary. */ if (macho) { exthead->binaryaddr = buf_target + (buf - (VasEBoot_uint8_t *) buf0); exthead->binarysize = machosize; if (VasEBoot_xnu_is_64bit) err = VasEBoot_macho_readfile64 (macho, filename, buf); else err = VasEBoot_macho_readfile32 (macho, filename, buf); if (err) goto fail; VasEBoot_macho_close (macho); buf += machosize; } VasEBoot_errno = VAS_EBOOT_ERR_NONE; /* Load the plist. */ if (infoplist) { exthead->infoplistaddr = buf_target + (buf - (VasEBoot_uint8_t *) buf0); exthead->infoplistsize = infoplistsize + 1; if (VasEBoot_file_read (infoplist, buf, infoplistsize) != (VasEBoot_ssize_t) (infoplistsize)) { VasEBoot_file_close (infoplist); if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("premature end of file %s"), infoplistname); return VasEBoot_errno; } VasEBoot_file_close (infoplist); buf[infoplistsize] = 0; buf += infoplistsize + 1; } VasEBoot_errno = VAS_EBOOT_ERR_NONE; exthead->nameaddr = (buf - (VasEBoot_uint8_t *) buf0) + buf_target; exthead->namesize = namelen + 1; VasEBoot_memcpy (buf, name, namelen); buf[namelen] = 0; buf += namelen + 1; /* Announce to kernel */ return VasEBoot_xnu_register_memory ("Driver-", &driversnum, buf_target, neededspace); fail: if (macho) VasEBoot_macho_close (macho); return err; } /* Load mkext. */ static VasEBoot_err_t VasEBoot_cmd_xnu_mkext (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { VasEBoot_file_t file; void *loadto; VasEBoot_addr_t loadto_target; VasEBoot_err_t err; VasEBoot_off_t readoff = 0; VasEBoot_ssize_t readlen = -1; struct VasEBoot_macho_fat_header head; struct VasEBoot_macho_fat_arch *archs; int narchs, i; if (argc != 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); file = VasEBoot_file_open (args[0], VAS_EBOOT_FILE_TYPE_XNU_MKEXT); if (! file) return VasEBoot_errno; /* Sometimes caches are fat binary. Errgh. */ if (VasEBoot_file_read (file, &head, sizeof (head)) != (VasEBoot_ssize_t) (sizeof (head))) { /* I don't know the internal structure of package but can hardly imagine a valid package shorter than 20 bytes. */ VasEBoot_file_close (file); if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("premature end of file %s"), args[0]); return VasEBoot_errno; } /* Find the corresponding architecture. */ if (VasEBoot_be_to_cpu32 (head.magic) == VAS_EBOOT_MACHO_FAT_MAGIC) { narchs = VasEBoot_be_to_cpu32 (head.nfat_arch); archs = VasEBoot_calloc (narchs, sizeof (struct VasEBoot_macho_fat_arch)); if (! archs) { VasEBoot_file_close (file); return VasEBoot_errno; } if (VasEBoot_file_read (file, archs, sizeof (struct VasEBoot_macho_fat_arch) * narchs) != (VasEBoot_ssize_t) sizeof(struct VasEBoot_macho_fat_arch) * narchs) { VasEBoot_free (archs); if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, N_("premature end of file %s"), args[0]); return VasEBoot_errno; } for (i = 0; i < narchs; i++) { if (!VasEBoot_xnu_is_64bit && VAS_EBOOT_MACHO_CPUTYPE_IS_HOST32 (VasEBoot_be_to_cpu32 (archs[i].cputype))) { readoff = VasEBoot_be_to_cpu32 (archs[i].offset); readlen = VasEBoot_be_to_cpu32 (archs[i].size); } if (VasEBoot_xnu_is_64bit && VAS_EBOOT_MACHO_CPUTYPE_IS_HOST64 (VasEBoot_be_to_cpu32 (archs[i].cputype))) { readoff = VasEBoot_be_to_cpu32 (archs[i].offset); readlen = VasEBoot_be_to_cpu32 (archs[i].size); } } VasEBoot_free (archs); } else { /* It's a flat file. Some sane people still exist. */ readoff = 0; readlen = VasEBoot_file_size (file); } if (readlen == -1) { VasEBoot_file_close (file); return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, "no suitable architecture is found"); } /* Allocate space. */ err = VasEBoot_xnu_align_heap (VAS_EBOOT_XNU_PAGESIZE); if (err) { VasEBoot_file_close (file); return err; } err = VasEBoot_xnu_heap_malloc (readlen, &loadto, &loadto_target); if (err) { VasEBoot_file_close (file); return err; } /* Read the file. */ VasEBoot_file_seek (file, readoff); if (VasEBoot_file_read (file, loadto, readlen) != (VasEBoot_ssize_t) (readlen)) { VasEBoot_file_close (file); if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("premature end of file %s"), args[0]); return VasEBoot_errno; } VasEBoot_file_close (file); /* Pass it to kernel. */ return VasEBoot_xnu_register_memory ("DriversPackage-", &driverspackagenum, loadto_target, readlen); } static VasEBoot_err_t VasEBoot_cmd_xnu_ramdisk (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { VasEBoot_file_t file; void *loadto; VasEBoot_addr_t loadto_target; VasEBoot_err_t err; VasEBoot_size_t size; if (argc != 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); file = VasEBoot_file_open (args[0], VAS_EBOOT_FILE_TYPE_XNU_RAMDISK); if (! file) return VasEBoot_errno; err = VasEBoot_xnu_align_heap (VAS_EBOOT_XNU_PAGESIZE); if (err) return err; size = VasEBoot_file_size (file); err = VasEBoot_xnu_heap_malloc (size, &loadto, &loadto_target); if (err) return err; if (VasEBoot_file_read (file, loadto, size) != (VasEBoot_ssize_t) (size)) { VasEBoot_file_close (file); if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("premature end of file %s"), args[0]); return VasEBoot_errno; } return VasEBoot_xnu_register_memory ("RAMDisk", 0, loadto_target, size); } /* Returns true if the kext should be loaded according to plist and osbundlereq. Also fill BINNAME. */ static int VasEBoot_xnu_check_os_bundle_required (char *plistname, const char *osbundlereq, char **binname) { VasEBoot_file_t file; char *buf = 0, *tagstart = 0, *ptr1 = 0, *keyptr = 0; char *stringptr = 0, *ptr2 = 0; VasEBoot_size_t size; int depth = 0; int ret; int osbundlekeyfound = 0, binnamekeyfound = 0; if (binname) *binname = 0; file = VasEBoot_file_open (plistname, VAS_EBOOT_FILE_TYPE_XNU_INFO_PLIST); if (! file) return 0; size = VasEBoot_file_size (file); buf = VasEBoot_malloc (size); if (! buf) { VasEBoot_file_close (file); return 0; } if (VasEBoot_file_read (file, buf, size) != (VasEBoot_ssize_t) (size)) { VasEBoot_file_close (file); if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("premature end of file %s"), plistname); return 0; } VasEBoot_file_close (file); /* Set the return value for the case when no OSBundleRequired tag is found. */ if (osbundlereq) ret = VasEBoot_strword (osbundlereq, "all") || VasEBoot_strword (osbundlereq, "-"); else ret = 1; /* Parse plist. It's quite dirty and inextensible but does its job. */ for (ptr1 = buf; ptr1 < buf + size; ptr1++) switch (*ptr1) { case '<': tagstart = ptr1; *ptr1 = 0; if (keyptr && depth == 4 && VasEBoot_strcmp (keyptr, "OSBundleRequired") == 0) osbundlekeyfound = 1; if (keyptr && depth == 4 && VasEBoot_strcmp (keyptr, "CFBundleExecutable") == 0) binnamekeyfound = 1; if (stringptr && osbundlekeyfound && osbundlereq && depth == 4) { for (ptr2 = stringptr; *ptr2; ptr2++) *ptr2 = VasEBoot_tolower (*ptr2); ret = VasEBoot_strword (osbundlereq, stringptr) || VasEBoot_strword (osbundlereq, "all"); } if (stringptr && binnamekeyfound && binname && depth == 4) { if (*binname) VasEBoot_free (*binname); *binname = VasEBoot_strdup (stringptr); } *ptr1 = '<'; keyptr = 0; stringptr = 0; break; case '>': if (! tagstart) { VasEBoot_free (buf); VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, "can't parse %s", plistname); return 0; } *ptr1 = 0; if (tagstart[1] == '?' || ptr1[-1] == '/') { osbundlekeyfound = 0; *ptr1 = '>'; break; } if (depth == 3 && VasEBoot_strcmp (tagstart + 1, "key") == 0) keyptr = ptr1 + 1; if (depth == 3 && VasEBoot_strcmp (tagstart + 1, "string") == 0) stringptr = ptr1 + 1; else if (VasEBoot_strcmp (tagstart + 1, "/key") != 0) { osbundlekeyfound = 0; binnamekeyfound = 0; } *ptr1 = '>'; if (tagstart[1] == '/') depth--; else depth++; break; } VasEBoot_free (buf); return ret; } /* Context for VasEBoot_xnu_scan_dir_for_kexts. */ struct VasEBoot_xnu_scan_dir_for_kexts_ctx { char *dirname; const char *osbundlerequired; int maxrecursion; }; /* Helper for VasEBoot_xnu_scan_dir_for_kexts. */ static int VasEBoot_xnu_scan_dir_for_kexts_load (const char *filename, const struct VasEBoot_dirhook_info *info, void *data) { struct VasEBoot_xnu_scan_dir_for_kexts_ctx *ctx = data; char *newdirname; if (! info->dir) return 0; if (filename[0] == '.') return 0; if (VasEBoot_strlen (filename) < 5 || VasEBoot_memcmp (filename + VasEBoot_strlen (filename) - 5, ".kext", 5) != 0) return 0; newdirname = VasEBoot_malloc (VasEBoot_strlen (ctx->dirname) + VasEBoot_strlen (filename) + 2); /* It's a .kext. Try to load it. */ if (newdirname) { VasEBoot_strcpy (newdirname, ctx->dirname); newdirname[VasEBoot_strlen (newdirname) + 1] = 0; newdirname[VasEBoot_strlen (newdirname)] = '/'; VasEBoot_strcpy (newdirname + VasEBoot_strlen (newdirname), filename); VasEBoot_xnu_load_kext_from_dir (newdirname, ctx->osbundlerequired, ctx->maxrecursion); if (VasEBoot_errno == VAS_EBOOT_ERR_BAD_OS) VasEBoot_errno = VAS_EBOOT_ERR_NONE; VasEBoot_free (newdirname); } return 0; } /* Load all loadable kexts placed under DIRNAME and matching OSBUNDLEREQUIRED */ VasEBoot_err_t VasEBoot_xnu_scan_dir_for_kexts (char *dirname, const char *osbundlerequired, int maxrecursion) { struct VasEBoot_xnu_scan_dir_for_kexts_ctx ctx = { .dirname = dirname, .osbundlerequired = osbundlerequired, .maxrecursion = maxrecursion }; VasEBoot_device_t dev; char *device_name; VasEBoot_fs_t fs; const char *path; if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); device_name = VasEBoot_file_get_device_name (dirname); dev = VasEBoot_device_open (device_name); if (dev) { fs = VasEBoot_fs_probe (dev); path = VasEBoot_strchr (dirname, ')'); if (! path) path = dirname; else path++; if (fs) (fs->fs_dir) (dev, path, VasEBoot_xnu_scan_dir_for_kexts_load, &ctx); VasEBoot_device_close (dev); } VasEBoot_free (device_name); return VAS_EBOOT_ERR_NONE; } /* Context for VasEBoot_xnu_load_kext_from_dir. */ struct VasEBoot_xnu_load_kext_from_dir_ctx { char *dirname; const char *osbundlerequired; int maxrecursion; char *plistname; char *newdirname; int usemacos; }; /* Helper for VasEBoot_xnu_load_kext_from_dir. */ static int VasEBoot_xnu_load_kext_from_dir_load (const char *filename, const struct VasEBoot_dirhook_info *info, void *data) { struct VasEBoot_xnu_load_kext_from_dir_ctx *ctx = data; if (VasEBoot_strlen (filename) > 15) return 0; VasEBoot_strcpy (ctx->newdirname + VasEBoot_strlen (ctx->dirname) + 1, filename); /* If the kext contains directory "Contents" all real stuff is in this directory. */ if (info->dir && VasEBoot_strcasecmp (filename, "Contents") == 0) VasEBoot_xnu_load_kext_from_dir (ctx->newdirname, ctx->osbundlerequired, ctx->maxrecursion - 1); /* Directory "Plugins" contains nested kexts. */ if (info->dir && VasEBoot_strcasecmp (filename, "Plugins") == 0) VasEBoot_xnu_scan_dir_for_kexts (ctx->newdirname, ctx->osbundlerequired, ctx->maxrecursion - 1); /* Directory "MacOS" contains executable, otherwise executable is on the top. */ if (info->dir && VasEBoot_strcasecmp (filename, "MacOS") == 0) ctx->usemacos = 1; /* Info.plist is the file which governs our future actions. */ if (! info->dir && VasEBoot_strcasecmp (filename, "Info.plist") == 0 && ! ctx->plistname) ctx->plistname = VasEBoot_strdup (ctx->newdirname); return 0; } /* Load extension DIRNAME. (extensions are directories in xnu) */ VasEBoot_err_t VasEBoot_xnu_load_kext_from_dir (char *dirname, const char *osbundlerequired, int maxrecursion) { struct VasEBoot_xnu_load_kext_from_dir_ctx ctx = { .dirname = dirname, .osbundlerequired = osbundlerequired, .maxrecursion = maxrecursion, .plistname = 0, .usemacos = 0 }; VasEBoot_device_t dev; char *newpath; char *device_name; VasEBoot_fs_t fs; const char *path; char *binsuffix; VasEBoot_file_t binfile; ctx.newdirname = VasEBoot_malloc (VasEBoot_strlen (dirname) + 20); if (! ctx.newdirname) return VasEBoot_errno; VasEBoot_strcpy (ctx.newdirname, dirname); ctx.newdirname[VasEBoot_strlen (dirname)] = '/'; ctx.newdirname[VasEBoot_strlen (dirname) + 1] = 0; device_name = VasEBoot_file_get_device_name (dirname); dev = VasEBoot_device_open (device_name); if (dev) { fs = VasEBoot_fs_probe (dev); path = VasEBoot_strchr (dirname, ')'); if (! path) path = dirname; else path++; newpath = VasEBoot_strchr (ctx.newdirname, ')'); if (! newpath) newpath = ctx.newdirname; else newpath++; /* Look at the directory. */ if (fs) (fs->fs_dir) (dev, path, VasEBoot_xnu_load_kext_from_dir_load, &ctx); if (ctx.plistname && VasEBoot_xnu_check_os_bundle_required (ctx.plistname, osbundlerequired, &binsuffix)) { if (binsuffix) { /* Open the binary. */ char *binname = VasEBoot_malloc (VasEBoot_strlen (dirname) + VasEBoot_strlen (binsuffix) + sizeof ("/MacOS/")); VasEBoot_strcpy (binname, dirname); if (ctx.usemacos) VasEBoot_strcpy (binname + VasEBoot_strlen (binname), "/MacOS/"); else VasEBoot_strcpy (binname + VasEBoot_strlen (binname), "/"); VasEBoot_strcpy (binname + VasEBoot_strlen (binname), binsuffix); VasEBoot_dprintf ("xnu", "%s:%s\n", ctx.plistname, binname); binfile = VasEBoot_file_open (binname, VAS_EBOOT_FILE_TYPE_XNU_KEXT); if (! binfile) VasEBoot_errno = VAS_EBOOT_ERR_NONE; /* Load the extension. */ VasEBoot_xnu_load_driver (ctx.plistname, binfile, binname); VasEBoot_free (binname); VasEBoot_free (binsuffix); } else { VasEBoot_dprintf ("xnu", "%s:0\n", ctx.plistname); VasEBoot_xnu_load_driver (ctx.plistname, 0, 0); } } VasEBoot_free (ctx.plistname); VasEBoot_device_close (dev); } VasEBoot_free (device_name); VasEBoot_free (ctx.newdirname); return VAS_EBOOT_ERR_NONE; } static int locked=0; static VasEBoot_dl_t my_mod; /* Load the kext. */ static VasEBoot_err_t VasEBoot_cmd_xnu_kext (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { VasEBoot_file_t binfile = 0; if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); if (argc == 2) { /* User explicitly specified plist and binary. */ if (VasEBoot_strcmp (args[1], "-") != 0) { binfile = VasEBoot_file_open (args[1], VAS_EBOOT_FILE_TYPE_XNU_KEXT); if (! binfile) return VasEBoot_errno; } return VasEBoot_xnu_load_driver (VasEBoot_strcmp (args[0], "-") ? args[0] : 0, binfile, args[1]); } /* load kext normally. */ if (argc == 1) return VasEBoot_xnu_load_kext_from_dir (args[0], 0, 10); return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); } /* Load a directory containing kexts. */ static VasEBoot_err_t VasEBoot_cmd_xnu_kextdir (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { if (argc != 1 && argc != 2) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, "directory name required"); if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); if (argc == 1) return VasEBoot_xnu_scan_dir_for_kexts (args[0], "console,root,local-root,network-root", 10); else { char *osbundlerequired = VasEBoot_strdup (args[1]), *ptr; VasEBoot_err_t err; if (! osbundlerequired) return VasEBoot_errno; for (ptr = osbundlerequired; *ptr; ptr++) *ptr = VasEBoot_tolower (*ptr); err = VasEBoot_xnu_scan_dir_for_kexts (args[0], osbundlerequired, 10); VasEBoot_free (osbundlerequired); return err; } } static inline int hextoval (char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'z') return c - 'a' + 10; if (c >= 'A' && c <= 'Z') return c - 'A' + 10; return 0; } static inline void unescape (char *name, char *curdot, char *nextdot, int *len) { char *ptr, *dptr; dptr = name; for (ptr = curdot; ptr < nextdot;) if (ptr + 2 < nextdot && *ptr == '%') { *dptr = (hextoval (ptr[1]) << 4) | (hextoval (ptr[2])); ptr += 3; dptr++; } else { *dptr = *ptr; ptr++; dptr++; } *len = dptr - name; } VasEBoot_err_t VasEBoot_xnu_fill_devicetree (void) { struct VasEBoot_env_var *var; FOR_SORTED_ENV (var) { char *nextdot = 0, *curdot; struct VasEBoot_xnu_devtree_key **curkey = &VasEBoot_xnu_devtree_root; struct VasEBoot_xnu_devtree_key *curvalue; char *name = 0, *data; int len; if (VasEBoot_memcmp (var->name, "XNU.DeviceTree.", sizeof ("XNU.DeviceTree.") - 1) != 0) continue; curdot = var->name + sizeof ("XNU.DeviceTree.") - 1; nextdot = VasEBoot_strchr (curdot, '.'); if (nextdot) nextdot++; while (nextdot) { name = VasEBoot_realloc (name, nextdot - curdot + 1); if (!name) return VasEBoot_errno; unescape (name, curdot, nextdot, &len); name[len - 1] = 0; curkey = &(VasEBoot_xnu_create_key (curkey, name)->first_child); curdot = nextdot; nextdot = VasEBoot_strchr (nextdot, '.'); if (nextdot) nextdot++; } nextdot = curdot + VasEBoot_strlen (curdot) + 1; name = VasEBoot_realloc (name, nextdot - curdot + 1); if (!name) return VasEBoot_errno; unescape (name, curdot, nextdot, &len); name[len] = 0; curvalue = VasEBoot_xnu_create_value (curkey, name); VasEBoot_free (name); if (!curvalue) return VasEBoot_errno; data = VasEBoot_malloc (VasEBoot_strlen (var->value) + 1); if (!data) return VasEBoot_errno; unescape (data, var->value, var->value + VasEBoot_strlen (var->value), &len); curvalue->datasize = len; curvalue->data = data; } return VasEBoot_errno; } struct VasEBoot_video_bitmap *VasEBoot_xnu_bitmap = 0; VasEBoot_xnu_bitmap_mode_t VasEBoot_xnu_bitmap_mode; /* Option array indices. */ #define XNU_SPLASH_CMD_ARGINDEX_MODE 0 static const struct VasEBoot_arg_option xnu_splash_cmd_options[] = { {"mode", 'm', 0, N_("Background image mode."), N_("stretch|normal"), ARG_TYPE_STRING}, {0, 0, 0, 0, 0, 0} }; static VasEBoot_err_t VasEBoot_cmd_xnu_splash (VasEBoot_extcmd_context_t ctxt, int argc, char *args[]) { VasEBoot_err_t err; if (argc != 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); if (! VasEBoot_xnu_heap_size) return VasEBoot_error (VAS_EBOOT_ERR_BAD_OS, N_("you need to load the kernel first")); if (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].set && VasEBoot_strcmp (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].arg, "stretch") == 0) VasEBoot_xnu_bitmap_mode = VAS_EBOOT_XNU_BITMAP_STRETCH; else VasEBoot_xnu_bitmap_mode = VAS_EBOOT_XNU_BITMAP_CENTER; err = VasEBoot_video_bitmap_load (&VasEBoot_xnu_bitmap, args[0]); if (err) VasEBoot_xnu_bitmap = 0; return err; } #ifndef VAS_EBOOT_MACHINE_EMU static VasEBoot_err_t VasEBoot_cmd_xnu_resume (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char *args[]) { if (argc != 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("filename expected")); return VasEBoot_xnu_resume (args[0]); } #endif void VasEBoot_xnu_lock (void) { if (!locked) VasEBoot_dl_ref (my_mod); locked = 1; } void VasEBoot_xnu_unlock (void) { if (locked) VasEBoot_dl_unref (my_mod); locked = 0; } static VasEBoot_command_t cmd_kernel64, cmd_kernel, cmd_mkext, cmd_kext; static VasEBoot_command_t cmd_kextdir, cmd_ramdisk, cmd_resume; static VasEBoot_extcmd_t cmd_splash; VAS_EBOOT_MOD_INIT(xnu) { cmd_kernel = VasEBoot_register_command ("xnu_kernel", VasEBoot_cmd_xnu_kernel, 0, N_("Load XNU image.")); cmd_kernel64 = VasEBoot_register_command ("xnu_kernel64", VasEBoot_cmd_xnu_kernel64, 0, N_("Load 64-bit XNU image.")); cmd_mkext = VasEBoot_register_command_lockdown ("xnu_mkext", VasEBoot_cmd_xnu_mkext, 0, N_("Load XNU extension package.")); cmd_kext = VasEBoot_register_command_lockdown ("xnu_kext", VasEBoot_cmd_xnu_kext, 0, N_("Load XNU extension.")); cmd_kextdir = VasEBoot_register_command_lockdown ("xnu_kextdir", VasEBoot_cmd_xnu_kextdir, /* * TRANSLATORS: OSBundleRequired is * a variable name in xnu extensions * manifests. It behaves mostly like * GNU/Linux runlevels. */ N_("DIRECTORY [OSBundleRequired]"), /* * TRANSLATORS: There are many extensions * in extension directory. */ N_("Load XNU extension directory.")); cmd_ramdisk = VasEBoot_register_command ("xnu_ramdisk", VasEBoot_cmd_xnu_ramdisk, 0, /* TRANSLATORS: ramdisk here isn't identifier. It can be translated. */ N_("Load XNU ramdisk. " "It will be available in OS as md0.")); cmd_splash = VasEBoot_register_extcmd ("xnu_splash", VasEBoot_cmd_xnu_splash, 0, 0, N_("Load a splash image for XNU."), xnu_splash_cmd_options); #ifndef VAS_EBOOT_MACHINE_EMU cmd_resume = VasEBoot_register_command ("xnu_resume", VasEBoot_cmd_xnu_resume, 0, N_("Load an image of hibernated" " XNU.")); #endif VasEBoot_cpu_xnu_init (); my_mod = mod; } VAS_EBOOT_MOD_FINI(xnu) { #ifndef VAS_EBOOT_MACHINE_EMU VasEBoot_unregister_command (cmd_resume); #endif VasEBoot_unregister_command (cmd_mkext); VasEBoot_unregister_command (cmd_kext); VasEBoot_unregister_command (cmd_kextdir); VasEBoot_unregister_command (cmd_ramdisk); VasEBoot_unregister_command (cmd_kernel); VasEBoot_unregister_extcmd (cmd_splash); VasEBoot_unregister_command (cmd_kernel64); VasEBoot_cpu_xnu_fini (); }