Rust auf Nicht-Standard-Plattformen
Nachdem ich im letzen Teil schon wieder eine Bibliothek per Hand einpflegen musste, hatte ich beschlossen, auf das Rust-eigene Management-Tool cargo
umzusteigen:
Schließlich ist die Einbindung von externen Bibliotheken eine der Stärken von Rust. Dem gegenüber stehen für das ɒiʜꟼOS-Projekt zwei Nachteile, die die Nutzung von
cargo
erschweren:
- Unsere Zielplattform wird nicht unterstützt, damit auch nicht die entsprechenden Bibliotheken
- Das Endprodukt (Kernel-Image) ist „untypisch“. Seine Erstellung verlangt Post-Linker-Aktionen, die ebenfalls nicht unterstützt werden.
Für beide Probleme gibt es (mindestens) eine Lösung.
Xargo
Zur Lösung des ersten Problems gibt es xargo
, eine cargo
-Variante für Cross-Umgebungen. Diese übersetzt automatisch die Core-Bibliothek und bei Bedarf auch andere Teile der Standard-Bibliothek und „schenkt“ dem Compiler die richtige Tool-Chain, indem rootdir
entsprechend gesetzt wird. Man kann sich damit auch angepasste Versionen der Standard-Bibliothek erstellen – mal sehen, ob ich das später brauchen werde.
Die Installation geht diesmal nicht über Homebrew, sondern schnell und reibungslos über cargo
:
$cargo install xargo
Cargo
/xargo
gehen von einem bestimmten Directory-Layout und der Existenz von (mindestens) einer Konfigurationsdatei cargo.toml aus, die das sogenannte
„Manifest“ enthält. Dort kann man auch benötigte Bibliotheken einbinden, die dann von cargo
automatisch geladen und übersetzt werden. Wir wollten ja die
compiler-buildins-Bibliothek nutzen. Dies geschieht im cargo.toml für unseren Kernel im Abschnitt [dependencies]
und sieht so aus:
[package]
name = "aihPOS"
version = "0.0.2"
authors = ["Matthias Werner <mwerner@informatik.tu-chemnitz.de>"]
publish = false
[dependencies]
compiler_builtins = { git = "https://github.com/rust-lang-nursery/compiler-builtins", features = ["mem"] }
compiler_error = "0.1.1"
[profile.dev]
panic = "abort"
lto = false
opt-level = 1
[profile.release]
panic = "abort"
lto = false
opt-level = 3
Diese Datei muss sich im Wurzelverzeichnis eines Projekts oder Subprojekts befinden. Die Quelldateien sind dann in einem Unterverzeichnis src/, das weitere
Verzeichnisse (mit ggf. eigenen cargo.toml-Dateien) befinden können. Beim Start einer Übersetzung mit cargo build
wird ein Verzeichnis target angelegt, in dem
übersetzte Dateien etc. angelegt werden.
Außer der compiler_buildins-Bibliothek laden wir auch eine andere
Bibliothek: compiler_error, die etwas ähnliches wie C‘s
#error
-Direktive zur Verfügung stellt.
Soweit es nicht anders angegeben – wie z.B. bei den
compiler_buildins – wird nach Bibliotheken auf
crates.io gesucht.
Neben dem Manifest gibt es optional noch eine Konfigurationsdatei, ebenfalls im TOML-Format. Sie heißt config und befindet sich in einem Unterverzeichnis .cargo/. Wir brauchen sie vor allem, weil die Erstellung des Kernels Compiler- bzw. Linkeroptionen benötigt, die nicht Standard sind:
[build]
traget= "arm-none-eabihf"
[target.arm-none-eabihf]
linker = "arm-none-eabi-gcc"
rustflags = [
"-C", "code-model=kernel",
"-C", "link-arg=-nostartfiles",
"-C", "link-arg=-nostdlib",
"-C", "link-arg=-Tlayout.ld",
"-C", "link-arg=-mfloat-abi=hard",
"-C", "link-arg=-ffreestanding",
]
Die Trennung der Steuerung von Compiler-Optionen (in den [profile.*]
-Abschnitten in
Post-Linker Aktionen
Wenn man sich das bisherige Makefile anschaut, dann sieht man, dass der Building-Prozess nicht – wie sonst meist üblich – mit dem Linken endet. Dies ist
in cargo
nicht vorgesehen. Zwar kennt cargo
die Möglichkeit, mit Hilfe eines Build-Scriptes Schritte vor der Übersetzung auszuführen; etwas ähnliches für danach
wird zwar in unter den Entwicklern diskutiert, wurde aber bisher nicht umgesetzt. Man kann zwar den Linker (in der Target-Datei) umdefinieren, aber ich fürchte, dass eine
solche Lösung schnell unübersichtlich wird. Es ein häufig genutzter und sogar empfohlener1 Ansatz ist daher, cargo
wiederum von einem Makefile
aufrufen lassen. Das
würde aber dazu führen, dass Abhängigkeiten z.T. doppelt gepflegt werden müssen. Wenn cargo
, dann richtig.
Glücklicherweise ist cargo
leicht erweiterbar: Wenn cargo
(oder xargo
) mit einem unbekannten Befehl gerufen wird, sucht es im Standardpfad nach einem Programm
cargo-
. Dass mache ich mir zunutze und schreibe ein kleines bash
-Skript cargo-kernel
, das erst xargo
aufruft und dann noch den „Rest“ erledigt:
shift
PROFILE=debug
TARGET=
for opt in "$@"; do
case $opt in
--release)
PROFILE=release
;;
--target=*)
TARGET=`echo $opt | cut -f2 -d'='`
esac
done
export RUST_TARGET_PATH=`pwd`
#echo RUST_TARGET_PATH = $RUST_TARGET_PATH
BINPATH=./target/$TARGET/$PROFILE
xargo build $@
PKG=`xargo pkgid -q | cut -d# -f2 | cut -d: -f1`
if [ -f $BINPATH/$PKG ]; then
echo "Building kernel"
arm-none-eabi-objcopy $BINPATH/$PKG -O binary $BINPATH/kernel.img
echo "Generate assembler dump"
arm-none-eabi-objdump -D $BINPATH/$PKG > $BINPATH/$PKG.list
fi
Diese Skript muss irgendwo im Ausführungsfad abgelegt werden. Dann kann man die Übersetzung im Rust-Stil anstoßen:
$cargo kernel –target=arm-none-eabihf
Compiling core v0.0.0 (file:///Users/mwerner/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/src/rust/src/libcore)
Finished release [optimized] target(s) in 22.87 secs
Compiling compiler_builtins v0.1.0 (https://github.com/rust-lang-nursery/compiler-builtins#f3ace110)
Compiling aihPOS v0.0.2 (file:///Users/mwerner/Development/aihPOS/aih_pos/kernel)
warning: crate aihPOS
should have a snake case name such as aih_pos
|
= note: #[warn(non_snake_case)] on by default
Finished dev [optimized + debuginfo] target(s) in 4.8 secs
Man beachte, dass immer noch cargo
und nicht xargo
aufgerufen werden muss. Zwar würde auch xargo
das Shellskript finden und ausführen, jedoch hat cargo
/xargo
einen Lock, der die nebenläufige Ausführung blockiert. xargo kernel
würde also hängen bleiben.
Diskussion
Bin ich nun vollständig in der Rust-Welt angekommen? Das hängt von der Betrachtung ab. Ich kann auf make
verzichten und alles, was ich mit make
gemacht habe nun mit
Rust-Mitteln tun. Andererseits kennt cargo
noch viele Befehle – wie z.B. für das Bauen von Tests und Dokumentationen oder die Arbeit mit Repositories – die noch nicht
berücksichtigt sind. Einiges könnte einfach so funktionieren, aber es besteht dafür keine Garantie.
GitHub
Wenn ich schon bei einer Reorganisation bin, gehe ich noch einen Schritt weiter: Der alte JTAG-Kernel (siehe Teil 3) kommt in ein
eigenes Verzeichnis, so dass er noch immer mit make
übersetzt werden kann. Ich nutze ihn als Boot-Kernel, so dass ich Änderungen im Entwicklungskernel schnell über das
JTAG-Interface laden und testen kann.
Außerdem habe ich ein GitHub-Projekt für ɒiʜꟼOS aufgesetzt. Dort kann der komplette Quellcode abgerufen werden.
-
Die cargo-Entwickler betonen immer wieder, dass cargo kein komplettes Build-Management-Tool ist und auch nicht sein will. ↩
Kommentare