Announcement

Collapse
No announcement yet.

DebugBreak und Delphi

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • DebugBreak und Delphi

    Hallo, Leute!<br>
    Ich versuche gerade, mir meine eigene Assertionbehandlung zu schreiben (mit AssertErrorProc). Klappt auch alles wunderbar, bis auf einen Schönheitsfehler:
    In einem Zweig meiner AssertErrorProc rufe ich DebugBreak auf. Wenn nun eine Assertion fehlschlägt und der Programmablauf über dieses DebugBreak führt, lande ich erstmal im NT-Quellcode und muss noch 3x Shift-F8 drücken: erst geht's in meine AssertErrorProc, dann in _Assert und dann erst lande ich in der Zeile mit dem fehlgeschlagenen Assert (die mich ja eigentlich interessiert). Habt ihr eine Idee, wie ich das (zumindest teilweise) korrigieren könnte?
    <p>Vielen Dank im Voraus,<br>Uli.

  • #2
    probiert statt DebugBreak mal

    <pre>

    asm
    INT 3
    end;

    </pre>

    aus.

    Gruß Hage

    Comment


    • #3
      Danke, Hagen. Damit ist schonmal eine der drei Ebenen aus dem Spiel. :-)
      <br>Uli

      Comment


      • #4
        Ich sehe gerade, deine Frage ist ja aus 2002, oder stimmt da was nicht ??

        Gruß Hage

        Comment


        • #5
          Doch, stimmt. Ich hatte sie eigentlich auch schon ganz vergessen. Ist sie bei dir als neu aufgetaucht?
          <br>Uli

          Comment


          • #6
            Jo, sonst hätte ich ja nicht geantwortet )
            Deine Frage war ja auch easy zu beantworten. Im Grunde macht der asm part das gleiche wie DebugBreak, bzw. es ist der gleiche code. Mit dem Unterschied das der Code im Modul deiner Anwendung steht und eine Stackebene höher liegt. Diese zwei Fakten haben aber ein anderes Verhalten des Debuggers zur Folge.<br>

            Welche zwei "Ebenen" verbleiben denn noch ?<br>
            Ich frage weil ich selber schon Probleme, Ärgernisse, mit der Assert Behandlung hatte. In D5 scheint der Debugger nicht an der Quelltextposition stehen zu bleiben in der die Assertion steht.<br>

            Gruß Hage

            Comment


            • #7
              <i>Welche zwei "Ebenen" verbleiben denn noch ?</i><br>
              Der Debugger steht auf dem "int 3" in meiner AssertErrorProc.
              Eine Stackebene höher kommt das "RET" hinter dem "CALL AssertErrorProc"
              in _Assert und noch eins höher die Zeile hinter dem fehlgeschlagenen
              Assert.<br>
              Rufe ich in meiner AssertErrorProc hingegen die (vorher gemerkte)
              delphi-eigene AssertErrorProc auf, so stehe ich wie gewünscht *auf*
              der Zeile mit dem bösen Assert (durch Tricksereien mit ErrorAddr offenbar).
              Leider sind dann aber die Werte der lokalen Variablen nicht mehr
              erkennbar (aufgrund der geworfenen Exception).<br>
              Dummerweise ist mir nichts eingefallen, wie ich die Vorteile dieser
              beiden Varianten gleichzeitig haben kann.

              <p>Ich bin mal so frei und häng meine bisherigen Ergebnisse an.
              Vielleicht siehst du ja irgendwelche offensichtlichen Fehler. :-)
              <pre class="sourcecode"><code>
              <b>unit</b> ChoiceAssert;
              <br>
              <b>interface</b>
              <br>
              <b>type</b>
              TAssertionBehaviour = (abIgnore, abHalt, abDebug, abUserChoice, abDelphi);
              <br>
              <b>procedure</b> InitChoiceAssert(Behaviour: TAssertionBehaviour);
              <b>procedure</b> DoneChoiceAssert;
              <br>
              <b>implementation</b>
              <br>
              <b>uses</b>
              Windows,
              SysConst,
              SysUtils;
              <br>
              <b>type</b>
              TAssertErrorProc = <b>procedure</b>(<b>const</b> Message, Filename: <b>string</b>;
              LineNumber: Integer; ErrorAddr: Pointer);
              <br>
              <b>var</b>
              AssertionBehaviour: TAssertionBehaviour;
              OldAssertErrorProc: TAssertErrorProc;
              <br>
              <b>function</b> GetUserChoice(<b>const</b> Message, Filename: <b>string</b>;
              LineNumber: Integer; ErrorAddr: Pointer): TAssertionBehaviour;
              <b>const</b>
              cNewLine = #13#10;
              cUserChoiceMsg = <font color="#9933CC">'%s'</font> + cNewLine +
              <font color="#9933CC">'(%s, Zeile %d, Adresse $%x)'</font> + cNewLine +
              cNewLine +
              <font color="#9933CC">'Wollen Sie die Anwendung beenden (&quot;Abbrechen&quot,'</font> + cNewLine +
              <font color="#9933CC">'debuggen (&quot;Wiederholen&quot oder die Assertion ignorieren?'</font>;
              <b>var</b>
              Choice: Integer;
              S: <b>string</b>;
              <b>begin</b>
              S := Format(cUserChoiceMsg,
              [Message, Filename, LineNumber, Pred(Integer(ErrorAddr))]);
              Choice := Windows.MessageBox(GetForegroundWindow, PChar(S), PChar(SAssertionFailed),
              MB_ABORTRETRYIGNORE <b>or</b> MB_ICONERROR);
              <b>case</b> Choice <b>of</b>
              IDABORT:
              Result := abHalt;
              IDRETRY:
              Result := abDebug;
              <b>else</b> <font color="#003399"><i>//IDIGNORE:</i></font>
              Result := abIgnore;
              <b>end</b>;
              <b>end</b>;
              <br>
              <b>procedure</b> ChoiceAssertErrorHandler(<b>const</b> Message, Filename: <b>string</b>;
              LineNumber: Integer; ErrorAddr: Pointer);
              <b>var</b>
              Behaviour: TAssertionBehaviour;
              Choice: Integer;
              S: <b>string</b>;
              <b>begin</b>
              <b>if</b> AssertionBehaviour = abUserChoice <b>then</b>
              Behaviour := GetUserChoice(Message, Filename, LineNumber, ErrorAddr)
              <b>else</b>
              Behaviour := AssertionBehaviour;
              <br>
              <b>case</b> Behaviour <b>of</b>
              abHalt:
              FatalExit(666);
              <font color="#003399"><i>//FatalAppExit(0, 'Hallo!');</i></font>
              abDebug:
              <font color="#003399"><i>//DebugBreak;</i></font>
              <b>asm</b> int 3 <b>end</b>;
              abDelphi:
              OldAssertErrorProc(Message, Filename, LineNumber, ErrorAddr);
              <b>else</b> <font color="#003399"><i>//abIgnore:</i></font>
              <font color="#003399"><i>// nix</i></font>
              <b>end</b>;
              <b>end</b>;
              <br>
              <b>procedure</b> InitChoiceAssert(Behaviour: TAssertionBehaviour);
              <b>begin</b>
              AssertionBehaviour := Behaviour;
              TAssertErrorProc(AssertErrorProc) := ChoiceAssertErrorHandler;
              <b>end</b>;
              <br>
              <b>procedure</b> DoneChoiceAssert;
              <b>begin</b>
              TAssertErrorProc(AssertErrorProc) := OldAssertErrorProc;
              <b>end</b>;
              <br>
              <br>
              <b>initialization</b>
              OldAssertErrorProc := AssertErrorProc;
              <b>finalization</b>
              DoneChoiceAssert;
              <b>end</b>.
              </code></pre>

              (to be continued...

              Comment


              • #8
                Benuztung ungefähr so:
                <pre class="sourcecode"><code>
                <font color="#003399"><i>// ... [snip] ...</i></font>
                <br>
                <b>procedure</b> TForm1.FormDblClick(Sender: TObject);
                <b>var</b>
                i: Integer;
                <b>begin</b>
                i := 666;
                Assert(i &lt; 300);
                i := i*i;
                <b>end</b>;
                <br>
                <b>initialization</b>
                InitChoiceAssert(abUserChoice);
                <b>finalization</b>
                DoneChoiceAssert;
                <b>end</b>.
                </code></pre>

                <i>In D5 scheint der Debugger nicht an der Quelltextposition stehen zu bleiben in der die Assertion steht.</i><br>
                Außer dem obigen Schönheitsfehler fällt mit grad keine Probleme ein (D5.01 Pro).
                <p>
                Ciao, Uli

                Comment


                • #9
                  <i>Benuztung, fällt mit grad keine Probleme ein, ...</i>
                  <br>Ähem..

                  Comment


                  • #10
                    Mann müsste anderes vorgehen. Statt den INT 3 so auszulösen, müsste dein Assert Handler den INT 3 Opcode HINTER oder VOR dem CALL zu Assert() plazieren. Der Debugger würde dann genau im Stackframe wo die Assert() Überprüfung stattfindet stehen bleiben.<br>

                    <I>In D5 scheint der Debugger nicht an der Quelltextposition stehen zu bleiben in der die Assertion steht.
                    Außer dem obigen Schönheitsfehler fällt mit grad keine Probleme ein (D5.01 Pro). </i>

                    Das stimmt, der Debugger hat in der Berechnungsfunktion für den Opcode ein Displacement drinnen. D.h. wenn du mit Exception.Create() at ErrorAddr; arbeitest dann nutzt der Debugger ABHÄNGIG von den Optimierungseinstellungen einen +1 Displacement ?!?!<br>

                    Er errechnet also die Fehleradresse falsch.<br>
                    Kannst dir nicht vorstellen wie ich gekotzt habe.<br>

                    Ich hatte ein ähnliches Problem wie Du. In meiner Large Integer Unit nutze ich Exceptions und Assertion() um z.b. eine mögliche Division durch Null o.ä. anzuzeigen.<br>
                    Normalerweise bleibt der Debugger an der Stelle stehen WO ich raise aufrufe, deshalb gibts ja "at ErrorAddr". Mein Ziel war nun im Stackframe, auf der Adresse die MEINE Funktion aufgerufen hat zu stehen. D.h. z.B. ruft der Anwender

                    <pre>

                    NDiv(A, B); // A := A div B

                    </pre>

                    auf, überprüfe ich in der Implementierung von NDiv() ob B = 0 ist. Sollte das der Fall sein raise ich eine Exception "Division by Null" aus. Nun sollte der debugger NICHT innerhalb NDiv stehen sondern im obigen Aufruf von NDiv() durch den User Code. !!<bR>

                    Ich habe dieses Problem nie richtig lösen können. Obwohl meine Implemntierung einen Stackframe benutzt hat, scheint der Debugger die "at ErrorAddr" nicht korrekt zu interpretieren.<br>

                    Gruß Hage

                    Comment


                    • #11
                      Für dein INT 3 gibts noch eine andere Lösung.<br>

                      Statt INT3 lösst du eine "virtuelle" Exception aus, im Exceptionhandler der dann durch das OS aufgerufen wird kannste nun die eigentliche Adresse der Assert() dem OS zurückgeben und erneut ein stilles raise aufrufen. Das OS würde dann an der Adresse vom Assert() die Exception-Auslösung vermuten und den Delphi Debugger starten. D.h. im Delphi Debugger würde erscheinen das an dieser stelle eine Exception aufgtreten ist, er würde aber mit F8,F9,F7 an dieser Stelle mit der Ausführung des Programmes fortfahren.<br>

                      So was ähnliches habe ich mal als Anti-Debugging Trick benutzt. Im Grunde wird über das SEH = Structured Exception handling des OS der Ausführungspfad der Anwendung so verändert das ein bestimmerter Code ZWEIMAL hintereinander ausgeführt wird und denoch ein anderes Exceptionhandling benutzt wird.<br>

                      Gruß Hage

                      Comment


                      • #12
                        Ah nochwas propier mal das, statt asm INT3 end;
                        Im Stack steht

                        <pre>

                        saved ESP
                        return address von CALL AssertProc
                        return addresse vom Assert<br>

                        Wir manipuliern nun so das dort steht<br>

                        saved ESP
                        return address von CALL AsserProc
                        return address vom assert
                        return adresss to INT3<br>


                        asm
                        POP EBP
                        MOV ESP,EBP
                        POP EAX
                        POP EDX
                        PUSH OFFSET @@1
                        PUSH EDX
                        PUSH EAX
                        RET

                        @@1: INT 3
                        RET
                        end;<br>

                        Normalerweise haben wir im Aufrufstack<br>

                        - call AssertErrorProc
                        - Assert()<br>

                        wir machen daraus nun: <br>

                        - call AssertErrorProc
                        - Assert()
                        - call @@1 = INT3<br>

                        wir jubeln also dem Aufrufstack unsere INT3 procedures so unter als wäre sie als erstes aufgerufen wurden.<br>
                        (topdown)

                        </pre>

                        Naja, natürlich ist das alles ungetestet und ganz WICHTIG dabei ist es zu wissen ob z.b. Assert() selber noch Daten im Stack liegen hat. Es muß also noch experimentell gestestet werden damit wie nicht den Stack zerstören.<br>
                        Aber generell ist es dadurch möglich das nach dem INT3 ein RET steht das direkt an die Adresse NACH dem Assert() zurückspringt.<br>

                        Gruß Hage

                        Comment


                        • #13
                          Hoppla.
                          <p>Mit den letzten 2,5 Postings hast du mich überfordert. :-)
                          <br>Was meinst du mit "virtueller Exception" und "stillem raise"?
                          Sowas wie in SysUtils.RaiseAssertException? Das dortige Vorgehen
                          zerstört mir leider die lokalen Variablen im Umfeld des Assert.
                          Das würde ich gerne vermeiden, falls möglich.
                          <br>Den asm-Block hab ich auch mal eingebaut, bin aber immer
                          wieder bzgl. einer "privilegierten Anweisung" ermahnt worden.
                          Ich hab ein bisschen mit der Reihenfolge der Anweisungen experimentiert
                          (irgendwie erschien mir die Reihenfolge der PUSHs und POPs unsymmetrisch :-)),
                          gelegentlich hat der Debugger dann sogar bei dem einen oder anderen RET angehalten,
                          die Ermahnung kam aber immer. Allerdings bin ich auch intuitiv und
                          völlig wissensfrei vorgegangen.
                          <p>Ciao, Uli.
                          <p>PS: Nochmal Danke für deine Zeit. Ich bin ja immer wieder platt,
                          was für eine Unmenge von Infos du unter uns Halbwissenden verteilst.
                          (Ich will ja nicht von Perlen und Säuen sprechen... :-)

                          Comment


                          • #14
                            Ok, heute abend setze ich mich mal ran. Das mit der Modifizierung des Callerstacks müsste eigentlich funktionieren. Nur eben das man sowas life testen muß da der Callerstack je nach übergeordnetem Code sich ändern kann.<br>
                            Im nachhinein würde ich sowieso lieber eine eigene Assemblerprocedure code da man dadurch geordnete Verhältnisse schafft.<br>

                            Das mit dem Exceptionhandler ist ein bißchen anders. Ich rede hier nicht von dem offensichtlichen Exceptionframe den man in SysUtils.pas o.ä. vorfindet. Normalerweise besteht ein Exceptionhandler aus folgendem
                            <pre>

                            @@1: try
                            @@2:
                            @@3: except
                            @@4: end;

                            </pre>

                            @@1 richtet inline des SEH ein, @@3 ist unser Code der ausgeführt wird wenn eine Exception ausgelösst wurde, zwischen @@1 und @@2. @@4 ist die finalization des SEH. Nun, wenn eine Exception auftritt wird der unsichtbare Code an @@2 aufgerufen, da diese Adresse in @@1 eingerichtet wurde. Der Compiler legt dort Code an der in die RTL springt und diese arbeitet abhänig vom Typ die Exception ab. Jenachdem teilt dieser RTL Code dem OS mit was passieren soll und gibt als Rücksrungadresse @@3 an. Nun, dieses Stückchen Code von @@2 bis RTL wird durch das OS wie eine Callbackfunktion behandelt und läuft auf Ring 0, d.h. höchster Priviligierungsstufe, so hoch wie VXD/SYS Treiber.<br> Dies ist auch der Grund warum Exceptions so langsam sind, da zwischen den Priveligierungen gewechselt wird.<br>

                            Statt nun einen normalen Delphi SEH einzurichten würden wir einen ähnlichen aber NICHT gleichen SEH per assembler einrichten. Statt @@3 als Rücksprungadresse würden wir eine ganz andere Adresse benutzen. Im Grunde ist es damit möglich an einer beliebigen Adresse im Program die Ausführung mit freiwählbaren Registern weiterlaufen zu lassen <br>

                            Gruß Hage

                            Comment


                            • #15
                              <pre>

                              <code><font size=2 face="Courier New"><b>procedure </b>ChoiceAssertErrorHandler(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer);
                              <b>var
                              </b>Behaviour: TAssertionBehaviour;
                              <b>begin
                              </b>Addr := ErrorAddr;
                              <br>
                              <b>if </b>AssertionBehaviour = abUserChoice <b>then
                              </b>Behaviour := GetUserChoice(<b>Message</b>, Filename, LineNumber, ErrorAddr)
                              <b>else
                              </b>Behaviour := AssertionBehaviour;
                              <b>case </b>Behaviour <b>of
                              </b>abHalt: FatalExit(666);
                              abDebug:
                              <b>asm
                              </b>JMP @@2
                              @@1: INT 3
                              RET
                              @@2: MOV EAX,OFFSET @@1
                              MOV [EBP + 4],EAX
                              <b>end</b>;
                              abDelphi:
                              OldAssertErrorProc(<b>Message</b>, Filename, LineNumber, ErrorAddr);
                              <b>else </b><i>//abIgnore:
                              // nix
                              </i><b>end</b>;
                              <b>end</b>;
                              </font>
                              </code></pre>

                              Damit kommste eine Ebene näher ran.<br>
                              Ich habe es zwar auch schon geschafft das der Debugger hinter der Assert() stehen bleibt, aber nur weil ich die Debugregister Dr0-Dr7 als Breakpoints benutzt habe. Leider reagiert der Debugger in D5 nicht korrekt darauf.<br>
                              Die einzigste, saubere Möglichkeit, sehe ich nun darin durch ein Interface, sprich BPL mit Interprocesskommunikation, auf die Delphi IDE aus dem Program heraus zuzugreifen. Diese Schnittstelle sollte dann einen Breakpoint IN der IDE an der entsprechenden Stelle setzen. <br> Normalerweise müsste man irgendwie dieses auch direkt dem Debugger mitteilen, Delphi macht dies ja auch.<br>

                              Gruß hage

                              Comment

                              Working...
                              X