Kernel Extensions for OS X

Uh. Kexts.

So, it looks like there's a socket mechanism for communicating with kexts. Basically, the kext, in its start routine, sets up a sort of server socket (using kernel routines in kern_control), then the client app opens a socket (using the normal socket API with the PF_SYSTEM protocol family), then the kext handles the traffic on the socket via routines that it declares in the setup. When you actually read through the docs, it's not so complicated - here are my notes, in the form of code which ought to compile and work (i haven't tested it, so it'll contain numerous bugs); kext code first:

/*
This is for Darwin 8, ie OS X 10.4; for Darwin 7, the naming of the
control is different - you have to work directly with a numeric ID. You
can register one on the Apple website - i got AMTB for my AMT Browser
kext.

The kext start and stop routines are called start and stop, 
respectively.
*/

#include <sys/systm.h>
#include <mach/mach_types.h>
#include <mach/kern_return.h> /* does one of the above drag this in? */
#include <sys/kern_control.h>
#include <SOMEWHERE YOU CAN GET STRNCPY IN THE KERNEL FROM>

char name[] = "org.example.mymodule" ;
struct kern_ctl_ref _ctlref ; /* an opaque reference to the control */

kern_return_t start(kmod_info_t *info, void *data)
{
	int err ;
	/* a kern_ctl_reg is an application form for a kernel control */
	struct kern_ctl_reg ctlreg ;
	bzero(&ctlreg, sizeof(ctlreg)) ;
	ctlreg.ctl_id = 0 ; /* ask for a dynamically allocated id */
	ctlreg.ctl_unit = 0 ; /* ditto for unit numbers */
	strncpy(ctlreg.ctl_name, name, sizeof(ctlreg.ctl_name)) ;
	/* leave the flags as 0 */
	/* those callbacks in full ... */
	ctlreg.ctl_connect_func = connect ;
	ctlreg.ctl_disconnect_func = disconnect ;
	ctlreg.ctl_send_func = send ;
	ctlreg.ctl_getopt_func = getopt ;
	ctlreg.ctl_setopt_func = setopt ;
	err = ctl_register(&ctlreg, &_ctlref) ;
	if (err) return KERN_FAILURE ;
	return KERN_SUCCESS ;
}

kern_return_t stop(kmod_info_t *info, void *data)
{
	/* do we need to switch the control off here? */
	return KERN_SUCCESS ;
}

/*
Here be callbacks. The first parameter to each is a kern_ctl_ref which
is a bit like self/this in an OO program - it tells the function exactly
which control is being operated, so you can have a single instance of a
callback routine service multiple controls. You also get either a unit
number (or a sockaddr_ctl structure, which includes it as its sc_unit
member - myself, i would have left the unit number as a parameter too,
but hey), so you can (if i understand this right) tell which instance of
that control is being operated - controls are a bit like server sockets
in networking, and units are like sockets opened from them. You also get
this unitinfo variable, which allows you to stash some data with each
unit; in connect, it's a pointer-to-pointer-to-void, ie it leads you to
a secret place where you can place a pointer to your data (as a void*),
and in the other routines, it's a pointer-to-void, and you get whatever
it was you put there in connect. The rest's pretty self-explanatory, i 
think. For the return value, 0 means success, as always, otherwise pick 
something from errno.h; EINVAL seems to be the catch-all error number in 
the Apple example code.
*/

errno_t connect(kern_ctl_ref ctlref, sockaddr_ctl *addr, void **unitinfo)
{
	/* nothing much of interest in addr except sc_unit */
	/* stash stuff into unitinfo here */
	return 0 ;
}

errno_t disconnect(kern_ctl_ref ctlref, u_unit32_t unit, void *unitinfo)
{
	return 0 ;
}

errno_t send(kern_ctl_ref ctlref, u_unit32_t unit, void *unitinfo, mbuf_t data, int flags)
{
	/*
	There's a strong chance that you'll want to send a reply to the 
	client round here; for that, you want the ctl_enqueuedata routine in 
	kern_control.h, which looks like:
	
	errno_t ctl_enqueuedata(kern_ctl_ref ctlref, u_int32_t unit, void *data, size_t data_len, u_int32_t flags) ;
	
	Which does exactly what it says on the tin - puts some data on the
	queue of client-bound messages on the socket. The only flag you
	might want is CTL_DATA_NOWAKEUP, which means 'put the data on the
	queue, but don't wake the client up so it can read it', a low act if
	ever there was one.
	*/
	return 0 ;
}

errno_t setopt(kern_ctl_ref ctlref, u_unit32_t unit, void *unitinfo, int opt, void *data, size_t data_len)
{
	return 0 ;
}

errno_t getopt(kern_ctl_ref ctlref, u_unit32_t unit, void *unitinfo, int opt, void *data, size_t *data_len)
{
	/*
	Note that data_len is a pointer; on the way in, the location it
	points to contains the length of the buffer pointed to by buf, and
	on the way out, it should contain the length of the data placed in
	it.
	
	Except that sometimes, data will be NULL, ie you don't have a
	buffer; that means the kernel is asking you how much space you want,
	so work that out, and store it in the location pointed to by 
	data_len.
	*/
	return 0 ;
}

See, not so bad, eh? Filling out the registration struct is a bit of a pain, but no big deal. This would be a doddle in C++ or whatever. Anyway, here's what it looks like on the client side ...

#include <socket.h>
#include <string.h>
#include <sys/kern_control.h>
#include <sys/ioctl.h>

u_int32_t getctlidbybame(int sock, char *name) ;

int socket_ctl(char *name)
{
	int sock = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL) ;
	if (sock == 0) return 0 ;
	u_int32_t id = getctlidbyname(sock, name) ;
	sockaddr_ctl addr ;
	addr.sc_len = sizeof(addr) ;
	addr.sc_family = AF_SYSTEM ;
	addr.sc_sysaddr = AF_SYS_CONTROL ;
	addr.sc_id = id ;
	addr.sc_unit = 0 ; /* allocate dynamically */
	int err ;
	err = connect(sock, (struct sockaddr*)&addr, sizeof(addr)) ;
	if (err) return 0 ;
	return sock ;
}

u_int32_t getctlidbybame(int sock, char *name)
{
	struct ctl_info info ;
	memset(&info, 0, sizeof(info)) ;
	strncpy(info.ctl_name, name, strlen(name)) ;
	int err = ioctl(sock, CTLIOCGINGO, &info) ;
	if (err) return 0 ; /* should do something stronger here! */
	return info.ctl_id ;
}

Again, not so bad, really; the socket/connect dance is irritating, as always, and the ioctl is a bit of a pain, but none of it's that bad.

The Apple example code suggests that the preferred style for subsequently doing things with the control is by using getsockopt/setsockopt essentially to get and set properties on the control, rather than doing reads and writes. I think the latter does work, though. I don't see any way to service general ioctls on the socket, though.

Building a Kernel Extension By Hand

That is, without XCode. Why would you want to do that? Exactly!

A kext is a bundle - a folder with a file extension which makes OS X treat it specially. The extension is .kext, funnily enough. Like other bundles, it has a standardised file layout inside it, which looks like:

As you can see, the key elements are the binary, the property list, and the strings. I guess if you want an icon, that lives in there too.

The Property List

An XML properties list. A multitude of items:

CFBundleDevelopmentRegion: string
the name of the locale used - "English" is the default, and i have no idea what vocabulary this is drawn from
CFBundleExecutable: string
the name of the binary - same as the name of the bundle, by convention; i guess this will be looked for in Contents/MacOS
CFBundleIdentifier: string
a reverse DNS unique identifier for the bundle (gb.hmg.dra.foo style)
CFBundleInfoDictionaryVersion: string
"6.0" - no idea why
CFBundlePackageType: string
"KEXT" - the MacOS type code
CFBundleSignature: string
"????" - the MacOS creator code
CFBundleVersion: string
the version, as a stringified Apple-style version number (a 'NumVersion') eg "1.0.0d1"; there's probably a routine in Carbon somewhere for doing the stringification, but it looks easy enough to do by hand (see Technical Note 1132 for the gory details)
OSBundleLibraries: dict
list of dependencies (kexts and kernel features); the items consist of a key which is the RDNS name of the depended-on item, and a value which is a string giving the version required (run 'kextstat' to see what versions you're running; Apple's docs have tables somewhere showing what versions components had in each release of OS X) - for starters, you'll need com.apple.kernel.mach and com.apple.kernel.bsd (both at 6.9.9 since 10.3)

The Strings

A plain-text properties list (looks a lot like constant definitions in C or something - use a plist editor). Four entries:

CFBundleName: string
the name of the bundle; should be the same as the name of the bundle, binary, etc
CFBundleShortVersionString: string
a human-readable text version of the formally-structured version string in the plist, eg "1.0" (Apple wouldn't say "1.0.0", it seems)
CFBundleGetInfoString: string
what it says on the tin; Apple's pattern is "1.0, Copyright Initech Corp 2006"
NSHumanReadableCopyright: string
again, self-explanatory; Apple just say "Copyright Initech Corp 2006"

The Binary

The hard part.

The basics are that it's a Mach-O object (a shared object? relocatable? what?), linked against Kernel.framework and nothing else, and with the bundle indentifier (the reverse DNS name) and version string embedded in it somehow. Still working on this. I've dumped a log from XCode which shows all the steps it takes to build the kext; it should be fairly straightforward (if a bit tedious) to go through this and work out what the steps you need to do manually are.

Some notes on coding the binary: