Assemblersprache - String-Verarbeitung
Die String-Verarbeitung ist eine häufige Anforderung in der Programmierung.
Der Umgang mit Strings in Assembler bedeutet die direkte Manipulation von Byteweihlen im Speicher; es ist hardwarenäher, aber auch flexibler.
Definition und Speicherung von Strings
In NASM sind Strings im Wesentlichen Byteströme und können mit der Pseudoanweisung DB definiert werden:
Beispiel
section .data
; Methode 1: Standard-String
str1 db 'Hello, example!', 0 ; C-Stil: mit Null-Byte abgeschlossen
; Methode 2: mit Zeilenumbruch
str2 db 'Line1', 0xA, 'Line2', 0xA
; Methode 3: Backticks unterstützen Escape-Zeichen
str3 db `hello\nworld\n`, 0 ; Escape-Sequenzen automatisch konvertieren
; Methode 4: Zeichen für Zeichen definieren
str4 db 'A', 'B', 'C', 'D', 0
; Methode 5: dup verwenden, um wiederholte Zeichen zu erzeugen
border db 40 dup('-') ; 40 Bindestriche
; String-Längenberechnung (zur Kompilierzeit)
str1_len equ $ - str1 ; Einschließlich des abschließenden Null-Bytes
Berechnung der String-Länge
Es gibt zwei Möglichkeiten, die String-Länge zu ermitteln: Berechnung zur Kompilierzeit (EQU) und Laufzeitberechnung (Durchlaufen bis zum Null-Byte):
Beispiel
; Zwei Wege zur Berechnung der String-Länge
section .data
; Methode A: Berechnung zur Kompilierzeit (für konstante Strings)
msg1 db 'Hello, EXAMPLE!', 0xA
msg1_len equ $ - msg1 ; Automatisch berechnet zur Kompilierzeit
; Methode B: Null-terminierter String (Laufzeitberechnung)
msg2 db 'Find my length', 0 ; Endet mit 0
section .text
global _start
_start:
; Methode A: Die zur Kompilierzeit berechnete Länge direkt verwenden
mov eax, 4
mov ebx, 1
mov ecx, msg1
mov edx, msg1_len ; Konstante direkt verwenden
int 0x80
; Methode B: Länge eines null-terminierten Strings zur Laufzeit berechnen
mov esi, msg2 ; esi zeigt auf den Anfang des Strings
mov ecx, 0 ; Zähler
strlen_loop:
cmp byte [esi], 0 ; Ist das aktuelle Byte null?
je strlen_done ; Ja, Ende
inc ecx ; Zähler + 1
inc esi ; Zeiger + 1
jmp strlen_loop
strlen_done:
; ecx enthält nun die String-Länge (ohne Null-Byte)
mov eax, 4
mov ebx, 1
mov ecx, msg2
mov edx, ecx ; Berechnete Länge verwenden
; Hier ist ein Problem: ecx wird überschrieben, sollte vorher gesichert werden
; Korrektes Vorgehen: ecx pushen und dann nach edx poppen
mov eax, 1
mov ebx, 0
int 0x80
String-Kopie
Verwenden Sie eine Schleife zum Byteweisen Kopieren oder x86-String-BefehleMOVSB:
Beispiel
; Zwei Implementierungen des String-Kopierens
section .data
src db 'example source string', 0
src_len equ $ - src
section .bss
dest_manual resb 64 ; Manuelles Ziel zum Kopieren
dest_fast resb 64 ; Schnelles Ziel zum Kopieren
section .text
global _start
_start:
; Methode A: Manuelles Bytweises Kopieren
mov esi, src ; Quelladresse
mov edi, dest_manual ; Zieladresse
mov ecx, src_len ; Byte-Anzahl
copy_loop:
mov al, [esi] ; Ein Byte lesen
mov [edi], al ; Ein Byte schreiben
inc esi ; Quellzeiger++
inc edi ; Zielzeiger++
loop copy_loop
; Methode B: String-Operationen verwenden (schneller)
cld ; Richtungs-Flag löschen (DF=0, Vorwärtskopieren)
mov esi, src ; Quelladresse (ESI)
mov edi, dest_fast ; Zieladresse (EDI)
mov ecx, src_len ; Byte-Anzahl (ECX)
rep movsb ; movsb ecx-mal wiederholt ausführen
; rep movsb: while(ecx>0) { [edi]=[esi]; esi++; edi++; ecx--; }
; Überprüfen: Die beiden Kopierergebnisse ausgeben
mov eax, 4
mov ebx, 1
mov ecx, dest_manual
mov edx, src_len
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, dest_fast
mov edx, src_len
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
REP MOVSBEs ist die klassischste Methode zum Kopieren von Strings auf x86. Auf modernen CPUs kann jedoch eine manuelle Kopierschleife mit Schleifenabrollung (Loop Unrolling) schneller sein als REP MOVSB, da moderne CPUs eine bessere Pipeline-Optimierung für einfache Operationen bieten.
String-Vergleich
Verwenden SieCMPSBzusammen mitREPEByteweiser Vergleich (wiederholen bis ungleich):
Beispiel
; Vergleichen, ob zwei Strings gleich sind
section .data
str_a db 'example', 0
str_b db 'example', 0
str_c db 'EXAMPLE', 0
eq_msg db 'Strings are equal', 0xA
eq_len equ $ - eq_msg
ne_msg db 'Strings are NOT equal', 0xA
ne_len equ $ - ne_msg
section .text
global _start
_start:
cld ; Vorwärtsvergleich
mov esi, str_a ; Erster String
mov edi, str_b ; Zweite Zeichenkette
mov ecx, 7 ; Bis zu 7 Bytes vergleichen (einschließlich Null)
repe cmpsb ; Vergleich wiederholen, bis ungleich oder ecx=0
; repe: fortfahren, wenn ZF=1 (gleich) und ecx>0
; cmpsb: [esi] und [edi] vergleichen, dann esi++, edi++
je strings_equal ; Wenn ZF=1, sind alle Bytes gleich
; Nicht gleich
mov eax, 4
mov ebx, 1
mov ecx, ne_msg
mov edx, ne_len
int 0x80
jmp compare_next
strings_equal:
mov eax, 4
mov ebx, 1
mov ecx, eq_msg
mov edx, eq_len
int 0x80
compare_next:
; str_a und str_c vergleichen (unterschiedliche Groß-/Kleinschreibung)
cld
mov esi, str_a
mov edi, str_c
mov ecx, 7
repe cmpsb
jne not_equal_2
mov eax, 4
mov ebx, 1
mov ecx, eq_msg
mov edx, eq_len
int 0x80
jmp exit
not_equal_2:
mov eax, 4
mov ebx, 1
mov ecx, ne_msg
mov edx, ne_len
int 0x80
exit:
mov eax, 1
mov ebx, 0
int 0x80
Zusammenfassung der Zeichenketten-Befehle
| Anweisung | Funktion | Verwendete Register |
|---|---|---|
| MOVSB | Byte kopieren: [EDI] = [ESI] | ESI=Quelle, EDI=Ziel, ECX=Anzahl, DF=Richtung |
| MOVSW | Wort kopieren (2 Bytes) | Wie oben |
| MOVSD | Doppelwort kopieren (4 Bytes) | Wie oben |
| STOSB | Byte speichern: [EDI] = AL | EDI=Ziel, AL=Wert, ECX=Anzahl |
| LODSB | Byte laden: AL = [ESI] | ESI=Quelle |
| CMPSB | Byte vergleichen: [ESI] - [EDI] | ESI=Quelle, EDI=Ziel, ECX=Anzahl |
| SCASB | Byte scannen: AL - [EDI] | EDI=Ziel, AL=Suchwert, ECX=Anzahl |
Beispiel zur Konvertierung der Groß-/Kleinschreibung
Beispiel
; Kleinbuchstaben in einer Zeichenkette in Großbuchstaben umwandeln
section .data
msg db 'Hello, example! Welcome to Assembly.', 0xA
len equ $ - msg
section .text
global _start
_start:
; Ursprüngliche Zeichenkette ausgeben
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
; Konvertierung: Zeichenkette durchlaufen, Kleinbuchstabe -> Großbuchstabe
mov esi, msg ; Auf den Anfang der Zeichenkette zeigen
mov ecx, len ; Schleifenzähler
convert_loop:
mov al, [esi] ; Zeichen lesen
cmp al, 'a' ; Ist es >= 'a'
jb next_char ; Kleiner als 'a', überspringen
cmp al, 'z' ; Ist es <= 'z'
ja next_char ; Größer als 'z', überspringen
; Klein zu Groß: 'a'(97) - 'A'(65) = 32
sub al, 32 ; In Großbuchstaben umwandeln
mov [esi], al ; Zurückschreiben
next_char:
inc esi
loop convert_loop
; Konvertierte Zeichenkette ausgeben
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Ausführungsergebnis:
$ nasm -f elf32 case_convert.asm -o case_convert.o $ ld -m elf_i386 case_convert.o -o case_convert $ ./case_convert Hello, example! Welcome to Assembly. HELLO, EXAMPLE! WELCOME TO ASSEMBLY.
Weitere ErweiterungenDas Richtungsflag DF bestimmt die Richtung von Zeichenkettenoperationen: Wenn DF=0, inkrementieren ESI/EDI (vorwärts), wenn DF=1, dekrementieren sie (rückwärts). Verwenden Sie
CLDum DF zu löschen, undSTDum DF zu setzen. Stellen Sie vor dem Aufruf von C-Funktionen oder Systemaufrufen sicher, dass DF=0 ist (cdecl-Anforderung).