201 lines
6.7 KiB
Markdown
201 lines
6.7 KiB
Markdown
layout: post
|
|
title: "Catching Up"
|
|
subtitle: "with a new toolchain"
|
|
tags: [osdev]
|
|
|
|
Well... I've been bad at updating the blog on my process again...
|
|
|
|
Last time I missed updating, I made a branch from an earlier git commit,
|
|
and threw away a bit of work and messy code rather than trying to catch
|
|
up.
|
|
|
|
This time, however, I'm actually quite happy that I didn't write too
|
|
much, because I've recently changed a lot of what I would have written
|
|
about.
|
|
|
|
So instead, I'll describe one of my most recent changes here - a new
|
|
cross compiler and a newlib port - and then in subsequent posts go over
|
|
chosen parts of the other things I've done.
|
|
|
|
So let's jump into it.
|
|
|
|
### Why a cross compiler
|
|
The be-or-not-to-be of cross compilers has been discussed at
|
|
length and it seems every time someone posts a description
|
|
on how to build one they are told that you don't need one if
|
|
you know how to use your compiler or whatever. For examples,
|
|
read
|
|
[the osdev wiki - Why do I need a Cross Compiler? talk page](http://wiki.osdev.org/Talk:Why_do_I_need_a_Cross_Compiler%3F)
|
|
(I don't recommend it if you're in a bad mood).
|
|
|
|
Well, I'm developing on OSX but have chosen to build an X86 kernel
|
|
in elf format due to the availability of documentation, support and
|
|
expertize. That's reason enough for a cross compiler build of binutils
|
|
at least.
|
|
|
|
Clang offers excellent support for cross compilation out of the box, so
|
|
I didn't use to have a need for a gcc cross compiler as illustrated in
|
|
[this post](/blog/2013/02/New-Environment/). As it turns out, however,
|
|
compiling newlib with that setup is rather annoying.
|
|
|
|
I'm also a fan of clean makefiles. Take a look at this:
|
|
|
|
:::make
|
|
VPATH := ../src
|
|
|
|
CC := i586-pc-myos-gcc
|
|
|
|
TARGETS := $(shell find ../src -name "*.c")
|
|
TARGETS := $(notdir $(TARGETS))
|
|
TARGETS := $(patsubst %.c, %, $(TARGETS))
|
|
|
|
all: $(TARGETS)
|
|
|
|
clean:
|
|
-rm $(TARGETS) 2>/dev/null
|
|
|
|
That's the makefile for the entire `/bin` directory in my os.
|
|
|
|
So let's move on...
|
|
|
|
### Building newlib and gcc
|
|
For my toolchain build, I followed the tutorial at
|
|
[the osdev wiki](http://wiki.osdev.org/OS_Specific_Toolchain) with a few
|
|
changes. I chose to build everything inside the scope of
|
|
[Homebrew](http://brew.sh) to keep track of all files and eventually
|
|
make a formula for it. So after applying the patches described in the
|
|
post (I even kept the name `i586-pc-myos` since I don't have a working
|
|
name for my kernel besides an iteration number...) I did
|
|
|
|
:::bash
|
|
$ export TARGET=i586-pc-myos
|
|
$ export PREFIX=/usr/local/Cellar/osdev/1.0
|
|
# Configure, build and install binutils
|
|
$ brew link osdev
|
|
# Configure, build and install gcc and libgcc
|
|
$ brew unlink osdev
|
|
$ brew link osdev
|
|
|
|
And that prepared me for building newlib.
|
|
|
|
### Building newlib
|
|
Now this was an adventure...
|
|
|
|
Newlib uses a directory structure that hasn't been supported by
|
|
automake and autoconf since a couple of versions. More specifically,
|
|
you need automake version 1.12 or earlier and autoconf version 2.64.
|
|
Unfortunately, those versions are not available through Homebrew, so ...
|
|
|
|
:::bash
|
|
$ curl -O http://ftp.gnu.org/gnu/automake/automake-1.12.tar.gz
|
|
$ tar -zxf automake-1.12.tar.gz
|
|
$ mkdir -p build-automake
|
|
$ pushd build-automake
|
|
$ ../automake-1.12/configure --prefix=/usr/local/Cellar/automake/1.12
|
|
$ make all -j
|
|
$ make install
|
|
$ popd
|
|
$ curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.64.tar.gz
|
|
$ tar -zxf autoconf-2.64.tar.gz
|
|
$ pushd build-autoconf
|
|
$ ../autoconf-2.64/configure --prefix=/usr/local/Cellar/autoconf/2.64
|
|
$ make all -j
|
|
$ make install
|
|
$ popd
|
|
$ brew switch automake 1.12
|
|
$ brew switch autoconf 2.64
|
|
|
|
Those last two lines tells Homebrew that you want to use those specific
|
|
versions for now.
|
|
|
|
Now for the neat part. I followed the wiki post and used the
|
|
[syscall interface](/blog/2013/06/System-Calls) i've described earlier
|
|
but I also wrapped `crt0.S` and `syscalls.c` in
|
|
|
|
:::c
|
|
#ifndef KERNEL_MODE
|
|
...
|
|
#endif
|
|
|
|
Then I built it all through
|
|
|
|
:::bash
|
|
$ pushd build-newlib
|
|
$ ../newlib/configure --target=$TARGET --prefix=$PREFIX
|
|
$ export CPPFLAGS_FOR_TARGET=-DKERNEL_MODE
|
|
$ make -j
|
|
$ make install
|
|
$ mv $PREFIX/$TARGET/lib/libc.a $PREFIX/$TARGET/lib/libkernel.a
|
|
$ rm -rf *
|
|
$ ../newlib/configure --target=$TARGET --prefix=$PREFIX
|
|
$ export CPPFLAGS_FOR_TARGET=
|
|
$ make -j
|
|
$ make install
|
|
$ popd
|
|
|
|
This gives me two versions of the newlib c library. One with all syscalls
|
|
defined and one without. The latter is suitable for linking into the
|
|
kernel and allows me to use a standard C library with things like
|
|
`sprintf` and even a ready made `malloc`.
|
|
|
|
"But wait," you say. "Doesn't malloc make an sbrk() syscall? You can't
|
|
make a syscall from inside the kernel, can you?"
|
|
|
|
Well. Remember [my last post](/blog/2013/06/System-Calls) where I said
|
|
that keeping versions of every syscall function inside the kernel would
|
|
turn out to have some really cool advantages? Here they are.
|
|
|
|
I need an `sbrk` anyway, so all I have to do differently is make it
|
|
check whether the request came from a user process or from the kernel
|
|
and handle them a slight bit differently (i.e. make sure the `brk` is
|
|
above `0xC0000000` for kernel and below for user mode...).
|
|
|
|
Similar checks can be made for all syscalls and some doesn't actually
|
|
need any changing at all.
|
|
|
|
Needless to say, this revelation changed my kernel structure
|
|
quite a bit and I entirely threw away my own
|
|
[kernel heap manager](/blog/2012/07/Memory-Heap).
|
|
|
|
### Using this
|
|
Having the cross compiler toolchain and library setup allows me to
|
|
compile the kernel using a very simple makefile.
|
|
|
|
Somewhat simplified:
|
|
|
|
:::make
|
|
OBJECTS := $(shell find . -name "*.S")
|
|
OBJECTS += $(shell find . -name "*.c")
|
|
OBJECTS := $(OBJECTS:%.S=%.o)
|
|
OBJECTS := $(OBJECTS:%.c=%.o)
|
|
|
|
CC := i586-pc-myos-gcc
|
|
LDFLAGS := -nostdlib -T linkfile.ld
|
|
LDLIBS := -lkernel
|
|
|
|
kernel: $(OBJECTS)
|
|
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
|
|
|
and everything else is taken care of by the default rules of gnu make,
|
|
including preprocessing and assembling .S files.
|
|
|
|
For executables running under my operating system it's even easier
|
|
|
|
:::make
|
|
CC := i586-pc-myos-gcc
|
|
|
|
That's all.
|
|
|
|
### GAS
|
|
On a final note, the automatic rules for building object files out of .S
|
|
files runs them through `CC`, which means they are assembled by
|
|
`i586-pc-myos-as` which uses AT&T syntax rather than NASM which uses
|
|
Intel syntax. I actually converted all my assembly code to AT&T syntax,
|
|
but you might want to use the `.intel_syntax` directive instead.
|
|
|
|
|
|
### Git
|
|
The methods described in this post has been implemented in the directory
|
|
`toolchain` of git commit
|
|
[f09bd57b8e](https://github.com/thomasloven/os5/tree/f09bd57b8eb8c27607dab8250574c8eaed2939a0/toolchain).
|