USANDO STRINGS EN ASSEMBLY

Para manipular Strings en assembly, el programador dispone de los comandos MOV(X) para desplazar segmentos de memoria y valores en direcciones de memoria, sin embargo, para manipulación de Strings se cuenta con comandos MOVS(x) adicionales diseñados específicamente para el tratamiento de cadenas en Assembly, estos comandos adiciones son:

MOVSB: Mueve un byte (8 bits)
MOVSW: Mueve una palabra (16 bits)
MOVSL: Mueve una palabra doble (32 bits)

Estos comandos utilizan la memoria localizada en los registers ESI (Fuente) EDI (Destino)

Listado StringBasic.s

#Ejemplo de manipulacion de variables.dataHelloWorld:

.asciz «Hello Assembly»

H3110:

.asciz «H3110»

.bss

.lcomm Destination, 100

.lcomm DestinationUsingRep, 100

.lcomm DestinationUsingStos, 100

.text

.globl _start

_start:

nop

#1. Uso de funciones simples MOVSB, MOVSW, MOVSL

movl $HelloWorld, %esi

movl $Destination, %edi

movsb

movsw

movsl

#2. Estableciendo y limpiando el valor de DF

std

cld

#3. Usando la instruccion REP.

movl $HelloWorld, %esi

movl $DestinationUsingRep, %edi

movl $25, %ecx #Establece la longitud de la cadena al register ECX

cld #Necesario invocar esta instruccion para limpiar el flag DF de EFLAGS register

rep movsb

std

#4. Manejo de String con instrucciones LOD

cld

leal HelloWorld, %esi

lodsb

movb $0, %al

dec %esi #Decrementa el ESI 1 bytes, para que apunte a la posición inicial, solamente 1 byte debido a que se ha invocado la instrucción lodsb que mueve solamente 1 byte de memoria y es necesario que el ESI apunte nuevamente a su posición inicial de la cadena (a la que hace referencia, a saber, HelloWorld)

lodsw

movw $0, %ax

subl $2, %esi #El ESI debe apuntar nuevamente a la posición inicial de la cadena que referencia, por lo tanto se disminuye la dirección de memoria en 2 bytes.

lodsl

#5. Uso de las operaciones STOS(x)

leal DestinationUsingStos, %edi

stosb

stosw

stosl

#6. Comparando cadenas.

cld

leal HelloWorld, %esi

leal H3110, %edi

cmpsb

dec %esi

dec %edi

cmpsw

subl $2, %esi

subl $2, %edi

cmpsl

subl $4, %esi

subl $4, %edi

repz cmpsb

dec %esi

dec %edi

repnz cmpsb

#Salir del Programa.

movl $1, %eax

movl $0, %ebx

int $0x80

Para compilar: as –gstabs -o StringBasic.o StringBasic.s

Para enlazar el programa Objeto con un programa ejecutable: ld -o MovDemo MovDemo.o

Finalmente ejecutarlo: ./StringBasic

Procedemos a depurar con gdb: gdb ./StringBasic

Para depurar el funcionamiento de este programa simple y verificar el comportamiento de los operadores MOVS(X) establecemos un punto de ruptura en la linea: movl $HelloWorld, %esi

Una vez el depurador detiene su ejecución en dicha linea, se examina la memoria del siguiente modo:

(gdb) print /x &HelloWorld

$1 = 0x6000cc

Como puede verse, tenemos una dirección de memoria para la variable HelloWorld, ahora se procede a examinar la memoria del register %esi

(gdb) print /x $esi

$2 = 0x0

Después de ejecutar la linea de ruptura (movl $HelloWorld, %esi), los valores de memoria de la variable y el register son:

(gdb) print /x &Destination

$3 = 0x6000e8

(gdb) print /x $esi

$4 = 0x6000cc

Ahora se examina la memoria de la variable $Destination y el register %edi antes de ejecutar la siguiente instrucción en la secuencia de ejecución del programa (movl $Destination, %edi) los valores resultantes son:

Despues de ejecutar la instrucción:

(gdb) print /x &Destination

$5 = 0x6000e8

(gdb) print /x $edi

$6 = 0x6000e8

La siguiente linea corresponde a la instrucción MOVSB, la cual tomara como origen el valor del register ESI y lo establecera en el register destino EDI, en este ejemplo concreto, la dirección de memoria del register ESI corresponde con la variable &HelloWorld y el register destino EDI corresponde con la variable &Destination, por lo tanto el valor almacenado en la variable &HelloWorld sera asignado al valor de la dirección de memoria de la variable &Destination, Cabe anotar que ya que se trata del comando MOVSB, solamente moverá el primer byte desde el origen hacia el destino, es decir, solamente moverá a la dirección de memoria destino el valor correspondiente a la primera letra de la cadena del origen.

Después de ejecutar la instrucción MOVSB, examinamos la memoria de registers y variables para ver como han cambiado:
(gdb) x/1s &HelloWorld

0x6000cc <HelloWorld>: «Hello Assembly»

(gdb) x/1s &Destination

0x6000e8 <Destination>: «H»

Como se ha podido ver, solamente se ha copiado la primera letra, es decir, el primer Byte de la dirección de memoria de la variable &HelloWorld.

Ahora, la siguiente instrucción corresponde al comando MOVSW la cual ejecutara un movimiento de memoria similar al comando MOVSB, pero con la diferencia que este copiara 16 bytes de memoria a la variable a la que apunte el register EDI, después de ejecutar dicho comando y examinar la memoria los resultados son los siguientes:

(gdb) x/1s &HelloWorld

0x6000cc <HelloWorld>: «Hello Assembly»

(gdb) x/1s &Destination

0x6000e8 <Destination>: «Hel»

Posteriormente, al ejecutar la instruccion movsl

(gdb) x/1s &HelloWorld

0x6000cc <HelloWorld>: «Hello Assembly»

(gdb) x/1s &Destination
0x6000e8 <Destination>: «Hello A»

DF (Direction Flag) y EFLAGS Register

El DF es parte del Eflags register que se encarga de aumentar o disminuir el tamaño de los registers ESI y EDI en las llamadas a las instrucciones MOVSx

Una nota interesante de la ejecución de los comandos anteriores MOVSx es que el tamaño de las direcciones de memoria origen y destino (%esi y %edi) son automáticamente incrementadas o disminuidas en función de los movimientos de memoria que se lleven a cabo con los comandos MOVS(X).

Cuando se ejecuta la instrucción MOVSB los registers EDI y ESI se incrementan en 1 byte

Cuando se ejecuta la instrucción MOVSW los registers EDI y ESI se incrementan en 2 bytes

Cuando se ejecuta la instrucción MOVSL los registers EDI y ESI se incrementan en 4 bytes

Por ejemplo el examen de memoria antes y después de invocar la instrucción MOVSB para los registers es:

ANTES

(gdb) print /x $esi

$2 = 0x6000cc

(gdb) print /x $edi

$3 = 0x6000e8

(gdb) s

DESPUES

(gdb) print /x $esi

$4 = 0x6000cd

(gdb) print /x $edi

$5 = 0x6000e9

Como puede apreciarse las direcciones de memoria han aumentado en un byte para ambos registers.

El funcionamiento del DF es el siguiente:

si DF es establecido (el valor es 1)→ El ESI y EDI aumenta en el valor de la dirección de memoria.

si DF es borrado (el valor es 0) → El ESI y EDI disminuye/se mantiene en el valor de la dirección de memoria.

Es posible establecer el valor del DF usando la instrucción STD

Es posible limpiar el valor del DF usando la instrucción CLD

En la ejecución del programa StringBasic, se encuentran definidas estas instrucciones al finalizar la llamada a las instrucciones MOVS(x), usando GDB examinamos los valores del register EFLAGS en cada momento de su ejecución

Al momento de invocar la instrucción STD

ANTES:

(gdb) info registers

rax 0x0 0

rbx 0x0 0

rcx 0x0 0

rdx 0x0 0

rsi 0x6000d7 6291671

rdi 0x6000ef 6291695

……..

eflags 0x202 [ IF ]

……..

DESPUES:

(gdb) info registers

rax 0x0 0

rbx 0x0 0

rcx 0x0 0

rdx 0x0 0

rsi 0x6000d7 6291671

rdi 0x6000ef 6291695

……..

eflags 0x602 [ IF DF ]

……..

Como puede apreciarse se ha establecido el flag DF con lo cual el tamaño de la dirección de memoria aumentará.

Al momento de invocar la instrucción CLD

ANTES:

(gdb) info registers

rax 0x0 0

rbx 0x0 0

rcx 0x0 0

rdx 0x0 0

rsi 0x6000d7 6291671

rdi 0x6000ef 6291695

……..

eflags 0x602 [ IF DF ]

……..

DESPUES:

(gdb) info registers

rax 0x0 0

rbx 0x0 0

rcx 0x0 0

rdx 0x0 0

rsi 0x6000d7 6291671

rdi 0x6000ef 6291695

……..

eflags 0x202 [ IF ]

……..

Ahora después de la instrucción cld el flag DF ha sido removido del register EFLAGS.

Instrucción REP

El primer inconveniente de usar los comandos MOVS(x) es precisamente las longitudes de bytes que se pueden copiar de una dirección a otra, si lo que se desea es copiar longitudes con un tamaño considerable, seria necesario invocar una instrucción MOVS(x) varias veces, lo que resulta ilógico, ya que si por ejemplo deseamos copiar 1024 bytes (1K) seria necesario invocar la instrucción MOVSL 32 veces, por esta razón existe la instrucción REP que permite invocar instrucciones MOVS(x) en un ciclo constante hasta que el register ECX tenga un valor mayor que 0, es decir, hasta que se haya copiado todo el flujo de bytes de una dirección origen en una dirección destino.

Su uso es simple, en primer lugar es necesario indicar la longitud del String al register ECX y posteriormente invocar la instrucción REP con la correspondiente instrucción MOVS(x) de esta forma la instrucción REP repetirá la instrucción MOVS(x) hasta que todos los bytes hayan sido copiados al destino, esta instrucción es muy similar a las instrucciones WHILE y FOR en lenguajes de alto nivel como C, C++, Pascal o Java.

El uso de esta instrucción se contempla en el ejemplo anteriormente mencionado (StringBasic.s) comenzando en la linea: movl $HelloWorld, %esi

A continuación se detallan los movimientos de la memoria en cada paso de la ejecución del programa desde este punto:

(gdb) x/1s &HelloWorld

0x6000e0 <HelloWorld>: «Hello Assembly»

(gdb) x/1s &DestinationUsingRep

0x600160 <DestinationUsingRep>: «»

(gdb) x/1s $esi

0x6000e7 <HelloWorld+7>: «ssembly»

(gdb) x/1s $edi

0x6000ff <Destination+7>: «»

Después de ejecutar la instrucción movl $HelloWorld, %esi

(gdb) x/1s &HelloWorld

0x6000e0 <HelloWorld>: «Hello Assembly»

(gdb) x/1s &DestinationUsingRep

0x600160 <DestinationUsingRep>: «»

(gdb) x/1s $esi

0x6000e0 <HelloWorld>: «Hello Assembly»

(gdb) x/1s $edi

0x6000ff <Destination+7>: «»

Después de ejecutar la instrucción movl $DestinationUsingRep, %edi

(gdb) x/1s $esi

0x6000e0 <HelloWorld>: «Hello Assembly»

(gdb) x/1s $edi

0x600160 <DestinationUsingRep>: «»

Como puede verse, los registers ESI y EDI están apuntando a las variables: &HelloWorld y &DestinationUsingRep respectivamente, la siguiente instrucción indica que el register ECX va a tener el valor de 15, que indica la longitud de la cadena HelloWorld:
movl $15, %ecx #Establece la longitud de la cadena al register ECX

(gdb) info registers

rax 0x0 0

rbx 0x0 0

rcx 0xf 15

…..

Posteriormente se invoca a la instrucción CLD para eliminar el flag DF del register EFLAGS.

Después de esto, se invoca a la instrucción REP usando la instrucción MOVSB que indica que en cada una de las 15 repeticiones del comando REP se copiara 1byte (es decir una letra) desde el origen (ESI) hacia el destino (EDI)

(gdb) x/1s &HelloWorld

0x6000e0 <HelloWorld>: «Hello Assembly»

(gdb) x/1s &DestinationUsingRep

0x600160 <DestinationUsingRep>: «Hello Assembly»

Uso de la instrucción LOD

Esta instrucción se encarga de cargar cadenas desde memoria hacia un register usando la instrucción LODS(X), en orden de cargar estos String, la instrucción LODS(X) Siempre carga las cadenas almacenadas en una variable en memoria en el register EAX y por otra parte el origen de dicha cadena debe ser apuntado por el register ESI, por lo tanto es necesario utilizar la instrucción movl para que la dirección de la variable sea almacenada en el register ESI, estas premisas deben de ser tenidas en cuenta al momento de utilizar las instrucciones LODS(X).

Del mismo modo que ocurre con las instrucciones MOVS(x), las instrucciones LODS(x) están diferenciadas en 3 categorías diferentes, dependiendo del tamaño de bytes a manejar de este modo, se reconocen las 3 principales instrucciones:
LODSB: Carga 1 byte (8 bits) desde la dirección de memoria hacia el AL

LODSW: Carga una palabra (16 bits) desde la dirección de memoria hacia el AX

LODSL: Carga una palabra doble (32 bits) desde la dirección de memoria hacia el EAX

Del mismo modo que ocurre con las instrucciones MOVS(X) la dirección de memoria del register ESI aumenta/disminuye en función de la bandera DF del register EFLAG justo después de que la instrucción LODS(X) sea invocada, esto es algo que se debe recordar siempre en el manejo de Strings con este tipo de instrucciones.

En la depuración del programa StringBasic las instrucciones LODS se emplean en el fragmento de código:

cld
leal HelloWorld, %esi

lodsb

movb $0, %al

NOTA: Como puede verse, es importante llamar a la instrucción CLD antes de ejecutar la instrucción LEAL, principalmente para eliminar la bandera DF del register EFLAGS y la dirección de memoria del register ESI no se incremente de forma automática, de esta forma los valores de la memoria de la variable y el register sean idénticos después de ejecutar la instrucción LEAL.

A continuación se enseñara el manejo de memoria en cada una de estas lineas.

La primera linea indica que la dirección de memoria efectiva de la variable HelloWorld sera copiada en el register %esi, una vez ejecutada esta instrucción los valores en memoria son los siguientes:

(gdb) print /x &HelloWorld

$1 = 0x6000ec

(gdb) print /x $esi

$2 = 0x6000ec

Como puede verse la dirección de memoria es exactamente la misma, si la bandera DF hubiera estado establecida, en el register EFLAGS, el resultado no hubiera sido el mismo, a saber, la dirección de memoria del register ESI se hubiera incrementado una vez terminada la ejecución de la instrucción LEAL.

Posteriormente se ejecuta la instrucción lodsb, sin embargo, antes de ejecutar dicha instrucción, examinamos la memoria para conocer el valor en la posición de memoria del register EAX.

(gdb) print $eax

$4 = 0

Despues de ejecutar la instrucción LODSB su valor sera:

(gdb) print $eax

$5 = 72

Como puede verse, el valor del register EAX es 72, que no es mas que el valor ASCII para la letra “H”, sin embargo, para conocer el valor en el formato legible utilizamos el comando print de GDB para establecer el formato, de la siguiente forma:

(gdb) print /c $eax

$6 = 72 ‘H’

Las instrucciones siguientes realizan las mismas actividades anteriormente mencionadas, es decir, cargar en el register ESI, valores de la cadena a la cual hace referencia, solamente cabe anotar, que son interesantes las lineas correspondientes a la manipulación del register ESI, es decir:

dec %esi #Decrementa el ESI 1 bytes, para que apunte a la posición inicial de la cadena a la cual hace referencia, solamente 1 byte debido a que se ha invocado la instrucción lodsb que mueve solamente 1 byte de memoria y es necesario que el ESI apunte nuevamente a su posición inicial de la cadena (a la que hace referencia, a saber, HelloWorld)

lodsw

movw $0, %ax

subl $2, %esi #El ESI debe apuntar nuevamente a la posición inicial de la cadena que referencia, por lo tanto retrocede la dirección de memoria en 2 bytes.

lodsl

Debido a que el register ESI, se ha movido para cargar los bytes localizados en la direcciones de memoria a la que apunta, la dirección del ESI debe ser nuevamente establecida a la posición inicial, para esto se utiliza la instrucción dec, que disminuye la dirección de memoria en 1 byte y la instrucción subl que disminuye la posición de memoria N bytes.
Continuando con la depuración del programa:

Después de la ejecución de la siguiente instrucción movb $0, %al, el valor del EAX es de 0:

(gdb) print /c $eax

$5 = 0 ’00’

Y antes de ejecutar la instrucción dec %esi el valor del register ESI es:

(gdb) print /x $esi

$7 = 0x6000f9

Después de ejecutar la instrucción

(gdb) print /x $esi

$8 = 0x6000f8

Como puede verse la instrucción dec a disminuido en 1 byte el tamaño de la dirección de memoria para el register ESI, por lo tanto ahora se encuentra posicionado en el inicio de la cadena HelloWorld

Después de ejecutar la instrucción lodsw, los valores en memoria son los siguientes:

(gdb) print /x $esi

$9 = 0x6000fa

(gdb) print /c $esi

$10 = -6 ‘\372′

(gdb) print /x $eax

$11 = 0x6548

(gdb) info registers

rax 0x6548 25928

………..

Nuevamente se limpia el register EAX con la instrucción movw $0, %ax

(gdb) print /x $eax

$17 = 0x0

(gdb) print /c $eax

$18 = 0 ’00’

Ahora se le indica al register ESI que apunte a la dirección de memoria ubicada justo al inicio de la cadena a la que hace referencia es decir HelloWorld, dado que se ha movido 2 bytes, se debe mover nuevamente 2 bytes hacia atrás.

(gdb) print /x $esi

$19 = 0x6000f8

El register ESI vuelve a apuntar a la primera posición de la cadena HelloWorld, en la siguiente instrucción lodsl copiara los cuatro primeros bytes al register EAX, los valores en memoria son los siguientes después de su ejecución:

(gdb) print /x $eax

$24 = 0x6c6c6548

Almacenamiento de Strings

Del mismo modo en el que se han utilizado las instrucciones MODS(x) y LODS(x) se emplean las instrucciones STOS(x) para almacenar el valor contenido en el register EAX directamente a una variable en memoria, a diferencia de las instrucciones LODS(x) las instrucciones STOS(x) utilizan el register EDI para apuntar a las variables de almacenamiento, es decir el destino donde se van almacenar los datos desde el register EAX. Las formas de esta instrucción son:

STOSB: Almacena el valor del register AL a memoria.

STOSW: Almacena el valor de AX a memoria

STOSL: Almacena el valor de EAX a memoria.

Del mismo modo que las instrucciones anteriores, el register EDI es aumentado/disminuido en función de la bandera DF después de que cada instrucción STOS(x) se ejecuta.

Continuando con la ejecución del programa:

La linea leal DestinationUsingStos, %edi como en casos anteriores carga la memoria efectiva

(gdb) print /x $edi

$1 = 0x600208

(gdb) print /x &DestinationUsingStos

$2 = 0x600208

Después de la ejecución de la instrucción stosb, el resultado es:

(gdb) x /1s &DestinationUsingStos

0x600208 <DestinationUsingStos>: «H»

Después de la ejecución de la instrucción stosw, el resultado es:

(gdb) x /1s &DestinationUsingStos

0x600208 <DestinationUsingStos>: «HHe»

Después de la ejecución de la instrucción stosl, el resultado es:

(gdb) x /1s &DestinationUsingStos

0x600208 <DestinationUsingStos>: «HHeHell»

El resultado anterior, corresponde al almacenamiento de los valores almacenados en el register EAX, el comportamiento ha sido el esperado, en el primer caso, se ha almacenado a la variable DestinationUsingStos solamente 1 byte correspondiente a la letra H, en el segundo caso, la instrucción stosw, ha escrito 2 bytes, correspondientes a las letras He, sin embargo, la posición del register EDI no ha sido establecida al principio de la cadena, por lo tanto el valor “H” almacenado anteriormente, no es sobrescrito y lo mismo ocurre con la ultima instrucción. Por este motivo, es importante manipular correctamente el valor de la posición de memoria del register EDI aumentando/disminuyendo su valor con el fin de ejecutar diferentes operaciones.

Comparando Cadenas

Para comparar dos cadenas, es importante en primer lugar anotar, que el register ESI, corresponde a la posición de memoria origen y EDI a la posición de memoria destino con la cual se comparara, al igual que en los casos anteriores, la bandera DF define el aumento/decremento de los registers ESI/EDI, al igual que en los casos anteriores se cuenta con las instrucciones CMPS(x) que contienen las siguientes formas:

CMPSB: Compara un byte

CMPSW: Compara 2 bytes (una palabra, que consta de 2 letras)

CMPSL: Compara 4 bytes (doble palabra, que consta de 4 letras)

Continuando con la ejecución del programa:

se cargan las direcciones de memoria efectiva en el origen y el destino, donde ESI (origen) apunta a la variable HelloWorld y EDI (destino) apunta a la variable H3110

(gdb) print /x $esi

$7 = 0x600120

(gdb) print /x &HelloWorld

$8 = 0x600120

(gdb) print /x $edi

$9 = 0x60012f

(gdb) print /x &H3110

$10 = 0x60012f

Después de ejecutar la instrucción cmpsb, vemos el valor del register eflags:

(gdb) info registers

rax 0x6c6c6548 1819043144

……………….

eflags 0x246 [ PF ZF IF ]

Ahora, apreciamos dos banderas adicionales, PF y ZF que corresponden a Parity Flag y Zero Flag, La primera indica que se debe realizar una comparación entre los registers ESI y EDI, y la segunda indica que existe una coincidencia, que en este caso corresponde con la letra “H” que se encuentra en el primer byte de las variables a las que apuntan los registers ESI y EDI es decir, las variables HelloWorld y H311O respectivamente.

Las siguientes instrucciones intentan posicionar los registers ESI/EDI en la primera posición de la cadena, para que de esta forma puedan ser comparados desde la dirección de inicio de las respectivas variables, después de esto, se ejecuta el comando cmpsb, cuyo resultado en memoria es:

(gdb) info registers

……………….

eflags 0x206 [ PF IF ]

……………….

Como puede apreciarse, se ha establecido la bandera de paridad, lo que indica que se ha efectuado una operación de comparación, sin embargo, no se encuentra establecido la bandera ZF, lo que indica que la operación ha fallado, y tanto origen como destino no son iguales en sus 2 primeros bytes.

Las instrucciones siguientes corresponden al correcto posicionamiento de los registers y a la comparación de los 4 primeros bytes del origen y el destino, como es natural, el resultado será exactamente igual que el anterior, es decir, no hay coincidencia exacta, por lo tanto, no se establece la bandera ZF en el register EFLAGS.

Finalmente, las instrucciones REPZ y REPNZ corresponden a la repetición de la instrucción de comparación mientras que exista coincidencia y a la instrucción de comparación mientras no exista coincidencia, similar a la instrucción REP para cadenas simples.