Visual C++-CRT ersetzen

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Visual C++-CRT ersetzen

Beitrag von Krishty »

Es gibt im Netz viel zu wenig Infos dazu, also steuere ich mal alles bei, was mir so bei /NODEFAULTLIB (ohne Microsoft-CRT kompilieren) begegnet.


1. Alle CRT-Ersatzfunktionen müssen in eine eigene .cpp ohne globale Optimierungen. Nicht aus kosmetischen Gründen, sondern weil viele CRT-Funktionen als Intrinsics realisiert sind:

  MyStruct x = { 0 };

ersetzt Visual C++ durch

  MyStruct x;
  memset(&x, 0, sizeof(x));


und der Optimizer optimiert bei kleinen structs den Funktionsaufruf weg und nutzt stattdessen direkt ein Nullregister oder so. Sowas ist nur möglich, wenn der Compiler das Verhalten einer Funktion fest eingebaut hat, und das passiert bei CRT-Funktionen oft. Wenn solche Intrinsics ersetzt werden sollen, bringt das den Optimizer durcheinander, darum kann man sie nur in einer gesonderten .cpp ohne globale Optimierung ersetzen und hat keinen Einfluss auf die Aufrufe, die geinlinet oder wegoptimiert werden.


2. new löst, selbst als Placement new, eine Referenz zu delete und type_info aus.
Sogar, wenn Exceptions und Run-Time Type Information deaktiviert sind (was mit Placement new legitimer Anwendungsfall ist). Darum muss man immer einen eigenen

  void __cdecl operator delete(void *, size_t)

zur Verfügung stellen, den man halt leer macht oder so. Fragt mich bitte nicht, wo der zweite Parameter herkommt. Das weiß wohl nur Microsofts Compiler-Team. Ebenso müsst ihr

  class type_info {

    ~type_info() { }

  };


zur Verfügung stellen, weil new aus irgendeinem Grund die Virtual Function Table von type_info braucht und die nunmal aus dem Destruktor besteht. Beachtet, dass die obige Version Speicher verliert, falls ihr type_info tatsächlich nutzt und auf Namen, Hash, o.ä. zugreift. In dem Fall müsst ihr einen vollwertigen Destruktor gemäß der Dokumentation irgendwo auf einer Kaffee-bekleckerten, vollgekritzelten College-Block-Rückseite aus der MSDN entwickeln. Im Debugger *irgendeines* Visual-C++-Projekts

  (type_info*)0

eingeben stellt euch ebenfalls das Layout zur Verfügung.


3. Dieses delete und type_info müssen in der selben .cpp definiert sein, in der ihr auch euer new definiert ist.
… oder alternativ in jeder .cpp, in der new vorkommt. Ihr erkennt das Muster dahinter, den Master Plan des Compilerbaus! … oder? Sonst kriegt ihr so schöne Meldungen wie

  error LNK2016: absolute symbol '@comp.id' used as target of REL32 relocation in section 8D

oder

  fatal error C1001: An internal error has occurred in the compiler.


4. Ihr dürft Run-Time Type nur deaktivieren, wenn type_info in der selben Datei wie new definiert ist.
Denn sonst werden eure Definitionen einfach ignoriert und es gibt

  error LNK2001: unresolved external symbol "const type_info::`vftable'"

Fragt nicht.


Mehr trage ich nach, wenn mir gerade danach ist.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von xq »

Bäh klingt das nervig. Ich bin grade echt froh, hauptsächlich GCC oder clang zu verwenden...
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Hast du denn mal einen Dump gemacht, wie viel von libc in deinen Programmen landet?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Biolunar
Establishment
Beiträge: 154
Registriert: 27.06.2005, 17:42
Alter Benutzername: dLoB

Re: Visual C++-CRT ersetzen

Beitrag von Biolunar »

Statisch kompiliertes Hello World in C ist mit gcc über 1MB groß… glibc lässt grüßen.
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von xq »

Krishty hat geschrieben:Hast du denn mal einen Dump gemacht, wie viel von libc in deinen Programmen landet?
Biolunar hat geschrieben:Statisch kompiliertes Hello World in C ist mit gcc über 1MB groß… glibc lässt grüßen.
Mir ging es eher um das Gefrickel mit den libcs, ich schreib grade an einem Kernel und muss damit auch meine libc selbst implementieren, habe aber bei weitem weniger Probleme damit als Krishty.
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Ah, okok. Ich war auch über die libc-Implementierung von Stack Probes gestolpert, und die ist, IIRC, tatsächlich einfacher. Microsofts _chkstk() arbeitet mit den Voraussetzungen
  1. keinen Stapelspeicher reservieren (alles nur in Registern machen)
  2. nur die Register rax, r10, r11 nutzen
und deshalb ist Assembler nötig. Die Gründe sind wahrscheinlich
  1. es muss im Fall eines Stack Overflows während _alloca() sauberes Unwinding ermöglichen
  2. es wird während des Funktionsprologs aufgerufen, es würde also keine gültige Kette von Stack Frames mehr existieren, sobald die Funktion an Stack oder aktiven Registern rumfummeln würde
Wie GCC das löst wäre interessant zu wissen (normaler Funktionsaufruf vor Initialisierung lokaler Variablen?), aber ich habe gerade keine Zeit, nachzusehen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Schon länger ein besonderes Sorgenkind ist für mich float-zu-String, also gcvt(), fcvt(), ftoa(), usw.

Ich habe mich jahrelang gesträubt, dtoa.c in meinen Code aufzunehmen, obwohl es der seit 30 Jahren auf Herz und Nieren getestete de-facto-Code ist, der in jedem Betriebssystem und jedem Compiler und jeder Runtime steckt, von PHP über Java bis C:
Exploring Binary: PHP Hangs On Numeric Value 2.2250738585072011e-308
Exploring Binary: Java Hangs When Converting 2.2250738585072012e-308

Zum einen habe ich es nicht selbst verfasst und würde dafür sicher auch Jahre brauchen; zum anderen ist es einfach gigantisch viel Code:
Stack Overflow: Why does “dtoa.c” contain so much code?
(Kurz: Weil es verflucht schwer ist, 2^64 Werte samt Denormalisierungen in eine andere Basis umzurechnen, wenn keine Variable, mit der man rechnet, mehr als 2^64 Werte speichern kann, und das dann auf jeder Architektur der Welt zum Laufen zu kriegen.)

Das sind für mich verdammt viele Gründe, lieber auf was zurückzugreifen, das eh schon in meinem Adressraum rumliegt … also habe ich angefangen, im Windows-Kernel nach Ersatz zu suchen. Ich bin ziemlich schnell fündig geworden:

User32.dll exportiert einen sprintf()-Klon!
Der ist zwar nur auf 1024 Zeichen limitiert, und hoffnungslos veraltet, aber für

  char buffer[349]; // längste mögliche Repräsentation einer double
  wsprintf(buffer, "%g", value);

wird es jawohl noch reichen, oder?
Nein.
Denn die Funktion ist so alt, dass sie keine Gleitkommazahlen unterstützt. Man kann nur Integer und Zeiger formatieren.

Jetzt suche ich weiter. Die Shell zeigt ja z.B. in Dateieigenschaften Gleitkommazahlen an; irgendwo muss Windows Konvertierungs-Code geladen haben …

Warum nicht einfach die CRT-DLL laden und mit GetProcAddress(crt, "_gcvt") reingreifen, fragt ihr? Sobald die CRT-DLL geladen wird, lädt automatisch auch der ganze Müll, den ich gerade erst aus meinen Programmen rausgeschmissen habe. Etliche Megabytes ANSI-zu-Unicode-Locale-Tabellen zum Beispiel. Und selbst wenn das dann geladen ist, stürzt die CRT gern mal ab, weil Thread-local Storage usw. nicht initialisiert wird, wenn man sie nachträglich lädt. (Den Code fügt der Compiler ja in den Einsprungspunkt der Programme ein, wenn man linkt.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

dtoa.c nutzt memcpy(), und das hat im Compiler eine ganz besondere Stellung.

Will man memcpy() ersetzen, kann man dafür nicht einfach eine Definition schreiben. Neeeeiiiiin. Denn: memcpy() ist ein Intrinsic, und Intrinsics kann man nicht überschreiben!

  error C2169: 'memcpy': intrinsic function, cannot be defined

Aber dafür gibt es ein wenig genutztes #pragma:

  #pragma function(memcpy)

Das teilt dem Compiler mit, dass er memcpy() ab jetzt wie eine normale Funktion zu behandeln hat, und wir können es dann definieren. Zunächst erhalten wir jedoch:

  warning C4164: 'memcpy': intrinsic function not declared

Das ist zwar nur eine Warnung, sorgt aber dafür, dass die Definition nicht anerkannt wird. Also deklarieren wir memcpy() und erhalten:

  extern "C" {

    void * __cdecl memcpy(void *, void const *, size_t); //
Deklaration aus formalen Gründen

#   pragma function(memcpy) // nicht als Intrinsic behandeln

    void * __cdecl memcpy(void * d, void const * s, size_t n) { // tatsächliche Definition
      __movsb(d, s, n); // seit ein, zwei CPU-Generationen die schnellste Art, Daten zu kopieren
      return d;
    }

  }


Wunderschön! memmove() ist einfacher: Definition hinklatschen und gut ist.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Natürlich merke ich erst jetzt, wo ich schon auf dtoa.c umgestiegen bin, dass es doch ein sprintf() im Windows-Kernel gibt: Und zwar direkt in ntdll.dll. Da ist es auch schon seit NT 3.51. Wie der gesamte Rest der CRT ebenfalls.

Ich bin eher zufällig drauf gestoßen, als ich diese ReactOS-News las.

Ist auch kein großes Ding, denn dieses sprintf() findet sich nicht in der offiziellen ntdll.lib, die man mit Visual Studio einbindet. Man müsste also entweder eine eigene Importbibliothek schreiben (Wartungsalptraum) oder die Funktion erst zur Laufzeit suchen (hässlich). Vielleicht bleibe ich dann doch lieber bei dtoa.c, da habe ich letztendlich die volle Kontrolle über das, was passiert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Umgestiegen auf Visual C++ 2017.

Mein x86-64-Code läuft problemlos – da gab es tatsächlich keine Breaking Changes.

Die x86-32-Version hat ein Problem:

  LIBCMT.lib(_ftol3_.obj) : error LNK2019: unresolved external symbol __except1 referenced in function __ftol3_except

Wenn man auf x86-32 double zu long castet, ruft Visual C++ die CRT-Funktion _ftol3_except() auf (ob die Konvertierung in Hardware oder Software durchgeführt werden kann, hängt von FPU-Nutzung, SSE-Verfügbarkeit und Exception-Einstellungen ab). Die führt die Konvertierung durch und ruft nötigenfalls _except1() auf (mit einem Unterstrich wegen C Calling Convention), um Exceptions à la NaN/Overflow/etc. zu schmeißen, falls FPU-Exceptions eingeschaltet sind (jedenfalls entnehme ich das der Wine-Kopie hier).

Das Ding ist: Visual C++ schluckt mein _except1() nicht. Ich habe keine Ahnung, woran’s liegt. Ich habe jetzt alle Calling Conventions und Parameterkombinationen durch, und es will einfach nicht gefunden werden. Fuck.

Ich nutze keine FPU-Exceptions, und unter Visual C++ 2015 hat’s noch funktioniert … ärgerlich :(

Nachtrag: Die Funktion muss in einer Übersetzungseinheit liegen, die ohne globale Optimierungen kompiliert wird. Bei allen anderen Intrinsics kommt eine entsprechende Fehlermeldung, nur bei _except1() nicht. Wahrscheinlich haben sie es nicht in die Compiler-interne Liste eingetragen. Jetzt ist jedenfalls alles bestens portiert.

  // Raises floating-point exceptions. Called after floating-point operations are emulated in software (e.g.
  // “double”-to-“long long“-conversion, which is not natively supported on 32-bit x86).
  double _except1(UInt4B, int, double, double result, UInt4B, void *) {
    return result;
  }
Zuletzt geändert von Krishty am 09.09.2018, 03:39, insgesamt 1-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
INe5xIlium(Freak5)
Beiträge: 32
Registriert: 29.07.2002, 11:43

Re: Visual C++-CRT ersetzen

Beitrag von INe5xIlium(Freak5) »

Früher in Visualstudio hatte ich etwas wie LIBCTINY.LIB oder so. Gibt es das eigentlich gar nicht mehr? Kleine Demonstrationsprogramme waren damit locker nur 8kb groß, selbst mit ein paar Bitmaps in den Ressourcen.
Ich habe vor ein paar Tagen mal gegoogled und dort die minicrt.lib gefunden, die es vor 7 Jahren wohl noch gab.

Mein Beweggrund waren die kleinen Dateigrößen und die MiniCRT hatt einen vor allen eigenartigen Dingen bewahrt. Man konnte einiges vielleicht nicht nutzen, aber solange man sehr einfachen Code geschrieben hat, ist auch nichts passiert. Wobei ich mir jetzt gar nicht sicher bin, ob die STL funktioniert hat. Der Heap hat nicht funktioniert.

Warum programmierst du ohne Standard Libraries?
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Sowas gibt es immernoch, aber Microsoft verändert die CRT von Release zu Release:
  • früher hat der Compiler die FPU für float-zu-int-Konvertierung genutzt; seit einigen Jahren braucht er dafür eine SSE-kompatible Funktion aus der CRT
  • früher gab es keinen Security Check (/GS), heute muss die CRT alle beteiligten Funktionen liefern
  • früher hat Visual Studio kein long long unterstützt, heute emuliert es 64-Bit-Arithmetik auf 32-Bit-Systemen mit der CRT
  • seit 2015 nutzt Visual C++ die Universal CRT aus Windows statt eine eigene mitzubringen
Alte Projekte wie Libctiny und minicrt werden deshalb unbrauchbar, wenn man sie nicht für jedes Visual C++-Release neu anpasst.

Was Verzicht auf Standardbibliotheken angeht:
  • ich will keine Abhängigkeiten
  • ich nutze keine Ausnahmebehandlung, aber dann darf man die STL nicht mehr benutzen
  • ich habe meine eigenen OS-Wrapper
  • weniger Bloat
  • Kompilierzeit (meine Projekte kompilieren größtenteils in unter einer Sekunde; mit STL verzehnfacht sich das locker)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
INe5xIlium(Freak5)
Beiträge: 32
Registriert: 29.07.2002, 11:43

Re: Visual C++-CRT ersetzen

Beitrag von INe5xIlium(Freak5) »

Krishty hat geschrieben: Alte Projekte wie Libctiny und minicrt werden deshalb unbrauchbar, wenn man sie nicht für jedes Visual C++-Release neu anpasst.
Genau das war der Grund. Ich wollte wieder etwas auf <20kb bringen, habe versucht das alte Projekt zu übersetzen und es hat nicht funktioniert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Visual C++-CRT ersetzen

Beitrag von Krishty »

Bei Handmade Hero gibt es einen ähnlichen Thread, der meinen um einiges ergänzt – z. B. mit Links zu funktionierenden Assembler-Versionen der fehlenden 64-Bit-Arithmetik in 32-Bit-Builds. Aber genießt ihn mit Vorsicht; es steht auch viel Halbwissen drin.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten