Announcement

Collapse
No announcement yet.

DebugBreak und Delphi

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

  • #16
    <pre>

    <code><font size=2 face="Courier New"><b>unit </b>Asserts;
    <i>{$A+,B-,C-,D-,E-,F-,G+,H+,I-,J+,K-,L-,M-,N+,O+,P+,Q-,R-,S-,T-,U+,V+,W-,X+,Y-,Z1}
    <br>
    </i><b>interface
    <br>
    type
    </b>TAssertionBehaviour = (abIgnore, abHalt, abDebug, abUserChoice, abDelphi);
    <br>
    <b>procedure </b>InitChoiceAssert(Behaviour: TAssertionBehaviour);
    <b>procedure </b>DoneChoiceAssert;
    <br>
    <b>implementation
    <br>
    uses
    </b>Windows, SysConst, SysUtils;
    <br>
    <b>type
    </b>TAssertErrorProc = <b>procedure</b>(<b>const Message</b>, FileName: <b>String</b>; LineNumber: Integer; ErrorAddr: Pointer);
    <br>
    <b>var
    </b>AssertionBehaviour: TAssertionBehaviour;
    OldAssertErrorProc: TAssertErrorProc;
    <br>
    <b>function </b>GetUserChoice(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer): TAssertionBehaviour;
    <b>const
    </b>cNewLine = #13#10;
    cUserChoiceMsg = '%s' + cNewLine +
    '(%s, Zeile %d, Adresse $%x)' + cNewLine +
    cNewLine +
    'Wollen Sie die Anwendung beenden (&quot;Abbrechen&quot,' + cNewLine +
    'debuggen (&quot;Wiederholen&quot oder die Assertion ignorieren?';
    <b>var
    </b>Choice: Integer;
    S: <b>string</b>;
    <b>begin
    </b>S := Format(cUserChoiceMsg,
    [<b>Message</b>, 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><i>//IDIGNORE:
    </i>Result := abIgnore;
    <b>end</b>;
    <b>end</b>;
    <br>
    <b>function </b>AssertAddr: PChar;
    <b>asm </b><i>// get address of _Assert in System.pas
    </i>MOV EAX,OFFSET System.@Assert
    <b>end</b>;
    <br>
    <b>procedure </b>Patch(Addr: PChar);
    <b>type </b><i>// patch at Addr to INT 3; CALL EDX
    </i>PBreakpoint = ^TBreakpoint;
    TBreakpoint = <b>packed record
    </b>INT3: Byte;
    CALL_EDX: Word;
    <b>end</b>;
    <b>var
    </b>Protect: DWord;
    <b>begin
    if </b>VirtualProtect(Addr, SizeOf(TBreakpoint), PAGE_READWRITE, @Protect) <b>then
    begin
    </b>PBreakpoint(Addr).INT3 := $CC;
    PBreakpoint(Addr).CALL_EDX := $D2FF;
    VirtualProtect(Addr, SizeOf(TBreakpoint), Protect, <b>nil</b>);
    FlushInstructionCache(GetCurrentProcess, Addr, SizeOf(TBreakpoint));
    <b>end</b>;
    <b>end</b>;
    <br>
    <b>procedure </b>Unpatch(Addr: PChar);
    <b>type </b><i>// patch at Addr CALL _Assert, we restore so above patch
    </i>PCall = ^TCall;
    TCall = <b>packed record
    </b>OpCode: Byte;
    Offset: Integer;
    <b>end</b>;
    <b>var
    </b>Protect: DWord;
    <b>begin
    if </b>VirtualProtect(Addr, SizeOf(TCall), PAGE_READWRITE, @Protect) <b>then
    begin
    </b>PCall(Addr).OpCode := $E8;
    PCall(Addr).Offset := AssertAddr - Addr - SizeOf(TCall);
    VirtualProtect(Addr, SizeOf(TCall), Protect, <b>nil</b>);
    FlushInstructionCache(GetCurrentProcess, Addr, SizeOf(TCall));
    <b>end</b>;
    <b>end</b>;
    </font>
    </code></pre>
    &#10

    Comment


    • #17
      <pre>

      <code><font size=2 face="Courier New"><br>
      <b>procedure </b>ChoiceAssertErrorHandler(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer);
      <b>asm
      </b>PUSH EBX <i>// save EBX
      </i>CMP AssertionBehaviour,abUserChoice
      MOV BL,AssertionBehaviour <i>// set BL to default behavior
      </i>JNZ @@1 <i>// AssertBehavior &lt;&gt; abUserChoice ??
      <br>
      </i>PUSH ErrorAddr <i>// NO, ask user
      </i>CALL GetUserChoice
      MOV BL,AL <i>// set BL to users choice behavior
      <br>
      </i>@@1: CMP BL,abHalt
      JNZ @@2 <i>// behavior &lt;&gt; abHalt ??
      </i>MOV EAX,666 <i>// NO, then FatalExist(666)
      </i>CALL FatalExit
      JMP @@9 <i>// jmp to restore stack, but never called normaly
      <br>
      </i>@@2: CMP BL,abDelphi <i>// behavior &lt;&gt; abDelphi ??
      </i>JNZ @@3
      PUSH ErrorAddr <i>// NO, delphi's assertion handling
      </i>CALL OldAssertErrorProc
      JMP @@9
      <br>
      @@3: CMP AL,abDebug <i>// behavior &lt;&gt; abDebug ??
      </i>JNZ @@9
      POP EBX <i>// NO, the take a breakpoint
      </i>POP EBP <i>// first restore EBX and EBP
      </i>POP EAX <i>// remove returnaddress to System._Assert
      </i>POP EAX <i>// get two time address of our assertion in source
      </i>POP EAX <i>// this address points right after CALL System._Assert
      </i>SUB EAX,3 <i>// subtract 3 bytes where we patch in INT 3, CALL EDX
      </i>PUSH EAX <i>// save EAX = patch address
      </i>CALL Patch <i>// patch in INT 3, CALL EDX
      </i>POP EAX <i>// restore address in EAX
      </i>MOV EDX,OFFSET @@8 <i>// setup EDX to @@8
      </i>JMP EAX <i>// jump to INT 3, CALL EDX right after out assertion in sourcecode
      </i>@@8: SUB EAX,2 <i>// subtract 2 from patch address, we need 5 bytes space for restauration to CALL System._Assert
      </i>CALL Unpatch <i>// restore original code
      </i>POP EAX <i>// get returnaddress from our CALL EDX, points now after Assert() in Sourcecode
      </i>JMP EAX <i>// jump to it
      <br>
      </i>@@9: POP EBX <i>// restore EBX, and let cleanup the compiler
      <br>
      { Remarks:
      To let the Delphi debugger do the work properly we MUST working with jumps to absolute addresses.
      In such a way as above.
      Tricks with RET or equals don't work properly, the debugger don't steps trough the following
      sourcecode right after our assertion.
      }
      </i><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>.
      </font>
      </code></pre&gt

      Comment


      • #18
        Hi Ulrich

        Obiger Code arbeitet so wie du es dir vorstellst. Jetzt solltest Du ihn noch ausführlich einem Härtetest unterziehen. D.h. ob alle lokalen Variablen auch im Watch korrekt angezeigt werden, ob der Code auch mit verschachtelten Procedure funtioniert usw. usw.<br>

        Der Code geht von Deiner Grundidee aus, hält aber wenn abDebug gewählt wird im Sourcecode genau über der fehlgeschlagenen Assertion() an. Mit F8,F7 usw. kann dann so als wäre ein Breakpoint im Source gesetzt weiterdebuggt werden.<br>

        Der von mir benutzte Trick patch, also modifiziert den Code der Anwendung. So daß ein Breakpoint = INT3 an die Stelle wo der Aufruf zur Assert() Routine erfolgt, eingefügt wird. Gleich nach diesem Breakpoint wird ein CALL zurück in unseren Code durchgeführt damit dieser Patch wieder entfernt wird.<br>
        Beim Debugging per F7,F8 usw. merkt man davon nichts, selbst der CPU View, wenn einmal offen, zeigt unseren Patch nicht an.<br>
        Die Compilerschalter gleich nach Unit Asserts sind wichtig. Ansonsten würdest du mit dem Debugger ständig in unseren Code springen.

        Gruß Hagen

        PS: achso, ich habe ihn natürlich NUR unter D5 entwickelt und getestet. Man muß also unbedingt auch noch die anderen Delphi Versionen austesten

        Comment


        • #19
          Eines noch, falls der unwahrscheinliche Fall eintritt das VirtualProtect() in Patch() nicht funktionieren sollte, so sollte man eine Exception auslösen. Anderenfalls springt der Code an eine Codeadresse mitten in einen OpCode rein und lösst eine Exception aus. Noch besser ist es sogar wenn Patch mit einem Resultat zurückkehrt, und falls nicht gepatcht werden konnte die procedure ChoiceAssertErrorHandler() eine Alternative wählt, zb. die EAssertitionError Exception auslösst.<br>

          Gruß Hagen

          PS: ich hoffe du verzeihst mir meine Tippfehler in den Remarks

          Comment


          • #20
            Oh mann, es nimmt kein Ende.<br>
            Also, wenn man den Code startet OHNE integrierten Debugger dann und denoch den Breakpoint setzten lässt, so kommt wie erwartet einen Externe Exception mit Fehlercode $80000003. Dies ist absolut korrekt da diese Exception die Breakpoint Exception ist.<br>
            Aber, da diese Exception unbehandelt ist durch unserer Anwendung wir bei einem erneuten Test der gleichen Funktion der Anwendungsstack zerstört. Es entsteht eine Zugriffsverletzung.<br>

            So, die mögliche Lösung liegt auf der Hand. Entweder
            <li>detektieren ob die Anwendung im Debugger gestartet wurde, oder
            <li>eine Exceptionframe in ChoiceAssertErrorHandler() einrichten.

            Bei der zweiten Möglichkeit fängt also die Anwendung selber diese Breakpointexception ab, falls KEIN Debugger dies tut.<br>

            Ich muß das mal testen.

            Gruß Hage

            Comment


            • #21
              <pre>

              <code><font size=2 face="Courier New"><b>unit </b>Asserts;
              <i>{$A+,B-,C-,D-,E-,F-,G+,H+,I-,J+,K-,L-,M-,N+,O+,P+,Q-,R-,S-,T-,U+,V+,W-,X+,Y-,Z1}
              <br>
              </i><b>interface
              <br>
              type
              </b>TAssertionBehaviour = (abDelphi, abIgnore, abHalt, abDebug, abUserChoice);
              <br>
              <b>procedure </b>InitChoiceAssert(Behaviour: TAssertionBehaviour = abDelphi);
              <br>
              <b>implementation
              <br>
              uses
              </b>Windows, SysConst, SysUtils;
              <br>
              <b>type
              </b>TAssertErrorProc = <b>procedure</b>(<b>const Message</b>, FileName: <b>String</b>; LineNumber: Integer; ErrorAddr: Pointer);
              <br>
              <b>var
              </b>AssertionBehaviour: TAssertionBehaviour;
              OldAssertErrorProc: TAssertErrorProc;
              <br>
              <b>function </b>GetUserChoice(<b>const Message</b>, Filename: <b>String</b>; LineNumber: Integer; ErrorAddr: Pointer): TAssertionBehaviour;
              <b>const
              </b>cUserChoiceMsg = '%s'#13#10'(%s, Zeile %d, Adresse $%x)'#13#10#13#10 +
              'Wollen Sie die Anwendung beenden (&quot;Abbrechen&quot, ' +
              'debuggen (&quot;Wiederholen&quot oder die Assertion ignorieren?';
              <b>var
              </b>S: <b>String</b>;
              <b>begin
              </b>S := Format(cUserChoiceMsg, [<b>Message</b>, Filename, LineNumber, Pred(Integer(ErrorAddr))]);
              <b>case </b>Windows.MessageBox(GetForegroundWindow, PChar(S), PChar(sAssertionFailed),
              MB_ABORTRETRYIGNORE <b>or </b>MB_ICONERROR) <b>of
              </b>IDABORT: Result := abHalt;
              IDRETRY: Result := abDebug;
              <b>else
              </b>Result := abIgnore;
              <b>end</b>;
              <b>end</b>;
              <br>
              <b>function </b>Patch(Addr: PChar): Boolean;
              <i>// patch at Addr to INT 3; CALL EDX
              // if not possible the return FALSE, otherwise TRUE
              </i><b>type
              </b>PBreakpoint = ^TBreakpoint;
              TBreakpoint = <b>packed record
              </b>INT3: Byte;
              CALL_EDX: Word;
              <b>end</b>;
              <br>
              <b>var
              </b>Protect: DWord;
              <b>begin
              </b>Result := VirtualProtect(Addr, SizeOf(TBreakpoint), PAGE_READWRITE, @Protect);
              <b>if </b>Result <b>then
              try
              </b>PBreakpoint(Addr).INT3 := $CC;
              PBreakpoint(Addr).CALL_EDX := $D2FF;
              <b>finally
              </b>VirtualProtect(Addr, SizeOf(TBreakpoint), Protect, <b>nil</b>);
              FlushInstructionCache(GetCurrentProcess, Addr, SizeOf(TBreakpoint));
              <b>end</b>;
              <b>end</b>;
              <br>
              <b>function </b>AssertAddr: PChar;
              <i>// get address of _Assert in System.pas
              </i><b>asm
              </b>MOV EAX,OFFSET System.@Assert
              <b>end</b>;
              <br>
              <b>procedure </b>Unpatch(Addr: PChar);
              <i>// patch at Addr CALL System._Assert, we restore so above patch
              </i><b>type
              </b>PCall = ^TCall;
              TCall = <b>packed record
              </b>OpCode: Byte;
              Offset: Integer;
              <b>end</b>;
              <br>
              <b>var
              </b>Protect: DWord;
              <b>begin
              if </b>VirtualProtect(Addr, SizeOf(TCall), PAGE_READWRITE, @Protect) <b>then
              try
              </b>PCall(Addr).OpCode := $E8;
              PCall(Addr).Offset := AssertAddr - Addr - SizeOf(TCall);
              <b>finally
              </b>VirtualProtect(Addr, SizeOf(TCall), Protect, <b>nil</b>);
              FlushInstructionCache(GetCurrentProcess, Addr, SizeOf(TCall));
              <b>end</b>;
              <b>end</b>;
              </font>
              </code></pre&gt

              Comment


              • #22
                <pre>

                <code><font size=2 face="Courier New"><b>procedure </b>RaiseAssert(Addr: Pointer);
                <i>// Called whenever we working without debugger and another exception
                // instead of a breakpoint exception was raised.
                // This procedure MUST raise a Exception to leave the actual assertion procedure.
                // Addr points to our Assertition.
                </i><b>begin
                </b>OldAssertErrorProc('Asserthandler', 'Asserts.pas', 0, Addr);
                <b>end</b>;
                <br>
                <b>procedure </b>ChoiceAssertErrorHandler(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer);
                <b>asm
                </b>PUSH EBX <i>// save EBX
                </i>MOV BL,AssertionBehaviour <i>// set BL to default behaviour
                </i>CMP BL,abUserChoice
                JNZ @@1 <i>// AssertionBehavior &lt;&gt; abUserChoice ??
                <br>
                </i>PUSH ErrorAddr <i>// NO, ask user
                </i>CALL GetUserChoice
                MOV BL,AL <i>// set BL to users choice
                <br>
                </i>@@1: CMP BL,abHalt
                JNZ @@2 <i>// behaviour &lt;&gt; abHalt ??
                </i>MOV EAX,666 <i>// NO, then FatalExist(666)
                </i>CALL FatalExit
                JMP @@9 <i>// jmp to restore stack, but never called normaly
                <br>
                </i>@@2: CMP BL,abDelphi <i>// behaviour == abDelphi ??
                </i>JZ @@8
                <br>
                @@3: CMP BL,abDebug <i>// behaviour &lt;&gt; abDebug ??
                </i>JNZ @@9
                <br>
                PUSH EAX <i>// save Message
                </i>MOV EAX,ErrorAddr <i>// patch in our INT3, CALL EDX
                </i>SUB EAX,3
                CALL Patch
                TEST AL,AL <i>// successfully patched ??
                </i>POP EAX
                JZ @@8 <i>// NO, jump to @@6
                <br>
                </i>POP EBX <i>// first restore EBX and EBP
                </i>POP EBP
                POP EAX <i>// remove returnaddress to System._Assert
                </i>POP EAX <i>// pop two times address of our assertion in source
                </i>POP EAX <i>// this address points right after CALL System._Assert
                <br>
                </i>MOV EDX,OFFSET @@4 <i>// setup EDX to @@8
                </i>SUB EAX,3 <i>// subtract 3 bytes where we patch in INT 3, CALL EDX
                <br>
                </i>PUSH OFFSET @@6 <i>// install exceptionframe, to take care of
                </i>PUSH DWord Ptr FS:[0] <i>// unhandled breakpoint exception
                </i>MOV DWord Ptr FS:[0],ESP
                <br>
                JMP EAX <i>// jump to INT 3, CALL EDX right after out assertion in sourcecode
                <br>
                </i>@@4: SUB EAX,2 <i>// subtract 2 from patch address, we need 5 bytes space for restauration to CALL System._Assert
                </i>CALL Unpatch <i>// restore original code
                </i>POP EAX <i>// get returnaddress from our CALL EDX, points now after Assert() in Sourcecode
                <br>
                </i>POP DWord Ptr FS:[0] <i>// remove SEH
                </i>ADD ESP,4
                <br>
                JMP EAX <i>// jump to following code address
                <br>
                </i>@@5: SUB EAX,2 <i>// called everytime where our exceptionframe has
                </i>CALL Unpatch <i>// handled another exception instead of breakpoints
                </i>POP EAX
                <br>
                POP DWord Ptr FS:[0] <i>// remove SEH
                </i>ADD ESP,4
                <br>
                JMP RaiseAssert <i>// reraise specific exception
                <br>
                </i>@@6: MOV EAX,[ESP + 004h]

                Comment


                • #23
                  <pre>

                  <code><font size=2 face="Courier New"><b>procedure </b>RaiseAssert(Addr: Pointer);
                  <i>// Called whenever we working without debugger and another exception
                  // instead of a breakpoint exception was raised.
                  // This procedure MUST raise a Exception to leave the actual assertion procedure.
                  // Addr points to our Assertition.
                  </i><b>begin
                  </b>OldAssertErrorProc('Asserthandler', 'Asserts.pas', 0, Addr);
                  <b>end</b>;
                  <br>
                  <b>procedure </b>ChoiceAssertErrorHandler(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer);
                  <b>asm
                  </b>PUSH EBX <i>// save EBX
                  </i>MOV BL,AssertionBehaviour <i>// set BL to default behaviour
                  </i>CMP BL,abUserChoice
                  JNZ @@1 <i>// AssertionBehavior &lt;&gt; abUserChoice ??
                  <br>
                  </i>PUSH ErrorAddr <i>// NO, ask user
                  </i>CALL GetUserChoice
                  MOV BL,AL <i>// set BL to users choice
                  <br>
                  </i>@@1: CMP BL,abHalt
                  JNZ @@2 <i>// behaviour &lt;&gt; abHalt ??
                  </i>MOV EAX,666 <i>// NO, then FatalExist(666)
                  </i>CALL FatalExit
                  JMP @@9 <i>// jmp to restore stack, but never called normaly
                  <br>
                  </i>@@2: CMP BL,abDelphi <i>// behaviour == abDelphi ??
                  </i>JZ @@8
                  <br>
                  @@3: CMP BL,abDebug <i>// behaviour &lt;&gt; abDebug ??
                  </i>JNZ @@9
                  <br>
                  PUSH EAX <i>// save Message
                  </i>MOV EAX,ErrorAddr <i>// patch in our INT3, CALL EDX
                  </i>SUB EAX,3
                  CALL Patch
                  TEST AL,AL <i>// successfully patched ??
                  </i>POP EAX
                  JZ @@8 <i>// NO, jump to @@6
                  <br>
                  </i>POP EBX <i>// first restore EBX and EBP
                  </i>POP EBP
                  POP EAX <i>// remove returnaddress to System._Assert
                  </i>POP EAX <i>// pop two times address of our assertion in source
                  </i>POP EAX <i>// this address points right after CALL System._Assert
                  <br>
                  </i>MOV EDX,OFFSET @@4 <i>// setup EDX to @@8
                  </i>SUB EAX,3 <i>// subtract 3 bytes where we patch in INT 3, CALL EDX
                  <br>
                  </i>PUSH OFFSET @@6 <i>// install exceptionframe, to take care of
                  </i>PUSH DWord Ptr FS:[0] <i>// unhandled breakpoint exception
                  </i>MOV DWord Ptr FS:[0],ESP
                  <br>
                  JMP EAX <i>// jump to INT 3, CALL EDX right after out assertion in sourcecode
                  <br>
                  </i>@@4: SUB EAX,2 <i>// subtract 2 from patch address, we need 5 bytes space for restauration to CALL System._Assert
                  </i>CALL Unpatch <i>// restore original code
                  </i>POP EAX <i>// get returnaddress from our CALL EDX, points now after Assert() in Sourcecode
                  <br>
                  </i>POP DWord Ptr FS:[0] <i>// remove SEH
                  </i>ADD ESP,4
                  <br>
                  JMP EAX <i>// jump to following code address
                  <br>
                  </i>@@5: SUB EAX,2 <i>// called everytime where our exceptionframe has
                  </i>CALL Unpatch <i>// handled another exception instead of breakpoints
                  </i>POP EAX
                  <br>
                  POP DWord Ptr FS:[0] <i>// remove SEH
                  </i>ADD ESP,4
                  <br>
                  JMP RaiseAssert <i>// reraise specific exception
                  <br>
                  </i>@@6: MOV EAX,[ESP + 004h]

                  Comment


                  • #24
                    <pre>

                    <code><font size=2 face="Courier New"><b>procedure </b>RaiseAssert(Addr: Pointer);
                    <i>// Called whenever we working without debugger and another exception
                    // instead of a breakpoint exception was raised.
                    // This procedure MUST raise a Exception to leave the actual assertion procedure.
                    // Addr points to our Assertition.
                    </i><b>begin
                    </b>OldAssertErrorProc('Asserthandler', 'Asserts.pas', 0, Addr);
                    <b>end</b>;
                    <br>
                    </font>
                    </code></pre&gt

                    Comment


                    • #25
                      <pre>

                      <code><font size=2 face="Courier New"><b>procedure </b>RaiseAssert(Addr: Pointer);
                      <i>// Called whenever we working without debugger and another exception
                      // instead of a breakpoint exception was raised.
                      // This procedure MUST raise a Exception to leave the actual assertion procedure.
                      // Addr points to our Assertition.
                      </i><b>begin
                      </b>OldAssertErrorProc('Asserthandler', 'Asserts.pas', 0, Addr);
                      <b>end</b>;
                      <br>
                      </font>
                      </code></pre&gt

                      Comment


                      • #26
                        <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>asm
                        </b>PUSH EBX <i>// save EBX
                        </i>MOV BL,AssertionBehaviour <i>// set BL to default behaviour
                        </i>CMP BL,abUserChoice
                        JNZ @@1 <i>// AssertionBehavior &lt;&gt; abUserChoice ??
                        <br>
                        </i>PUSH ErrorAddr <i>// NO, ask user
                        </i>CALL GetUserChoice
                        MOV BL,AL <i>// set BL to users choice
                        <br>
                        </i>@@1: CMP BL,abHalt
                        JNZ @@2 <i>// behaviour &lt;&gt; abHalt ??
                        </i>MOV EAX,666 <i>// NO, then FatalExist(666)
                        </i>CALL FatalExit
                        JMP @@9 <i>// jmp to restore stack, but never called normaly
                        <br>
                        </i>@@2: CMP BL,abDelphi <i>// behaviour == abDelphi ??
                        </i>JZ @@8
                        <br>
                        @@3: CMP BL,abDebug <i>// behaviour &lt;&gt; abDebug ??
                        </i>JNZ @@9
                        <br>
                        PUSH EAX <i>// save Message
                        </i>MOV EAX,ErrorAddr <i>// patch in our INT3, CALL EDX
                        </i>SUB EAX,3
                        CALL Patch
                        TEST AL,AL <i>// successfully patched ??
                        </i>POP EAX
                        JZ @@8 <i>// NO, jump to @@6
                        <br>
                        </i>POP EBX <i>// first restore EBX and EBP
                        </i>POP EBP
                        POP EAX <i>// remove returnaddress to System._Assert
                        </i>POP EAX <i>// pop two times address of our assertion in source
                        </i>POP EAX <i>// this address points right after CALL System._Assert
                        <br>
                        </i>MOV EDX,OFFSET @@4 <i>// setup EDX to @@8
                        </i>SUB EAX,3 <i>// subtract 3 bytes where we patch in INT 3, CALL EDX
                        <br>
                        </i>PUSH OFFSET @@6 <i>// install exceptionframe, to take care of
                        </i>PUSH DWord Ptr FS:[0] <i>// unhandled breakpoint exception
                        </i>MOV DWord Ptr FS:[0],ESP
                        <br>
                        JMP EAX <i>// jump to INT 3, CALL EDX right after out assertion in sourcecode
                        <br>
                        </i>@@4: SUB EAX,2 <i>// subtract 2 from patch address, we need 5 bytes space for restauration to CALL System._Assert
                        </i>CALL Unpatch <i>// restore original code
                        </i>POP EAX <i>// get returnaddress from our CALL EDX, points now after Assert() in Sourcecode
                        <br>
                        </i>POP DWord Ptr FS:[0] <i>// remove SEH
                        </i>ADD ESP,4
                        <br>
                        JMP EAX <i>// jump to following code address
                        <br>
                        </i>@@5: SUB EAX,2 <i>// called everytime where our exceptionframe has
                        </i>CALL Unpatch <i>// handled another exception instead of breakpoints
                        </i>POP EAX
                        <br>
                        POP DWord Ptr FS:[0] <i>// remove SEH
                        </i>ADD ESP,4
                        <br>
                        JMP RaiseAssert <i>// reraise specific exception
                        </i></font>
                        </code></pre>
                        &#10

                        Comment


                        • #27
                          <pre>

                          <code><font size=2 face="Courier New"><br>
                          @@6: MOV EAX,[ESP + 004h] <i>// our Exceptionhandler, we known now NO debugger is present (small hint to Antidebgging tricks
                          </i>CMP DWord Ptr [EAX],080000003h <i>// ExceptionCode = $80000003, eg. it is a Breakpoint ??
                          </i>MOV EAX,[ESP + 00Ch] <i>// load thread context
                          </i>JZ @@7
                          MOV DWord Ptr [EAX + 0A8h],OFFSET @@5 <i>// set EDX to @@5 because not a breakpoint exception was raised
                          </i>@@7: INC DWord Ptr [EAX + 0B8h] <i>// context.eip +1, skip INT3 to CALL EDX
                          </i>SUB EAX,EAX <i>// 0 = continue_exception_frame
                          </i>RET
                          <br>
                          DB 'copyright 2003 by Hagen Reddmann, ' <i>//
                          </i>DB 'idea and basics by Ulrich Gerhardt'
                          <br>
                          @@8: PUSH ErrorAddr <i>// delphi's assert handling
                          </i>CALL OldAssertErrorProc
                          <br>
                          @@9: POP EBX <i>// restore EBX, and let cleanup the compiler
                          <br>
                          { Remarks:
                          To let the Delphi debugger do the work properly we MUST working with jumps to absolute addresses.
                          In such a way as above.
                          Tricks with RET or equals don't work properly, the debugger don't steps trough the following
                          sourcecode right after our assertion.
                          We have to take care to handle the stack correct and to use only registers EAX,EDX,ECX.
                          These three registers are preserved trough compiler for the assertions.
                          }
                          </i><b>end</b>;
                          <br>
                          <b>procedure </b>InitChoiceAssert(Behaviour: TAssertionBehaviour);
                          <b>begin
                          </b>AssertionBehaviour := Behaviour;
                          <b>if </b>Behaviour &lt;&gt; abDelphi <b>then </b>TAssertErrorProc(AssertErrorProc) := ChoiceAssertErrorHandler
                          <b>else </b>TAssertErrorProc(AssertErrorProc) := OldAssertErrorProc;
                          <b>end</b>;
                          <br>
                          <b>initialization
                          </b>OldAssertErrorProc := AssertErrorProc;
                          <b>finalization
                          </b>InitChoiceAssert;
                          <b>end</b>.
                          </font>
                          </code></pre&gt

                          Comment


                          • #28
                            Oh mann, mühsam nährt sich das Eichhörnchen beim Posten <br>

                            Also, obiger Code arbeitet nun einwandfrei. Ich habe noch das Exceptionhandling eingebaut, falls eben kein Debugger present ist.<br>

                            Geile Entspannung, das

                            Gruß Hagen

                            Übrigens, bevor jemand mich korregiert wegen der Aussage das der Exceptionhandler auf Ring0 läuft. Dies ist natürlich falsch. Ich habe da was mit einer "speziellen Methode" der Programmierung verwechselt :

                            Comment


                            • #29
                              Für alle die den Postings nicht so richtig folgen können.<br>

                              Obiger Source installiert einen eigenen Assertition Handler. Normalerweise wird durch den Delphi Assertition handler eine EAssertitionError Esxception ausgelösst falls die Assertition nicht zutirfft. Angezeigt werden dann der Dateiename und die Position im Quelltext.<br>

                              Die Idee von Ulrich war nun das bei der Entwicklung keine Exception ausgelösst wird sondern das der Debugger der IDE an der Quelltextposition in der die Asertition gemacht wurde stehen bleibt. So als ob man einen Breakpoint gesetzt hat.<br>

                              Mit obigem Source ist dies nun möglich. Dadurch kann man jetzt im Debug-Watch, überwachte Ausdrücke, auch alle seine Variablen abchecken.<br>

                              Zudem kann man diesen Handler so aktivieren das er den Programmierer fragt was passieren soll. Entweder wird die Anwendung beendet, oder der Debugger an die Assertition herrangeführt oder aber der Fehler einfach ignoriert. Das Program läuft dann so weiter als wäre nichts gewesen.<br>

                              Als kleines Beispiel:
                              <pre>

                              var
                              I: Integer;
                              begin
                              InitChoiceAssert(abUserChoice);<br>

                              I := 1;
                              Assert(I = 0, 'I <> 0');
                              Inc(I, I);
                              if I <> 0 then ;
                              end;<br>

                              </pre>

                              Ein weiterer Vorteil ist das man dises Verhalten per Program direkt beeinflussen kann.<br>

                              Also insgesamt eine sehr sinnvolle Sache.<br>

                              Gruß Hagen

                              PS: wer's testen will, bitte nicht vergessen in den Compileroptionen die Assertitions anzuchecken.<br&gt

                              Comment


                              • #30
                                Und noch eins drauf <br>
                                Kopiert folgenden Code in die Source:

                                <pre>

                                <code><font size=2 face="Courier New"><b>procedure </b>Debug(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer);
                                <b>var
                                </b>S: <b>String</b>;
                                <b>begin
                                </b>S := Format('Assert %s: %s at line %d on address $%p', [<b>Message</b>, FileName, LineNumber, ErrorAddr]);
                                OutputDebugString(PChar(S));
                                <b>end</b>;
                                <br>
                                <b>procedure </b>ChoiceAssertErrorHandler(<b>const Message</b>, Filename: <b>string</b>; LineNumber: Integer; ErrorAddr: Pointer);
                                <b>asm
                                </b>PUSH EAX
                                PUSH EDX
                                PUSH ECX
                                PUSH ErrorAddr
                                CALL Debug
                                POP ECX
                                POP EDX
                                POP EAX
                                <br>
                                PUSH EBX <i>// save EBX
                                </i>MOV BL,AssertionBehaviour <i>// set BL to default behaviour
                                </i>CMP BL,abUserChoice
                                JNZ @@1 <i>// AssertionBehavior &lt;&gt; abUserChoice ??
                                </i></font>
                                </code></pre>

                                Nun wird im Debugger Ereignisprotokoll sogar noch diese Assertion mitprotokolliert.

                                Gruß Hage

                                Comment

                                Working...
                                X