ProcFS
An implementation of the /proc file system for OS X
Install / Use
/learn @kimtopley/ProcFSREADME
ProcFS
An implementation of the /proc file system for OS X
What is procfs?
procfs lets you view the processes running on a UNIX system as nodes in the file system, where each process is represented by a single directory named from its process id. Typically, the file system is mounted at /proc, so the directory for process 1 would be called /proc/1. Beneath a process’ directory are further directories and files that give more information about the process, such as its process id, its active threads, the files that it has open, and so on. procfs first appeared in an early version of AT&T’s UNIX and was later implemented in various forms in System V, BSD, Solaris and Linux. You can find a history of the implementation of procfs at https://en.wikipedia.org/wiki/Procfs.
In addition to letting you visualize running processes, procfs also allows some measure of control over them, at least to suitably privileged users. By writing specific data structures to certain files, you could do such things as set breakpoints and read and write process memory and registers. In fact, on some systems, this was how debugging facilities were provided. However, more modern operating systems do this differently, so some UNIX variants no longer include an implementation of procfs. In particular, OS X doesn’t provide procfs so, although it’s not strictly needed, I thought that implementing it would be an interesting side project. The code in this repository provides a very basic implementation of procfs for OS X. You can use it to see what processes and threads are running on the system and what files they have open. Later, I plan to add more features, neginning with the ability to inspect a thread’s address space to see which executable it is running and what shared libraries it has loaded.
If you install procfs on your system, mount it at /proc and take a look at it with Finder, you’ll see a hierarchy of files that looks something like this:

Each directory in the left column represents one process on the system. By default you can only see your own processes, although it is possible to set an option when mounting the file system that will let you see and get details for every process. Obviously this is a security risk, so it’s not the default mode of operation. Within each process directory are seven files and two further directories, shown in the second column of the screenshot. All of the files can be read in the normal way, but the data that they contain is not text, so they are really intended to be used in applications rather than for direct human consumption. The following table summarizes what’s in each file. You’ll find definitions of the structures in this table in the file /usr/include/sys/proc_info.h.
| File | Summary | Structure |
|---------|----------------------------------|-------------------------------|
|pid | Process id | pid_t |
|ppid | Parent process id | pid_t |
|pgid | Process group id | pid_t |
|sid | Session id | pid_t |
|tty | Controlling tty | string, such as /dev/tty000 |
|info | Basic process info | struct proc_bsdinfo |
|taskinfo | Info for the process’s Mach task | struct proc_taskinfo |
The fd directory contains one entry for each file that the process has open. Each entry is a directory that’s numbered for the corresponding file descriptor. Most processes will have at least entries 0, 1 and 2 for standard input, output and error respectively. Within each subdirectory you’ll find two files called details and socket. The details file contains a vnode_fdinfowithpath structure, which contains information about the file including its path name if it is a file system file. If the file is a socket endpoint, you can read a socket_fdinfo structure from the socket file.
The threads directory contains a subdirectory for each of the process’ threads. The process in the screenshot above has two threads with ids 550 and 1284. Each thread directory contains a single file called info the contains thread-specific information in the form of a proc_threadinfo structure.
Using procfs for OS X
To use procfs, you'll have to build your own copy of the OS X kernel. Booting a new kernel on your own hardware is a risky process, so I recommend that you start by getting a second disk and installing OS X on it. Instead of installing your kernel on your main disk, you'll put it on your second drive and boot from that for testing. If anything goes wrong, you can always get a working system back by rebooting from your primary disk. I also recommend that you use two OS X systems--one on which you run the development kernel (the target system) and another on which you build the kernel (the development system). You'll need to do this if you want to debug any problems with your kernel.
Building and Booting a Kernel
To get started, download a copy of the kernel sources from http://www.opensource.apple.com. procfs was developed with OS X version 10.11.2. Follow the link from the main opensource page to see all of the available source code for that version and locate the xnu subproject (the current release is called xnu-3248.20.55). Click on the arrow icon to download a tarball and extract the source code to your development machine.
Next, you'll need to get the tools required to build the kernel and procfs. You'll need Xcode (I used version 7.2) and the command line tools, both of which you can get from https://developer.apple.com/downloads. While you're there, get the Kernel Debug Kit as well, making sure that you get the correct version for the release of OS X that you're developing on. You need to be sure that the versions of the kernel source code and the Kernel Debug Kit are the same and you'll need to have that version of OS X running on both your target and development machines. Install Xcode, the command line tools and Kernel Debug Kit on your development machine.
Building the kernel is surprisingly easy if you have all of the prerequisites installed. You can find a complete set of instructions for this at http://shantonu.blogspot.com. Look for the article called "Building xnu for OS X 10.11 El Capitan". procfs does not add any new system calls, so you don't need to be concerned about the section that describes how to do that. Once you've built your kernel, skip paragraphs 1 through 4 in that section and proceed to section 5. You'll need to disable system integrity protection to install a new kernel. To do that, boot your target system into recovery mode by restarting and holding down the Option key when you hear the chime, then open a terminal window and enter this command:
csrutil disable
You'll also need to set NVRAM boot arguments, which you can do by entering the following command:
sudo nvram boot-args="-v debug=0x146 kdp_match_name=enX"
Take note of the last part of that line-the device name that appears after kdp_match_name is the device that you'll use to connect to the target system for debugging. You can read about how to set up 2-machine debugging in the README file of the Kernel Debug Kit. In my case, the machines I use for debugging are connected over Ethernet and the name of the Ethernet connector on the target machine is en3, so my NVRAM boot arguments are set up like this:
sudo nvram boot-args="-v debug=0x146 kdp_match_name=en3"
Now reboot the target machine using its primary disk. When you do that, you'll see one result of setting boot arguments--for most of the boot process, you'll be looking at console messages instead of the usual graphical user interface. If something goes wrong while booting, you're very likely to see something here that will help you figure out what the problem is. If you value the integrity of your system, you'll install the kernel in on /System/Library/Kernels on your second disk, not the primary disk. To do that, plug it in so that it appears as /Volumes/Something, backup your existing kernel, copy the new kernel to /Volumes/Something/System/Library/Kernels and then use the command sudo kextcache -invalidate /Volumes/Something to rebuild the kernel extension cache. Note that you need to do this every time you install a new kernel, before you reboot.
You're finally ready to try your new kernel. Reboot the target machine, hold down the Option key and select to boot from your second drive. If all goes well, OS X should start up as normal and will be running your new kernel. You can check that it's your kernel by typing the command uname -a. For Apple's release kernel, you'll see something like this:
Darwin Kims-iMac.local 15.2.0 Darwin Kernel Version 15.2.0: Fri Nov 13 19:56:56 PST 2015; root:xnu-3248.20.55~2/RELEASE_X86_64 x86_64
If you are running your own kernel, the date and time should match those of your build and your login name should appear instead of root.
Building a Kernel with procfs
Now that you can build and boot your own kernel, you can add procfs to it. Download the source code from the GitHub repository (https://github.com/kimtopley/ProcFS) and open the project file at ProcFS/ProcFS.xcodeproj in Xcode. You'll see that the project has three targets:
- The
mount_procfstarget builds themountcommand that's required to mount the procfs file system. - The
procfstarget contains the kernel source code. The project is set up to allow the code to be built with the same options and header files that are available during a kernel build, so that editing can be done in Xcode. The code can be compiled and will produce a static library, but the library is not used
