Gianluca,
Lo primero que hay que tener claro es que los PUSH y POP son independientes, o sea uno no está obligado a hacer POP del mismo registro que hizo PUSH.
La instrucción PUSH reg16 lo que hace es "pushear" en el stack el contenido del registro reg16. Luego que se hace con eso es otro tema. De la misma manera, la instrucción POP reg16 lo que hace es "popear" una palabra del stack y reemplazar el contenido del registro reg16 con esa palabra.
Lo que hacemos a veces para visualizar el stack de dibujar las palabras que allí hay y poner un registro es solo a efectos de visualización, pero lo que hay en el stack son palabras de 16 bits. Luego uno hace PUSH o POP de los registros cuyo contenido quiera respaldar o recuperar pero no tienen por qué ser los mismos.
Luego para entender qué hace un programa escrito en assembler tenés varias opciones:
- Intentar "decompilar" el programa a otro lenguaje de más alto nivel, como C
- Ejecutar el programa con distintas entradas y ver las salidas que produce. Esto se puede hacer ejecutando a mano o con el simulador o con una CPU real.
- Una mezcla de ambas, y por ejemplo ejecutar paso a paso el programa para ir viendo exactamente que va haciendo y cómo modifica los registros y el stack (y eventualmente otras partes de memoria)
Tomemos el ejemplo y tratemos de decompilarlo a C. Ojo que puede no ser una decompilación exacta 1 a 1 porque tal vez el que hizo el programa en assembler no arrancó desde un programa en C, o se tomó ciertas libertades al pasarlo a assembler, como en este caso . Hay que tener en cuenta cómo se llama la rutina y cómo retorna los resultados. En este caso el n viene en AX y el resultado en DX:AX
int factRec(short n) ; factRec proc
{
int resultado;
if(n > 0) ; cmp ax, 0
{ ; jbe paso_base_factRec
// este push es a efecto de no perder el contenido de BX
; push bx
short res1 = (short)factRec(n-1) ; push ax
; dec ax
; call factRec
// este pop coloca en BX el valor n (que fue pusheado antes de la llamada recursiva)
; pop bx
resultado = n * res1 ; mul bx
// este pop recupera el valor de BX original
; pop bx;
} ; jmp fin_factRec
else { ; paso_base_factRec:
resultado = 1 ; mov ax, 1
}
return resultado ; fin_factRec:
; ret
} ; factRec endp
El programa en alto nivel parece correcto (calcula el factorial), pero solo cuando los resultados intermedios entran en 16 bits. Por eso al decompilarlo puse short res1 asignando el resultado de factRec(n-1), porque el DX que contiene la parte alta de la llamada no es tenido en cuenta en la subsiguiente multiplicación e incluso es sobrescrito por la misma.
Al esamblarlo faltó un detalle, que en el paso base falta poner DX en 0. O sea cuando se pone mov ax,1 faltaria poner un mov dx,0 también. Esto solo tiene problemas si la llamada inicial a la rutina es con n=0.
Además el programa solo funciona para entradas de 0 a 9, con valores mayores no funciona porque un dato intermedio sobrepasa los 16 bits.
Saludos,
Gustavo