Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kapitel 19: Unsafe Rust und FFI – Systemnahe Integration und Speichersicherheit

Die Entwicklung von Betriebssystem-Kerneln, Treibern oder die Einbindung bestehender C-Bibliotheken erfordert fortgeschrittene Techniken von Unsafe Rust. In diesem Abschnitt betrachten wir die Details der fünf unsicheren Superkräfte sowie die Fremdsprachen-Schnittstelle (FFI).


1. Lernziele – Das wirst du heute lernen

  • Die 5 Superkräfte beherrschen: Sie setzen alle Aspekte von unsafe sicher ein.
  • Globale Zustände verwalten: Sie verstehen die Risiken von static mut und kennen sichere Alternativen.
  • Unions einsetzen: Sie verwenden überlappende Speicherbereiche für systemnahe Protokolle.
  • C-Funktionen aufrufen (FFI): Sie binden externe Bibliotheken über extern "C" ein.
  • Rust für C bereitstellen: Sie exportieren Funktionen mittels #[no_mangle].
  • Der Move-Fallstrick bei Zeigern: Sie verhindern hängende Zeiger bei Datenverschiebungen.

2. Die fünf Superkräfte im Detail

1. Rohe Zeiger dereferenzieren

Wie im Anfänger-Teil gezeigt, greifen Sie über *const T und *mut T direkt auf Speicheradressen zu.

2. Unsichere Funktionen aufrufen

Eine Funktion wird mit unsafe fn deklariert, wenn der Aufrufer bestimmte Vorbedingungen (Invarianten) einhalten muss, die der Compiler nicht prüfen kann:

#![allow(unused)]
fn main() {
/// # Sicherheit
/// Der Zeiger `ptr` darf nicht null sein und muss auf einen gültigen i32 zeigen.
pub unsafe fn absolut_unsicher(ptr: *const i32) -> i32 {
    *ptr
}
}

3. Unsichere Traits implementieren

Ein Trait ist unsicher (unsafe trait), wenn die Implementierung Garantien geben muss, auf die sich sicherer Code blind verlässt. Beispiel:

#![allow(unused)]
fn main() {
unsafe trait Threadsicher {}
struct MeinTyp;
unsafe impl Threadsicher for MeinTyp {}
}

4. Globale veränderliche Variablen (static mut)

Globale Variablen sind in Rust standardmäßig unveränderlich. Möchten wir sie zur Laufzeit modifizieren, müssen wir sie als static mut deklarieren. Da dies in Multithreading-Umgebungen zu Datenrennen führen kann, ist jeder Lese- und Schreibzugriff darauf unsafe:

#![allow(unused)]
fn main() {
static mut ANZAHL: u32 = 0;

fn zaehlen() {
    unsafe {
        ANZAHL += 1;
    }
}
}

5. Auf Felder einer union zugreifen

Eine union lässt alle Felder an derselben Speicheradresse beginnen. Da Rust beim Lesen nicht weiß, welcher Typ gerade aktiv ist, ist der Lesezugriff stets unsafe:

#![allow(unused)]
fn main() {
#[repr(C)]
union Daten {
    zahl: u32,
    byte: u8,
}
}

3. FFI: Fremdsprachen-Schnittstelle (Foreign Function Interface)

C-Bibliotheken aus Rust aufrufen

Wir deklarieren externe Signaturen in einem extern "C"-Block. Jeder Aufruf dieser Funktionen ist unsafe:

// Deklaration der C-Funktion
extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        // Aufruf erfordert unsafe
        let positiv = abs(-10);
        println!("Absolut: {}", positiv);
    }
}

Rust für C bereitstellen

Damit C-Programme eine Rust-Bibliothek aufrufen können, müssen wir Name Mangling (Namensverzerrung des Compilers) verhindern und das C-ABI deklarieren:

#![allow(unused)]
fn main() {
// #[no_mangle] zwingt den Compiler, den Namen exakt so in der Symboltabelle zu lassen.
#[no_mangle]
pub extern "C" fn addiere_in_rust(a: i32, b: i32) -> i32 {
    a + b
}
}

4. Der Move-Fallstrick: Hängende Zeiger (Dangling Pointers)

Ein häufiger Fehler bei der Arbeit mit rohen Zeigern entsteht durch Rusts Verschiebe-Semantik. Wenn eine Variable im Speicher verschoben (moved) oder deallokiert wird, zeigt ein zuvor erstellter roher Zeiger auf eine ungültige Adresse:

fn main() {
    let zeiger: *const String;
    {
        let text = String::from("Hallo");
        zeiger = &text as *const String;
    } // 'text' wird hier gelöscht!
    
    // GEFAHR! 'zeiger' zeigt auf freigegebenen Speicher (Use After Free).
    // Das Lesen führt zu undefiniertem Verhalten!
    // unsafe { println!("{}", *zeiger); }
}