In this article, we will make a brief introduction to the driver development for macOS discussing the basics of kernel extension (KEXT) implementation. We will discuss the typical tasks requiring kernel extensions, tools and environment for their creation, and some aspects of this process.

Written by:
Vladimir Vashurkin
Software Developer of Apriorit Mac Development Team

 

Contents

1. Overview

1.1 What is macOS kernel

1.2 Kernel extensions - .kext

1.3 Why do I need custom kernel extension?

2. Inside kernel extension

2.1 KEXT bundle structure

2.2 Enter / exit routines

2.3 Kernel-user interaction (IO)

3. KEXT Development

3.1 Develop .kext using Xcode

3.2 Testing & debugging techniques

3.3 Signing and Installing your .kext

 

Overview

What is macOS kernel

The core of the OS (kernel) is the central part of the operating system, which provides applications with coordinated access to computer resources: processor, memory, external hardware, external input and output devices. Also, the kernel typically provides file system and network protocol services.

As a fundamental element of the OS, the kernel represents the lowest level of abstraction for accessing applications to the system resources necessary for their operation. Usually the kernel provides such access to the executable processes of the corresponding applications due to the use of mechanisms for inter-process communication and application access to OS system calls.

macOS kernel is an XNU - a hybrid core developed by Apple and used in the macOS family.

Kernel extensions - .kext

Kernel eXtension (KEXT) is a special application bundle that extends the functionality of the macOS kernel.

KEXT is the minimum unit of executable code that can be loaded and used in the kernel.

Why do I need custom kernel extension?

When developing for macOS, usually there is no need for creating a Kernel Extension. Often, the functionality available in user-mode is sufficient for most tasks.

Another one restriction of creating KEXT is that the code of KEXT itself should be close to ideal. This is indicated by the official documentation of Apple.

The reason is simple enough: if the worst-case scenario for an application is a crash and emergency exit. If the kernel module fails, the worst option is to crash the entire OS and reboot the device. If KEXT is loaded at system startup and contains an error, it will crash the system each startup and this will further complicate the recovery of the entire system.

However, despite possible inconveniences and dangers, there are cases that couldn’t be implemented without Kernel extension development:

  • support a certain type of file system (including creating own ones)
  • if a very large number of concurrent applications need quick access to the same resource provided by the KEXT code.
  • interception and substitution of the OS API system call. Used to intercept traffic, I/O, other system calls; also can be used for hiding files or processes.
  • to write the device driver (network controllers, graphics / audio drivers, etc.).

Inside kernel extension

KEXT bundle structure

KEXT, like any other macOS application, is a bundle with the .kext extension. Bundle is a special folder that encapsulates application resources (in this case - KEXT resources).

KEXT bundle must contain 2 files:

  • compiled binary file with executable code
  • Info.plist containing information about this kernel extension: name, version, identifier, dependencies from the kernel libraries and so on.

Sometimes, the bundle.kext contains additional files (resources, plugins, etc.):

  • device firmware
  • resources (including localized for use by user-mode applications)
  • plugins, including others KEXT

Enter / exit routines

Depending on the type of extension, it can be written in C or C ++ and has its own peculiarities when loading and unloading to / from the kernel:

Type

Generic kernel extension

IOKit driver

Programming language

Usually C

C++

Implementation

C functions registered as callbacks with relevant subsystems

Subclasses of one or more I/O Kit driver classes, such as IOGraphicsDevice

Entry points

Start and stop functions with C linkage

C++ static constructors and destructors

Loading behavior

Must be loaded explicitly

Loaded automatically by the I/O Kit when needed

Unloading behavior

Must be unloaded explicitly

Unloaded automatically by the I/O Kit after a fixed interval when no longer needed

Since this article is devoted to the usual KEXT, let's take a closer look at loading and unloading kernel extension.

In the kernel extension code, you must implement entry points - functions that are called when KEXT is loaded into the kernel and at the time it is unloaded.

These functions can have arbitrary names that must be specified in the project file:

Kernel Extention - Functions

These functions have fixed prototypes:

			kern_return_t Kext_start(kmod_info_t* ki, void* d);
			kern_return_t Kext_stop(kmod_info_t* ki, void* d);

The function Kext_start usually contains an initialization code, which must be executed at the start of the extension, for example:

  • initialization of the mechanisms of communication with user-mode
  • initialization of global variables, setting startup parameters
  • other actions to be performed at startup

The function Kext_stop usually contains the de-initialization code, the release of resources, and other actions that must be performed at the time the extension expires.

Load and unload .kext into the kernel macOS has a number of Command-Line tools of the family kext...xxx designed to manage KEXTs. Commands associated with KEXT must be run as root, e.g. with sudo.

kextstat - display status of loaded kernel extensions (kexts)

kextload -load kernel extensions (kexts) into the kernel

kextunload - unload kernel extensions (kexts) into the kernel

Kernel-user interaction (IO)

There are different mechanisms for the interaction between KEXT and the user-space. Since a detailed review of all them is huge and deserves separate specialized article, let’s look on a generalized description of each of them:

  • ioctl / sysctl (Kernel Control API for KEXT Control)


This method allows to interact between user and kernel via special sockets. 
A similar socket must be configured in the KEXT itself (for example, when KEXT starts in the Kext_start function). 


Different clients in user-space can connect to the KEXT socket and send there interaction commands.

  • kern_event (Kernel Events Notifications)


Kernel Events Notifications allows applications to receive notifications about a particular event that occurred in the kernel. This interaction is also based on the sockets.


  • IOKit (only device drivers)

Apple’s large framework designed for communicating with device drivers.

  • Preference file / Startup info

One of the most frequently asked questions is the question of how to implement reading the settings from the file when KEXT starts. In fact, there is no API that allows KEXT to read the files directly. 


To perform this, KEXT must work in conjunction with daemon, which works in user-mode
. At launch, daemon reads the data from the configuration file, connects to KEXT using the tools described above, and passes settings to it.

KEXT Development

Develop .kext using Xcode

Unlike other platforms, Kernel extension programming under macOS is much easier and more comfortable. KEXT can be developed in Xcode - a powerful development environment that is provided by Apple for free.

In addition to other templates, Xcode has templates for the convenient creation of KEXT and IOKit Driver.

In the KEXT template, the developer is immediately provided with:

  • implementation file, same named as project. Templates for the start / stop functions of KEXT itself are also prepared
  • Info.plist that should be supplemented with the necessary info about KEXT (name, identifier, library dependencies, etc.)
  • project file, which stores various settings of the developed kernel extension

In addition to direct KEXT developing, Xcode allows to combine and collect all the necessary related products in one project.

For example, a project may contain the following products:

  • Kernel Extension - directly KEXT product
  • Daemon - daemon that configures KEXT according to the saved settings at the time the system starts
  • KextController -  macOS Application with a graphical interface that allows user easily manage the Kernel Extension
  • Installer/Uninstaller - because KEXT, unlike a simple macOS application, requires a number of specific actions during installation (copying to the right folder, manual start KEXT after installation (not to require rebooting), initial setup, etc.), Installer / Uninstaller can be created for convenient installation and removal of the product

This way, using Xcode, it is possible to develop KEXT together with a complete set of auxiliary applications directly “out of the box”.

Testing & debugging techniques

Since errors in kernel extensions are critical (can involve OS crash), thoroughly testing and debugging a Kernel extension is a must. At the same time, KEXT is in the kernel, and it is not possible to use usual debug techniques. There are 2 general KEXT debugging techniques that can be used either separately from each other or together.

Logs, logs, logs

All the code of KEXT, especially critical, complex or questionable areas, should be covered as much as possible with logs. This technique will accurately identify the place, and probably the cause of the error in KEXT. In addition, in log messages developer can detect warning messages, which did not lead to an error now, but could lead the errors in the future. The log messages from KEXT can be seen in the standard macOS application - Console.app.

Debugging

Yes, you can debug KEXT. But this is not so simple as usual app. For the expansion of the kernel, the Remote Debugging technique is used. That way debugging is performed with the participation of 2 devices.

On device 1, the debugging tools are configured and the product code is located.

On device 2, the necessary environment is prepared, KEXT is loaded. Device 1 communicates to the device 2 and debugging can be performed.

The technique of remote debugging using 2 devices is a complex topic, its detailed description is beyond the scope of this article.

There are also some materials from Apple, where debugging techniques of kernel extension are given:

Signing and Installing your .kext

Apple and macOS take care of the security of their users. In the latest versions of macOS all KEXT must be signed with a special signature "Developer ID for Signing Kexts". This requirement has become even stronger with “System Integrity Protection”, which appeared in OS X 10.11 and is enabled by default.

For installation, the signed KEXT (as described above) should be simply copied to the “/Library/ Extensions” directory, which is specifically designed for non-system kernel extensions.

 

Learn more about Apriorit macOS development services.

Subscribe to updates