viernes, 7 de agosto de 2015

[FASM] Ensamblador en línea

Hoy les traigo una manera muy simple de como utilizar ensamblador en sus programas, solo vamos a necesitar dos items, una libreria externa y ganas de programar.

Empezamos descargando FASM.dll de este enlace: ¡Descargar!

Esta libreria nos ofrece 3 procedimientos.

Proc fasm_Assemble(Referencia:Cadena,,,,:Entero):Entero, "FASM.dll"
Proc fasm_GetVersion:Entero, "FASM.dll" ' Sin parametros, Void...
Proc fasm_AssembleFile(Referencia:Cadena,,,,:Entero):Entero, "FASM.dll"

fasm_Assemble: Este procedimiento (o función) nos permite "compilar" nuestro código ensamblador con sintaxis FASM a Opcodes (o offsets) que pueden ser manipulados a nuestra voluntad.

fasm_GetVersion: Este procedimiento retorna la versión de la libreria, se debe separar entre HiWord y LoWord.

fasm_AssembleFile: Nos permite procesar un archivo de código fuente con formato ASM, muy util si quieren hacer procedimientos dinamicos.

Por si no se puede apreciar, fasm_Assemble y fasm_AssembleFile tienen exactamente la misma cantidad de parametros y su uso es muy semejante.

2 de 3 de estos procedimientos devuelven punteros a estructuras, estas son las siguientes:

' Unión #1
Unión FASM_DATA1,_
output_length,_ ' Cantidad de opcodes.
error_code:Entero ' Código del error.

' Unión #2
Unión FASM_DATA2,_
output_data,_ ' Puntero a los offsets.
error_line:Entero ' Puntero a FASM_LINE_HEADER con la información del error.

Unión FASM_DATA3,_
file_offset,_
macro_calling_line:Entero

Estruc FASM_STATE,_ ' Estructura que obtiene los offsets / Opcodes.
condition:Entero,_
a:FASM_DATA1,_ ' Unión #1
b:FASM_DATA2   ' Unión #2

Estruc FASM_LINE_HEADER,_
file_path,_
line_number:Entero,_
c:FASM_DATA3,_
macro_line:Entero

fasm_Assemble acepta 5 parametros, los unicos que nos importan son los 3 primeros.

Parametro #1: Código que se procesará.
Parametro #2: Puntero al buffer que recibirá los datos de las estructuras.
Paraemtro #3: Tamaño del buffer.
Parametro #4: Debe ser 100.
Parametro #5: Puntero a CallBack, debe ser cero.
Retorno: 0 Si fue exitoso, otro valor si hubo un error.

Un ejemplo de el uso de este procedimiento es el siguiente:

Proc ProcesarASM(Referencia Código:Cadena):Byte[]
      Var Offsets[]:Byte ' Declaramos el buffer.
      Var Info:FASM_STATE ' Estructura que contendra los datos recibidos del buffer.
      Var Error:FASM_LINE_HEADER ' Estructura que obtiene los errores encontrados.
      Var Verificar:Entero ' Variable que utilizamos para obtener el código de error (si hubo).
      ReDim Offsets,10000 ' ReDimencionamos el buffer (tamaño recomendado).
      Verificar = fasm_Assemble(Código,Offsets[0]@,10000,100,0)
      Si Verificar = 0 Entonces ' Si no hubo ningún problema.
      AdmErr ' Verificamos si se produce un error.
            CopyMemory(Info@,Offsets[0]@,Long(Info)) ' Copiamos el contenido de "Offsets" en Info para _
            ' rellenar la estructura.
            CopyMemory(Offsets[0]@,Info.b.output_data,Long(Info.b.output_data)) ' Rellenamos el buffer _
            ' con los datos que estan en la dirección de "Info.b.output_data" (offsets).
      Controlar ' Si se produjo algún error.
            Salir ' Salimos del procedimiento sin devolver nada.
      FinAdmErr
            ReDim Preservar Offsets,Info.a.output_length ' Redimencionamos la matriz con la cantidad de offsets presentes.
            Resultado = Offsets ' Devolvemos los offsets.
      SiNo ' Si hubo un problema.
            CopyMemory(Info@,Offsets[0]@,Long(Info)) ' Copiamos el contenido de "Offsets" en Info para _
            ' rellenar la estructura.
            CopyMemory(Error@,Info.b.error_line,Long(Error)) ' Rellenamos la estructura Error con el contenido obtenido de la dirección "Info.b.error_line".
            Resultado = [0,ERROR.line_number,ERROR.c.file_offset]
      FinSi
FinProc

Creo que el código de ejemplo esta muy claro.

El procediento fasm_AssembleFile es muy parecido a fasm_Assemble, lo unico que cambia es que el primer parametro, en vez de ser código, es la dirección de un archivo ASM en nuestro sistema, en caso de no encontrarse "info.a.error_code" devuelve "FASM_SOURCE_NOT_FOUND" (-4).

Saludos!

martes, 14 de julio de 2015

Consejos para programar mejor

No se si yo sea el mas indicado para crear una entrada así, aunque si escribo código, pero hay cierto tabu en la programación si no programas en Java, Python, .NET o algún lenguaje relativamente popular, me ha pasado que me han preguntado:

"¿En que lenguaje programás?"

yo les dijo:

"Yo programo en Autoit, Vb6 y Pauscal, a veces."

Y ni saben que es, aunque ese no es el problema, nunca falta el cabeza que me dice:

"Ah, entonces vos no programás."

Cuando de hecho yo se hacer mas cosas con AutoIT que el (o ellos) en C Sharp, Python y PHP combinados.

Pero bueno, la trama no es esa, el chiste aca es recibir consejos para programar mejor. Les informo que estos consejos se aplican a CUALQUIER LENGUAJE de programación existente.

Comencemos.

1 - Comentarios
Para los que no creen que los comentarios son importantes, es porque nunca se encontraron con un proyecto real.

Como sabemos, los comentarios son palabras que el compilador ignora y que nos sirven para poder entender mejor el código, nada mas util si se trabaja con código de terceros.

Es recomendable comentar lo mas que se pueda el código con la definición mas humana posible, para mejores resultados, es bueno agregar etiquetas a los comentarios.


  • FIXME: para marcar código problemático potencial que requiere una atención especial y/o revisión.
  • NOTE: peligros potenciales para documentar el funcionamiento interno del código y de indicar.
  • TODO: para indicar las mejoras planificadas.
  • XXX: para advertir a otros programadores de código problemático o equivoco.

Aunque no me las se de memoria, estas etiquetas las e visto en código de Pauscal (obviamente codeadas por otra persona) y me ha sido de utilidad.

2 - Funciones
Un error comun para muchos es llamar multiples veces una función que devuelve un mismo resultado, este error es bastante importante y es cometido mas que nada por los programadores novatos o con poca experiencia, relentiza la velocidad del programa (ya que invoca rutinas pre-programadas multiples veces) y es totalmente innecesario.

Lo mejor que puedes hacer (con funciones que sabes que devolveran el mismo valor no importa cuantas veces la llames) es almacenar el valor de la función en una variable de su tipo y utilizar esta variable, tal vez no notes cambios visibles, pero tu procesador te lo agradecera.

Un código de ejemplo que muestra el mal habito de un programador, esta función convierte una cadena binaria en su valor octal.

Function BinToOct(BinNum As String) As String
   Dim BinLen As Integer, i As Integer
   Dim OctNum As Variant
   On Error GoTo ErrorHandler
   BinLen = Len(BinNum)
   For i = BinLen To 1 Step -1
'     Check the string for invalid characters
      If Asc(Mid(BinNum, i, 1)) < 48 Or Asc(Mid(BinNum, i, 1)) > 49 Then ' < ERROR
         OctNum = ""
         Err.Raise 1002, "BinToOct", "Invalid Input"
      End If
'     Calculate Octal value of BinNum
      If Mid(BinNum, i, 1) And 1 Then
         OctNum = OctNum + 2 ^ Abs(i - BinLen)
      End If
   Next i
'  Return OctNum as String
   BinToOct = Oct(OctNum)
ErrorHandler:
End Function

Como se puede apreciar, el programador de este código (no, no fui yo) utilizo dos veces la misma sentencia "Asc(Mid(BinNum, i, 1))". ¿No conoce las variables? pff...

3 - Tabulaciones
Por fin llegamos a mi parte favorita, las tabulaciones, estas son tan importantes en mi código como el mismísimo código.

No son solo para que el código se vea mejor, nos hace diferenciar las instrucciones (por ejemplo) de dentro de un procedimiento a las de afuera de este, ocupan menos memoria ROM (¿a poco no es mejor usar tabulaciones que 6 espacios normales? por favor... son 5 bytes ahorrados...) y son mas estéticamente correctas.

En el ejemplo anterior les mostre un código de Visual Basic 6.0 sin tabular.

Function BinToOct(BinNum As String) As String
  Dim BinLen As Integer, i As Integer
  Dim OctNum As Variant
  On Error GoTo ErrorHandler
  BinLen = Len(BinNum)
  For i = BinLen To 1 Step -1
     'Check the string for invalid characters
     If Asc(Mid(BinNum, i, 1)) < 48 Or Asc(Mid(BinNum, i, 1)) > 49 Then ' < ERROR
     OctNum = ""
        Err.Raise 1002, "BinToOct", "Invalid Input"
     End If
     'Calculate Octal value of BinNum
     If Mid(BinNum, i, 1) And 1 Then
        OctNum = OctNum + 2 ^ Abs(i - BinLen)
     End If
  Next i
  'Return OctNum as String
  BinToOct = Oct(OctNum)
  ErrorHandler:
End Function

¿No les parece mas bonito?

4 - Parametros
Los parámetros son general mente pasados por valor (se crea una copia en la función o procedimiento con el mismo valor), esto puede ser conveniente aveces, pero si no vas a modificar el parámetro en ningún aspecto, no olvides pasarlos por referencia para evitarle al compilador tener que crear la copia a la variable parámetro.

5 - POO
La programación orientada a objetos es muy util, pero muchas personas se vuelven "adictas" a este paradigma, imaginense a alguien que haga una función unica pero dentro de una clase, ¿que chiste tiene?. Siempre que sea posible evita utilizar POO, ya que añade código innecesario y por lo tanto, reduce el rendimiento de tus aplicaciones, la programación orientada a objetos es utilizada para encerrar procedimientos en una tematica en particular, como una clase llamada "SO" que administre el sistema operativo, pero si programás en C# o Java, lamentablemente este tip no te sirve.

¡Saludos!

martes, 7 de julio de 2015

Tipo de dato Variante

En algunos lenguajes de programación el tipo de dato Variante no existe, no es como si todos supieran programar en Python ¿verdad?, pero en lenguajes como Visual Basic este tipo de dato es tan comun como la estructura "if", entonces, ¿que hay detras de este tipo de dato?.

Yo recuerdo que pensaba que este tipo de dato era una simple unión (para los que no saben que es una unión, vease: Unión) de todos los datos existentes, pero es algo un poquitito mas complicado, veamos.

En el mundo real, este tipo de dato puede ser declarado como una estructura con tres miembros de tipo entero (int - integer).

  1. Tipo - El tipo de dato que contendra la variante .
  2. Reservado - Este miembro debe ser cero.
  3. Ptr - Este miembro debe contener la dirección del dato.
El primer miembro puede contener uno de los siguientes valores.

Tipo de variableBytes de datosTipo C/C++Nombre de tipo
0Limpio1
1Nulo2
102A000A80HRESULT (long int)Error
1080020004HRESULT (long int)Missing3
172ABYTE (unsigned char)Byte
11FFFFVARIANT_BOOL (short int)Booleano
22A00short intEntero
32A000000long intLargo
400002842floatReal
50000000000004540doubleDoble
6A068060000000000CY EstructuraDecimal
700000000C0D5E140DATE (double)Dato
8xxxxxxxxBSTR (wchar_t pointer)Cadena
900000000Puntero IUnknownNada4
9xxxxxxxxPuntero IUnknownReferencia a objeto5

Muy simple, probemor escribir el código.

Estruc Variante,_
            Tipo:Entero,_
            Reservado:Entero,_
            Ptr:Entero

Supongamos lo siguiene, en Visual Basic por X motivo necesitamos utilizar un procedimiento de una DLL externa que NOSOTROS programamos, y por X motivo el primer parametro debe ser si o si variante, ¿como choclo hago?.

Pues muy facil, veamos el código de Visual Basic.

Private declare function Mensaje lib "MiDLL.dll" (Parametro1 as Variant)

Sub Form_Load()
       Mensaje "Hola Mundo!" ' Llamamos al procedimiento de nuestra DLL.
End Sub

Entonces el código de la DLL seria el siguiente.

Importar "Cadena.prp"
' API
Proc MessageBox(hWnd:Entero,Referencia Texto,Título:Cadena,Bandera:Entero):Entero,"User32" "MessageBoxA"


Estruc Variante,_
            Tipo:Entero,_
            Reservado:Entero,_
            Ptr:Entero

Proc Mensaje(Parametro1:Variante) Exportar
       Seleccionar Parametro1.Tipo ' Seleccionamos el tipo devuelto por la llamada de VB.
           Caso 8 ' Si el tipo del dato variante es una cadena (vease la tabla anterior).
               MessageBox(0,CadDePtr(Parametro1.Ptr),"Título!",48) ' Mostrar el contenido obtenido de Ptr miembro.
           Caso Otro
               Salir
       FinSeleccionar
FinProc

Saludos!