====== Lyx – Foreign Function Interface (FFI) ====== Das **Foreign Function Interface** ruft C-Funktionen aus Lyx auf. Die Grundlage ist die C-ABI der Zielplattform: System V AMD64 auf Linux/macOS, AAPCS64 auf ARM64. > **Philosophie:** Die Units in ''std/'' und ''data/'' kommen ohne externe C-Bibliotheken aus. FFI ist für Fälle gedacht, in denen eine vorhandene C-Bibliothek genutzt werden soll. FFI ist in Lyx **kein offenes Tor**: Der Compiler klassifiziert jedes Symbol und weist gefährliche Aufrufe ab, bevor sie in den Build gelangen. Diese Seite beschreibt beides — die Deklarationsform und das Sicherheitsmodell dahinter. → [[lyx_-_programmiersprache:sprache:capabilities|Capabilities (LCBS)]] · [[lyx_-_programmiersprache:sprache:attributes-pragmas|Attribute & Pragmas]] · [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]] ---- ===== 1. Externe Funktionen deklarieren ===== Eine externe Funktion wird mit dem Schlüsselwort ''extern'' deklariert — **ein Attribut ''@extern'' gibt es nicht**. Die ''link''-Klausel nennt die Bibliothek und ist Pflicht. @capabilities([system.exit, system.memory.heap]) import std.io; extern fn strlen(s: pchar): int64 link "libc.so.6"; fn main(): int64 { PrintLn(IntToStr(strlen("hallo"))); // 5 return 0; } * Kein Funktionskörper — nur die Signatur, abgeschlossen mit '';'' * Die Bibliothek wird mit **vollständigem Dateinamen** angegeben: ''"libc.so.6"'', nicht ''"c"'' * Aufrufkonvention: System V AMD64 (Linux/macOS), AAPCS64 (ARM64) > **Die ''link''-Klausel ist seit lyxc 1.0.17C Pflicht** (#1179). Fehlt sie, bricht schon der Parser ab: ''extern fn: die link-Klausel fehlt''. Bis 1.0.16x übersetzte die Deklaration auch ohne, das Symbol wurde nie gebunden und der Aufruf lieferte still ''0'' — bei C-Funktionen ein plausibler Wert, der Fehler fiel damit kaum auf. Die eigene Standardbibliothek trug 33 solcher Deklarationen (''std.time.Now()'' gab 0, ''env_get'' immer ''""'', ''abort()'' beendete nichts); alle sind ersetzt. > > Ein **falscher** Bibliotheksname wird weiterhin nicht zur Übersetzungszeit gemeldet — den vollständigen Dateinamen angeben (''"libc.so.6"'', nicht ''"c"'') und einen neuen FFI-Aufruf einmal gegen einen bekannten Wert prüfen. ---- ===== 2. Das Sicherheitsmodell: fünf Klassen ===== Der Compiler ordnet jedes ''extern''-Symbol einer Klasse zu und entscheidet danach, ob und unter welcher Bedingung es zugelassen wird. ^ Klasse ^ Bedeutung ^ Bedingung ^ | **0 – Safe** | Reine Berechnung oder lesender Zugriff ohne Rechteausweitung | ohne weitere Angabe zugelassen | | **1 – OS-Ressource** | Datei-, Netz-, Umgebungs- oder Speicherzugriff | ''@capabilities([…])'' erforderlich | | **2 – Prozess** | Prozesssteuerung und Rechteänderung | ''@capabilities([…])'' erforderlich | | **3 – Verboten** | Puffer-Überlauf-anfällig oder Kommando-Ausführung | **immer abgewiesen** | | **4 – Unbekannt** | Nicht klassifiziertes Symbol | in Nutzercode abgewiesen, in ''std.*''-Units zugelassen | ==== Klasse 0 — ohne Capability nutzbar ==== ''strlen'' · ''strlcpy'' · ''strlcat'' · ''snprintf'' · ''vsnprintf'' · ''memcpy'' · ''memmove'' · ''memset'' · ''memcmp'' · ''malloc'' · ''free'' · ''realloc'' · ''calloc'' · ''time'' · ''clock_gettime'' ==== Klasse 1 — OS-Zugriff ==== ''open'' · ''close'' · ''read'' · ''write'' · ''fopen'' · ''fclose'' · ''fread'' · ''fwrite'' · ''socket'' · ''bind'' · ''listen'' · ''connect'' · ''getenv'' · ''setenv'' · ''exit'' · ''abort'' · ''mmap'' · ''munmap'' · ''mprotect'' · ''mlock'' · ''munlock'' · die ''strncpy''/''strncat''-Familie ==== Klasse 2 — Prozesssteuerung ==== ''fork'' · ''kill'' · ''wait'' · ''waitpid'' · ''clone'' · ''prctl'' · ''unshare'' · ''setuid'' · ''setgid'' · ''setresuid'' · ''setresgid'' ==== Klasse 3 — immer abgewiesen ==== ''gets'' · ''system'' · ''popen'' · ''sprintf'' · ''vsprintf'' · ''strcpy'' · ''strcat'' · ''wcscpy'' · ''wcscat'' · ''dlopen'' · die gesamte ''exec''-Familie (''execve'', ''execvp'', ''execl'', …) sema error: extern fn in FFI-Blacklist (Klasse 3) Diese Sperre lässt sich **nicht** durch eine Capability aufheben — sie gilt unbedingt. ==== Klasse 4 — das unbekannte Symbol ==== Jedes Symbol, das der Compiler nicht in einer der vier Listen findet, fällt hierher. **Das ist der Regelfall**, sobald eine fremde Bibliothek eingebunden wird: ''sqrtf'', ''png_create_read_struct'', ''curl_easy_init'' — nichts davon steht in einer Liste. sema error (line 6): extern fn: unbekanntes FFI-Symbol erfordert eine Capability-Angabe (FFI-Sandbox Fail-Closed) Drei Dinge sind daran zu wissen, und alle drei kosten sonst Zeit: - **Es ist keine bestimmte Capability gemeint.** Die Angabe ist eine **Absichtserklärung** — jede gültige Liste hebt die Sperre auf. Der Compiler kann das Symbol nicht einordnen und verlangt deshalb, dass der Autor die Verantwortung sichtbar übernimmt. - **Die Annotation gehört unmittelbar vor die Deklaration**, nicht an den Unit-Kopf. Auf Modulebene wirkt sie für diesen Zweck nicht. - Für genau eine Capability gibt es die Kurzform ''@cap(…)''. unit main; import std.io; import std.string; @cap(system.exit) extern fn sqrtf(x: f32): f32 link "libm.so.6"; fn main(): int64 { var a: f32 := 16.0; var r: f32 := sqrtf(a); Print("sqrtf(16) = "); PrintF64(r as f64); return 0; } sqrtf(16) = 4.000000 > **In ''std.*''-Units gilt die Sperre nicht.** Der Bindungs-Layer der Standardbibliothek zählt zur vertrauenswürdigen Basis und darf unbekannte Symbole ohne Angabe deklarieren. Wer eigenen Code gegen eine Bibliothek schreibt, ist davon **nicht** erfasst — dort greift Fail-Closed. > > Der Sinn: ohne diese Sperre wäre jedes beliebige Symbol als „Klasse 0" durchgewunken worden, und die Capability-Prüfung ließe sich vollständig umgehen. ---- ===== 3. Weitere Prüfungen ===== ==== Format-String-Funktionen ==== ''printf'', ''fprintf'', ''scanf'', ''sscanf'' und ihre ''v''-Varianten bilden eine eigene Gruppe. Sie sind nur mit der Capability ''system.unsafe.format_string'' zugelassen, sonst gelten sie als Klasse 3: @capabilities([system.exit, system.memory.heap, system.unsafe.format_string]) ==== Tarnung über die link-Angabe ==== Der Bibliotheks- und Symbolname in der ''link''-Klausel wird mitgeprüft. Ein verbotenes Symbol lässt sich also nicht unter anderem Funktionsnamen einschleusen: extern fn harmlos(cmd: pchar): int64 link "system"; sema error: extern fn: link-Zielsymbol in FFI-Blacklist ==== Signaturprüfung ==== Eine nicht klassifizierte Funktion mit **zwei oder mehr ''pchar''-Parametern** wird abgewiesen — das ist die typische Form von ''strcpy''-artigen Funktionen ohne Längenbegrenzung: sema error: extern fn: ≥2 pchar-Parameter ohne Größenlimit > **Ein Längenparameter hebt die Sperre seit lyxc 1.0.17K auf** (#1180): ''%%probe(a: pchar, b: pchar, n: int64)%%'' wird angenommen, ''%%probe2(a: pchar, b: pchar)%%'' weiterhin abgewiesen (''≥2 pchar-Parameter ohne Größenlimit (Klasse 3 via Signatur)''). Eigene C-Funktionen mit zwei Strings sind damit einbindbar, sobald die Signatur eine Längenangabe trägt; der frühere Ausweg über ''int64''-Adressen ist nicht mehr nötig. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1180|Issue #1180]]) ---- ===== 4. Typ-Zuordnung Lyx ↔ C ===== ^ C-Typ ^ Lyx-Typ ^ Anmerkung ^ | ''int'' | ''int32'' | | | ''long'', ''int64_t'' | ''int64'' | | | ''unsigned char'' | ''uint8'' | | | ''size_t'', ''ssize_t'' | ''int64'' | auf allen Zielplattformen 64 Bit | | ''double'' | ''f64'' | | | ''float'' | ''f32'' | seit der Behebung von [[https://github.com/SEOLizer/LyX-Compiler/issues/1566|#1566]] nutzbar | | ''char*'' | ''pchar'' | nullterminiert | | ''void*'' | ''int64'' | Adressen sind Ganzzahlen — [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]] | | ''void'' | ''void'' | oder Rückgabetyp weglassen | Einen Zeigertyp gibt es in Lyx nicht: Jeder ''void*''- oder Struct-Zeiger wird als ''int64'' übergeben, der Zugriff läuft über ''peek''/''poke''. ==== Breite der Rückgabe angeben ==== ''extern fn'' kann die Breite des Rückgabewerts ausdrücken: ''int8'', ''int16'', ''int32'' und die vorzeichenlosen Gegenstücke. Der Codegen erweitert dann vorzeichenrichtig (''movsx''/''movsxd'') beziehungsweise füllt mit Nullen (''movzx'') — [[https://github.com/SEOLizer/LyX-Compiler/issues/1607|#1607]]. @cap(system.exit) extern fn atoi(s: pchar): int32 link "libc.so.6"; fn main(): int64 { Print("atoi(\"-1\") = "); PrintLn(IntToStr(atoi("-1"c))); return 0; } atoi("-1") = -1 **Ohne Angabe bleibt alles wie bisher**: die Regel greift nur, wo sie dasteht, an bestehenden Deklarationen ändert sich nichts. Das ist zugleich die Falle — eine C-Funktion mit ''int''-Rückgabe, die als ''int64'' deklariert wird, liefert ''-1'' als ''4294967295''. Genau daran gab ''std.net.tls'' Fehlercodes falsch weiter; ''SSL_read'', ''SSL_write'', ''SSL_connect'' und Geschwister sind deshalb mit umgestellt. ==== Mehr als sechs Gleitkommaargumente ==== Nach der SysV-Konvention gehen die ersten **acht** Gleitkommawerte in ''xmm0''–''xmm7''; entsprechend nimmt eine ''extern fn'' bis zu acht ''f64''-Argumente ohne Stapel entgegen. Bis lyxc 1.1.3B lag die Schranke bei sechs, weil der FFI-Pfad die Werte aus den GP-Registern in die xmm-Register schob ([[https://github.com/SEOLizer/LyX-Compiler/issues/1640|#1640]], abgespalten aus [[https://github.com/SEOLizer/LyX-Compiler/issues/1486|#1486]]). Grafik-APIs wie OpenGL (''glOrtho'', ''glFrustum'', Matrixfunktionen) waren damit nicht ansprechbar. import std.io; @cap(ui.display) extern fn f8(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64, g: f64, h: f64): f64 link "./libargsum.so"; fn main(): int64 { var r: f64 := f8(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0); PrintF64(r); // 36.000000 return 0; } Ab dem **neunten** Gleitkommawert geht es über den Stapel; dort meldet der Compiler weiterhin, dass es nicht umgesetzt ist. ==== Mehr als sechs Argumente ==== Die ersten sechs Argumente liegen in Registern, alles Weitere auf dem Stapel. Bis lyxc 1.1.1C bekam ein ''extern fn'' ab dem **siebten** Argument Müll: die FFI-Ausrichtung (''and rsp, -16'') verschob den Stapel unmittelbar nach dem Ablegen der Argumente ([[https://github.com/SEOLizer/LyX-Compiler/issues/1620|#1620]]). Native Lyx-Aufrufe waren nie betroffen, weil sie ohne diese Ausrichtung auskommen. Behoben; Deklarationen mit sieben und mehr Argumenten sind wieder benutzbar. > **''float''-Schnittstellen sind wieder nutzbar** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1566|#1566]] behoben). Nachgemessen mit 1.0.21A: > > > sqrtf(16) als f32 = 4.000000 > sqrt(16) als f64 = 4.000000 > > > Frühere Fassungen dieser Seite rieten, ersatzweise die ''double''-Variante der C-Funktion zu nehmen — das ist nicht mehr nötig. > > Der gewöhnliche Cast zwischen ''f32'' und ''f64'' arbeitet ebenfalls korrekt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1511|#1511]]). @capabilities([system.exit, system.memory.heap]) import std.io; import std.alloc; extern fn memset(dest: int64, c: int32, n: int64): int64 link "libc.so.6"; fn main(): int64 { var puffer: int64 := alloc(16); memset(puffer, 65, 16); // mit 'A' füllen PrintLn(IntToStr(peek8(puffer))); // 65 free(puffer, 16); return 0; } ---- ===== 4a. Windows-DLLs (''%%--target=win64 --format=pe%%'') ===== Eine ''extern fn'' mit ''link "x.dll"'' landet seit lyxc 1.1.3E in der Import-Tabelle der erzeugten PE-Datei; Argumente werden nach der **Microsoft-x64-Konvention** übergeben. Vorher war von einer Windows-Binary aus keine einzige Win32-Funktion erreichbar: der Aufruf endete im page fault ([[https://github.com/SEOLizer/LyX-Compiler/issues/1671|#1671]]), Argumente kamen falsch an ([[https://github.com/SEOLizer/LyX-Compiler/issues/1674|#1674]]), und eine zweite DLL erbte den Namenszeiger der ersten ([[https://github.com/SEOLizer/LyX-Compiler/issues/1676|#1676]]). unit Main; import std.io; @cap(ui.display) extern fn lstrlenA(s: pchar): int64 link "kernel32.dll"; @cap(ui.display) extern fn GetSystemMetrics(i: int64): int64 link "user32.dll"; fn main(): int64 { PrintLn(lstrlenA("abcde")); // 5 PrintLn(GetSystemMetrics(0) > 0); // 1 return 0; } lyxc dll2.lyx --target=win64 --format=pe -o dll2.exe Zwei Punkte dazu: * Mehrere DLLs in einem Programm sind zulässig — jede Gruppe der Import-Tabelle trägt ihren eigenen Namenszeiger. * Umgebungsvariablen funktionieren unter Windows: ''EnvLookupRaw'' und alles, was darauf aufsetzt (''std.datetime'', ''std.os''), lesen den Windows-Umgebungsblock statt des SysV-Speicherbilds ([[https://github.com/SEOLizer/LyX-Compiler/issues/1677|#1677]]). ---- ===== 5. FFI im Safety-Umfeld ===== ^ Situation ^ Empfehlung ^ | DAL-A/B-Regelzyklus | Kein FFI — die WCET fremden Codes ist nicht nachweisbar | | Werkzeuge, Tests, Simulation | FFI zulässig, Capabilities eng fassen | | Vorhandene C-Bibliothek unvermeidbar | Aufrufe in einer eigenen Unit kapseln und dort per ''grant'' beschränken | Jede ''extern''-Deklaration erscheint im Sicherheits-Audit des Builds. Ein Programm ohne ''@capabilities'' erhält keinen seccomp-Filter — siehe [[lyx_-_programmiersprache:sprache:capabilities|Capabilities]]. ---- ===== 6. Was es nicht gibt ===== > Frühere Fassungen dieser Seite beschrieben mehrere Konstrukte, die der Compiler nicht kennt — jedes davon wird mit ''unbekanntes Attribut'' abgewiesen: > > * **''@extern''** — die Deklaration läuft über das Schlüsselwort ''extern'' > * **''@export''** — Lyx-Funktionen lassen sich derzeit nicht für C exportieren > * **''@stdcall''** — keine Wahl der Aufrufkonvention; es gilt die der Zielplattform > * **''@variadic''** — variadische C-Funktionen sind nicht deklarierbar > > Ebenfalls nicht vorhanden: Callback-Funktionen an C übergeben und dynamisches Nachladen zur Laufzeit (''dlopen'' steht auf der Klasse-3-Liste). ---- **Weiterführende Seiten:** * [[lyx_-_programmiersprache:sprache:capabilities|Capabilities — das Rechtemodell hinter FFI]] * [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher — Puffer als int64-Adressen]] * [[lyx_-_programmiersprache:sprache:attributes-pragmas|Attribute & Pragmas]] Letzte Aktualisierung: 2026-08-19 — acht Gleitkommaargumente ([[https://github.com/SEOLizer/LyX-Compiler/issues/1640|#1640]]) und der Abschnitt „Windows-DLLs" ([[https://github.com/SEOLizer/LyX-Compiler/issues/1671|#1671]], [[https://github.com/SEOLizer/LyX-Compiler/issues/1674|#1674]], [[https://github.com/SEOLizer/LyX-Compiler/issues/1676|#1676]], [[https://github.com/SEOLizer/LyX-Compiler/issues/1677|#1677]]) ergänzt; beide Beispiele mit ''lyxc 1.1.3I'' übersetzt und ausgeführt (das PE unter Wine). Vorherige Aktualisierung: 2026-08-18 — Breite der Rückgabe (''int8''/''int16''/''int32'' + unsigned, [[https://github.com/SEOLizer/LyX-Compiler/issues/1607|#1607]]) und die Stapelargumente ab dem siebten ([[https://github.com/SEOLizer/LyX-Compiler/issues/1620|#1620]]) ergänzt; Beispiel mit ''lyxc 1.1.2E'' übersetzt. Vorherige Aktualisierung: 2026-08-13 — Längenparameter hebt die ≥2-''pchar''-Sperre seit lyxc 1.0.17K auf (#1180). Codebeispiele geprüft: gegen **lyxc 1.2.5C** übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die ''pub fn''-Signaturen in ''aurum/std'' gehalten, 0 Abweichungen).