Lenguaje ensamblador - Procesamiento de cadenas
El procesamiento de cadenas es una necesidad común en la programación.
Procesar cadenas en ensamblador significa manipular directamente secuencias de bytes en la memoria, aunque es de más bajo nivel también es más flexible.
Definición y almacenamiento de cadenas
En NASM, una cadena es esencialmente una secuencia de bytes, que se puede definir con la pseudoinstrucción DB:
Ejemplo
section .data
; Método 1: Cadena estándar
str1 db 'Hello, ejemplo!', 0 ; Estilo C: terminada en null
; Método 2: Con salto de línea
str2 db 'Line1', 0xA, 'Line2', 0xA
; Método 3: Las comillas invertidas admiten secuencias de escape
str3 db `hello\nworld\n`, 0 ; Convierte automáticamente las secuencias de escape
; Método 4: Definición carácter por carácter
str4 db 'A', 'B', 'C', 'D', 0
; Método 5: Usar dup para generar caracteres repetidos
border db 40 dup('-') ; 40 guiones
; Cálculo de la longitud de la cadena (en tiempo de compilación)
str1_len equ $ - str1 ; Incluye el null final
Calcular la longitud de una cadena
Hay dos formas de obtener la longitud de una cadena: cálculo en tiempo de compilación (EQU) y cálculo en tiempo de ejecución (recorrer en busca de null):
Ejemplo
; Dos formas de calcular la longitud de una cadena
section .data
; Método A: Cálculo en tiempo de compilación (para cadenas constantes)
msg1 db 'Hello, EJEMPLO!', 0xA
msg1_len equ $ - msg1 ; Cálculo automático en tiempo de compilación
; Método B: Cadena terminada en null (cálculo en tiempo de ejecución)
msg2 db 'Find my length', 0 ; Termina en 0
section .text
global _start
_start:
; Método A: Usar directamente la longitud calculada en tiempo de compilación
mov eax, 4
mov ebx, 1
mov ecx, msg1
mov edx, msg1_len ; Usar la constante directamente
int 0x80
; Método B: Calcular en tiempo de ejecución la longitud de una cadena terminada en null
mov esi, msg2 ; esi apunta al inicio de la cadena
mov ecx, 0 ; Contador
strlen_loop:
cmp byte [esi], 0 ; ¿El byte actual es null?
je strlen_done ; Si es así, terminar
inc ecx ; Contador +1
inc esi ; Puntero +1
jmp strlen_loop
strlen_done:
; ecx ahora contiene la longitud de la cadena (sin incluir null)
mov eax, 4
mov ebx, 1
mov ecx, msg2
mov edx, ecx ; Usar la longitud calculada
; Aquí hay un problema: ecx ha sido sobrescrito, debería guardarse primero
; Forma correcta: push ecx y luego pop a edx
mov eax, 1
mov ebx, 0
int 0x80
Copia de cadenas
Usa un bucle para copiar byte a byte, o utiliza las instrucciones de operación con cadenas de x86.MOVSB:
Ejemplo
; Dos implementaciones de copia de cadenas
section .data
src db 'ejemplo source string', 0
src_len equ $ - src
section .bss
dest_manual resb 64 ; Destino de copia manual
dest_fast resb 64 ; Destino de copia rápida
section .text
global _start
_start:
; Método A: Copia manual byte a byte
mov esi, src ; Dirección de origen
mov edi, dest_manual ; Dirección de destino
mov ecx, src_len ; Número de bytes
copy_loop:
mov al, [esi] ; Leer un byte
mov [edi], al ; Escribir un byte
inc esi ; Puntero de origen ++
inc edi ; Puntero de destino ++
loop copy_loop
; Método B: Usar instrucciones de operación con cadenas (más rápido)
cld ; Limpiar el indicador de dirección (DF=0, copia hacia adelante)
mov esi, src ; Dirección de origen (ESI)
mov edi, dest_fast ; Dirección de destino (EDI)
mov ecx, src_len ; Número de bytes (ECX)
rep movsb ; Repetir movsb ecx veces
; rep movsb:while(ecx>0) { [edi]=[esi]; esi++; edi++; ecx--; }
; Verificación: mostrar los dos resultados de la copia
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 el método clásico de copia de cadenas en x86. Sin embargo, en las CPU modernas, un bucle de copia manual combinado con desenrollado de bucle puede ser más rápido que REP MOVSB, porque las CPU modernas tienen una mejor optimización de pipeline para operaciones simples.
Comparación de cadenas
UsaCMPSBjunto conREPE(repetir hasta que no sean iguales) para comparar byte a byte:
Ejemplo
; Comparar si dos cadenas son iguales
section .data
str_a db 'ejemplo', 0
str_b db 'ejemplo', 0
str_c db 'EJEMPLO', 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 ; Comparación hacia adelante
mov esi, str_a ; Primera cadena
mov edi, str_b ; Segunda cadena
mov ecx, 7 ; Comparar como máximo 7 bytes (incluyendo null)
repe cmpsb ; Repetir la comparación hasta que no sean iguales o ecx=0
; repe: si ZF=1 (iguales) y ecx>0, continúa
; cmpsb: compara [esi] y [edi], luego esi++, edi++
je strings_equal ; Si ZF=1, todos los bytes son iguales
; No iguales
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:
; Comparar str_a y str_c (difieren en mayúsculas/minúsculas)
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
Resumen de instrucciones de operaciones con cadenas
| Instrucción | Función | Registros utilizados |
|---|---|---|
| MOVSB | Copiar byte: [EDI] = [ESI] | ESI=origen, EDI=destino, ECX=veces, DF=dirección |
| MOVSW | Copiar palabra (2 bytes) | Igual que arriba |
| MOVSD | Copiar doble palabra (4 bytes) | Igual que arriba |
| STOSB | Almacenar byte: [EDI] = AL | EDI=destino, AL=valor, ECX=veces |
| LODSB | Cargar byte: AL = [ESI] | ESI=origen |
| CMPSB | Comparar byte: [ESI] - [EDI] | ESI=origen, EDI=destino, ECX=veces |
| SCASB | Escanear byte: AL - [EDI] | EDI=destino, AL=valor buscado, ECX=veces |
Ejemplo de conversión de mayúsculas y minúsculas
Ejemplo
; Convertir las letras minúsculas de la cadena a mayúsculas
section .data
msg db 'Hello, ejemplo! Welcome to Assembly.', 0xA
len equ $ - msg
section .text
global _start
_start:
; Mostrar la cadena original
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
; Conversión: recorrer la cadena, minúscula -> mayúscula
mov esi, msg ; Apuntar al inicio de la cadena
mov ecx, len ; Número de iteraciones del bucle
convert_loop:
mov al, [esi] ; Leer carácter
cmp al, 'a' ; ¿Es >= 'a'?
jb next_char ; Menor que 'a', omitir
cmp al, 'z' ; ¿Es <= 'z'?
ja next_char ; Mayor que 'z', omitir
; Minúscula a mayúscula: 'a'(97) - 'A'(65) = 32
sub al, 32 ; Convertir a mayúscula
mov [esi], al ; Escribir de vuelta
next_char:
inc esi
loop convert_loop
; Mostrar la cadena convertida
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Resultado de la ejecución:
$ nasm -f elf32 case_convert.asm -o case_convert.o $ ld -m elf_i386 case_convert.o -o case_convert $ ./case_convert Hello, ejemplo! Welcome to Assembly. HELLO, EJEMPLO! WELCOME TO ASSEMBLY.
Otras extensionesEl indicador de dirección DF determina la dirección de las operaciones con cadenas: cuando DF=0, ESI/EDI se incrementan (hacia adelante); cuando DF=1, se decrementan (hacia atrás). Se usa
CLDpara limpiar DF, y se usaSTDpara establecer DF. Antes de llamar a funciones C o llamadas al sistema, se debe asegurar que DF=0 (requisito de cdecl).