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

; Métodos de definición de cadenas

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

; Ruta del archivo: strlen_demo.asm
; 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

; Ruta del archivo: strcpy_demo.asm
; 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

; Ruta del archivo: strcmp_demo.asm
; 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ónFunciónRegistros utilizados
MOVSBCopiar byte: [EDI] = [ESI]ESI=origen, EDI=destino, ECX=veces, DF=dirección
MOVSWCopiar palabra (2 bytes)Igual que arriba
MOVSDCopiar doble palabra (4 bytes)Igual que arriba
STOSBAlmacenar byte: [EDI] = ALEDI=destino, AL=valor, ECX=veces
LODSBCargar byte: AL = [ESI]ESI=origen
CMPSBComparar byte: [ESI] - [EDI]ESI=origen, EDI=destino, ECX=veces
SCASBEscanear byte: AL - [EDI]EDI=destino, AL=valor buscado, ECX=veces

Ejemplo de conversión de mayúsculas y minúsculas

Ejemplo

; Ruta del archivo: case_convert.asm
; 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.

El 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 usaCLDpara 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).

Otras extensiones