Heftiger Bug in RAD Studio 2007 mit auto_ptr und Delphi-Klassen?
-
Argh so, keine Zeit gehabt wieder reinzuschauen.
Auch im komplexen Kontext ist es nur sporadisch / nicht zuverlässig reproduzierbar. Seit dem ich ein paar Umbauarbeiten gemacht habe, lässt es sich bis dato auch nicht mehr reproduzieren (weder Debug noch Release, weder mit noch ohne CodeGuard). Das spricht eigentlich dafür, dass irgendwo an anderer Stelle der Speicher zersägt wird und vermutlich ein Stackframe oder so zerschossen wird. Das funktionierende Minimalbeispiel spricht ja auch dafür.
Da ich keine Ahnung habe woran es liegt und auch toolgestützt nix finden konnte, bleibt mir wohl erstmal nix übrig als das Problem ad akta zu legen
-
So, ich glaub ich hab ihn an den Eiern...
Problem ist anscheinend so ein Konstrukt:
#include <memory> class AImpl; class A { public: A(); A( const A&); A& operator=( const A&); private: std::auto_ptr<AImpl> impl_; }
Okay, ich könnte mich selbst dafür schlagen
aber der Compiler warnte erst im Release-Build nach Aktivierung aller Warnungen (im Debug-Build mit allen Warnungen hielt er es anscheinend nicht für nötig). Die zutreffende war natürlich per Default deaktiviert. Muss mal den Standard durchwühlen, ob das nicht sogar ein Compile-Fehler sein sollte bzw. ob ein Diagnostic erforderlich ist.
Der Code war natürlich an völlig anderer Stelle (wurde allerdings benutzt). Ist auch nicht so verwunderlich, dass der KotGuard nix gefunden hat.
Ich lass den exakten Grund mal gerade noch offen, falls noch jemand drüber grübeln will
(Hm - 5.3.5/5 sagt nur undefined behaviour)
-
Gerade bei sowas wäre eben boost::shared_ptr angebracht, wenn man denn wirklich kopieren will.
-
Ja nee, das hat mit dem Urpsrungsproblem nichts mehr zu tun hier
- es wirkt sich nur darauf aus
- und - boost::shared_ptr wäre genauso auf die Nase gefallen (und außerdem nicht verwendbar in diesem konkreten Beispiel, außer ich schreibe die großen 3 auch alle selbst), bzw. der mistige Compiler hält es nicht für nötig zu warnen.
-
Obwohl der Thread schon ein wenig im Sande versandet ist, mal ein Update für euch.
Es hat nix mit auto_ptr zu tun, es hat auch nix mit Delphi-Klassen zu tun. (vermute ich zumindest...)
Das Problem ist sowas hier:
const B A::f() { throw 0xdeadbeef; }
B ist ein Wert-Objekt. Hier wird nun der Destruktor von B aufgerufen.
Ja. Im Ernst. Wirklich.
Bisher schaffe ich es nicht, ein funktionierendes Minimalbeispiel zu basteln, das klappt aber hoffentlich auch noch. Es sind allerdings auch schon mindestens 2 Compilerbugs bei Kotgier gemeldet, wo fälschlich ein Destruktor ausgeführt wird.
Das Ding ist doch wirklich der totale Bockmist...
-
7H3 N4C3R schrieb:
Bisher schaffe ich es nicht, ein funktionierendes Minimalbeispiel zu basteln, das klappt aber hoffentlich auch noch.
Da bin ich sehr gespannt drauf.
7H3 N4C3R schrieb:
Es sind allerdings auch schon mindestens 2 Compilerbugs bei Kotgier gemeldet, wo fälschlich ein Destruktor ausgeführt wird.
Hast du QC-Nummern dafür?
-
audacia|off schrieb:
Da bin ich sehr gespannt drauf.
Still trying...
audacia|off schrieb:
Hast du QC-Nummern dafür?
Die beiden hier:
http://qc.codegear.com/wc/qcmain.aspx?d=61487
http://qc.codegear.com/wc/qcmain.aspx?d=61962Hier auch ein Fall, wo der Destruktor garnicht aufgerufen wird:
http://qc.codegear.com/wc/qcmain.aspx?d=61519Das "lustige" ist ja auch noch, dass 61487 und 61519 auf fixed stehen, aber kein Resolved in Version angegeben ist und mein RAD-Studio das December Update + den April Hotfix enthält und die Fehler allesamt reproduzierbar sind. Ich find's erschreckend, wie man sich damit an die Öffentlichkeit trauen kann.
Mein derzeitiger Workaround sieht übrigens so aus (die in der QC angegebenen Workarounds sind bei mir nicht anwendbar, da ich diese Konstellationen nicht habe):
A::A() : impl_( new Impl) , pattern_(0xdeadbeef) // analog in cctor { } A::~A() { if( pattern == 0xdeadbeef) { delete impl_; pattern ^= 0xFFFFFFFF; } }
Ist zwar auch nicht hieb- und stichfest, sollte aber für hoffentlich 99,9% meiner Fälle ausreichen. (dem anderen 0,1% möchte ich nicht begegnen, sonst muss ich ausrasten :))
-
Am sichersten wäre es sogar, wenn du im Konstruktor pattern den this-Zeiger zuordnest und im Destruktor prüfst, ob pattern == this ist.
Das Problem hatte ich übrigens auch, als ich eine eigenschaft definiert hab, der ich als index- Element ein Klassenobjekt übergebe. Im klassenobjekt wurde Speicher dynamisch allokiert.
Codeguard meinte immer, das im Destruktor des Klassenobjekts auf bereits freigegebenen Speicher zugegriffen wird. Nach genauen Verfolgen über den Debugger hab ich rausbekommen, das eine Kopie des Klassenobjektes in der Eigenschaft angelegt wird, dies erfolgt aber nicht über einen Kopierkonstruktor (obwohl er definiert wurde), sondern einfach durch simples Kopieren des Klassenmembers. Als dann die Kopie zerstört wurde, wurde demzufolge auch der vom Originalobjekt angelegte Speicher mit zerstört (weil nämlich der Destruktor der Kopie aufgerufen wurde), so dass das Originalobjekt also dann auf bereits freigegeben Speicher zugriff.
Mit Hilfe eines solchen "Kennzeichens" wie dein Pattern habe ich das dann so gelöst:Vorher:
[cpp] __fastcall TMyObjekt::TMyObjekt(void *data,int len) { m_data = new BYTE[len]; memmove(m_data,data,len); } __fastcall TMyObjekt::~TMyObjekt() { delete []m_data; } [/cpp]
Nach Änderung:
[cpp] __fastcall TMyObjekt::TMyObjekt(void *data,int len) : m_assigned(this) { m_data = new BYTE[len]; memmove(m_data,data,len); } __fastcall TMyObjekt::~TMyObjekt() { if (m_assigned == this) { delete []m_data; } } [/cpp]
-
7H3 N4C3R schrieb:
Das "lustige" ist ja auch noch, dass 61487 und 61519 auf fixed stehen, aber kein Resolved in Version angegeben ist und mein RAD-Studio das December Update + den April Hotfix enthält und die Fehler allesamt reproduzierbar sind. Ich find's erschreckend, wie man sich damit an die Öffentlichkeit trauen kann.
Wenn die Build-Nummer nicht da steht, bedeutet das i.d.R., daß das Problem für die nächste, noch nicht öffentliche Version behoben wurde; es ist somit nur für die Öffentlichkeit nicht sichtbar. Dementsprechend sollte das Problem in C++Builder 2009 behoben sein.
-
Äähm... soll das heißen, dass für den C++ Builder 2007 kein Fix zu erwarten ist?
-
Das steht zu befürchten.
-
Holy Shit...
in dem Falle kommt eigentlich nur noch BackPort auf BCB 5 in Frage... in der Hoffnung, dass der Fehler dort nicht auftritt. An den Pimpls kann ich was machen, aber nicht an den anderen Klassen. Es kracht auch noch an weiteren Stellen. Wenn es tatsächlich ein Fehler in der Anwendung sein sollte, habe ich keine Ahnung, wie ich mit dem [Frust]unfähigen[/Frust] Debugger noch was finden soll.
-
Burkhi schrieb:
Am sichersten wäre es sogar, wenn du im Konstruktor pattern den this-Zeiger zuordnest und im Destruktor prüfst, ob pattern == this ist.
Das Problem hatte ich übrigens auch, als ich eine eigenschaft definiert hab, der ich als index- Element ein Klassenobjekt übergebe. Im klassenobjekt wurde Speicher dynamisch allokiert.
Codeguard meinte immer, das im Destruktor des Klassenobjekts auf bereits freigegebenen Speicher zugegriffen wird. Nach genauen Verfolgen über den Debugger hab ich rausbekommen, das eine Kopie des Klassenobjektes in der Eigenschaft angelegt wird, dies erfolgt aber nicht über einen Kopierkonstruktor (obwohl er definiert wurde), sondern einfach durch simples Kopieren des Klassenmembers. Als dann die Kopie zerstört wurde, wurde demzufolge auch der vom Originalobjekt angelegte Speicher mit zerstört (weil nämlich der Destruktor der Kopie aufgerufen wurde), so dass das Originalobjekt also dann auf bereits freigegeben Speicher zugriff.
Mit Hilfe eines solchen "Kennzeichens" wie dein Pattern habe ich das dann so gelöst:Vorher:
[cpp] __fastcall TMyObjekt::TMyObjekt(void *data,int len) { m_data = new BYTE[len]; memmove(m_data,data,len); } __fastcall TMyObjekt::~TMyObjekt() { delete []m_data; } [/cpp]
Nach Änderung:
[cpp] __fastcall TMyObjekt::TMyObjekt(void *data,int len) : m_assigned(this) { m_data = new BYTE[len]; memmove(m_data,data,len); } __fastcall TMyObjekt::~TMyObjekt() { if (m_assigned == this) { delete []m_data; } } [/cpp]
Ist das der vollständige Code? Ich sehe da weder Copy Constructor noch Assignment Operator. Wird von der Klasse abgeleitet? Kannst du vielleicht ein Minimalbeispiel posten, das den Fehler reproduziert?
Gruß,
Doc
-
7H3 N4C3R schrieb:
Holy Shit...
in dem Falle kommt eigentlich nur noch BackPort auf BCB 5 in Frage...
Nicht so hastig
Übrigens läßt ein wesentlicher Teil des Exception-Handling in der im Quelltext mitgelieferten RTL anpassen. Möglicherweise kann der Fehler sogar dort behoben werden.
7H3 N4C3R schrieb:
Wenn es tatsächlich ein Fehler in der Anwendung sein sollte, habe ich keine Ahnung, wie ich mit dem [Frust]unfähigen[/Frust] Debugger noch was finden soll.
Mit dem Debugger bist du doch recht weit gekommen. Nimm dir einen Branch des Projektes, in dem es auftritt, und entferne nach und nach so viel wie möglich. Sobald du hinreichend wenig eigenen Code drin hast, schau ich gerne mal rein.
@Burkhi: für ein Minimalbeispiel deines Problems wäre ich auch dankbar.
-
Naja, ist leider etwas schwierig. In ein paar Wochen soll die neue Version rausgehen und wir kämpfen mit so einem üblen Fehler, und auch leider nicht der einzige und letzte. Die Möglichkeiten die mir bleiben sind nur, entweder den Grund des Fehlers zu finden (um dann vielleicht festzustellen, dass ich nichts tun kann), oder den Code so oder so wieder ans Laufen zu kriegen.
Das mit der RTL klingt mal recht gut. Zumindest ein gewisses Grundwissen über SEH habe ich, das könnte ich mir mal anschauen.
Und der Debugger...
Der Debugger blieb bei der Access Violation immer in einem falschen Stackframe hängen. Darauf, dass dort ein totes Objekt zerstört wird, bin ich nur durch eine Portion Glück und einen "zufälligen" Haltepunkt im Destruktor gekommen.
Btw - weißt du eigentlich, wie ich den Debugger dazu bekommen kann, im "C++-Mode" mir auch Delphi-Sourcen anzuzeigen und mich durch diese durchsteppen zu lassen? Oder ist das überhaupt nicht möglich? Diese Möglichkeit vermisse ich aus meinen Delphi-Zeiten ziemlich.Wie dem auch sei, wenn ich was neues rausfinde, werd ich's hier reinschreiben.
-
Hallo,
7H3 N4C3R schrieb:
Und der Debugger...
Der Debugger blieb bei der Access Violation immer in einem falschen Stackframe hängen. Darauf, dass dort ein totes Objekt zerstört wird, bin ich nur durch eine Portion Glück und einen "zufälligen" Haltepunkt im Destruktor gekommen.
Der Stackframe sollte eigentlich richtig sein, nur landest du nach einer AV meist in irgendeiner Kernel-Funktion. In diesem Fall gehe ich gewöhnlich in die erste von mir implementierte oder im Quelltext einsehbare Funktion des Stacks, setze einen Breakpoint auf die Zeile vor der markierten, lasse das Programm nochmal ablaufen und lokalisiere die AV durch Single-Stepping.
7H3 N4C3R schrieb:
Btw - weißt du eigentlich, wie ich den Debugger dazu bekommen kann, im "C++-Mode" mir auch Delphi-Sourcen anzuzeigen und mich durch diese durchsteppen zu lassen? Oder ist das überhaupt nicht möglich? Diese Möglichkeit vermisse ich aus meinen Delphi-Zeiten ziemlich.
Versuche mal, die Runtime-Packages zu deaktivieren; das reichte in C++Builder 2006 aus. Evtl. gibt es sogar in den Projektoptionen eine Einstellung dafür (schau mal nach "Use debug DCUs" oder ähnlichem); im Zweifelsfall setze den Library-Pfad im Debug-Mode, sofern er das noch nicht ist, auf $(BDS)\lib\debug.
-
audacia|off schrieb:
Der Stackframe sollte eigentlich richtig sein, nur landest du nach einer AV meist in irgendeiner Kernel-Funktion. In diesem Fall gehe ich gewöhnlich in die erste von mir implementierte oder im Quelltext einsehbare Funktion des Stacks, setze einen Breakpoint auf die Zeile vor der markierten, lasse das Programm nochmal ablaufen und lokalisiere die AV durch Single-Stepping.
Ist er leider nicht.
Der Stacktrace sieht eigentlich immer so aus:
:200079e0 rtl100.@System@@LStrClrqqrpv + 0xc :32823b68 ; C:\\WINDOWS\\system32\\CC3280MT.DLL [ca. 8 mal] :008fab45 __ExceptionHandler + 0x1E :7c91378b ntdll.RtlConvertUlongToLargeInteger + 0x46 :7c91eafa ntdll.KiUserExceptionDispatcher + 0xe :32823cee ; C:\\WINDOWS\\system32\\CC3280MT.DLL :32825bf1 CC3280MT.@_ThrowExceptionLDTCqpvt1t1t1uiuiuipuct1 + 0x31
Das ist wie's aussieht eine Bereinigungsfunktion von AnsiString.
"LStrClr$qqrpv" (ich bekomme nicht mal demangled names... :() wird allerdings von meinentwegen A::~A -> B::~B aufgerufen, das wird aber nicht angezeigt. Auch ein Singlestep nach der Exception durchläuft diese Destruktoren nicht. Mit einem Breakpoint, wenn man weiß wo, hält der Debugger aber vor dem Absturz wieder an. Reinsteppen tut er von alleine nicht. Debug kompiliere ich natürlich immer, nur um das auszuschließen.Ich habe mal die Option "langsame Exception-Epiloge" aktiviert, die wohl ein Inlining des Exception-Codes verhindern soll, hat allerdings nix gebracht.
Selbst die Funktion, die das throw gemacht hat, erscheint nichtmal im Stacktrace, sondern ihr Aufrufer. Die IDE bleibt trotz fehlendem Eintrag im Stacktrace auf dem throw stehen - nach der AV allerdings auf dem Aufrufer der werfenden Funktion, die mit der AV nix zu tun hat. Also irgendwie garnicht hilfreich.
audacia|off schrieb:
]Versuche mal, die Runtime-Packages zu deaktivieren; das reichte in C++Builder 2006 aus. Evtl. gibt es sogar in den Projektoptionen eine Einstellung dafür (schau mal nach "Use debug DCUs" oder ähnlichem); im Zweifelsfall setze den Library-Pfad im Debug-Mode, sofern er das noch nicht ist, auf $(BDS)\lib\debug.
Runtime-Packages, ja die könnte ich mal deaktivieren. Den alten "Use debug DCUs" habe ich nicht gefunden, allerdings gibt es die Linker-Option "mit vollständigen Debug-Informationen linken", die im Debug-Build sowieso immer an ist. Der Library-Pfad im Debug-Compile enthält lib\debug ja auch schon von alleine.
-
7H3 N4C3R schrieb:
Der Stacktrace sieht eigentlich immer so aus:
:200079e0 rtl100.@System@@LStrClrqqrpv + 0xc :32823b68 ; C:\\WINDOWS\\system32\\CC3280MT.DLL [ca. 8 mal] :008fab45 __ExceptionHandler + 0x1E :7c91378b ntdll.RtlConvertUlongToLargeInteger + 0x46 :7c91eafa ntdll.KiUserExceptionDispatcher + 0xe :32823cee ; C:\\WINDOWS\\system32\\CC3280MT.DLL :32825bf1 CC3280MT.@_ThrowExceptionLDTCqpvt1t1t1uiuiuipuct1 + 0x31
Und sonst kommt nichts mehr?
Falls ja: was passiert, wenn du in _ThrowExceptionLDTC einen Breakpoint setzt und dann die gleiche Situation herbeiführst; bekommst du dann einen ausführlicheren Stacktrace mit den aufrufenden Funktionen?Weiter ist es hier möglicherweise hilfreich, die dynamische RTL zu deaktivieren, dann bekommst du im Debug-Mode AFAIK auch Symbolnamen für die RTL-Funktionen. So könntest du z.B. in _CatchCleanup() mal einen Breakpoint setzen und schauen, wo und wieso genau dein Objekt ohne Notwendigkeit destruiert wird.
-
audacia|off schrieb:
Falls ja: was passiert, wenn du in _ThrowExceptionLDTC einen Breakpoint setzt und dann die gleiche Situation herbeiführst; bekommst du dann einen ausführlicheren Stacktrace mit den aufrufenden Funktionen?
Vor _ThrowExceptionLDTC ist ein korrekter Stacktrace da (bis auf die Funktion, die die Exception wirft - die fehlt). Sorry, wenn ich da unverständlich war. (Ich meinte den Übergang von CC3280MT.DLL (__get_lock_level) zu LStrClr, die verantwortlichen Destruktoren dazwischen erscheinen nicht im Trace)
audacia|off schrieb:
Weiter ist es hier möglicherweise hilfreich, die dynamische RTL zu deaktivieren, dann bekommst du im Debug-Mode AFAIK auch Symbolnamen für die RTL-Funktionen. So könntest du z.B. in _CatchCleanup() mal einen Breakpoint setzen und schauen, wo und wieso genau dein Objekt ohne Notwendigkeit destruiert wird.
Okay, das habe ich soweit gemacht. Ich sehe nun auch die richtigen Symbolnamen. Allerdings zeigt mir der Debugger die zugehörigen Sourcen nicht an. Ich habe bereits ($BDS)\source\cpprtl\Source\except und weitere Kandidaten in den Suchpfad des Debuggers gesetzt.
Allerdings ist mir unklar, was ich sonst vielleicht noch machen muss, um den Debugger davon zu überzeugen, mir den Source anstatt des ASM-Listings anzuzeigen. Weißt du, was ich hier tun muss?
Edit 3000: Ich baue mal gerade eine Debug-Version der RTL. Anscheinend ist die garnicht per Default vorhanden...
Edit 4000: HA - mit einer händisch gebauten Debug-RTL sehe ich auch die Sourcen und kann steppen.Anbei ein neuer Stacktrace:
:200079e0 rtl100.@System@@LStrClr$qqrpv + 0xc :0090B848 callDestructor(dst=:0012F488, dstType=:00402E28, dstFlags=0, dtorAddr=:0090F160, dtorMask=3, dtVbases=1) :0090C47B destroyOneObject(varAddr=:0012F488, varType=:00402E28, flags=1, vbFlag=0, errPtr=:0012F40C) :0090C5E8 destroyVariable(varAddr=:0012F488, varType=:00402E28, flags=0, dtorCnt=1, vbFlag=1, newBP=1242532, errPtr=:0012F40C) :0090C7DC destroyVariable(varAddr=:0012F488, varType=:0047455C, flags=0, dtorCnt=1, vbFlag=0, newBP=1242532, errPtr=:0012F40C) :0090C4F7 destroyBases(varAddr=:00000001, flags=0, nblLast=:0046EF20, nblFirst=:0046EF20, count=1242248, isVirt=0, newBP=1242532, errPtr=:0012F40C) :0090C6E9 destroyVariable(varAddr=:0012F488, varType=:0046EEE8, flags=0, dtorCnt=1, vbFlag=1, newBP=1242532, errPtr=:0012F40C) :0090CDD8 dtorCleanup(dttPtr=:00967A18, dtcMin=1, errPtr=:0012F40C, newBP=1242532) :0090C20D local_unwind(errPtr=:0012F40C, target=84) :0090DD95 ___ExceptionHandler(excPtr=:0000005C, errPtr=:0012F40C, ctxPtr=:0012E600, _dspPtr=:0012E5A0, OSEsp=1238244, =1238276, =0, =0, =0, flags=3) :00901229 __ExceptionHandler + 0x1E :7c91378b ntdll.RtlConvertUlongToLargeInteger + 0x46 :7c91eafa ntdll.KiUserExceptionDispatcher + 0xe :0090B9CE tossAnException(tpid=:004733D4, throwAddr=:24DF94B8, friendList=NULL, cctrAddr=:00473434, cctrMask=1, flags=0, throwLine=639, throwFile=:0094211D, throwPC=4665968, reThrow=0, errPtr=:0012EA38, userRegisters=:0012E9DC) :0090D889 _ThrowExceptionLDTC(tpid=:0012EEAC, throwAddr=:03415E40, friendList=:00000001, cctrAddr=:0012EC08, cctrMask=9441803, flags=9715880, throwLine=1239876, throwFile=NULL, errPtr=:24DF5AB0)
-
Was hast du nun in dieser Frage eigentlich unternommen?
Kann es außerdem sein, daß dein Problem in diesem QC-Report beschrieben wird? Falls ja, dann ist es in C++Builder 2009 behoben (wie dieser Liste zu entnehmen ist).