10.1 Berlin Update 1: clang fehlerhaftes Stack-Layout beim Aufruf von Delphi Funktionen



  • Hallo,

    ich habe hier mal wieder ein seltsames Phänomen mit dem RAD Studio 10.1 Berlin Update 1:
    Ich habe eine minimal Delphi Klasse, die nur eine Funktion anbietet, Diese Funktion hat 6 Parameter, summiert alle auf und gibt das Ergebnis zurück.

    unit DelphiCall;
    
    interface
    
    Type
      TDelphiCall = class( TObject )
    
      public
        Constructor Create;
    
        function Func( id: Integer; X, Y, w, h, angle: Extended) : Extended;
      end;
    
    implementation
      Constructor TDelphiCall.Create;
      begin
      end;
    
      Function TDelphiCall.Func(id: Integer; X: Extended; Y: Extended; w: Extended; h: Extended; angle: Extended) : Extended;
      begin
        result := id + X + Y + w + h + angle;
      end;
    end.
    

    Der Aufruf in C++ sieht so aus:

    void TForm1::OnClickButton( TObject* Sender )
    {
       TDelphiCall* Call = new TDelphiCall();
       Call->Func( 0, 1.0, 2.0, 3.0, 4.0, 5.0 );
    }
    

    Mit dem klassischen C++ Compiler läuft das sauber durch, die Parameter sehen gut aus und das Ergebnis ist 15. Mit dem clang Compiler läuft das allerdings nicht durch, und beim Debuggen sehe ich in der Methide Func() für die Variablen folgende Werte:

    id: 0 (ok)
    X: 2.23372036854775808E-4822 (soll sein: 1)
    Y: 0.00000000391412264E-4933 (soll sein: 2)
    w: 0.00025651985534516E-4933 (soll sein: 3)
    h: 1.68120547642404768E-4932 (soll sein: 4)
    angle: 5 (ok)
    

    Das kann ich mir jetzt überhapt nicht erklären. Der erste und der letzte Parameter sind ok, die 4 dazwischen nicht. Wenn der letzte jetzt auch kaputt wäre hätte ich den verdacht, dass das komplette Stack-Layout kaputt ist und sich alle Adressen verschoben haben. Interessanterweise ist das aber nur für die Parameter 2-4 so, Parameter 1 und 5 sind ok.
    Gibt´s da irgendwo eine Einstellung für den clang oder Delphi Compiler, an der man schrauben kann. Oder ist das ein Bug?



  • Na, ein Bug wirds auf jeden Fall sein. Was mir gerade so einfällt:

    - soweit ich weiß, ist in den 64-Bit-Targets von Delphi und C++Builder sizeof(long double) == 8 , anders als sizeof(long double) == 10 in den 32-Bit-Versionen. Nachdem der 32-Bit-Clang-Compiler ja aus dem 64-Bit-Compiler erwuchs, kann es sein, daß er da was durcheinanderbringt (ich nehme an, dein Target ist 32 Bit?). Daß das Problem beim ersten und letzten Argument nicht auftritt, könnte daran liegen, daß diese per Register übergeben werden. Schau dir doch mal sizeof(long double) an, und probiere aus, ob du die Problem mit Double -Argumenten auch hast.

    - evtl. gibt es auch Probleme mit der in Delphi standardmäßigen register -Aufrufkonvention (entspricht __fastcall in C++). Versuch doch mal, die Delphi-Funktion explizit mit einer anderen Aufrufkonvention auszustatten, z.B. stdcall oder cdecl . Vermutlich sind dann alle Werte korrumpiert, weil keine mehr über Register übergeben werden 🙂



  • Ja richtig, das Executable ist eine 32bit Anwendung. Hab in den C++ und Delphi Compiler Einstellungen schon alles erfolglos ausprobiert.

    Der BCC32C legt die Extended Werte in umgekehrter Reihenfolge mit jeweils 10 Byte auf den Stack und übergibt den Integer in EAX. Der Delphi Compiler holt sich die Extended Werte mit jeweils 12 Byte vom Stack. Das erklärt, warum der erste (wird im Register übergeben) und der letzte Parameter (liegt als erster auf dem Stack) korrekt sind und der Rest nicht (verschiedene Offsets beim Ablegen/Lesen vom Stack).
    Sieht so aus, als erwarte der Delphi Compiler alle Variablen auf DWORD-aligned Adressen und der BCC32C packt die halt dicht.

    [Sarkasmus on]
    Hauptsache wir können mit dem RAD Studio bald auf Linux entwickeln, wen interessiert da schon korrekter Code?
    [Sarkasmus off]

    Ich glaube, bei Embarcadero folgt man deiner ehemaligen Signatur (Hey, it compiles! Ship it!). Testen tut da niemand mehr.

    Wird langsam Zeit für einen Sticky-Thread nach Vorlage des Threads gegen Bücher von Jürgen Wolf: Warum man das RAD Studio von Embarcadero besser nicht benutzt.

    Edit:
    In C++ liefert sizeof( Extended ) 10 zurück, genauso wie SizeOf( Extended ) in Delphi.



  • Ich nehme an, double zu verwenden ist keine Option? Dann ginge noch Folgendes:

    type
      CppExtended = type Extended; { mapped to correctly aligned struct via $HPPEMIT }
      {$EXTERNALSYM CppExtended}
    
      (*$HPPEMIT 'namespace ...'*)
      (*$HPPEMIT '{'*)
      (*$HPPEMIT ''*)
      (*$HPPEMIT 'struct CppExtended'*)
      (*$HPPEMIT '{'*)
      (*$HPPEMIT '    long double value;'*)
      (*$HPPEMIT '    unsigned short padding;'*)
      (*$HPPEMIT ''*)    // TODO: implicit conversion operators from/to long double etc.
      (*$HPPEMIT '};'*)
      (*$HPPEMIT ''*)
      (*$HPPEMIT '} // namespace ...'*)
    

    Falls du Linkerfehler bekommst, mußt du evtl. mit $OBJTYPENAME nachhelfen:

    (*$OBJTYPENAME CppExtended 'CppExtended'*) // bzw. hier eben den dekorierten Namen eintragen
    


  • Hast du dafür bei Embarcadero schon ein Ticket aufgemacht? Ich will die absolut nicht in Schutz nehmen, aber vermutlich wurde das da einfach vergessen zu testen. 😕



  • Burkhi schrieb:

    Hast du dafür bei Embarcadero schon ein Ticket aufgemacht? Ich will die absolut nicht in Schutz nehmen, aber vermutlich wurde das da einfach vergessen zu testen. 😕

    Unabhängig das ein Ticket sinnvoll ist: Es gibt viele Fehler die schon seit langer Zeit nicht gefixt wurden (Teilweise Fehler aus dem BCB3). Der Weggang einzelner Entwickler, das Schließen eines Entwicklungsstandorts... wird auch seinen Teil zum weiteren Niedergang haben.

    Persönlich kann ich nur abraten den BCB für neue Projekte zu nutzen, egal wie klein diese sind - Irgendwann sind die dann doch wieder zu viele oder zu große um einfach wechseln zu können.



  • So langsam macht sich Ernüchterung breit. Ich war nicht der erste, der diesem Bug begegnet ist, es gibt die Tickets RSP-15737 und RSP-15497. RSP 15497 kommt von mir.

    Das nächste Feature ist auch wieder großes Kino:

    struct RecordHeader
    {
       unsigned char  HeaderID;
       unsigned short RecordID;
       unsigned int   RecordSize;
       unsigned char  CompressionType;
    
       RecordHeader() :
          HeaderID( 0 ),
          RecordID( 0 ),
          RecordSize( 0 ),
          CompressionType( 0 )
       {
       }
    };
    
    RecordHeader read_record_header( StreamReader& Reader )
    {
       RecordHeader Header;
       unsigned int BytesRead = Reader.read_exact( &Header, sizeof( Header ) );
       if( Header.HeaderID != BeginningOfHeaderID )
       {
          throw runtime_error( "blah blah blah" );
       }
       return Header;
    }
    

    Der BCC32C wendet hier immer und in jeder Konfiguration NRVO an, d.h. man sieht die Variable Header in der Funktion nicht. Nie. Nicht nur ich sehe sie nicht, auch der Debugger sieht sie nicht. Man hat keine Chance sich die gelesenen Daten anzuschauen, selbst mit dem Variableninspektor kommt man nicht ran. Ist nicht nur in diesem Fall so, sondern jedes Mal, wenn RVO oder NRVO möglich ist. Oder wenn das Ergbnis einer Funktion nicht über den Stack sondern über ein Register zurückgegeben wird. Macht das Debuggen schwierig bis unmöglich. Ich bin begeistert.

    Ich frage mich auch so langsam, inwiefern Embarcadero da in Regress genommen werden kann. Was die da verkaufen ist ein großer Sack heiße Luft.


Anmelden zum Antworten