C H A P T E R  3

Xcode and the Kernel Development Environment

Apple has a good track record of taking care of its developers and providing them with intuitive, user-friendly tools and APIs to develop for the Mac and iOS platforms. Anyone who has written application software for the Mac or iPhone will be familiar with the object-oriented Cocoa framework, which provides a rich set of interfaces to support graphical user interfaces and other services required by user applications. Likewise, kernel developers are provided with APIs that are designed to help with the tasks performed by a kernel extension. For driver development, Apple provides the I/O Kit, which is an object-oriented framework for interfacing with hardware. The following chapter discusses the tools and frameworks you will need to get started with kernel development and includes a tutorial for building and installing a simple kernel extension.

Language of Choice: C++

The C language has been the de facto system-level language for decades. Indeed, the language was originally developed as an alternative to writing non-portable assembly code specifically for the original Unix system. The XNU kernel and many Mac OS X core services are written in C, while the I/O Kit framework used for driver development is written in a subset of the C++ language. Apple chose C++ for the I/O Kit because it is an object-oriented language and therefore allows a driver model that abstracts the physical hardware connections. Apple did toy with the idea of an Objective-C-based framework for drivers, but finally settled on C++. Despite the widespread use of C++ for the development of application software, Mac OS X is still one of the few operating systems that allows and in fact encourages C++ code to be run in its kernel. However, this is not to say the Mac OS X kernel is immune from the same problems that make C++ code problematic in other kernels. To avoid some of these problems, Mac OS X kernel code must use a restricted subset of the features provided by C++, referred to as Embedded C++. The features that are not available include the following:

  • Exceptions
  • Multiple inheritance
  • Templates
  • Runtime type information

It is worth noting that, because Embedded C++ is a subset of the standard C++ language, any code written for Embedded C++ is compatible with a regular C++ compiler.

While technically possible to include these language features in the kernel, Apple decided to disable them because they can greatly increase the size of the compiled code, which in turn increases the memory footprint of the kernel. Support for exceptions was disabled not only because of the additional code size but also because failure to catch an exception would result in a kernel panic.

Although the standard runtime type information is disabled, the I/O Kit does provide its own limited implementation, which is discussed in the following chapter. Kernel developers also have access to a limited implementation of the C++ runtime library and with language support for templates disabled, the STL classes are unavailable.

As a general rule, C++ is used when writing kernel extensions based on the I/O Kit framework, whereas C is used for everything else, including implementation of file systems and low-level networking code.

Xcode

To begin developing a kernel extension, you will need to install Apple's development tools, known as Xcode. These are available from the Mac App Store. Installing the Xcode package adds a directory to the root level of your hard disk named “Developer,” which includes everything that is required for both Mac OS X and iOS development, including the following:

  • An integrated development environment (the Xcode application)
  • Compilers for C, C++, and Objective-C
  • A source code debugger
  • The APIs and header files used for kernel and application development
  • Profiling tools for measuring your code's execution time and identifying performance bottlenecks
  • Utilities for examining the hardware devices connected to the system and the driver that has been loaded for each device

Of these tools, the Xcode application is the one in which you will spend most of your time when writing a kernel extension, since it provides the source code editor and a front-end to the compiler. Figure 3-1 shows the Xcode 4 user interface.

images

Figure 3-1. The Xcode 4 user interface

Under the hood, Xcode is a front-end to the command-line compiler and debugger. In fact, there is nothing to stop you from bypassing Xcode completely and building your kernel extensions by invoking GCC directly on the command line. However, as we will see in the next section, Xcode provides project templates that pass the appropriate compiler flags for building a kernel extension.

Previous versions of Xcode used the GCC compiler; however, starting from Xcode 4, an alternative and modern compiler based on LLVM is provided as the default compiler. The LLVM compiler is an open source project led by Apple that supports Objective-C, C, and C++. The goal of LLVM is to provide faster compile times than GCC and to provide tighter integration into IDEs, such as Xcode, by providing more legible warning and error messages and by allowing syntax highlighting and code completion to be driven by the semantic analysis performed by the compiler itself.

images Note More information about Xcode, including information about how to obtain it, can be found at http://developer.apple.com/xcode.

“Hello World” Kernel Extension

To get started with kernel programming, let's begin by implementing a very simple example, the much beloved “Hello World” application or, in our case, kernel extension. First, launch Xcode and choose “Create a new Xcode project” from the welcome screen. This will present you with a list of templates for the new project. If you select the “System Plug-in” category, you will see that Xcode provides templates for both a “Generic Kernel Extension” and an “I/O Kit Driver.” Although both templates create a kernel extension, an I/O Kit driver requires us to nominate a hardware device it will match against and will load only if that device is present. A generic kernel extension, on the other hand, is not a hardware driver and can be loaded any time by the user.

For this tutorial, we will create a project based on the “Generic Kernel Extension” template, so select that item and click the “Next” button. We are now asked for a product name and company identifier. The product name corresponds to the name of the executable file as seen by the user, so for this example, we will use “HelloWorld.” The company identifier should be a reverse DNS style string for a domain name you or your company has registered. For this example, you are free to use “com.osxkernel,” which is a domain registered for the purpose of this book. By appending the product name to the company identifier, Xcode creates a string that is guaranteed to be unique for the project and will not collide with the name of any existing kernel extension (the unique identifier for this tutorial would be “com.osxkernel.HelloWorld.”)

images Note The reverse DNS convention is used throughout Mac OS X in places where a unique identifier is required. We will see in later chapters that the I/O Kit uses a similar scheme to ensure the names of C++ classes are unique. Previous versions of Mac OS 9 used a unique four-character constant to identify applications, which required developers to register their chosen string with Apple. Using reverse DNS allows developers to generate their own unique identifier without having to register them with Apple.

After clicking the “Create” button, Xcode will generate a project for you, including an implementation file named “HelloWorld.c.” You can examine the contents of the source file named “HelloWorld.c” by clicking on its icon in the left part of the project window. For this tutorial, modify the generated source code to include the header file <libkern/libkern.h> and to add two calls to printf(). The function named HelloWorld_start() will be called when our kernel extension is loaded and the function named HelloWorld_stop() will be called when the kernel extension is unloaded. When you have finished editing the file, the code should look like Listing 3-1.

Listing 3-1. The “HelloWorld.c” Tutorial

#include <mach/mach_types.h>
#include <libkern/libkern.h>

kern_return_t   HelloWorld_start (kmod_info_t * ki, void * d) {
    printf("Hello world ");
    return KERN_SUCCESS;
}


kern_return_t   HelloWorld_stop (kmod_info_t * ki, void * d) {
    printf("Goodbye world ");
    return KERN_SUCCESS;
}

As we mentioned in Chapter 1, the APIs used for writing kernel code are generally different to those available to user applications; this applies even to functions such as printf(). Rather than including the user space header <stdio.h>, the kernel has its own implementation of printf that is declared in the header file <libkern/libkern.h>. If you try to include <stdio.h> in a kernel project, the compiler will report that it cannot find the included header file.

As well as including the header file that defines the printf() function, we also need to link our kernel extension against the library that provides the actual implementation of printf. Rather than a compile-time linking, the kernel resolves any library dependencies of a kernel extension only when the kernel extension is loaded. To inform the kernel of our dependencies, we need to declare the libraries we wish to link against in our kernel extension's property list, a file that goes by the name “HelloWorld-Info.plist” in this tutorial project.

To modify the property list, click on the file named “HelloWorld-Info.plist” in the project window. Although the format of the file is text-based XML, Xcode contains a graphical editor for manipulating property list files, as shown in Figure 3-2. Add a new item to the OSBundleLibraries dictionary of your property list to include an item with the key name com.apple.kpi.libkern and the value 9.0.0 When you are finished, your property list should look identical to Figure 3-2.

images

Figure 3-2. The graphical property list editor in Xcode

The XML that corresponds to the addition we made to the property list file is shown in Listing 3-2.

Listing 3-2. The Value of the OSBundleLibraries Entry for Our Tutorial Kernel Extension

<key>OSBundleLibraries</key>
<dict>
        <key>com.apple.kpi.libkern</key>
        <string>9.0.0</string>
</dict>

The project's property list is not used by the compiler (other than to perform some preprocessing, which replaces variables such as ${PRODUCT_NAME} with their actual value), but is intended for the kernel. The property list is copied to the compiled kernel extension and is read when the extension is loaded. The entry we added to the dictionary consists of a key-value pair; the key identifies a kernel library on which we depend and the value corresponds to the minimum required version of that library. In our case, we are informing the kernel that we require a library with the unique identifier com.apple.kpi.libkern and that we require version 9.0.0 or later of this library. The library identifier uses a reverse DNS prefix to ensure the name is unique; in this case, the prefix “com.apple” allows us to recognize the library as a standard library provided by Apple.

images Tip The version of the library, in our case 9.0.0, is the version of the Mac OS X kernel, not the version of Mac OS X itself. Version 9.0.0 corresponds to Mac OS X 10.5.0. You can determine the version of the kernel on your machine by typing the command uname –r into Terminal.

images Note You may have noticed that the project created from the Xcode template includes an item named “Kernel.framework” in the “Frameworks” group. This is not used by the linker when the project is built, but is simply included to help the developer by providing easy access to kernel headers.

The kernel extension project is now complete and is ready to be built. To do this, choose “Build” from the “Project” menu. You should not receive any build errors, but if you do, make sure the contents of your “HelloWorld.c” file match those shown in Listing 3-1.

Before we run this kernel extension, it is worth taking a moment to understand how the kernel knows which entry points to call, given that the two functions contained in the source file appear to be user-defined. As you may have suspected, Xcode gives us a gentle push and generates some of the boilerplate code for us automatically. In generating this code, Xcode uses two values that are defined in the project's settings, which define the kernel extension's start and stop routines. These values are shown in Figure 3-3. You are free to rename the entry points from HelloWorld_start and HelloWorld_stop, as long as you change the name of the functions defined in the source code and the values in the project build settings.

images

Figure 3-3. The project settings for the “Hello World” kernel extension

Following a successful compile of the project, Xcode will have created a kernel extension with the name “HelloWorld.kext.” This file is packaged into a special file known as a KEXT bundle. If you are not familiar with bundles, they are essentially a directory that contains all the files required by the executable, but which the Finder presents to the user as a single file. Listing 3-3 shows the contents of the bundle created when we built the “Hello World” kernel extension.

Listing 3-3. The Contents of the HelloWorld.kext Bundle

HelloWorld.kext/
HelloWorld.kext/Contents/Info.plist
HelloWorld.kext/Contents/MacOS
HelloWorld.kext/Contents/MacOS/HelloWorld
HelloWorld.kext/Contents/Resources
HelloWorld.kext/Contents/Resources/en.lproj
HelloWorld.kext/Contents/Resources/en.lproj/InfoPlist.strings

The file named “Info.plist” should be familiar, since this is a copy of the property list we modified earlier (with some minor processing applied by Xcode along the way). The other file that deserves a mention is simply named “HelloWorld” and is located in the subdirectory of the bundle with the path “Contents/MacOS.” This file contains the actual executable code of the kernel extension.

Loading and Unloading Kernel Extensions

A kernel extension is a code module that runs inside the operating system kernel. Having built our kernel extension, it now needs to be loaded into the kernel where it can be run. While Xcode is great for writing and building kernel extensions, it cannot be used for testing or debugging a kernel extension; in fact, for a kernel extension, the button named “Run” in the Xcode window will build the project only, but won't actually load or run the resulting output. Instead, kernel extensions on Mac OS X can be loaded one of two ways, automatically, by copying the kernel extension bundle to the directory /System/Library/Extensions, or manually through the command line.

To load the kernel extension, we first need to locate the compiled binary that was built by Xcode. By default, Xcode 4 will place the output from the compiler in a different location than the project directory that contains the source code, which can make it difficult to find the path to the kernel extension in order to load it on the command line. To locate the path in which Xcode has written the kernel extension, right-click on the product named “HelloWorld.kext,” which displays a contextual menu, and select the item “Show in Finder,” as shown in Figure 3-4.

images

Figure 3-4. Locating the path to the built kernel extension

A kernel extension that is copied to the /System/Library/Extensions directory will be loaded when needed by the operating system. This could be when the system boots or, in the case of a driver, when a hardware device that requires the driver is connected to the computer. However, during development, it is typically more convenient to load the kernel extension manually from the command line.

For security, because a kernel extension is granted the same elevated privileges as the core operating system code, kernel extensions can only be installed or loaded by a user with administrative access to the system. As a further security measure, the system has strict requirements regarding the file permissions of the kernel extension's bundle and will refuse to load a kernel extension that does not meet these requirements, particularly the following.

  • The KEXT bundle and all files and folders inside it must be owned by the user “root” (user id 0).
  • The KEXT bundle and all files and folders inside it must be owned by the group “wheel” group id 0).
  • The KEXT bundle and any directory inside it must have the permissions mask 0755 (rwxr-xr-x).
  • All files inside the KEXT bundle must have the permissions mask 0644 (rw-r--r--).

When you build a kernel extension in Xcode, the KEXT bundle it produces will have the correct permission mask for the bundle and its contents, but user and group ownership will correspond to the user who ran the compiler. To correct the file ownership to that required by a kernel extension, you can use the following command in Terminal.

sudo chown -R root:wheel HelloWorld.kext

Note that if you change the ownership of the KEXT inside the Xcode build directory, Xcode will not have sufficient permission to overwrite the KEXT when the project is next built, which will result in a build error. To overcome this, you can copy the KEXT from the Xcode build directory to another directory (such as /tmp) before changing its ownership and loading it.

Mac OS X contains a number of command line utilities for the purpose of working with kernel extensions. Some of the commonly used commands include:

  • kextload, which loads a KEXT into the kernel
  • kextunload, which stops a loaded KEXT and unloads it from the kernel
  • kextutil, which is a developer-oriented utility for loading KEXTs into the kernel and can provide diagnostic information detailing why a kernel extension failed to load and can produce symbols that are useful when debugging an active kernel extension
  • kextstat, which displays a list of all KEXTs loaded into the kernel

With the exception of kextstat, which does not actively modify the state of the kernel, all these commands must be run with super-user permissions. This can be accomplished by prefixing commands with sudo.

We are now ready to load the “Hello World” kernel extension. To do this, run the following command in Terminal:

sudo kextload HelloWorld.kext

Although the “Hello World” kernel extension has been loaded and its start entry point called, you won't see the result of our call to printf in the terminal window. Instead, the output from calling the kernel's implementation of printf is written to a log file. To confirm the “Hello World” kernel extension was loaded, you can use the kextstat command, as follows:

kextstat

This will print a list of the running kernel extensions. Since the “Hello World” extension will be one of the most recent extensions to have been loaded, it should appear at the end of the list. An example of the output from kextstat is shown in Listing 3-4.

Listing 3-4. The Output from the kextstat Command, with Our Kernel Extension Highlighted

Index  Refs   Address             Size        Wired     Name (Version) <Linked Against>
  1      85  0xffffff7f80742000   0x683c      0x683c    com.apple.kpi.bsd (11.1.0)
  2       6  0xffffff7f8072e000   0x3d0       0x3d0     com.apple.kpi.dsep (11.1.0)
  3     110  0xffffff7f8074c000   0x1b9d8     0x1b9d8   com.apple.kpi.iokit (11.1.0)
  4     116  0xffffff7f80738000   0x9b54      0x9b54    com.apple.kpi.libkern (11.1.0)
  5     103  0xffffff7f8072f000   0x88c       0x88c     com.apple.kpi.mach (11.1.0)
...
130       0  0xffffff7f810ce000   0x51000     0x51000   com.apple.filesystems.afpfs (9.8) <129 7 6 5 4 3 1>
144       0  0xffffff7f807b8000   0x2000      0x2000    com.osxkernel.HelloWorld (1) <4>

Finally, we will unload the “Hello World” extension, which will result in the HelloWorld_stop() function being called and the kernel extension being unloaded from the kernel. This can be accomplished with the following command:

sudo kextunload HelloWorld.kext
Using Console to View Output

The resulting output from calling printf() in the kernel is written to a log file on disk. This log takes the format of a plain text file that allows it to be examined with the standard Unix commands tail and cat, passing the path to the logfile /var/log/kernel.log. Alternatively, the contents of the log can be inspected with an application included with Mac OS X named “Console,” which can be found in the /Applications/Utilities directory. The Console application consolidates logs from a wide range of system services and applications, including the kernel logfile. A screenshot of the output from our tutorial viewed through Console is shown in Figure 3-5.

images

Figure 3-5. The output from a successful load and unload of our kernel extension, as shown in the Console utility

Although it may seem primitive if you are accustomed to source level debuggers from user-space development, being able to print debug output to the console remains one of the fundamental debug techniques for kernel code. You can find more information on debugging in Chapter 16.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.167.161