waldbauer.com

waldbauer.com (http://www.waldbauer.com/vb/index.php)
-   SPI OA4 Open Access II/III/IV (2,3,4) Anwender Forum (http://www.waldbauer.com/vb/forumdisplay.php?f=57)
-   -   OpenAccess IV String handling bug (http://www.waldbauer.com/vb/showthread.php?t=2303)

OpenAccess IV String handling bug
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo zusammen,

Mich ereilte kürzlich ein weiterer Bugreport zu OpenAccess IV:

Problembeschreibung
Versucht man, in einem OA4 Programm eine dynamische View mithilfe von mehreren Variablen zu erstellen, so hängt sich das kompilierte Programm unter bestimmten Bedingungen in einer Endlosschleife auf:

1. Es müssen mehrere variablen miteinander verkettet werden (zumindest 2)
2. Am Ende der Bedingung muss ein Anführungszeichen gefolgt von einer beliebigen Anzahl von Zeichen stehen (also kein direktes Ende des Strings)

Code:

STR v1=""
STR v2=""
BOOL pause=TRUE

PUT "OAT", DO NEWLINE, DO NEWLINE ;GET pause

PUT "Mit Leerzeichen aber ohne 2. Variable";GET pause
v1='FROM kunden WHERE kdnr>1 AND name>"" '  ! kein Absturz
LOCAL VIEW daten=v1 
USE daten
USE daten END
PUT DO NEWLINE, "Geht!", DO NEWLINE

PUT "mit 2. Variable aber ohne Zeichen nach Anfz.";GET pause
v1='FROM kunden WHERE kdnr>1 AND name>""'
v2=""
LOCAL VIEW daten=v1&v2 
USE daten
USE daten END
PUT DO NEWLINE, "Geht!", DO NEWLINE

PUT "mit Leerzeichen nach Anfuerhrungsz. -> Absturz";GET pause
v1='FROM kunden WHERE kdnr>1 AND name>" " '
v2=" "
LOCAL VIEW daten=v1&v2 
USE daten
USE daten END
PUT DO NEWLINE, "Geht nicht!", DO NEWLINE

PUT "anderes Beispiel fuer Zeichen nach Anfz.";GET pause
v1='FROM kunden WHERE kdnr>1 AND name>"" affe'  ! Absturz
v2="blablabla"
LOCAL VIEW daten=v1&v2
USE daten
USE daten END
PUT DO NEWLINE, "Geht auch nicht!", DO NEWLINE

Dasselbe funktioniert aber problemlos in OpenAccess III!

Ursache
Die Suche nach diesem Fehler gestaltet sich sehr schwierig. OA4 bleibt nämlich im virtuellen P-Code hängen, über den ich ja schonmal in diesem Thread erzählt habe.
Kurz gesagt sind das virtuelle opcodes, welche eine bestimmte Bedeutung haben und dann von der PCode-Runtime, auf der OpenAccess basiert, ausgeführt werden. Bislang hatte ich ja keinerlei Informationen zu diesen Opcodes, aber durch intensive Internet-Recherche habe ich tatsächlich eine Doku zum von OA verwendeten SofTech P-System gefunden!
1-140.41.A_pSysInternArc_83.pdf
Durch ein break in einem Debugger konnte ich zumindest im Assemblercode einmal die Opcodes lesen, welche ausgeführt werden und habe dadurch dann die Stelle in der RT.LIB mit dem P-Code indentifiziert, welcher dort immer in einer Endlosschleife geloopt ist.
Zu beachten ist, dass der Code ursprünglich in der RT.LIB liegt, aber beim Einbinden von Programmen auch in die APP.SPI kopiert wird. Dh. die RT.LIB ist in jedem Fall zu patchen und die APP.SPI, falls sich dort der Code drinnen befindet (also beim Patcher nicht wundern, wenn er die APP.SPI nicht patcht).
Zur weiteren Analyse habe ich mir dann einen eigenen Disassembler geschrieben, welcher die binären Opcodes in lesbaren "P-Code Assemblercode" wandelt.
Ausgestattet damit habe ich anschließend den P-Code disasembliert und die Stelle aufgezeichnet, wo die Endlosschleife auftritt. In den Kommentaren stehen die Werte, die zum Zeitpunkt der Ausführung im Speicher waren (konnte ich wiederum über den debugger lesen).
Um den Code zu verstehen empfiehlt sich die Lektüre des oben erwähten Handbuchs:

Code:

00000000 87 80 89      LDL 137        ; 0x04
00000003 87 80 88      LDL 136        ; 0x0B
00000006 B3            GEQI            ; 0x04 >= 0x0B -> False
00000007 9F            BNOT            ; !False -> True
00000008 D5 1D 02      FJPL 541 (->552); If False, then Exit
00000011 87 80 8A      LDL 138        ; 0x00 = False
00000014 D4 13          FJP 19 (->35)  ; False Jump to 35!
00000016 87 80 89      LDL 137
00000019 ED            INCI
00000020 A4 80 89      STL 137
00000023 00            SLDC0
00000024 86 09          LAO 9
00000026 87 80 89      LDL 137
00000029 94 0D 04      CXG 13 , 4
00000032 6B            SSTL4
00000033 01            SLDC1
00000034 69            SSTL2
00000035 80 20          LDCB 32        : 32
00000037 6E            SSTL7          ; Store 32 into 7
00000038 23            SLDL4          ; 0x7802
00000039 E7 03          INC 3          ; 0x7808
00000041 00            SLDC0          ; 0
00000042 A7            LDB            ; DS:[7808] = D6 = 214 = Length of string
00000043 6D            SSTL6          ; Store D6 into 6
00000044 21            SLDL2          ; 0xD7
00000045 25            SLDL6          ; 0xD6
00000046 B2            LEQI            ; SLDL2 (0xD7) <= SLDL6 (0xD6) -> False
00000047 23            SLDL4          ; 0x7802
00000048 E7 03          INC 3          ; 0x7808
00000050 21            SLDL2          ; 0xD7
00000051 A7            LDB            ; DS:[0x7808+0xD7] = DS[0x78DF] = 0
00000052 80 27          LDCB 39        ; 39 = '
00000054 B1            NEQI            ; 0 <> 39 -> True
00000055 A1            LAND            ; False AND True -> False
00000056 23            SLDL4          ; 0x7802
00000057 E7 03          INC 3          ; 0x7808
00000059 21            SLDL2          ; 0xD7
00000060 A7            LDB            ; DS:[0x7808+0xD7] = DS[0x78DF] = 0
00000061 80 22          LDCB 34        ; 34 = "
00000063 B1            NEQI            ' 0 <> 34 -> True
00000064 A1            LAND            ; False AND True -> False
00000065 D4 05          FJP 5 (->72)    ; False Jump to 72
00000067 21            SLDL2
00000068 ED            INCI
00000069 69            SSTL2
00000070 8A E4          UJP -28 (->44)
00000072 21            SLDL2          ; 0xD7
00000073 25            SLDL6          ; 0xD6 (strlen)
00000074 B2            LEQI            ; SLDL2 (0xD7) <= SLDL6 (0xD6) -> False
00000075 D5 D7 01      FJPL 471 (->549); False Jump to 549 -> Jump to 0
......
00000105 21            SLDL2          ; 0xD7
00000106 25            SLDL6          ; 0xD6
00000107 B2            LEQI            ; SLDL2 (0xD7) <= SLDL6 (0xD6) -> False
00000108 9F            BNOT            ; !False -> True
00000109 D5 A3 01      FJPL 419 (->531); False Jump 531
.....
00000529 8A 12          UJP 18 (->549)
00000531 21            SLDL2          ; 0xD7
00000532 25            SLDL6          ; 0xD6
00000533 B3            GEQI            ; 0xD7>=0xD6 -> True
00000534 F1 09          TJP 9 (->545)  ; Jump on True
00000536 21            SLDL2
00000537 ED            INCI
00000538 69            SSTL2
00000539 00            SLDC0
00000540 A4 80 8A      STL 138
00000543 8A 04          UJP 4 (->549)
00000545 01            SLDC1          ; 1
00000546 A4 80 8A      STL 138        ; Load next String = 1
00000549 8B D8 FD      UJPL -552 (->0)
00000552 96 81 8B      RPU 395

Die Routine prüft vermutlich am Anfang, ob alle zu verarbeitenden Strings gelesen wurden und springt auf der Return am Ende, wenn das der Fall ist.
In unserem Fall also nicht.
Der lokale Wert 138, der anschließend geprüft wird, scheint ein Boolean-Wert zu sein, der besagt, ob der nächste String gelesen werden soll oder nicht. Bei uns ist er 0, also springt er weiter auf Adresse 35, wo der aktuelle String verarbeitet wird. Der aktuelle String hatte eine Länge von 214 (=0xD6 hex) Bytes. Die Länge wird in Arbeitsregister 6 gespeichert.
Der aktuelle Zeiger für das zu verarbeitende Zeichen (in Arbeitsregister 2) ist jedoch schon hinter der Stringlänge, nämlich auf 215 (=0xD7 hex).
Es folgen 2 Prüfungen auf einfache und doppelte Anführungszeichen, die beide nicht erfüllt werden können, da man sich ja schon hinter der Stringlänge befindet. Wenn der aktuelle Stringzeiger eben größer der Stringlänge ist, dann wird auf Adresse 549 gesprungen, welche wiederum an den Anfang der Routine zurückspringt und so loopt OA4 fröhlich in einer Endlosschleife vor sich hin.
Bei genauerer Betrachtung der Routine findet man an Adresse 531, welche auch an Adresse 109 referenziert wird, dieselbe Prüfung, die jedoch bei Überschreiten der maximalen Stringlänge den Boolean-Wert in 138 auf 1 setzt und erst dann zurückspringt. Wenn der Wert 1 am Anfang ist, wird der nächste String gelesen, also eigentlich genau das, was man in diesem Fall möchte.
Daraus ergibt sich ein denkbar einfacher Patch:
Man ändert der Sprung an Adresse 75 vom Ziel 549, welches ja direkt wieder loopt, auf 545, wo dann auch korrekterweise der BOOL-Wert gesetzt wird, um zum nächsten String zu springen.
Ergibt also einfach eine Änderung der des Hex-Werts D7 an Offset 75 in D3 und der Fehler ist behoben :)

Wuerde man den Assemblercode als C-Pseudocode schreiben, so kann man das Problem einfacher nachvollziehen:

Code:

// strings == Apex-String mit pString[0] = Stringlaenge
void ParseStrings(char **strings, int nStrings)
{
        int iStr;      // Aktueller String
        int iPos, iStrLen;
        int iPos2, iStrLen2;
        char *pString;
        BOOL bNext = TRUE;
        char cByte;

        for (iStr=0; iStr<nStrings; )
        {
                if (bNext)
                {
                        pString = strings[++iStr];
                        iPos = 1;
                }
                cByte=' ';
                iStrLen = pString[0];
                /* Seek zum ersten Anfuehrungszeichen */
                /* Beim 2. Durchlauf kommen wir hier an das Ende des Strings, denn
                  es gibt kein Anfuehrungszeichen mehr! */
                while (iPos <= iStrLen && pString[iPos]<>"'" && pString[iPos]<>"\"")
                        iPos++;
                /* Beim 2 Durchlauf ist hier iPos > iStrLen, aber bNext ist FALSE! */
                if (iPos > iStrLen) continue;  // FEHLER: Kein bNext gesetzt, ENDLOSSCHLEIFE!!
                cByte = pString[iPos];  // Aktuelles Anfuehrungszeichen in cByte
                iPos++;
                /* Weiterlaufen bis zum naechsten Anfuehrungszeichen = Ende des gequoteten Strings */
                while (iPos <= iStrLen && (pString[iPos] != cByte))
                        iPos++;
                /* Ab hier ist bNext gesetzt, damit keine Gefahr mehr bis Schleifenende! */
                bNext = TRUE;
                /* Wenn das letzte Zeichen ein Anfuehrungszeichen ist, dann ist hier
                  Schluss und OK */
                if (iPos > iStrlen) continue;
                /* Wenn alle Strings verarbeitet, abbruch, also auch kein Problem */
                if (iStr > nStrings) continue;
                /* Auf zum naechten String */
                pString = strings[++iStr];
                iStrLen2[0] = pString[0];
                /* Im naechsten String wieder nach nem Anfuehrungszeichen suchen. */
                iPos2 = 1;
                while (iPos2 <= iStrLen2 && (pString[iPos2] != cByte))
                        iPos2++;
                /* Achtung: Im urspruenglichen String in iPos befinden wir uns immer
                  noch auf einer Position VOR dem Ende!!
                  Sehen wir also wieder am den Beginn der Schleife weiter. */
                // ....
                // restlicher Code fuer Bug uninteressant
                //
                bNext = FALSE; // Ab hier wieder Gefahr eines Haengers!
        }
}

Patch
Einen praktischen kleinen Patcher für den Hausgebrauch gibts wie immer im Anhang.

Lg. und gute Nacht ;)

Funktioniert perfekt - vielen Dank!
Gruß
Hans Jürgen

Für alle die sich NICHT registrieren wollen, gibt es den Patcher natürlich wie immer auch auf der Download Seite: http://www.waldbauer.com/tmp/reference.php


Alle Zeitangaben in WEZ +1. Es ist jetzt 17:04 Uhr.

Powered by vBulletin® Version 3.8.7 (Deutsch)
Copyright ©2000 - 2024, vBulletin Solutions, Inc.