direccionamiento y aritmética
8. Copia de una cadena al revés
5.4.2 Instrucciones PUSH y POP
Instrucción PUSH
La instrucción PUSH primero decrementa a ESP y después copia un operando de origen de 16 o 32 bits en la pila. Un operando de 16 bits hace que ESP se decremente por 2. Un operando de 32 bits hace que ESP se decremente por 4. Hay tres formatos para la instrucción:
PUSH r/m16 PUSH r/m32 PUSH imm32
Si su programa llama a los procedimientos de la biblioteca Irvine32, siempre debe meter valores de 32 bits; si no es así, las funciones de consola Win32 utilizadas por esta biblioteca no funcionarán correctamente. Si su programa llama a los procedimientos de la biblioteca Irvine16 (en modo de direccionamiento real), puede meter valores de 16 o de 32 bits.
5.4 Operaciones de la pila 131
Los valores inmediatos siempre son de 32 bits en modo protegido. En modo de direccionamiento real, los valores inmediatos son de 16 bits de manera predeterminada, a menos que se utilice la directiva del procesa- dor .386 (o superior) (en la sección 3.2.1 hablamos sobre la directiva .386).
Instrucción POP
La instrucción POP primero copia el contenido del elemento de la pila al que apunta ESP, en un operando de destino de 16 o 32 bits, y después incrementa ESP. Si el operando es de 16 bits, ESP se incrementa por 2; si el operando es de 32 bits, ESP se incrementa por 4:
POP r/m16 POP r/m32
Instrucciones PUSHFD y POPFD
La instrucción PUSHFD mete el registro EFLAGS de 32 bits en la pila, y POPFD saca el valor de la pila y lo mete en EFLAGS:
pushfd popfd
Los programas de 16 bits utilizan la instrucción PUSHF para meter el registro FLAGS de 16 bits en la pila, y POPF para sacar un valor de la pila y meterlo en FLAGS.
La instrucción MOV no puede usarse para copiar las banderas a una variable, por lo que PUSHFD puede ser la mejor forma de guardar las banderas. Hay veces que es útil realizar una copia de respaldo de las ban- deras, para poder restaurarlas más adelante a los valores que tenían. A menudo, encerramos un bloque de código dentro de PUSHFD y POPFD:
pushfd ; guarda las banderas
;
; aquí va cualquier secuencia de instrucciones... ;
popfd ; restaura las banderas
Al utilizar instrucciones de este tipo, hay que asegurarnos de que la ruta de ejecución del programa no ignore a la instrucción POPFD. Cuando se modifi ca un programa después de cierto tiempo, puede ser difícil recordar en dónde se encuentran toda las instrucciones que meten y sacan del stack. ¡Es imprescindible tener una documentación precisa!
Una manera menos propensa a errores de guardar y restaurar las banderas es meterlas en la pila, e inme- diatamente después sacarlas y colocarlas en una variable:
.data
guardarBanderas DWORD ? .code
pushfd ; mete las banderas en la pila
pop guardarBanderas ; las copia en una variable
Las siguientes instrucciones restauran las banderas desde la misma variable:
push guardarBanderas ; mete los valores guardados de las banderas
popfd ; los copia a las banderas
PUSHAD, PUSHA, POPAD y POPA
La instrucción PUSHAD mete todos los registros de propósito general de 32 bits en la pila, en el siguiente orden: EAX, ECX, EDX, EBX, ESP (su valor antes de ejecutar PUSHAD), EBP, ESI y EDI. La instrucción POPAD saca los mismos registros de la pila, en orden inverso. De manera similar, la instrucción PUSHA, que se introdujo con el procesador 80286, mete los registros de propósito general de 16 bits (AX, CX, DX, BX, SP, BP, SI, DI) en la pila, en el orden listado. La instrucción POPA saca los mismos registros en orden inverso.
Si escribe un procedimiento para modifi car varios registros de 32 bits, use PUSHAD al principio del procedimiento y POPAD al fi nal para guardar y restaurar los registros. El siguiente fragmento de código es un ejemplo:
MiSub PROC
pushad ; guarda los registros de propósito general
. . mov eax,... mov edx,... mov ecx,... . .
popad ; restaura los registros de propósito general
ret MiSub ENDP
Debemos recalcar una importante excepción al ejemplo anterior: los procedimientos que devuelven resulta- dos en uno o más registros no deben utilizar PUSHA y PUSHAD. Suponga que el siguiente procedimiento
LeerValor devuelve un entero en EAX; la llamada a POPAD sobrescribe el valor de retorno de EAX:
LeerValor PROC
pushad ; guarda los registros de propósito general
. .
mov eax,valor_retorno .
.
popad ; ¡sobrescribe EAX!
ret LeerValor ENDP
Ejemplo: invertir una cadena
El programa InvCad.asm itera a través de una cadena y mete cada uno de sus caracteres en la pila. Después saca las letras de la pila (en orden inverso) y las almacena de vuelta en la misma variable de cadena. Como la pila es una estructura UEPS (último en entrar, primero en salir), se invierten las letras en la cadena:
TITLE Invertir una cadena (InvCad.asm)
; Este programa invierte una cadena. ; Última actualización: 06/01/2006 INCLUDE Irvine32.inc
.data
unNombre BYTE "Abraham Lincoln",0 tamanioNombre = ($ - unNombre) - 1 .code
main PROC
; Mete el nombre en la pila. mov ecx,tamanioNombre mov esi,0
L1: movzx eax,unNombre[esi] ; obtiene el carácter
push eax ; lo mete en la pila
inc esi loop L1
; Saca el nombre de la pila, en orden inverso, ; y lo almacena en el arreglo unNombre.
mov ecx,tamanioNombre mov esi,0
5.4 Operaciones de la pila 133
L2: pop eax ; obtiene el carácter
mov unNombre[esi],al ; lo almacena en la cadena
inc esi loop L2
; Muestra el nombre.
mov edx,OFFSET unNombre call Writestring call Crlf exit main ENDP END main 5.4.3 Repaso de sección
1. ¿Cuáles son los dos registros (en modo protegido) que manejan la pila?
2. ¿Qué diferencia hay entre la pila en tiempo de ejecución y el tipo de datos abstracto pila? 3. ¿Por qué a la pila se le conoce como estructura UEPS?
4. Cuando se mete un valor de 32 bits en la pila, ¿qué ocurre con ESP?
5. (Verdadero/Falso): cuando se utiliza la biblioteca Irvine32, sólo deben meterse valores de 32 bits en la pila.
6. (Verdadero/Falso): cuando se utiliza la biblioteca Irvine16, sólo deben meterse valores de 16 bits en la pila. 7. (Verdadero/Falso): las variables locales en los procedimientos se crean en la pila.
8. (Verdadero/Falso): la instrucción PUSH no puede tener un operando inmediato. 9. ¿Qué instrucción mete todos los registros de propósito general de 32 bits en la pila? 10. ¿Qué instrucción mete el registro EFLAGS de 32 bits en la pila?
11. ¿Qué instrucción saca elementos de la pila y los coloca en el registro EFLAGS?
12. Reto: otro ensamblador (llamado NASM) permite que la instrucción PUSH liste varios registros específi cos. ¿Por qué este método podría ser mejor que la instrucción PUSHAD en MASM? He aquí un ejemplo de NASM:
PUSH EAX EBX ECX
13. Reto: suponga que no existe la instrucción PUSH. Escriba una secuencia de otras dos instrucciones que rea- licen lo mismo que PUSH EAX.
5.5
Defi nición y uso de los procedimientos
Si ya ha estudiado un lenguaje de programación de alto nivel, entonces sabe lo útil que puede ser dividir los pro- gramas en subrutinas. Por lo general, un problema complicado se divide en tareas separadas para poder compren- derlo, implementarlo y probarlo con efectividad. En el lenguaje ensamblador, por lo regular, usamos el término
procedimiento para indicar una subrutina. En otros lenguajes, a las subrutinas se les llama métodos o funciones.
En términos de la programación orientada a objetos, las funciones o métodos de una clase individual son apenas equivalentes a la colección de procedimientos y datos encapsulados en un módulo en lenguaje en- samblador. Este lenguaje se creó mucho antes de la programación orientada a objetos, por lo que no tiene la estructura formal que se encuentra en los lenguajes orientados a objetos. Los programadores de ensamblador deben imponer su propia estructura formal en los programas.