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.
Excelente blog, solo me gustaría que existiera un listado de los posts, por lo menos de los mas vistos o algo por el estilo para navegar de forma mas facil.
Por lo demás, muy buenos artículos. es bueno cuando los blogs generan información y no la copian.
Me gustaMe gusta