Mac OS X Internals: A Systems Approach

4.10. BootX

BootX is the default bootloader on PowerPC-based Mac OS X systems.[17] As the first software that runs during system startup, it prepares an initial execution environment for the kernel, to which it passes control eventually.

[17] BootX is also the name of a third-party open source bootloaderunrelated to Apple's BootXthat allows dual-booting Mac OS and Linux on Old World machines.

4.10.1. File Format

The BootX file is in the bootinfo format: It contains an XML header, various types of data (such as icons), Forth source, FCode bytecodes, and machine code.

Figure 416 shows an example of a bootinfo file. The OS-BADGE-ICONS element can contain icons to be displayed in the Open Firmware boot selector.

Figure 416. A bootinfo file

<CHRP-BOOT> <COMPATIBLE> MacRISC MacRISC3 MacRISC4 </COMPATIBLE> <DESCRIPTION> Boot Loader for Mac OS X. </DESCRIPTION> <OS-BADGE-ICONS> 1010 ... </OS-BADGE-ICONS> <BOOT-SCRIPT> load-base begin ... until ( xcoff-base ) load-size over load-base - - ( xcoff-base xcoff-size ) load-base swap move init-program go </BOOT-SCRIPT> </CHRP-BOOT> ^D ... machine code

BootX is compiled from source into a Mach-O executable, which is then converted to XCOFF format. The XCOFF file is appended to a bootinfo header to yield the BootX file that resides in /System/Library/CoreServices/. The /usr/standalone/ppc/ directory contains the XCOFF file (bootx.xcoff), along with a copy of BootX in bootinfo format (bootx.bootinfo). Recall that Open Firmware can load both the bootinfo file and the XCOFF binary.

It is possible to create your own bootloaderrather, a boot chooserby creating a bootinfo file with a Forth script (the BOOT-SCRIPT element) that displays various booting options such as the following:

  • Boot from a disk drive (specified by variations of the hd alias).

  • Boot from an optical drive (specified by the cd alias).

  • Boot from a FireWire drive (specified by a device tree path).

  • Boot over the network (using the enet alias).

  • Enter the target disk mode (using the target-mode word).

  • Power cycle the computer (using the reset-all word).

  • Shut down the computer (using the shut-down word).

  • Eject an optical disc (using the eject word).

Each of these options can be served by existing Open Firmware words. Such a bootloader could even be graphical, where you use the framebuffer to display a menu and use the mouse to make a selection. Pressing the option key during startup launches a similar Open Firmware application, the OS Picker.

4.10.2. Structure

BootX can be functionally divided into a client interface, a file system interface, a secondary loader, and a utility library. These components are implemented in the ci.subproj, fs.subproj, sl.subproj, and libclite.subproj subdirectories, respectively, in the BootX source.

BootX implements a plug-in interface for file systems that it supports. Apple's default implementation of BootX can load kernels from the HFS, HFS Plus, UFS, and Ext2 file systems. BootX also includes a file system abstraction for the Network file systemessentially a wrapper around a TFTP client implementation. Besides kernel binaries in the Mach-O format, BootX can also load ELF kernels.

ELF Support

Mac OS X does not use the ELF support in BootX. Old World Macintosh computers had various issues with the implementation of Open Firmware. This caused many booting problems for Apple engineers and even more problems for third parties porting Linux to the PowerPC. Having access to the firmware's source, Apple solved most of the problems either via NVRAM patches or by integrating the required changes into BootX itself. The latter was done in the instances where the changes could not be implemented as patches. As BootX matured, Apple added support for Ext2 and ELF with the goal of making the platform more amenable to PowerPC Linux.

4.10.3. Operation

Let us look at the sequence of events that occur when BootX starts executing after being handed control by Open Firmware.

  • The entry point of the BootX executable is a symbol called StartTVector, which points to a function called Start(). BootX is called with a pointer to the Open Firmware client interface. Start() moves the stack pointer 256 bytes from the end of a 32K chunk of BootX's heap, from where it will grow upward during use. Start() then calls Main().

    const unsigned long StartTVector[2] = {(unsigned long)Start, 0}; char gStackBaseAddr[0x8000]; ... static void Start(void *unused1, void *unused2, ClientInterfacePtr ciPtr) { long newSP; // Move the Stack to a chunk of the BSS newSP = (long)gStackBaseAddr + sizeof(gStackBaseAddr) 0x100; __asm__ volatile("mr r1, %0" : : "r" (newSP)); Main(ciPtr); }

  • Main() calls InitEverything(), which, as its name suggests, performs a variety of initialization steps. It initializes the Open Firmware client interface that BootX uses to talk to the firmware. It also retrieves the firmware version.

  • BootX then creates an Open Firmware pseudo-device called sl_words (sl stands for secondary loader) and defines various Forth words in it. For example, the code for the spinning cursor seen during booting is set up here.

  • BootX uses the firmware's client interface to look up the options device, which contains various system configuration variables that may be viewed and set using the printenv and setenv words in Open Firmware.

    0 > dev /options .properties name options little-endian? false real-mode? false auto-boot? true diag-switch? false ... boot-command mac-boot ...

    You can also examine the properties of the options device and even browse a representation of the device tree from Mac OS X. Tools such as IORegistryExplorer.app and ioreg can be used for this purpose.

    $ ioreg -p IODeviceTree -l 0 -w | less ... +-o options <class IODTNVRAM, registered, matched, ... | { | "fcode-debug?" = No | "skip-netboot?" = <"false"> ...

  • BootX looks up the chosen device, which contains system parameters chosen or specified at runtime: instance handles for entities such as memory, the console input and output devices, the MMU, the PMU, the CPU, the programmable interrupt controller (PIC), and so on. If the keyboard cannot be initialized based on chosen's contents, BootX attempts to obtain an instance handle to the keyboard device by explicitly trying to open the keyboard and kbd devices. It then initializes the keymap by calling slw_init_keymap, which is one of the sl words.

    0 > dev /chosen .properties name chosen stdin ffbc6e40 stdout ffbc6600 memory ffbdd600 mmu ... ...

  • BootX checks the value of the security-mode firmware variable. If this variable is set and has a value other than none, BootX sets the "secure" bit in its boot mode variable. It also checks whether the verbose mode (the key combination) or single-user mode (the key combination) were specified, enabling verbose messages to be printed during booting if either were specified. Note that no messages are printed in the secure boot mode, regardless of the verbosity flags.

  • By default, BootX is compiled to display a failure screen if booting fails. Alternatively, BootX can be compiled to go back to Open Firmware on failure.

  • BootX checks whether the system is booting in safe mode. If so, it sets the corresponding bit in its boot mode variable.

  • BootX claims memory for various purposes. A typical memory map assumed by BootX occupies 96MB of physical memory starting at address 0x0. The beginning of this physical range contains the PowerPC exception vectors. The end of this range contains the Open Firmware image. The hole in the middle is free memory, which is claimed by BootX. Table 45 shows a breakdown of the memory map normally used by BootX.[18]

    [18] The memory map may change across BootX versions.

    Table 45. BootX Logical Memory Map

    Starting Address

    Ending Address

    Purpose

    0x00000000

    0x00003FFF

    Exception vectors.

    0x00004000

    0x03FFFFFF

    Kernel image, boot structures, and drivers.

    0x04000000

    0x04FFFFFF

    File load area.

    0x05000000

    0x053FFFFF

    Simple read-time cache for file system metadata. Cache hits are serviced from memory, whereas cache misses result in disk access.

    0x05400000

    0x055FFFFF

    Malloc zone: a simple memory allocator is implemented in BootX's libclite subproject. The starting and ending addresses of this range define the block of memory used by the allocator.

    0x05600000

    0x057FFFFF

    BootX image.

    0x05800000

    0x05FFFFFF

    Unused (occupied by the Open Firmware image).

  • BootX allocates 0x4000 bytes for the vector save area.

  • BootX finds all displays and sets them up. It does this by searching for nodes of type display in the device tree. The primary display is referred to by the screen alias.

    0 > dev screen .properties name ATY,Bee_A compatible ATY,Bee width 00000400 height 00000300 linebytes 00000400 depth 00000008 display-type 4c434400 device_type display character-set ISO859-1 ...

  • While setting up one or more displays, BootX calls the Open Firmware set-colors word to initialize the CLUT for the display if its depth is 8 bit. It also sets the screen color of each display to a 75% gray color by calling the Open Firmware fill-rectangle word. At this point, InitEverything returns to Main.

  • BootX looks up the boot device and boot arguments to determine the location of the kernel.

  • The default name of the kernel file is mach_kernel. BootX refers to several pieces of information while constructing the path to the kernel file. It first attempts to use the path contained in the bootpath property of the chosen node. If that fails, it looks at the boot-device property of the options node. It also looks for a file called com.apple.Boot.plist, which, if found, is loaded and its contents are parsed.

  • Just as Open Firmware can fetch the bootloader from either a local disk or a remote computer, BootX can load locally or remotely resident kernels. Consequently, the kernel path constructed by BootX depends on whether it is booting from a block device or a network device. In the usual case of a block device, BootX also calculates paths for loading kernel caches.

  • Eventually, BootX sets the rootpath and boot-uuid properties of the chosen node. The boot-uuid property contains a file system UUID[19] that BootX calculates for the boot volume. These and other properties of chosen can be seen on a running system through the ioreg utility (Figure 417).

    [19] Universally unique identifier.

Figure 417. Properties of the chosen device node as seen from Mac OS X

$ ioreg -p IODeviceTree -n chosen +-o Root <class IORegistryEntry, retain count 12> +-o device-tree <class IOPlatformExpertDevice, registered, matched, ...> +-o chosen <class IOService, !registered, !matched, active, busy 0, ...> | | { | | "nvram" = <ffb6f200> | | "stdin" = <ffb44000> | | "bootpath" = <"/ht/pci@7/k2-sata-root/k2-sata@0/disk@0:3,\\:tbxi"> | | "memory" = <ffb7c980> | | "cpu" = <ffb7ca00> | | "name" = <"chosen"> | | "pmu" = <ffb6f080> | | "boot-uuid" = <"B229E7FA-E0BA-XXXX-XXXX-XXXXXXXXXXXX"> | | "rootpath" = <"/ht/pci@7/k2-sata-root/k2-sata@0/disk@0:3,\mach_kernel"> | | "BootXCacheHits" = <000000a6> | | "mmu" = <ffb7ca00> | | "uni-interrupt-controller" = <ff981ee0> | | "bootargs" = <00> | | "stdout" = <00000000> | | "BootXCacheMisses" = <0000000f> | | "platform" = <ff9a6c38> | | "AAPL,phandle" = <ff891bf0> | | "BootXCacheEvicts" = <00000000> | | } ...

Kernel Extension Caches

There may be close to a hundred kernel extensions loaded on a typical Mac OS X installation, and perhaps twice as many residing in the system's designated directories for such extensions. A kernel extension can have dependencies on other extensions. Rather than scan all extensions every time the system boots (or worse, every time an extension is to be loaded), Mac OS X uses caching for kernel extensions. It also caches a version of the kernel that is prelinked with the necessary kernel extensions. The general name for such a cache is a kext cache. Mac OS X uses three types of kext caches: a kernel cache, an mkext cache, and a kext repository cache.

A kernel cache contains the kernel code prelinked with several kernel extensionstypically those deemed essential to early system startup. This cache can also contain the information dictionaries of any number of kernel extensions. The default cache directory for kernel caches is /System/Library/Caches/com.apple.kernelcaches/. Files in this directory are named kernelcache.XXXXXXXX, where the suffix is a 32-bit Adler checksum.[20]

An mkextor multiextensioncache contains multiple kernel extensions and their information dictionaries. Such caches are used during early system startup as BootX attempts to load a previously cached list of device drivers. If an mkext cache is corrupt or missing, BootX looks in the /System/Library/Extensions/ directory for extensions needed in that boot scenarioas determined by the value of the OSBundleRequired property in the Info.plist file of an extension's bundle. The default mkext cache exists as /System/Library/Extensions.mkext. Note that the system will not regenerate this cache unless the /System/Library/Extensions/ directory is newer than /mach_kernel: a caveat that is especially noteworthy if a new extension is to be installed for auto-loading at boot time. An mkext cache can be created or updated through the kextcache program. You can use the mkextunpack program to extract the contents of an mkext archive.

$ mkextunpack -v /System/Library/Extensions.mkext Found 148 kexts: ATTOExpressPCIPlus - com.ATTO.driver.ATTOExpressPCIPlus (2.0.4) CMD646ATA - com.apple.driver.CMD646ATA (1.0.7f1) ... IOSCSIFamily - com.apple.iokit.IOSCSIFamily (1.4.0) IOCDStorageFamily - com.apple.iokit.IOCDStorageFamily (1.4)

The kext repository cache contains the information dictionaries for all kernel extensionsincluding their plug-insresiding in a single repository directory. This cache exists by default as /System/Library/Extensions.kextcache, which is simply a large, XML-based, gzip-compressed property list file.

[20] The checksum algorithm is named after its inventor, Mark Adler, who also wrote parts of the popular gzip compression program.

  • Next, by default, if BootX fails to construct or use the boot paths, it draws a failed boot picture and goes into an infinite loop.

  • BootX draws the Apple logo splash screen. If booting from a network device, it draws a spinning globe instead.

  • BootX attempts to retrieve and load the kernel cache file. For a kernel cache file to be used, several conditions must be satisfied. For example, the file's name must match the kernel that BootX has found, the cache must not be expired, and the current booting mode must not be safe or network. If BootX determines that the kernel cache cannot be used, it uses its file system abstraction layer to access the kernel binary.

    Making the Globe Go Round

    The process for drawing the spinning globe is similar to the Apple logo example we discussed in Section 4.8.9. The globe data is contained in the netboot.h file in the BootX source. It contains 18 animation frames, each a 32x32 image, in contiguous memory. The secondary loader words slw_spin_init and slw_spin are responsible for setting up and performing, respectively, the animation, which occurs at a rate of 10 frames per second.

  • BootX "decodes" the kernel. If the kernel header indicates a compressed[21] kernel, BootX attempts to decompress it. If the kernel binary is fat, BootX "thins" itthat is, it locates the Mach-O binary for the architecture it is running on.

    [21] A compressed kernel uses typical LZSS compression, which is suitable for data that is compressed once but expanded many times. LZSS stands for Lempel-Ziv-Storer-Szymanski. Published in 1982 by J. A. Storer and T. G. Szymanski, LZSS is a compression algorithm based on the earlier LZ77 algorithm.

  • BootX attempts to decode the filepossibly "thinned"as a Mach-O binary. The Mach-O header's magic number must be the constant MH_MAGIC (0xfeedface). As decoding proceeds, BootX iterates through Mach-O load commands, handling them as appropriate. Note that BootX processes only the LC_SEGMENT, LC_SYMTAB, and LC_UNIXTHREAD Mach-O commands, ignoring any other types found in the executable.

  • If decoding the kernel as a Mach-O binary fails, BootX tries to decode it as an ELF binary. If that too fails, BootX gives up. It then draws a designated failed boot picture and goes into an infinite loop.

    The Kernel's Mach-O Load Commands

    The LC_SEGMENT command defines a segment of the executable to be mapped into the address space of the process that loads the file. The command also includes all of the sections contained in the segment. When BootX comes across the __VECTORS segment, it copies the segment's dataup to a maximum of 16KBto a special vector save area whose address is contained in the gVectorSaveAddr BootX variable. The __VECTORS segment contains the kernel's exception vectors, such as the low-level system call and interrupt handlers.

    The LC_SYMTAB command specifies the symbol table for the executable. BootX handles this command by decoding the symbol table and copying it to a range in the kernel's memory map.

    The LC_UNIXTHREAD command defines the initial thread state of the main thread of the process. On the PowerPC, the flavor of the thread data structure specified by the Mac OS X kernel's LC_UNIXTHREAD command is PPC_THREAD_ STATE. This flavor includes a PowerPC register state consisting of GPRs 0 through 31 along with the CR, CTR, LR, XER, SRR0, SRR1, and VRSAVE registers. SRR0 contains the entry point of the kernel: the address of the first instruction in the kernel to be executed.

  • If BootX is successful thus far, it performs its last set of actions in preparation of launching the kernel. It saves BootX file system cache hits, misses, and evicts as BootXCacheHits, BootXCacheMisses, and BootXCacheEvicts, respectively, in the chosen node.

  • It sets up various boot arguments and values that it will communicate to the kernel.

  • It calls a recursive function to flatten the device tree.

  • Shortly before handing over control to the kernel, BootX quiesces Open Firmware, an operation that causes any asynchronous tasks in the firmware, timers, and DMA to be stopped.

  • Next, BootX saves the MSR and SPRs G0 through G3; turns off data address translation by setting the DR bit of the MSR to 0; moves Open Firmware's exception vectors from 0x0 to a vector save address (gOFVectorSave); and copies the kernel's exception vectors from gVectorSaveAddr to 0x0. At this point, all preparations for launching the kernel have been completed.

  • BootX finally calls the kernel's entry point. If this succeeds, BootX's job is done, and it exists no more. If calling the kernel fails, BootX restores Open Firmware's exception vectors, restores the registers it saved prior to calling the kernel, restores data address translations, and returns a -1 value as an error.

BootX passes control to the kernel along with a signature[22] and a set of boot arguments, which it packs into a boot arguments structure (struct boot_args). The structure contains critical information needed at boot time and is propagated throughout the initial kernel startup. The kernel and BootX share this structure's type definition.

[22] The signature is the number 0x4D4F5358, which corresponds to the string "MOSX".

// pexpert/pexpert/ppc/boot.h // x86-specific structures are in pexpert/pexpert/i386/boot.h struct Boot_Video { unsigned long v_baseAddr; // Base address of video memory unsigned long v_display; // Display code (if applicable) unsigned long v_rowBytes; // # of bytes per pixel row unsigned long v_width; // Width unsigned long v_height; // Height unsigned long v_depth; // Pixel depth }; ... struct DRAMBank { unsigned long base; // physical base of DRAM bank unsigned long size; // size of DRAM bank }; ... struct boot_args { // Revision of boot_args structure unsigned short Revision; // Version of boot_args structure unsigned short Version; // Passed in the command line (256 bytes maximum) char CommandLine[BOOT_LINE_LENGTH]; // Base/range pairs for DRAM banks (26 maximum) DRAMBank PhysicalDRAM[kMaxDRAMBanks]; // Video information Boot_Video Video; // Machine type (Gestalt) unsigned long machineType; // Base of the flattened device tree void *deviceTreeP; // Length of the flattened device tree unsigned long deviceTreeLength; // Last (highest) address of kernel data area unsigned long topOfKernelData; };

BootX populates the boot_args structure as follows.

  • It sets the Revision field to 1.

  • The value of the Version field can be either 1 or 2. Version 2 of the boot_args structure contains page numbers in the physical memory banks, whereas version 1 contains byte addresses. BootX determines the version to pass based on the #address-cells and #size-cells properties of the device tree's root node: If either of these two values is greater than 1, BootX uses page numbers for bank ranges and marks the boot_args structure as being version 2.

  • The CommandLine string consists of the contents of the Open Firmware boot-args variable. If a special booting modesuch as safe, single user, or verbosewas specified via snag keys, BootX adds the corresponding characters to the string.

  • It queries the reg property of the /memory node in the device tree. It breaks down the contents of reg into pairs of base and size values, and it populates the PhysicalDRAM array.

  • It retrieves various display properties using the Open Firmware client interface. For example, the v_baseAddr field of boot_args is assigned the address returned by the frame-buffer-adr Open Firmware word.

  • It sets the machineType field to 0.

  • It recursively flattens the device tree in kernel memory. At the end of the flattening operation, it sets the deviceTreeP and deviceTreeLength fields appropriately.

  • The last step of the boot argument setup is the assignment of the topOfKernelData field. BootX maintains a pointer to the "last" kernel address throughout its operation. It uses this pointer as the basis for a simple-minded memory allocation scheme: "kernel" memory is allocated by incrementing the pointer by the requested memory size, rounded up to a page size multiple. BootX sets the final value of this pointer as the value of topOfKernelData.

Closed After Boot

The Open Firmware standard does not require the user interface to operate correctly after a client programfor example, the operating systemhas begun execution. Nevertheless, some implementations do allow the firmware to be accessed by the end user from a running operating system. For example, on a SPARC machine, you can access the OpenBoot monitor through the STOP-A key combination by "suspending" a normally running operating system. In contrast, Apple's Open Firmware is not available once the operating system has booted.

Категории