In VS2010 gibt der verwaltete Debugging-Assistent eine pInvokeStackImbalance-Ausnahme ( pInvokeStackImbalance MDA ), wenn Sie eine Funktion aufrufen, die die falsche Aufrufkonvention verwendet, weil Sie CallingConvention = Cdecl beim Aufrufen einer C-Bibliothek nicht angegeben haben. Z.B. du hast geschrieben
%Vor%statt
%Vor%und somit die Aufrufkonvention StdCall anstelle von Cdelc erhalten.
Wenn Sie dies beantworten, kennen Sie den Unterschied bereits, aber für andere Besucher dieses Threads: StdCall bedeutet, dass der Angerufene die Argumente vom Stapel löscht, während Cdecl bedeutet, dass der Aufrufer den Stapel bereinigt.
Wenn also die Aufrufkonvention in Ihrem C-Code falsch ist, wird Ihr Stack nicht aufgeräumt und Ihr Programm stürzt ab.
.NET-Programme scheinen jedoch nicht abzustürzen, obwohl sie StdCall für Cdecl-Funktionen verwenden. Die Überprüfung des Stack-Ungleichgewichts war in VS2008 nicht standardmäßig aktiviert, daher verwenden einige VS2008-Projekte das falsche Aufruf von Kongressen ohne Wissen ihrer Autoren. Ich habe gerade versucht GnuMpDotNet und das Beispiel läuft gut, obwohl die Cdelc-Deklaration fehlt. Gleiches gilt für X-MPIR .
Beide werfen die pInvokeStackImbalance MDA-Ausnahme im Debug-Modus, stürzen aber nicht im Freigabemodus ab. Warum ist das? Spreizt die .NET-VM alle Aufrufe an systemeigenen Code und stellt den Stack anschließend selbst wieder her? Wenn ja, warum sollte man sich überhaupt mit der Eigenschaft CallingConvention beschäftigen?
Dies liegt an der Art und Weise, wie der Stapelzeiger beim Beenden der Methode wiederhergestellt wird. Der Standard-Prolog einer Methode, dargestellt für den x86-Jitter;
%Vor%Und wie es endet:
%Vor%Es ist hier kein Problem, den esp-Wert aus dem Gleichgewicht zu bringen. Es wird vom ebp-Registerwert wiederhergestellt. Der Jitteroptimierer optimiert dies jedoch nicht selten, wenn er lokale Variablen in CPU-Registern speichern kann. Sie werden abstürzen und brennen, wenn die RET-Anweisung die falsche Antwortadresse vom Stapel abruft. Hoffentlich sowieso, wirklich fies, wenn es zufälligerweise zufällig auf einem Stück Maschinencode landet.
Dies kann passieren, wenn Sie den Release-Build ohne einen Debugger ausführen, der schwer zu beheben ist, wenn Sie nicht über den MDA verfügen, der Ihnen hilft.
Die Laufzeitumgebung kann das Stapelungleichgewicht erkennen, da der Stapelzeiger nicht dort ist, wo er erwartet wird. Das heißt, im Falle von StdCall
, wo erwartet wird, dass die aufgerufene Funktion den Stapel bereinigt, könnte die Laufzeit dies tun:
Wenn nun der Wert von SP
kleiner ist als SavedSP
, dann gibt es zusätzliche Sachen auf dem Stapel - was bedeutet, dass die Laufzeit einfach fortfahren und den gespeicherten Stapelzeiger wiederherstellen kann.
Die Laufzeit sollte immer in der Lage sein, ein Stapelungleichgewicht zu erkennen. Ob es sich immer wieder erholen kann oder nicht, weiß ich nicht. Aber im Falle eines unbeabsichtigten Aufrufs einer Cdecl
-Methode als StdCall
, sollte es in der Lage sein, ohne Probleme wiederherzustellen, da es zusätzliche Sachen auf dem Stapel gibt, die ignoriert werden können.
Warum warum stören? Wie Sie sagen, ist der Unterschied zwischen StdCall
und Cdecl
wirklich nur, wer für die Stapelbereinigung verantwortlich ist. Außerdem ist StdCall
nicht mit Variablenargumentlisten kompatibel (d. H.% Co_de% in C), obwohl ich nicht weiß, ob es überhaupt möglich ist, eine solche Methode von .NET aufzurufen (hatte ich noch nie nötig). In jedem Fall, obwohl es kein besonderes Problem beim Aufruf einer printf
-Methode mit Cdecl
zu geben scheint, möchte ich irgendwie wissen, dass es einen möglichen Fehler gibt. Für mich ist es wie die Fehlermeldung, die der Compiler beim Schreiben gibt:
Ich weiß, dass die Zuweisung in Ordnung ist, aber der Compiler verbietet es, weil es eine potentielle Fehlerquelle ist. In meinen Augen ist ein unausgewogener Stack eine potentielle Fehlerquelle. Nein, es ist ein Fehler, der einige sehr schlimme Dinge verursachen kann. Ich hätte lieber die Laufzeit mir davon erzählt, damit ich das Problem beheben kann.