EFI und MinGW inline API

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

1void* malloc(size_t size);
2int printf(const char * format, ...);
3int getchar(void);
4int putchar(int character);
5...

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:

 1int sprintf(char* __stream, const char* __format, ...)
 2{
 3  int __retval;
 4  __builtin_va_list __local_argv;
 5  __builtin_va_start(__local_argv, __format);
 6  __retval = __mingw_vsprintf(__stream, __format, __local_argv);
 7  __builtin_va_end(__local_argv);
 8  return __retval;
 9}

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 …

📧 📋 🐘 | 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!