Ampliación 5. Ensamblador desde Turbo Pascal.
Curso: Curso de Pascal, por Nacho Cabanes
Curso de Pascal. Ampliación 5. Ensamblador desde Turbo Pascal.
El ensamblador es un lenguaje de muy bajo nivel, que nos permite el máximo control del procesador y la máxima velocidad de ejecución. Como inconveniente, es mucho más difícil de programar y depurar que los lenguajes de alto nivel como Pascal.Para poder conseguir la máxima velocidad en los puntos críticos,
sin necesidad de realizar todo el programa en ensamblador, la mayoría
de los lenguajes actuales nos permiten incluir "trozos" de ensamblador
en nuestros programas.
Ya desde las primeras versiones de Turbo Pascal podíamos incluir código máquina "en linea", entre líneas en Pascal, con la orden inline: por ejemplo para imprimir la pantalla, la secuencia de instrucciones en ensamblador sería:
PUSH BP ( Salva en la pila el registro BP, para
que no se modifique )
INT 5 ( Llama a la interrupcion 5,
que imprime la pantalla )
POP BP ( Restaura el valor del registro
BP )
Estas líneas, en código máquina (el ensamblador tiene una traducción casi directa) serían:
PUSH BP -> $55
INT 5 -> $CD $05
POP BP -> $5D
Así que si introducimos esta secuencia de 4 bytes en un punto de nuestro programa, se imprimirá la pantalla. Entonces, nos basta con hacer:
procedure PrintScreen;
begin
inline($55/$CD/$05/$5D);
end;
Desde el cuerpo de nuestro programa escribimos "PrintScreen" y ya está.
Un comentario sobre este sistema: imprime la pantalla a través del DOS, por lo que no habrá problema si es una pantalla de texto, pero puede que nos haga falta tener cargado GRAPHICS o algún dispositivo similar si es una pantalla en modo gráfico.
Desde la versión 6.0 de Turbo Pascal, la cosa es aún más sencilla. Con "inline" conseguíamos poder introducir órdenes en código máquina, pero es un sistema engorroso: tenemos que ensamblar "a mano" o con la ayuda de algún programa como DEBUG, después debíamos copiar los bytes en nuestro programa en Pascal, y el resultado era muy poco legible. A partir de esta versión de TP, podemos emplear la orden "asm" para incluir ensamblador directamente:
procedure PrintScreen;
begin
asm
push bp
int 5
pop bp
end
end;
Es decir, nos basta con encerrar entre asm y end la secuencia de órdenes en ensamblador que queramos dar. Si queremos escribir más de una orden en una línea, deberemos separarlas por punto y coma (;), siguiendo la sintaxis normal de Pascal, pero si escribimos cada orden en una línea, no es necesario, como se ve en el ejemplo. Los comentarios se deben escribir en el formato de Pascal: encerrados entre { y } ó (* y *).
Las etiquetas (para hacer saltos a un determinado punto de la rutina en ensamblador) pueden usar el formato de Pascal (tener cualquier nombre de identificador válido), y entonces tendremos que declararlas con label, o bien podemos emplear las llamadas "etiquetas locales", que no se pueden llamar desde fuera de la rutina en ensamblador, y que no hace falta declarar, pero su nombre debe empezar por @. Un ejemplo puede ser la versión en ensamblador de la rutina para sincronizar con el barrido de la pantalla que vimos en la ampliación 2 ("Gráficos sin BGI"):
procedure Retrace;
begin
asm
mov dx, $03da
@ntrace:
{ Espera fin del barrido actual }
in al, dx
test al, 8
jnz @ntrace
@vtrace:
{ Espera a que comience el nuevo barrido }
in al, dx
test al, 8
jz @vtrace
end;
end;
Como son etiquetas locales, a las que sólo vamos a saltar desde dentro de este mismo procedimiento, comenzamos su nombre con @ y no necesitamos declararlas.
Tenemos a nuestra disposición todas las ordenes de ensamblador del 8086. También podemos acceder a las del 80286 si usamos la directiva, y/o las del 8087 si empleamos {$N+}. Por ejemplo, para dibujar puntos en la pantalla en modo 320x200 de 256 colores podemos usar:
{$G+}
Procedure Putpixel (X,Y : Integer; Col : Byte);
Begin
Asm
mov ax,$A000
mov es,ax
mov bx,[X]
mov dx,[Y]
mov di,bx
mov bx, dx
{ bx = dx }
shl dx, 8
{ dx = dx * 256 }
shl bx, 6
{ bx = bx * 64 }
add dx, bx
{ dx = dx + bx (= y*320) }
add di, dx
{ Posición final }
mov al, [Col]
stosb
End;
End;
Es decir: para multiplicar por 320, no usamos las instrucciones de multiplicacion, que son lentas, sino las de desplazamiento, de modo que al desplazar 8 posiciones estamos multiplicando por 256, al desplazar 6 multiplicamos por 64, y como 256+64=320, ya hemos hallado la fila en la que debemos escribir el punto. El {$G+} lo hemos usado porque instrucciones como "shl dx, 8" sólo están disponibles en los 286 y superiores; en un 8086 deberíamos haber escrito 8 instrucciones "shl dx,1" o haber usado el registro CL.
Podemos hacer una optimización: en todos los procedimientos que hemos visto, todo era ensamblador. Esto no tiene por qué ocurrir así: podemos tener Pascal y ensamblador mezclados en un mismo procedimiento o función. Pero cuando sea sólo ensamblador podemos emplear la directiva assembler, que permite al compilador de Turbo Pascal hacer una serie de optimizaciones cuando genera el código. El formato de un procedimiento que emplee esta directiva es:
procedure Modo320; assembler;
asm
mov ax,$13
int $10
end;
(debemos indicar "assembler;" después de la cabecera, y no hacen
falta el "begin" y el "end" del procedimiento).
Como ejemplo de todo esto, un programita que emplea estos procedimientos para dibujar unas líneas en pantalla, esperando al barrido antes de dibujar cada punto (al final de este tema hay otro ejemplo más):
{--------------------------} { Ejemplo en Pascal: } { } { Dibujo de puntos en } { pantalla con ensam- } { blador } { GRAFASM.PAS } { } { Este fuente procede de } { CUPAS, curso de Pascal } { por Nacho Cabanes } { } { Comprobado con: } { - Turbo Pascal 7.0 } {--------------------------} program GrafAsm; {$G+} uses crt; procedure Modo320; assembler; asm mov ax,$13 int $10 end; procedure ModoTxt; assembler; asm mov ax,3 int $10 end; Procedure Putpixel (X,Y : Integer; Col : Byte); assembler; Asm mov ax,$A000 mov es,ax mov bx,[X] mov dx,[Y] mov di,bx mov bx, dx { bx = dx } shl dx, 8 { dx = dx * 256 } shl bx, 6 { bx = bx * 64 } add dx, bx { dx = dx + bx (= y*320) } add di, dx { Posición final } mov al, [Col] stosb end; procedure Retrace; begin asm mov dx, $03da @ntrace: { Espera fin del barrido actual } in al, dx test al, 8 jnz @ntrace @vtrace: { Espera a que comience el nuevo barrido } in al, dx test al, 8 jz @vtrace end; end; var i, j: integer; begin Modo320; for i := 0 to 40 do for j := 1 to 200 do begin PutPixel(j+i*3,j,j); retrace; end; readkey; ModoTxt; end.
Finalmente, también tenemos la posibilidad de usar un ensamblador externo, como Turbo Assembler (TASM, de Borland), o MASM, de Microsoft. Estos programas crean primero un fichero objeto (con extensión OBJ), antes de enlazar con el resto de módulos (si los hubiera) y dar lugar al programa ejecutable.
Pues nosotros podemos integrar ese OBJ en nuestro programa en Pascal usando la directiva {$L}, y declarando como "external" el procedimiento o procedimientos que hallamos realizado en ensamblador, así:
procedure SetMode(Mode: Word); external;
{$L MODE.OBJ}
Actualizado el: 20-07-2013 11:20