EFI und MinGW inline API
« | 17 Sep 2022 | »Es hätte so einfach sein können: Man implementiert einfach alle Funktionen der C-Standard Bibliothek selbst in einer statischen Bibliothek und linkt gegen diese. Schon hätte man C Programme direkt als EFI App laufen lassen können.
Aber nicht so mit MinGW … denn der hat keine “echte” C API.
Die klassischen Header wie stdlib.h
und stdio.h
deklarieren bei allen
Compilern die gleichen Funktionen wie
Die Implementierungen dieser Funktionen innerhalb der C-Laufzeitbibliothek,
die automatisch in alle C Programme injiziert wird, verweisen auf das
jeweilige Betriebssystem, also wie z.B.: ReadFile()
und WriteFile()
in
der WinAPI
oder die read()
und write()
Systemcalls
in Linux.
Hat man kein OS (wie z.B. für EFI Apps), lässt man seinen Code mit
/NODEFAULTLIB
unter MSVC
oder -ffreestanding -nostdlib
beim GCC
kompilieren und/oder linken, womit dann alle C-Standard-Funktionen als
“undefinierte Symbole” erkannt werden.
Dann implementiert man diese Funktionen selbst und leitet sie auf direkte
Hardware- oder Firmware Features um.
Und das funktioniert auch einigermaßen, wenn man ein paar Ungleichheiten
bei den C Headern ausbessert, wie der sporadische Einsatz von restrict
oder
const
bei Parametern. (Das lässt sich mit Makros leicht umsetzen).
MinGW nutzt inline C-Funktionen
Mein Konzept brach aber total zusammen, als ich den MinGW bemühte meine
Quellcodes zu übersetzen.
Denn der lieferte hunderte Zeilen, die wie folgt aussahen:
1/.../gnu-efi-stdc/src/stdio_impl.c:194:5: error: redefinition of ‘sprintf’ 2 194 | int sprintf(char* str, const char* format, ...) 3 | ^~~~~~~ 4/usr/share/mingw-w64/include/stdio.h:382:5: note: previous definition of ‘sprintf’ was here 5 382 | int sprintf (char *__stream, const char *__format, ...) 6 | ^~~~~~~
Meine Funktion hatte die gleiche Signatur wie die in stdio.h
doch soll nun
eine Redefinition
sein.
Und der Grund dafür befindet sich in Zeile 382
von stdio.h
, denn dort
steht:
MinGW definiert also sprintf
gar nicht als eine extern C
Funktion, sondern
nur als inline-Funktion im Header, die auf eine interne Funktion namens
__mingw_vsprintf
umleitet.
Und das gleiche passiert auch bei allen anderen I/O Funktionen.
Fazit
Tja … leider ist das erlaubt, denn ein “API” definiert nur, wie eine Funktion heißt und welche Parameter sie hat und nicht wie sie implementiert sein muss.
Mich stellt das jetzt vor die Herausforderung, ob ich nun für den MinGW
Spezialfälle wie __mingw_vsprintf
implementieren soll, oder versuchen soll,
stdio.h
und seine Freunde auf von mir erstellten Header umzuleiten.
Interessant finde ich nur, dass MSVC und der Standard-GCC (mit glibc) hier sehr “kompatibel” sind, während das GCC-Derivat MinGW ganz anders aufgebaut ist.
Leider zeigt sich hier, dass die MingGW-Hersteller nicht wirklich modular
gedacht haben. Denn leicht hätten sie diese inline Umleitungen auch in
(austauschbare) “extern C” Funktionen packen können um daraus ein echtes
“ABI” zu machen und um z.B: __mingw_vsprintf
dann inline einzubinden.
Das hätte performance-technisch dann keinen Unterschied gemacht und wäre mir jetzt sehr entgegengekommen.
Aber wie langweilig wäre die Welt ohne weitere Herausforderungen …