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 0 👍

    ABER:

    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 alles 🙄

    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
    			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 Ende

    Wenn 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 schleife

    pop 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 Ende

    Wenn 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 schleife

    pop 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 ist 😃

    in_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 geht

    Alternativ 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


Anmelden zum Antworten