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.
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.
An XML properties list. A multitude of items:
A plain-text properties list (looks a lot like constant definitions in C or something - use a plist editor). Four entries:
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: