Kernel Programming (xv6) - Step 0, Build Environments
Today we’ll go through setting up the development environment for XV6. XV6 is a small Unix clone developed by MIT to aid in their 6.828 Operating System Engineering course. I will loosely follow along with the course material, but we’ll take some extended detours into topics that I find interesting. Accompanying the MIT course material, there is also a small book for XV6 that goes through much of the theory. I’d highly recommend reading it, but it’s not required if you want to follow along with this blog.
This week I’ll go through setting up a development environment for either OSX or GNU/Linux. At a minimum we will need:
- binutils (One that can create ELF binaries)
- gcc
- qemu (To test our xv6 kernel)
- git
The first thing we need to do is get copies of gcc/binutils. For GNU/Linux, distro packages for both of these should be adequate.
Linux build instructions
Linux builds are very easy. You can either use your distro packages, or compile more recent versions from their source. The distro packages are the easiest and quickest way to get a functional system.
Ubuntu
Arch
Fedora
OSX build instructions
Building on OSX is a pain in the ass, in order to run XV6 we will need a toolchain capable of producing ELF executables. In addition, OSX uses a different binary format (Mach-O), so the default compiler/linker will not be sufficient^1. My initial attempt to build a cross-compiler for xv6 got pretty far, but for some reason, the kernel crashed Qemu when it tried to turn on paging in entry.S.
Ultimately the best approach was to use Crosstools-ng to create a functional toolchain. It wasn’t without trials and tribulation; I’m going to detail the steps I took here, hopefully, it will be reproducible:
- Install homebrew.
- Install crosstools-ng pre-requisites.
- Install crosstools-ng from homebrew, and add some OSX specific patches to linux-4.3 and gcc-7.1.0
- Create a case sensitive disk image for cross tools-ng to build in (HFS by default is case insensitive and that can screw up the build process).
- Configure for x86_64-unknown-linux-musl
- Disable STATIC= , we don’t need a statically built toolchain and OSX can’t create static binaries
- run ct-ng build
- Wait for about an hour to 1:30
- See if the build failed….
1. Install Homebrew
Homebrew should be a familiar tool to Mac/OSX developers, we’ll use it to manage the dependencies for crosstool-ng. You can run the following command below to install Homebrew directly.
2. Install Crosstools-ng Pre-requisites.
Crosstools-ng requires GNU Grep (It has a problem with the grep provided by OSX), and a native version of GCC (by default, gcc on OSX is a symlink to clang/llvm which doesn’t seem to work well with crosstools-ng).
You will also need to ensure that /usr/local/bin
is in your path:
Now that we have the relevant pre-requisites, lets get started with crosstools-ng.
2. Crosstools-ng install
Crosstools-ng is available via Homebrew. If you’d like a detailed explanation about how cross-compilation toolchains are built I’d recommend reading this which is a great overview of the high level steps required.
Additionally, we need to ensure that crosstools-ng actually uses gnu-grep (the
one in /usr/local/bin), instead of the system grep. To do that we need to edit
the following file $ sed -i '' 's/=".*\/grep"/="\/usr\/local\/bin\/grep"/' /usr/local/Cellar/crosstool-ng/1.22.0_1/lib/crosstool-ng-1.22.0/paths.sh
Patch the kernel headers
Now that we have crosstools-ng installed, we need to add two patches. The first
fixes the __headers:
make target for linux-4.3. The linux kernel headers are
required to build the cross libc implementation, and unfortunately by default
the __headers:
target in the kernel makefile has a circular dependency, which
can lead to an error like this:
Thankfully the fix is simple enough, I’ve provided a copy
here.
It turns out all we need to do is remove the ‘archscripts’ dependency from the
kernel Makefile’s __headers:
target.
All you need to do is copy that patch into the relevant patches subdirectory for crosstools-ng, note that the linux/4.3 subdir might not exist:
Patch GCC 7.1.0
The second patch we need is to fix one of GCC’s header files. The symptom is an
error like cfns.gperf:101:1:error: 'const char* libc_name_p(const char*, unsigned int)' redeclared inline with 'gnu_inline' attribute
. The problem is
that one of the generated source files for GCC 7.1.0 seems to declare a
function as inline whereas the header containing the declaration does not. It
turns out that the patch is relatively simple, it’s availalbe
here:
Similar to the above patch, we just need to copy it into place in the crosstools-ng patches directory:
4. Make a case sensitive filesystem image.
Crosstools-ng requires a case-sesnitive filesystem for it’s build. By default OSX uses HFS+ which is case insensitive (irritating), however we can use hdiutil to create a disk image for us:
Now we are ready to start configuring crosstools-ng and building our toolchain!
5. Configuring Crosstools-ng
The next step is to configure crosstools-ng to build or toolchain. By default
(using the version I have anyway), crosstools-ng doesn’t list a x86 target with
musl. Instead I had to run ct-ng menuconfig
in order for it to show up. I’d
advise against selecting other libc implementations, they take longer to
compile and I couldn’t get them to work properly.
If x86_64-unknown-linux-musl
is among the samples listed, then you are in luck! You can go ahead and pick it:
If it’s not quite there yet then the following should work:
If all goes well ct-ng show-tuple
should output x86_64-unknown-linux-musl
.
Double check to ensure that static linking is not set (or binutils will
complain later):
One final thing to do is to edit the resulting .config
to ensure that the
tools are built and installed within the filesystem image we created earlier.
Additionally, it would be nice to run a few make jobs in parallel to speed
things up a bit. The following should do the trick:
Now you are ready to fire off a build using ct-ng build
.
Build qemu-system
The final step in setting up our toolchain is to get a copy of Qemu. Qemu is the emulator we will run the XV6 kernel under. You could probably manage with installing a copy from Homebrew, but I prefer to compile my copy from source, I’ve listed the commands I’ve used to compile and install Qemu below:
Obtaining the XV6 sources
I’ve forked a copy of the XV6 source tree at https://github.com/mtottenh/xv6
. If you’d
like to follow along, feel free, otherwise you can get access to the upstream
source
Now we just need to add our toolchain to $PATH export PATH=$PATH:/Volumes/crosstools/xtools/x86_64-unknown-linux-musl/bin
, and set
$TOOLPREFIX, before running make.
Next, run make qemu
, and you should be dropped into an xv6 shell! it will look
something like this:
Next time we are going to step through the boot process, for those who are interested, the files we are going to be peeking at are Makfile
, bootmain.c
, and bootasm.S
.