GELÖST: Schleife wird nicht richtig gezählt (LOOP mit MASM32)
-
Hallo Gemeinde,
ich habe eine Funktion erstellt die mir einen String in einem Text sucht und
mir die Stelle mit dem Beginn ausgeben soll. Hab in der WinApi nichts dergleichen
gefunden und deshalb die Arbeit
Soweit so gut, hier mal die kleine Prozedur:mov ecx, durchl ;ECX mit 9 laden suchestring: push ecx ;ECX sichern inc esi ;ESI auf nächstes Zeichen des Speichers push esi ;++++++++++++++++++++++++++++++++++++++++++++++++++ call vergleiche ; Prozedur vergleiche aufrufen ;++++++++++++++++++++++++++++++++++++++++++++++++++ cmp ergebnis, TRUE ;Zeichen gefunden? je gefunden ;wenn ja weiter zu "gefunden" mov edi, offset suchstring ;EDI wieder auf den Anfang des Suchstrings pop esi ;ESI vom Stack holen pop ecx ;ECX vom Stack holen loop suchestring ;Nächste Runde jmp ende_suchestring ;nach allen Durchläufen zu "ende_suchstring" gefunden: pop esi ;ESI vom Stack holen pop ecx ;ECX vom Stack holen ende_suchestring: mov eax, durchl ;EAX mit 9 laden sub eax, ecx ;EAX = 9 - Rundenzahl call PRINT ;EAX in ASCII umwandeln invoke SetWindowText, porthwnd, eax ;ASCII-Zeichen nach Feld porthwnd schreiben
Als Text habe ich "1234567890" genommen und als Suchstring "12" und "23".
Und genau hier klafft eine Lücke.Während mir der Suchstring "12" den Wert 0 zurück gibt, was auch stimmt,
bekomme ich von Suchstring "23" den Wert 2 zurück (1 währe aber richtig).Meine Schleife wird mit ECX = 9 gestartet.
Bei Suchstring "12" wird LOOP nicht gewertet, da der String in der ersten
Runde gefunden wurde und ECX enthällt immer noch den Wert 9.Ab Zeile 30 wird EAX mit dem Wert 9 geladen und ECX (9) davon subtrahiert.
Ergebnis ist 0ABER:
Suchstring "23" wird in der ersten Runde nicht gefunden. LOOP decrementiert
ECX auf 8 und wieder von vorn.
Im zweiten Durchgang wird "23" gefunden und wir verlassen die Schleife.
Ab Zeile 30 wiederum erhält EAX den Wert 9 und ECX "müsste" 8 enthalten.Ich habe ECX mal zwischengespeichert um mir den Wert anzusehen. Er ist "7".
9 - 7 = 2...
Seit gestern Abend sitze ich nun hier und finde den (Denk)Fehler nicht.Falls es hilfreich sein sollte, hier noch die Prozedur "vergleichen"
Ich bin mir aber sicher das hier kein Fehler vorliegt.vergleiche proc ;ESI auf Text- 1 ;EDI auf Suchstring - 1 pusha mov ecx, suchl dec ecx mov ergebnis, 1 vergleichen: inc esi inc edi mov al, byte ptr [esi] mov ah, byte ptr [edi] cmp al, ah jne ungleich loop vergleichen jmp ende_vergleichen ungleich: mov ergebnis, 0 ende_vergleichen: popa Ret vergleiche EndP
Wenn die Schleife komplett durchlaufen wird, bleibt "Ergebnis" auf 1.
Wird sie vorzeitig verlassen (String nicht gleich), wird "Ergebnis" auf 0 gesetzt.Gruß, Nicky
Habe den Fehler gefunden... Zeile 19 hat gefehlt dec edi, da in der PROC "vergleiche"
ESI und EDI mit den String's -1 aufgerufen werden!
Nun geht allesmov ecx, durchl ;ECX mit 9 laden suchestring: push ecx ;ECX sichern inc esi ;ESI auf nächstes Zeichen des Speichers push esi ;++++++++++++++++++++++++++++++++++++++++++++++++++ call vergleiche ; Prozedur vergleiche aufrufen ;++++++++++++++++++++++++++++++++++++++++++++++++++ cmp ergebnis, TRUE ;Zeichen gefunden? je gefunden ;wenn ja weiter zu "gefunden" mov edi, offset suchstring ;EDI wieder auf den Anfang des Suchstrings dec edi pop esi ;ESI vom Stack holen pop ecx ;ECX vom Stack holen loop suchestring ;Nächste Runde jmp ende_suchestring ;nach allen Durchläufen zu "ende_suchstring" gefunden: pop esi ;ESI vom Stack holen pop ecx ;ECX vom Stack holen ende_suchestring: mov eax, durchl ;EAX mit 9 laden sub eax, ecx ;EAX = 9 - Rundenzahl call PRINT ;EAX in ASCII umwandeln invoke SetWindowText, porthwnd, eax ;ASCII-Zeichen nach Feld porthwnd schreiben
-
Prima Job.
Aber Wahnsinn wie oft push/pop verwendet wird. Beispielsweise mit ECX.
Man kann anstelle von loop auch ersatzweise "dec register" "jnz Adresse" verwenden.
Das könnte dann so aussehen. (Nur mal so als Anregung gedacht.)push ebp ; ganz am Anfang falls man es sichern muss
....mov ebp, suchl
dec ebp
......
vergleichen:
......
dec ebp
jnz vergleichen....
pop ebp ; am EndeWenn diese Such-Routine doch nur von einer einzigen Stelle aus angesprungen wird, dann macht es wenig Sinn dafür eine Subroutine zu nehmen.
Ich würde die Such-Routine wieder in die andere Routine mit integrieren. Das macht das Listing kommpakter und übersichtlicher.Nirgendwo wird EDX, oder EBX verwendet. Anstelle innerhalb einer Schleife push und pop zu verwenden könnte man dort Registerinhalte retten und wieder auslesen. Dadurch werden Zugriffe auf den Stack im langsamen Speicher vermieden.
Beispiel:
push edx ; nur am Anfang einmal wenn nötig
schleife:
mov edx, esi....
mov esi, edx
loop schleifepop edx ; nur wenn wir edx nicht mehr brauchen, es oben aber retten mussten
Dirk
-
freecrac schrieb:
Prima Job.
Aber Wahnsinn wie oft push/pop verwendet wird. Beispielsweise mit ECX.
Man kann anstelle von loop auch ersatzweise "dec register" "jnz Adresse" verwenden.
Das könnte dann so aussehen. (Nur mal so als Anregung gedacht.)push ebp ; ganz am Anfang falls man es sichern muss
....mov ebp, suchl
dec ebp
......
vergleichen:
......
dec ebp
jnz vergleichen....
pop ebp ; am EndeWenn diese Such-Routine doch nur von einer einzigen Stelle aus angesprungen wird, dann macht es wenig Sinn dafür eine Subroutine zu nehmen.
Ich würde die Such-Routine wieder in die andere Routine mit integrieren. Das macht das Listing kommpakter und übersichtlicher.Nirgendwo wird EDX, oder EBX verwendet. Anstelle innerhalb einer Schleife push und pop zu verwenden könnte man dort Registerinhalte retten und wieder auslesen. Dadurch werden Zugriffe auf den Stack im langsamen Speicher vermieden.
Beispiel:
push edx ; nur am Anfang einmal wenn nötig
schleife:
mov edx, esi....
mov esi, edx
loop schleifepop edx ; nur wenn wir edx nicht mehr brauchen, es oben aber retten mussten
Dirk
Hallo Dirk,
dachte ja nicht daß das noch gelesen wird
Ich habe diese Routine umgeschrieben als Prozedur, da sie mehrmals aufgerufen
wird. Dazu kann ich ihr Paramter übergeben mit dem Text, zu suchender String
und ab welcher Stelle gesucht werden soll.Als Rückgabe erhalte ich die gefundene Stelle im Text oder -1 wenn nichts
gefunden wurde.
Damit kann ich jetzt auch bestimmte Textstellen extrahieren.Gibts sowas nicht in der WinApi??? In .NET gibts Instr()
Mit dem Push/Pop hast du wohl recht, ich schau mal nach wo ich nur einen Wert
retten muss, dann nehme ich ein freies Register.Meistens habe ich jedoch 2 Register zu sichern.
Ich bin bei Windows immer etwas vorsichtig, da mir die WinApi mehr oder weniger
alle Registerwerte zerstört.
Mit LOOP kann ich zudem noch erkennen das es eine Schleife istin_string proc text:DWORD,suchtext:DWORD,abzeichen:DWORD LOCAL stelle:DWORD,durchl:DWORD ;text = Zeiger auf den zu durchsuchenden Text ;suchtext = Zeiger auf den Suchstring ;abzeichen = ab welcher Stelle im Text soll gesucht werden ; mov stelle, 0d ;Lokale Variable mit NULL erstellen mov edx, text ;EBX zeigt auf den Anfang des Speichers call LEN mov ecx, eax ;ECX enthält die Anzahl der Buchstaben im Speicher mov ebx, text ;Zeiger des Speichers nach EBX umwandeln: mov al, byte ptr [ebx] ;Lade einen Buchstaben nach AL cmp al, "A" ;Vergleich AL mit "A" jb kein_grossbuchstabe cmp al, "Z" jg kein_grossbuchstabe add al, 32d ;Groß nach Klein umwandlen = +32d mov byte ptr [ebx], al inc ebx loop umwandeln jmp fertig_umwandeln kein_grossbuchstabe: inc ebx loop umwandeln fertig_umwandeln: invoke SetWindowText, edithwnd, text ;Speicher wieder ins Editfeld schreiben in Kleinbuchstaben ;++++++++++++++++Umwandlung abgeschlossen.. ausgenommen sind ü,ö,ä und ß nur reines ASCII kann umgewandelt werden mov eax, gesamtl ;Speicherlänge nach EAX mov ebx, suchl ;Anzahl der zu suchenden Buchstaben nach EBX sub eax,ebx inc eax ;durchl = EAX - EBX + 1 mov durchl, eax ;Durchläufe in durchl speichern ; Das erste Vorkommen im String herausfinden, Rückgabe ist das x. Zeichen! mov esi, text ;ESI auf Speicherbereich add esi, abzeichen ;Ab welchem Zeichen wird gesucht??? Std = 0 mov edi, suchtext ;EDI auf den Anfang des Suchstrings dec esi dec esi ;ESI - 2 (vor den Speicherbereich) dec edi ;EDI - 1 (vor den Speicherbereich) mov ecx, durchl ;ECX mit den Schleifendurchgängen laden ;dec ecx suchestring: push ecx ;ECX sichern inc esi ;ESI auf nächstes Zeichen des Speichers push esi ;++++++++++++++++++++++++++++++++++++++++++++++++++ call vergleiche ; Prozedur vergleiche aufrufen ;++++++++++++++++++++++++++++++++++++++++++++++++++ cmp ergebnis, TRUE ;Zeichen gefunden? je gefunden ;wenn ja weiter zu "gefunden" mov edi, suchtext ;EDI wieder auf den Anfang des Suchstrings dec edi pop esi ;ESI vom Stack holen pop ecx ;ECX vom Stack holen loop suchestring ;Nächste Runde jmp ende_suchestring ;nach allen Durchläufen zu "ende_suchstring" gefunden: pop esi ;ESI vom Stack holen pop ecx ;ECX vom Stack holen ende_suchestring: mov eax, durchl ;EAX mit 9 laden sub eax, ecx ;EAX = 9 - Rundenzahl mov edx, eax cmp eax, durchl jb zeichen_gefunden mov eax, -1 mov stelle, eax mov eax, edx jmp fehler_im_vergleich zeichen_gefunden: mov stelle, eax fehler_im_vergleich: mov eax, stelle Ret in_string EndP
Wenn alles läuft werde ich auf jeden Fall mal nachsehen was ich verbessern kann.
Gruß und einen schönen Abend noch
Nicky
-
StrStr/I
Ansonsten CRT.Außerdem: LOOP&Co sollte man nicht mehr verwenden (siehe Dokumentation)
-
Guten Morgen
supernicky schrieb:
Hallo Dirk,
dachte ja nicht daß das noch gelesen wird
Solange es nicht zu viele Befehle sind, macht es nicht so viel Umstände.
Manchmal schaue ich mir nur die Struktur an wie die Befehle gruppiert sind und welche Befehle verwendet werden, ohne zu überprüfen ob alle Zeiger und Registerinhalte stimmen, oder welches Ergebniss herauskommt.Ich habe diese Routine umgeschrieben als Prozedur, da sie mehrmals aufgerufen
wird. Dazu kann ich ihr Paramter übergeben mit dem Text, zu suchender String
und ab welcher Stelle gesucht werden soll.Als Rückgabe erhalte ich die gefundene Stelle im Text oder -1 wenn nichts
gefunden wurde.
Damit kann ich jetzt auch bestimmte Textstellen extrahieren.Ach so, dann ist es zweckmäßig es als Unterprogramm zu verwenden.
Mit dem Push/Pop hast du wohl recht, ich schau mal nach wo ich nur einen Wert
retten muss, dann nehme ich ein freies Register.Ich vermeide es meistens Push/Pop zu verwenden.
Das hat verschiedene Gründe:
a) auf älteren Rechner dauern diese Befehle viele Taktzyklen lang
b) weil der Stackpointer sich verändert ist der Überblick wo welche Werte sind schwerer
c) wenn man einen Wert später noch einmal benötigt nachdem er von Stack gepopt wurde muss er vorher erneut gepusht werden, damit er nicht verloren gehtAlternativ dazu kann man im Datenbereich genug Platz für unsere zu rettenden Werte reservieren, wenn keine Register mehr frei sind.
Beispiel:
ZWISCHENWERT DB ?, ?, ?, ?
BLABLAPOINTER DD ?
RETTEAX1 DD ?
RETTEAX2 DD ?
RETTEBX1 DD ?
RETTECX1 DD ?
RETTEDX1 DD ?
...usw..Nun können wir z.B. mit "mov [RETTEAX1], eax" den Wert retten und danach beliebig oft mit "mov eax, [RETTEAX1]" auslesen, solange dort kein neuer Wert reingeschrieben wird.
Der Ort wo unser Wert gespeichert wurde ist damit quasi unverwechselbar und immer leicht zu adressieren.Meistens habe ich jedoch 2 Register zu sichern.
Es kommt natürlich immer darauf an wie konplex eine Aufgabe ist.
Wenn man mit drei Tabellen arbeitet, die unterschiedlich aufgebaut sind, dann wird man manchmal wohl auch drei Adressregister-Paare benötigen, um einen Zugriff auch die Inhalte der Tabellen vornehmen zu können.
Dabei können jede Menge Werte zu retten sein, je nachdem was mit den Werten gemacht werden soll.Ich bin bei Windows immer etwas vorsichtig, da mir die WinApi mehr oder weniger
alle Registerwerte zerstört.Wenn man ganz am Anfang alle Registerinhalte rettet und ganz am Ende wieder herstellt, dann kann nichts passieren.
In unseren eigenen Subroutinen brauchen wir nur die Register retten die wir dort auch verwenden und nur dann, wenn es wirklich erforderlich ist.
Wenn nach der Subroutine das von uns verwendete Register eh wieder neu geladen wird, dann brauchen wir dieses Register auch nicht retten und können es bis dahin frei benutzen.Mit LOOP kann ich zudem noch erkennen das es eine Schleife ist
Mhm, das mag schon sein.
Doch man kann sich relativ schnell umgewöhnen und dann auch Befehle mit "dec" und "jnz" als Schleife erkennen. (Mit dec wird beim erreichen von Null das Zeroflag gesetzt.)
Weil sonst nehmen wir kein dec-Befehl sondern wenn es geht leiber den lea-Befehl(wenn gar keine Flags verändert werden brauchen).
Beispiel: "lea ecx, [ecx-1]"Wenn alles läuft werde ich auf jeden Fall mal nachsehen was ich verbessern kann.
Dadurch bekommt man auch einen Blick dafür es nächstes Mal gleich so zu machen, auch wenn es eine Weile dauert bis man es verinnerlicht.
Gruß und einen schönen Abend noch
Nicky
Einen schönen Tag wünsche ich auch allen Mitlesern.
Dirk