Ir al contenido



Foto

OPCodes


  • Por favor identifícate para responder
23 respuestas en este tema

#1 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.111 mensajes
  • LocationMéxico

Escrito 16 diciembre 2008 - 03:47

Si quieres pues habrimos un hilo y le entramos a los OPCodes estos, asi sale y aprendo un poco mas yo tambien.


Bueno pues "pa luego es tarde" Aquí está el hilo de OPCodes

Salud OS
  • 0

#2 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 05:40

Bueno tomando el ASM de la mosca:

Tenemos:


delphi
  1. asm
  2. call  GetTickCount   // obtenemos el la cantidad de Ticks actual
  3. sub   eax, [Start]   // la restamos Start obteniendo el lapso en milisegundos


Call es un llemado a una subrutina, en este caso GetTickCount, debe regresar el valor en eax (asi es en mis micros)
eax es el registro accumulator (acumulador) o Registro "A" en el 8051 en los Pentium se llama EAX, luego hablamos de este registro que es uno, si no el mas importante de todos.

en la proxima linea de codigo:

sub eax,[Start]  // le esta restando al contenido de eax (el que regreso de la funcion " GetTickCount" y que se encuentra en eax)

Que el maestro nos aclare si esta correcto esto o no? recuerden que mi ASM es de micros 8051, nunca programe asm para 32 bits Intell  x86. :p

Creo que la mejor forma de entender es ir paso a paso que es lo que hace.

Yo tengo dos preguntas al Maestro: ¿ que es exactamente [Start] en este codigo? ¿EAX retorna con el contenido o resultado que se obtiene de " GetTickCount"?

Un Saludo.

#3 Caral

Caral

    Advanced Member

  • Administrador
  • 4.241 mensajes
  • LocationCosta Rica

Escrito 16 diciembre 2008 - 06:38

Hola
He eliminado mi mensaje, me parece que es un hilo en donde se puede aprender mucho y no esta bien interrumpirlo y mucho menos desvirtuarlo. (y)
Saludos
  • 0

#4 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 06:48

Hola
He eliminado mi mensaje, me parece que es un hilo en donde se puede aprender mucho y no esta bien interrumpirlo y mucho menos desvirtuarlo. (y)
Saludos


Hola Carlos,  respeto tu desicion pero no era necesario. ;)

La idea es aprender un poco de asm y pues creo que entre todos podemos estudiar algo y pues tendremos al Maestro para que nos ilumine el camino.

Un Saludo.

#5 Fenareth

Fenareth

    Advanced Member

  • Administrador
  • 3.486 mensajes
  • LocationMexico City

Escrito 16 diciembre 2008 - 07:17

...
Yo tengo dos preguntas al Maestro: ¿ que es exactamente [Start] en este codigo? ¿EAX retorna con el contenido o resultado que se obtiene de " GetTickCount"?

Un Saludo.


Start: Cardinal; // contendra el momento del "inicio" del juego, lo que nos permitira cronometrar


Entiendo que es el valor que se sustraerá a lo que se haya cargado al Acumulador... algo así como tiempo final-tiempo inicial ?

Es verdad o ando más perdida que Pingüino en el Caribe ?  :$




  • 0

#6 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 07:25

Hola Fena, si asi lo entiendo [Start] es una localizacion y pues el valor que se encuentre a donde apunte Start es el que se restara al acumulador.Asi lo entiendo yo tambien.

Un Saludo.

#7 Fenareth

Fenareth

    Advanced Member

  • Administrador
  • 3.486 mensajes
  • LocationMexico City

Escrito 16 diciembre 2008 - 07:27

Hola Fena, si asi lo entiendo [Start] es una localizacion y pues el valor que se encuentre a donde apunte Start es el que se restara al acumulador.Asi lo entiendo yo tambien.

Un Saludo.


Guardándose en el mismo Acumulador cierto ?... estoy tratando de desempolvar todas mis clases de Sistemas Digitales... Disculpen si las preguntas son un poco "sosas" son con puro afán de aprender...  :)
  • 0

#8 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 07:30


Hola Fena, si asi lo entiendo [Start] es una localizacion y pues el valor que se encuentre a donde apunte Start es el que se restara al acumulador.Asi lo entiendo yo tambien.

Un Saludo.


Guardándose en el mismo Acumulador cierto ?... estoy tratando de desempolvar todas mis clases de Sistemas Digitales... Disculpen si las preguntas son un poco "sosas" son con puro afán de aprender...  :)


No te preocupes, yo estoy aprendiendo tambien.Nunca estudie asm para procesadores de 32 bits.El asm que se es para los intell 8051 que solo son de 8 bits y pues muy pocos registros en comparacion con un pentium.Los mnemonic son algo parecidos y es por eso que entiendo algo mas o menos. ;)

Un Saludo.

#9 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 07:35

Quiero postear aqui el codigo en cuestion para que este mas a mano:


delphi
  1. function Elapsed: PChar; {$j+} // retorna el lapso entre Start hasta el momento de la
  2.                               //llamada en una cadena con formato de hora
  3. const Time: array [0..11] of Char = '00:00:00.00';
  4. asm
  5. call  GetTickCount  // obtenemos el la cantidad de Ticks actual
  6. sub  eax, [Start]  // la restamos Start obteniendo el lapso en milisegundos
  7. xor  edx, edx      // las divisiones son realizadas con un entero de 64 bits (EAX:EDX)
  8. push  ebx            // guardamos el valor de EBX
  9. push  0Ah
  10. pop  ecx            // ECX := 10; uso push y pop para reducir la cantidad de opcode generada
  11. div  ecx            // dividimos el lapso de tiempo entre diez para usar centesimas en lugar
  12.                       //de milesimas; ésto por el formato de salida
  13. mov  ecx, 183C3C64h // ECX sera rotado para los procesos de division; primero las centesimas (100),
  14.                       //luego los segundos (60), minutos (60) y horas (24)... al mismo tiempo cuando
  15.                       //se realicen estas operaciones la funcion retornará
  16. lea  ebx, Time[12]  // EBX := Result[12]; definimos y usamos como puntero a EBX el cual inicia apuntando
  17.                       // a las centesimas mas dos posiciones (pensado originalmente para optimizar con milesimas)
  18. @loop: push ecx      // guardamos ECX pues modificaremos su valor temporalmente
  19. xor  edx, edx      // (EAX:EDX)
  20. movzx ecx, cl        // solo usamos los primeros 8 bits (100, 60, 60, 24)
  21. div  ecx            // dividimos
  22. xchg  edx, eax      // EDX contiene el módulo (residuo) que es lo que nos interesa, por ello lo intercambiamos
  23.                       // con EAX para volver a realizar una division
  24. mov  cl, 0Ah       
  25. div  cl            // lo dividimos por 10, con lo cual obtenemos en AL y AH los valores
  26. or    ax, '00'      // los convertimos a ASCII
  27. sub  ebx, 3        // restamos e puntero del buffer de retorno en 3 para continuar el mismo proceso con los segundos,
  28.                       //minutos y horas
  29. mov  [ebx], ax      // guardamos el resultado en ASCII de las divisiones
  30. xchg  edx, eax      // restauramos EAX que debe contener el resultado de la division original y no asi el residuo
  31. pop  ecx            // restauramos ECX para continuar con las otras divisiones
  32. shr  ecx, 8        // y rotamos ECX para dividr respectivamente entre 60, 60 y 24
  33. jnz  @loop          // terminamos al haber realizado las 4 divisiones (centesimas, segudos, minutos y horas)
  34. xchg  eax, ebx      // la funcion retornara el valor que apunta EBX (PChar)
  35. pop  ebx            // restauramos el valor original de EBX pues es un registro utilizado "internamente" por el lenguaje
  36. end;

Pues aqui esta mas a mano. ;)

Un Saludo.

#10 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 08:23

Bueno amigos, esta otra funcion esta clara para mi tambie.

mov  ecx, 183C3C64h // ECX sera rotado para los procesos de division;
primero las centesima (100)    //luego los segundos (60), minutos (60) y horas (24)... al mismo tiempo cuando


mov al registro ECX los valores 183C3C64h ( en Hex)  y como esta comentado son las constantes para las centecimas, segundos,minutos y horas.

18 hex = 24 dec
3C hex = 60 dec
3C hex = 60 dec
64 hex = 100 dec

Este mnemonic Mov ECX,183C3C64h
el OpCode de esto seria: A1 64 3C 3C 18 // por eso en lo comentado primero estan las centecimas(100 0 64h), luego 3C segundos y el otro 3C minutos (60 0 3Ch) y por ultimo las horas (24 0 18h)

A1 es el OpCode de Mov ECX, (valor) ;)

Un Saludo.

#11 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 16 diciembre 2008 - 09:41

Un pequeño ejercicio:


delphi
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   result: integer;
  4. begin
  5. asm
  6. Mov eax, 18h;  // ponemos el valor 18 hex en el acumulador
  7. Mov result,eax;
  8. end;
  9. edit1.text:=inttostr(result); // aqui nos regresara el 24 decimal
  10. end;


Un Saludo.

#12 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.409 mensajes
  • LocationRepública Dominicana

Escrito 17 diciembre 2008 - 07:15

Hola josé, me está interesando este asunto del ASM, ¿por casualidad existe una tabla de equivalencias de esos valores?.

Saludos.
  • 0

#13 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 17 diciembre 2008 - 07:20

Hola josé, me está interesando este asunto del ASM, ¿por casualidad existe una tabla de equivalencias de esos valores?.

Saludos.


Hola Fernando, si claro que existe y se llama OpCode para el microprocesador que tu quieras, en este caso Intell 32 bits.
Deja ver si encuentro algun enlace y lo pego aqui.

Un Saludo.

#14 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 17 diciembre 2008 - 07:24

Aqui tienes un buen documento para comenzar:
http://www.comms.sci...fAsm/APNDXD.PDF

Un Saludo.

#15 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.409 mensajes
  • LocationRepública Dominicana

Escrito 17 diciembre 2008 - 09:00

Aqui tienes un buen documento para comenzar:
http://www.comms.sci...fAsm/APNDXD.PDF

Un Saludo.


Gracias José, viendo el PDF pues me he quedado igualito :s
  • 0

#16 cHackAll

cHackAll

    Advanced Member

  • Administrador
  • 598 mensajes

Escrito 17 diciembre 2008 - 09:17

En primer lugar creo necesario aclarar que éste hilo ha comenzado por la necesidad de enseñar un código utilizado en éste juego.

Como segunda y ultima aclaración es bueno entender claramente que un opcode es el código binario generado a partir de instrucciones nemotécnicas (lenguaje de maquina en este caso), y casi siempre es un tema inherente al momento de aprender y desarrollar una aplicación escrita en lenguaje de maquina.

Al grano, vamos bien Jose; ahora nutramos un poco estas ideas (y otras); creo que lo primero es hablar acerca de la API GetTickCount, la cual retorna la cantidad de milisegundos transcurridos desde que el SO inicia. Sabiendo esto podemos usarla para obtener el tiempo transcurrido entre un momento y otro;


delphi
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var Start: Cardinal;
  3. begin
  4. Start := GetTickCount;
  5.  
  6. // operacion cronometrada...
  7.  
  8. ShowMessage(IntToStr(GetTickCount - Start) + ' milisegundos transcurridos.');
  9. end;


La resta de el Tick actual con uno anterior nos dará la cantidad de milisegundos transcurridos. Habiendo obtenido dicho valor el siguiente paso es realizar unas cuantas divisiones para convertir el valor entero en una cadena comprensible a nuestra vista:


delphi
  1. // ...
  2.  
  3. var
  4. Start: Cardinal;
  5. Buffer: array [0..11] of Char = '00:00:00.00';
  6.  
  7. procedure TForm1.Button1Click(Sender: TObject);
  8. var Time, Value: Cardinal;
  9. begin
  10. Time := (GetTickCount - Start) div 10; // "convertimos" milesimas a centesimas
  11. // en cualquier conversion de base se debe utilizar divisiones y residuos
  12. Value := Time mod 100; // centesimas (lo que nos interesa es el residuo)
  13. Time := Time div 100; // eliminamos de Time dicho valor para la siguiente operacion
  14. Buffer[10] := Char((Value mod 10) or Ord('0')); // "mod 10" retornara el valor "derecho" de 99
  15. Buffer[9] := Char((Value div 10) or Ord('0')); // "div "10" el valor "izquierdo"
  16. // en ambos casos le aumentamos el valor decimal de "0" para convertirlo a ASCII
  17.  
  18. Value := Time mod 60; // segundos (pseudo "base 60"?)
  19. Time := Time div 60;
  20. Buffer[7] := Char((Value mod 10) or Ord('0'));
  21. Buffer[6] := Char((Value div 10) or Ord('0'));
  22.  
  23. Value := Time mod 60; // minutos
  24. Time := Time div 60;
  25. Buffer[4] := Char((Value mod 10) or Ord('0'));
  26. Buffer[3] := Char((Value div 10) or Ord('0'));
  27.  
  28. Value := Time mod 24; // horas
  29. Time := time div 24;
  30. Buffer[1] := Char((Value mod 10) or Ord('0'));
  31. Buffer[0] := Char((Value div 10) or Ord('0'));
  32.  
  33. // podriamos seguir operando en "Time" para saber la cantidad de dias transcurridos
  34.  
  35. Caption := Buffer;
  36. end;
  37.  
  38. procedure TForm1.FormCreate(Sender: TObject);
  39. begin
  40. Start := GetTickCount;
  41. end;


El anterior código es un paso previo a la conversión a lenguaje de maquina, y por ende es más comprensible. En un proceso de depuración se puede ver la poca optimización y gran opcode generado para realizar las conversiones necesarias.

Elapsed.cpu.png

El anterior código generado por el compilador ocupa alrededor de 235 bytes, que en algún momento me ha parecido demasiado para realizar un a operación tan simple… variaciones del código podrían alivianar la “carga”, pero no lo suficiente. Esa es la causa original de la creación de dicho código en lenguaje de maquina.

En el código original, se ha utilizado la directiva {$j+}, para que al momento de necesitar probar el código utilizando lenguaje pascal, no den errores al momento de cambiar el valor de una constante. Esta directiva ya no es necesaria.

No es mi intención aclarar las causas por las cuales es posible cambiar el valor de una constante, solo creo necesario decir que en éste caso “Time” actuara como una variable inicializada!

 

const Time: array [0..11] of Char = '00:00:00.00';


call  GetTickCount // CALL llama a la API GetTickCount, ya bien ha sido comentado, repito que EAX (el registro acumulador) contendrá luego de la llamada el valor de retorno (Ticks)

sub  eax, [Start] // SUB resta (en este caso) a EAX el valor de "Start". Técnicamente "Start" contiene una posición en memoria donde se almacena el valor real de la variable, ese es el motivo de utilizar los corchetes. Es como si le pidiéramos al micro que a EAX le reste el valor apuntado en la memoria "Start".

EAX ahora tiene el lapso en milisegundos desde Start hasta éste momento, ahora debemos convertir dicha valor a centésimas; para ello debemos DIVidirlo entre 10... (1000 / 10 = 100)

DIV tiene tres reacciones diferentes dependiendo del tipo de registro usado en la llamada;
-El primero solo afecta al registro AX, y realiza la división a 8~16 bits (máximo 65535)
  AL = (AX / valor), donde el registro usado en la llamada es de 8 bits!
  AH = residuo
-El segundo (no ha sido utilizado en el ejemplo) afecta a AX y DX, trabajando a 16~32 bits (a 32 bits DX debe ntener los 16 bits superiores y AX los inferiores)
  AX = (DX:AX / valor), donde el registro usado en la llamada es de 16 bits!
  DX = residuo
-El tercero afecta a EAX y EDX (registros de 32 bits) trabajando a 32~64 bits
  EAX = (EDX:EAX / valor), donde el registro usado en la llamada es de 32 bits!
  EDX = residuo

El primer caso no nos sirve porque no podremos dividir un entero de 32 bits en una función de 8,
El segundo caso tampoco nos sirve porque si dividimos un entero de 32 bits entre un numero muy pequeño (Ej. 200000 div 2) el resultado (100000) desbordará a AX (100000 > 65535)
Entonces debemos utilizar el tercer método, para ello EDX deberá ser 0 para que en la división el microprocesador no intente realizar una división a un valor de 64 bits.


xor  edx, edx // XOR (o exclusivo) dice que (1 ^ 1 = 0 & 0 ^ 0 = 0), entonces si realizamos ZOR de un numero a si mismo el resultado siempre será 0. En éste caso no definimos directamente a EDX como 0 porque dicha operación generaría mas opcode.
NOTA; por que reducir opcode? para tener un binario más ligero y para que el microprocesador consuma (en la mayoría de los casos) menos ciclos en sus operaciones


push  ebx // PUSH guardará en la pila el valor de EBX. La pila es un vector que sirve para almacenar valores en forma temporal (pila~LIFO)

push  0Ah // ahora almacenamos en la pila el valor 10

pop  ecx // y con POP restauramos de la pila dicho valor en ECX. Con éstas dos operaciones reducimos el opcode, aunque paradójicamente en ESTE caso aumentamos los ciclos de procesador utilizados.

div  ecx // EAX = EDX:EAX div ECX => EAX = 0:Ticks div ECX & EDX = residuo (en éste caso no lo usamos)

mov  ecx, 183C3C64h // MOV mueve o define a un registro un valor (prácticamente todas las instrucciones trabajan con registros y direcciones en memoria)
ECX ahora contiene el exadecimal 183C3C64, éste no es un valor al azar [18(24)]+[3C(60)]+[3C(60)]+[64(100)] contiene las divisiones que serán realizadas... 100, 60, 60 y 24 son los múltiplos necesitados


lea  ebx, Time[12] // LEA trabaja como MOV, con la diferencia que en lugar de mover un valor, mueve la dirección de memoria ocupada por su segundo argumento
EBX ahora contiene la dirección en memoria (+2) donde esta almacenado "Time" (específicamente tres bytes a la "derecha" de las centésimas)


@loop: push ecx // guardamos ECX, los cuatro valores guardados serán 183C3C64h, 183C3Ch, 183Ch, 18h

xor  edx, edx // "limpiamos" EDX para realizar la división

movzx ecx, cl // MOVZX es una variación de MOV. Su diferencia es la diferencia entre las dimensiones de sus argumentos... en el primer caso: (ECX = 183C3C64h) por ende (CL = 64), moverá a ECX el valor de CL, eliminando así los 24 bits restantes
Es equivalente a (ECX = ECX and 0FFh), sin embargo dicha instrucción genería mas opcode y ciclos que la utilizada.


div  ecx // DIVidimos, los cuatro casos suscitados consecutivamente serán; [Ticks div 64h], [Ticks div 3Ch], [Ticks div 3Ch], [Ticks div 18h]

xchg  edx, eax // XCHG intercambia los valores de sus argumentos, en éste caso EAX contendrá el residuo de la división y EDX el resultado... esto para poder trabajar con los residuos de las operaciones realizando una división adicional.

mov  cl, 0Ah // CL = 10

div  cl // EAX que contiene el residuo, tiene un valor menor a 100 dada la anterior division. Ahora realizamos una división a 8 bits para realizar el "mod 10" "div 10" del ejemplo en Delphi.

or    ax, '00' // AL contendrá el resultado y AH el residuo, por lo cual ahora le aumentamos el valor del caracter ASCII "0" para retornar una cadena comprensible

sub  ebx, 3 // En cada paso del bucle restamos 3 a EBX para trabajar primero con las centésimas, luego con los segundos, los minutos y finalmente las horas.

mov  [ebx], ax // ahora, guardamos en la posición apuntada por EBX [centésimas->segundos->minutos->horas] el resultado de la división y modulo (residuo) en ASCII

xchg  edx, eax // intercambiamos los valores de EDX y EAX... EDX contenía el valor de la division con ECX que son los Ticks divididos que nos falta convertir

pop  ecx // restauramos ECX tras haber modificado el valor de CL

shr  ecx, 8 // SHR rota los bits de un argumento N veces a la "derecha"; en éste caso los cuatro valores serán 183C3Ch, 183Ch, 18h, 0h

jnz  @loop // JNZ salta hacia una dirección en caso de que el "Flag Zero" este definido; estará definido en la ultima iteración cuando "ECX = 0"

xchg  eax, ebx // intercambiamos el puntero "actual" de EBX y EAX. EAX define el resultado de la función, y siendo EBX la posición de "Time" (el buffer utilizado), optimizamos código.

pop  ebx // finalmente restauramos el valor guardado en la pila de EBX pues dicho registro es utilizado por el lenguaje para realizar operaciones internas.

 

Se ha hablado del caso de modificación de ECX si CL era modificado ECX también;

Regs.jpg

Para entender;

TReg.gif

Saludos (h)
  • 0

#17 eduarcol

eduarcol

    Advanced Member

  • Administrador
  • 4.483 mensajes
  • LocationVenezuela

Escrito 17 diciembre 2008 - 09:42

simplemente excelente :D
  • 0

#18 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 17 diciembre 2008 - 10:33


Aqui tienes un buen documento para comenzar:
http://www.comms.sci...fAsm/APNDXD.PDF

Un Saludo.


Gracias José, viendo el PDF pues me he quedado igualito :s


No quieras aprender todo de una vez, concentrate en una linea de codigo y pues aprende todo sobre ella.Pregunta y si no pues Googlea un rato con ese codigo.Algo saldra a la luz y asi es algo menos que aprender. ;)

Un Saludo.

PD: Un ejemplo de donde usar asm es para un tiempo de retardo.Un timer solo nos permite ponerlo en su propiedad intervalo a un minimo de (1) esto seria un milisegundo. Por codigo asm se puede hacer mas pequeña esta demora.

#19 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 17 diciembre 2008 - 11:42

Gracias cHackAll por esa explicacion.Tengo buen material para entretenerme un rato y aprender mejor el codigo.

Un Saludo.

#20 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 17 diciembre 2008 - 03:38

Bueno, seguimos jugando con el codigo.



delphi
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   result: integer;
  4. begin
  5. asm
  6. Mov eax, 183C3C64h;
  7. movzx eax, al;  // aqui nos quedamos con 'AL' solamente en el acumulador(64)
  8. Mov result, eax;
  9. end;
  10. edit1.text:=inttostr(result); // aqui nos muestra AL en decimal (100)
  11. end;



Que les parece muchachos , ¿Se animan?

Un Saludo.