AnteriorPosterior

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

AnteriorPosterior