Para explicar ‚sta parte, voy a hacerlo lo m s mundanamente posible y
sin mucho t‚rmino complicado, porque las explicaciones muchas veces suelen
liar m s sobre una cosa tan sencilla como es ‚sto.
La pila es una especie de "almac‚n de variables" que se encuentra en una
dirección determinada de memoria, dirección que viene indicada por SS:SP,
como mencion‚ antes, registros que son SS de segmento de pila y SP de
Offset de ‚sta.
Entonces nos encontramos con dos órdenes b sicas respecto a la pila, que
son PUSH y POP. La órden PUSH empuja una variable a la pila, y la órden POP
la saca. Sin embargo, no podemos sacar el que queramos, no podemos decir
"quiero sacar el valor de DX que he metido antes y que fue el cuarto que
metí", por ejemplo.
La estructura de la pila se denomina LIFO, siglas inglesas que indican
'Last In First Out'. Esto significa que al hacer un POP, se sacar el
último valor introducido en la pila. Vamos con unos ejemplitos majos:
PUSH DX ; Mete en la pila el contenido de DX
PUSH CX ; Y ahora el contenido de CX
POP AX ; Ahora saca el último valor introducido ( CX )
;y lo coloca en AX.
POP BP ; Y ahora saca en valor anterior introducido, que
;es el contenido de DX cuando hicimos el PUSH DX
;y se lo asigna a BP.
Ahora, una rutina algo m s detallada:
MOV DX,0301h ; DX vale ahora 0301 hexadecimal.
PUSH DX ; Empuja DX a la pila. SP se decrementa en dos.
MOV DX,044C4h ; Ahora DX vale 044C4h
POP CX ; Y con ‚sto, CX vale 0301 hexadecimal, el valor
;que habíamos introducido con anterioridad.
Dije en la segunda línea: SP se decrementa en dos. Cuando por ejemplo
ejecutamos un .COM, SS es el segmento del programa ( o sea, igual que CS,
y si no han sido modificados, DS y ES ), y SP apunta al final, a 0FFFFh.
Cuando empujamos un valor a la pila, SP se decrementa en dos apuntando a
0FFFDh, y en ‚sta dirección queda el valor introducido. Cuando lo saquemos,
se incrementar de nuevo en dos el valor de SP, y el valor se sacar de
la pila.
Se puede operar con ‚sta instrucción con los registros AX, BX, CX, DX,
SI, DI, BP, SP, CS, DS y ES, sin embargo no se puede hacer un POP CS, tan
sólo empujarlo a la pila.
He aquí un ejemplo de lo que hace en realidad un POP en t‚rminos de MOVs,
aunque sea un gasto inútil de código, tiene su aplicación por ejemplo para
saltarse la heurística en un antivirus, que busca un POP BP y SUB posterior,
bueno, supongo que ya aprender‚is a aplicarlo cuando ve is el curso de
virus/antivirus:
Partamos de que hay cierto valor en la pila que queremos sacar.
MOV BP,SP ; Ahora BP es igual al offset al que apunta SP
MOV BP,Word ptr [BP] ; Y ahora BP vale el contenido del offset al
;que apunta, que al ser el offset al que apunta
;el de pila, ser el valor que sacaríamos
;haciendo un POP BP.
ADD SP,2 ; Para acabarlo, sumamos dos al valor de offset
;de la pila.
Y ‚sto es lo que hace un POP BP, símplemente. Para ver lo que hace un PUSH
no habría m s que invertir el proceso, lo pongo aquí, pero sería un buen
ejercicio que lo intent rais hacer sin mirarlo y luego lo consult rais, por
ejemplo introduciendo DX a la pila.
SUB SP,2
MOV BP,SP
MOV Word ptr[BP],DX
Como última recomendación, hay que tener bastante cuidado con los PUSH
y POP, sacar tantos valores de la pila como se metan, y estar pendiente de
que lo que se saca es lo que se tiene que sacar. La pila bien aprovechada
es fundamental para hacer programas bien optimizados, ya que entre otras
cosas las instrucciones PUSH y POP sólo ocupan un byte.
Es por ejemplo mucho mejor usar un PUSH al principio y un POP al final
en vez de dejar partes de código para almacenar variables, m s velocidad
y menos tama¤o.
Y finalmente, hay otras dos órdenes interesantes respecto a la pila,
PUSHF y POPF, que empujan el registro ( 16 bits ) de flags y lo sacan,
respectivamente
LA ORDEN CALL
Se trata de una órden que se utiliza para llamar a subrutinas, y est
relacionada con la pila, por lo que la incluyo en ‚sta lección del curso.
La sintaxis del Call es casi la de un Jmp, pudi‚ndose tambi‚n utilizar
etiquetas, direcciones inmediatas o registros. Si compar semos un Jmp con
un 'GOTO', el Call sería el 'GOSUB'. Es una instrucción que nos va a servir
para llamar a subrutinas.
Su forma de actuación es sencilla. Empuja a la pila los valores de CS e
IP ( o sea, los del punto en el que est en ese momento el programa ),
aunque IP aumentado en el tama¤o del call para apuntar a la siguiente
instrucción, y hace un salto a la dirección indicada. Cuando encuentre una
instrucción RET, sacar CS e IP de la pila, y así retornar al lugar de
origen. Veamos un ejemplo:
xor ax,ax ; Ax vale ahora 0
Call quebi‚n ; Mete CS e IP a la pila y salta a quebi‚n
Int 20h ; sta órden sale al dos, explicar‚ todo ‚sto
;en el próximo capítulo, sólo que sep is eso
quebi‚n: mov ax,30h
Ret ; Vuelve a la instrucción siguiente al punto
;de llamada, o sea, a la de "INT 20h"
La órden RET puede tener tambi‚n varios formatos: RETN o RETF, según se
retorne desde un sitio cercano ( RETN, near ) o lejano ( RETF, far ). No
obstante, pr cticamente no lo usaremos, la mayoría de las veces se quedar
en RET y punto.
Existe entonces la llamada directa cercana, en la que sólo se introduce
IP ( lógicamente, apuntando a la órden siguiente al Call ), y al retornar,
lo hace en el mismo segmento, y la llamada directa lejana, en la que se
introducen CS e IP ( y luego se sacan, claro ). A veces se podrían producir
confusiones, con lo que quiz pueda ser conveniente usar RETN y RETF
respectivamente.
Y el próximo capítulo empezamos con interrupciones,... venga, que ya
queda menos para poder programar ;-)