A New Screen of Death for Mac OS X

© Amit Singh. All Rights Reserved.

Introduction

This document is based on information available in Section 5.6 of the book Mac OS X Internals: A Systems Approach.

Operating systems "crash" and "hang". The in-practice frequencies of such events can differ significantly across operating systems. Such differences, whether apocryphal or technically justifiable, are often important weapons for faithful warriors in inter-system combats. A system crash in a typical Unix operating system (UNIX, UNIX-derived, or UNIX-like) culminates in the panic() routine being called in the kernel, and as such is called a kernel panic.

 pan‧icfrom Greek panikos, meaning of or from the god Pan. According to a Greek legend, the god Pan induced a sense of great fear in the enemy during the battle of Marathon. A common meaning of panic is overwhelming fear or terror.

Looking Back

UNIX was not designed to be a fault-tolerant operating system. The fact that UNIX simply gave up the ghost when a serious enough problem was detected was a great simplifying factor for the implementers. The criteria for fatally serious problems included situations when system integrity could not be guaranteed and therefore, it was not desirable or sensible for the system to continue operating. Examples of panic-worthy situations in early UNIX include an I/O error while swapping, an unexpected trap (such as a bus error in kernel mode), and the exhaustion of inodes. Figure 1 shows the implementation of the panic() routine in Third Edition UNIX.

/* Third Edition UNIX, early 1973 */ char *panicstr; ... panic(s) char *s; { prproc(); update(); printf("panic: %s\n", s); for(;;) idle(); }

Figure 1. The implementation of panic() in early UNIX


The panicstr variable shown in Figure 1 contained the argument to the (last) call to panic(). If the system console was turned off or otherwise unavailable, the contents of this variable could be examined during post-mortem analysis. The prproc() routine walked the list of all processes and for each process with a non-zero process status (as determined by the value of the p_stat field of the proc structure), printed several fields of the proc structure. The update() routine flushed in-memory file system state to disk. idle(), an assembly language routine, dropped the processor priority to zero and executed the wait instruction "forever" (It could only be terminated in the case of a hardware interrupt.)

The Mac OS X kernel retains the panic() routine and the panicstr variable. Whereas the Third Edition UNIX kernel had about fifteen instances of calls to panic(), there are many, many more—about 1800— in the Mac OS X kernel, which also has a far more complex panic() implementation. This shouldn't be surprising when you consider the relative sizes of the two kernels: at almost a million lines of code, the xnu source tree is over a 100 times larger than that of the Third Edition UNIX kernel.

Panic Personalities

One can reasonably assume that kernel panics are undesirable events from an end user's standpoint. Nevertheless, a kernel panic, or rather, its manifestation, is an important aspect of the operating system's personality. In fact, panics are part of operating system folklore. The most notorious example is that of the Windows "blue screen of death," which is perhaps the card most often pulled by Windows detractors. Besides blue, such screens of death in other systems come in several hues such as black, green, red, white, and so on.

One might opine that the look-and-feel of a kernel panic is hardly important—what matters is the usefulness of the information provided to the user when a panic occurs. Whereas a system programmer is likely to be interested in obtaining as much detail as possible about the crash, an end user would perhaps only be interested in knowing that the system needs to be restarted. Operating systems differ in the content and crypticness of a panic "dialog" (what gets displayed on the screen). However, it is common to see information such as the following in most panics:

That said, our focus here is not on the debugging-related content of a Mac OS X kernel panic. We are interested in the look-and-feel aspects.

Details of kernel panic generation and content can be seen in Section 10.6.1 of Mac OS X Internals: A Systems Approach.

Initially, a Mac OS X kernel panic was presented in its un-prettified raw form, as Figure 2 shows. You can still set the system up for this raw version by including the bit value 0x100 in the debug boot-time argument. Doing so disables the graphical panic screen so that textual panic data can be logged to the screen.

Even in the case of the default graphical panic configuration, a panic's raw details are not (normally) lost: if possible, the kernel will save panic data to nonvolatile memory, from where it will be picked up and logged to a file upon reboot.

Figure 2. The default kernel panic dialog in Mac OS X 10.0 and 10.1


Figure 3 shows the "user-friendly" graphical panic dialog introduced in Mac OS X 10.2. It is simply a multi-lingual text box instructing the user to restart the computer. Figure 4 shows the flavor present in Mac OS X 10.3 and above.

Figure 3. The default kernel panic dialog in Mac OS X 10.2


Figure 4. The default kernel panic dialog in Mac OS X 10.3 and above


Note that we qualified the panic dialogs shown in figures 3 and 4 as "default." That's because Mac OS X allows a custom panic image to be loaded into the kernel from user space. This can be useful in certain circumstances—for example, if it is desired that the user of a managed system notify the administrator in the case of a panic, a custom image can be used to instruct the user.

Moreover, Mac OS X allows you to programmatically test how the currently loaded panic image would look when displayed in the case of a real panic. In the rest of this document, we will see how to customize the Mac OS X kernel panic graphical dialog and how to test the customization.

The default image shown in Figure 4 can be found in the xnu source tree as xnu/osfmk/console/panic_ui/images/panic_dialog.tiff.

Generating a Suitable Image

Before we can load a custom panic image, we need to convert the source image to a suitable format. The kernel interface for loading a panic image requires the image to be in a special "kernel-raw" (kraw) format. The xnu kernel source tree includes source for a program (qtif2kraw) that converts from the QuickTime Image File (QTIF) format to the kraw format. The input QTIF image for qtif2kraw must be an uncompressed 256-color image.

Therefore, you first need to convert your source image to a suitable QTIF image. Since QTIF is not a common image format, this might be annoyingly difficult, depending on which image conversion programs you have at your disposal. Apple's own Preview.app used to be able to perform this conversion, but it can't beginning with Mac OS X 10.4. If you have access to Mac OS X 10.3 (or Preview.app from that system version), you can use Preview.app to convert an image to the desired format as follows:

Once you have an appropriate QTIF image, you can run qtif2kraw on it. There is a caveat if your target platform is x86: the current version of qtif2kraw doesn't take endian-ness into account, so you will have to alter the program to make it generate an image that can be used on the x86 version of Mac OS X. Figure 5 shows the necessary modifications to qtif2kraw.c: the highlighted lines must be added, after which the source can be compiled as a PowerPC executable that can yield an image usable on the x86 platform.

// xnu/osfmk/console/panic_ui/qtif2kraw.c ... #include <libkern/OSByteOrder.h> int main( int argc, char *argv[] ) ... sum = OSSwapInt32(sum); fwrite(&sum, sizeof(sum), 1, ostream); sum = encodedSize; encodedSize += (256*3); encodedSize = OSSwapInt32(encodedSize); fwrite(&encodedSize, sizeof(encodedSize), 1, ostream); encodedSize = sum; tag = OSSwapInt32(tag); fwrite(&tag, sizeof(tag), 1, ostream); width = OSSwapInt16(width); fwrite(&width, sizeof(width), 1, ostream); height = OSSwapInt16(height); fwrite(&height, sizeof(height), 1, ostream); ... }

Figure 5. Modifying the qtif2kraw program source for the x86 version of Mac OS X


Figure 6 shows an example of running qtif2kraw. Note that the kernel's built-in limit on the maximum amount of memory consumable by a custom panic image is 128 KB. Therefore, the kraw image emitted by qtif2kraw must be within this limit. The limit can be programmatically queried through a sysctl() call using the { CTL_KERN, KERN_PANICINFO, KERN_PANICINFO_MAXSIZE } mib.

$ ./qtif2kraw -i /tmp/image.qtif -o /tmp/image.kraw Image info: width: 1024 height: 569 depth: 8... Converting image file to 8 bit raw... Converted 582656 pixels... Found 582656 color mathces in CLUT... Encoding image file... Writing to binary panic dialog file image.kraw, which \ is suitable for loading into kernel...

Figure 6. Using qtif2kraw to generate a custom kernel panic image


Loading a Panic User Interface Image into the Kernel

Figure 7 shows a program that loads the given kraw file as a custom panic image into the kernel. The kernel exposes this functionality to user space through the sysctl() interface.

// load_panic_image.c #define PROGNAME "load_panic_image" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/sysctl.h> int main(int argc, char **argv) { int ret, fd; char *buf; size_t oldlen = 0, newlen; struct stat sb; int mib[3] = { CTL_KERN, KERN_PANICINFO, KERN_PANICINFO_IMAGE }; if (argc != 2) { fprintf(stderr, "usage: %s <kraw image file path>\n", PROGNAME); exit(1); } if (stat(argv[1], &sb) < 0) { perror("stat"); exit(1); } newlen = sb.st_size; buf = (char *)malloc(newlen); // assume success fd = open(argv[1], O_RDONLY); // assume success ret = read(fd, buf, sb.st_size); // assume success close(fd); if (sysctl(mib, 3, NULL, (void *)&oldlen, buf, newlen)) { perror("sysctl"); } exit(ret); }

Figure 7. Loading a replacement panic user interface image into the kernel


Causing a Fake "Panic"

Figure 8 shows a program that will cause the panic image (be it the built-in image or the custom image, if one is loaded) to be displayed, complete with the characteristic dimming of the screen that occurs in the case of an actual kernel panic. This "panic" is merely a displaying of the image—no other panic-related activities occur. Again, the interface is exposed to user space through sysctl().

// panic_test.c #include <sys/types.h> #include <sys/sysctl.h> #define KERN_PANICINFO_TEST (KERN_PANICINFO_IMAGE + 2) int main(void) { size_t oldnewlen = 0; int mib[3] = { CTL_KERN, KERN_PANICINFO, KERN_PANICINFO_TEST }; return sysctl(mib, 3, NULL, (void *)&oldnewlen, NULL, oldnewlen); }

Figure 8. Testing the panic user interface


A Blue Screen of Death for Mac OS X

This brings us to the obvious joke: arranging for a "blue screen of death" for Mac OS X.

At least one reader (David Hodge) of Mac OS X Internals: A Systems Approach has independently thought of using these interfaces for creating a Mac OS X "blue screen of death."

We can start with an image like the one shown in Figure 9.

Figure 9. A Microsoft Windows "Blue Screen of Death"


Figure 10 shows an example command-line sequence for setting up and testing our "blue screen of death."

$ ./qtif2kraw -i /tmp/bsod.qtif -o /tmp/bsod.kraw ... $ gcc -o load_panic_image load_panic_image.c $ gcc -o panic_test panic_test.c $ sudo ./panic_test # show default panic dialog $ sudo ./load_panic_image /tmp/bsod.kraw # load our custom image $ sudo ./panic_test # show our custom panic dialog

Figure 10. Setting up a "Blue Screen of Death" for Mac OS X


Download

Finally, here are some ready-made "blue screen of death" (BSOD) images in kraw format. You can use one of these along with the programs from figures 7 and 8 to test things in a pinch.

Ready-Made BSOD Panic Dialog Images

PlatformResolutionSize (bytes)Download
x861440x90057,062
x861024x56941,123
PowerPC1024x56941,123