sábado, 28 de mayo de 2016

Desensamblando (conclusión): armemos el rompecabezas binario

Llegamos a la tercera parte de esta novela de romance y bits. ¿Que dónde está el romance? Mmm... ¿no lo viste? Seguí practicando hasta que veas la danza binaria entre acumuladores...

Este es el programa desensamblado:

   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
   $C008 4F       bucle   CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  bucle
   $C015 20 FE    fin     BRA  fin

Con esto nos podemos dar una idea de cuál es su propósito, pero el trabajo aun no termina: había una porción de memoria que aun no procesamos:

$0000: 00 06 05 01 02 03 04 99 54 65 32  

Además en el programa estamos usando direcciones de memoria para acceder los operandos cuando quedaría más prolijo y claro utilizar etiquetas. En primer lugar veamos cómo deducir el tamaño y cantidad de los operandos:

Las instrucciones con operandos de 8 bits están marcadas en rojo. Las que manejan operandos de 16 en azul. ¡Cuidado! No debemos confundir operandos de 8 o 16 bits con direcciones de 8 o 16 bits. En modo directo solo manejamos la parte baja de la dirección, o sea 8 bits de ella, y en modo extendido los 16 bits de la dirección. Pero, podemos emplear modo directo y trabajar con 16 bits (por ejemplo, ADDD $03) o trabajar en modo extendido y operar con 8 bits (por ejemplo DEC $0002).
Empecemos con las marcadas en azul, las de 16 bits. Se carga IX con lo que hay en $0000 (es modo directo, por tanto la dirección completa es $0000). Podemos por tanto determinar que en $0000 hay una dirección de 16 bits, llamémosla dir1. 
También se realiza una suma de 16 bits con almacenado en $0003 (por lo tanto ocupa $0003 y $0004). Ahí tenemos un operando de 16 bits, podemos llamarlo double1.
Nos quedan los operandos de 8 bits. Pero observe que aunque los dos primeros CLR del programa trabajan en 8 bits, realmente se está limpiando el double1 en mitades.
Queda entonces la carga del elemento apuntado por IX, que se realiza leyendo 8 bits y cargándolo en el acumulador B. Por tanto lidiamos con un vector de elementos de 8 bits.
El último operando de 8 bits es el que utilizamos con la instrucción DEC $0002. Llamémosle byte1. Note que luego de cada decremento se verifica si llegó a cero, y en caso de que eso no ocurra se repite el bucle. De esto se desprende que se trata de un contador, que inicialmente contendría la cantidad de elementos del vector.
Pasemos en limpio las reservas de memoria y comparémoslo con el contenido de la memmoria a partir de $0000:


$0000 00 06     dir1    RMB   2 <- direccion inicial del vector
$0002 05        byte1   RMB   1 <- cantidad de elementos del vector
$0003 01 02     double1 RMB   2 <- ¿ya te diste cuenta?
$0005 03 04 99 54 65 32         

La dirección inicial del vector es $0006 ¿qué hay en la dirección $0005? Solo una posición de memoria sin usar. Llamémosle caza pichones. Lo mismo aplica al contenido inicial de double1. El vector tiene los elementos (04,99,54,65,32), ya que la dirección dir1 y la cantidad byte1 así lo marcan.

Empleando los nombres de los símbolos nos queda esto:

   $C000 7F 00 03         CLR  double1
   $C003 7f 00 04         CLR  double1 +1
   $C006 de 00            LDX  dir1
   $C008 4F       bucle   CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD double1
   $C00D DD 03            STD  double1
   $C00F 08               INX
   $C010 7A 00 02         DEC byte1 // cantidad
   $C013 26 F3            BNE  bucle
   $C015 20 FE    fin     BRA  fin
 

Queda la frutilla del postre: ¿qué hace el programa? Tomese un momento para repasar:
Sabemos que: 
  • procesa un vector de elementos de 8 bits, del que se conocen dirección inicial y cantidad de elementos
  • efectúa una sumatoria en 16 bits
  • guarda un resultado de 16 bits
Tal vez el único detalle hasta ahora no esclarecido es que los elementos se leen en 8 bits en el AccB y justo antes se limpia AccA, de esta forma cada operando de 8 bits se puede manejar como de 16. En lenguaje C hablaríamos de un cast.
¡Resuelto! Tomó un rato pero lo logramos.

Uso de THRSim para desensamblar

¿Cómo podríamos utilizar el simulador para verificar que estamos en lo correcto? Relativamente fácil: utilice la opción Memory Dump para cargar las dos porciones de memoria provistas inicialmente. Tendría que quedar así (combiné dos capturas de la ventana Memory Dump):

Paso importantísimo: verifique el valor del PC, debiera ser $C000. Ahora vamos al menú View -> Dissassemble:

Se presentará entonces esta ventana:

¡Así es mucho más fácil! Un detalle para tomar en cuenta: el THRSim presenta la dirección de destino junto a los saltos (BNE y BRA) pero no inventa ninguna etiqueta. 

Pan comido, no?

miércoles, 25 de mayo de 2016

Desensamblemos (continuación): revolcándose en bits

Veamos cómo termina esta historia del "bajo mundo" binario. Habíamos llegado a desensamblar hasta aquí:

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004
$C006 de 00            LDX $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B

y teníamos aun esta porción por procesar:

$C00B: d3 03 dd 03 08 7a 00 02 26 f3 20 fe

El procedimiento se repite: buscamos D3 en el set de instrucciones, en la columna de códigos de operación. 


Corresponde a la instrucción ADDD en modo directo, por tanto el byte que sigue es la parte baja de la dirección (dd). Completemos el programa:

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004
$C006 de 00            LDX $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B D3 03            ADDD $03
$C00D

ya nos queda menos:

$C00D: dd 03 08 7a 00 02 26 f3 20 fe

Un par de comentarios: no debemos olvidar que las direcciones se expresan en hexadecimal, mucha atencn a eso. Tampoco debemos confundir los modos de direccionamiento. El modo directo permite expresar los operandos con la parte baja de su dirección real, asumiendo que la parte alta es 00. Por tanto en esta instrucción el operando se encuentra en $0003, pero expresamos solo $03, porque justamente lo que el modo directo permite es mantener implícito el $00 de la parte alta (que sería la página cero).
Continuemos, ahora tenemos el código de operación DD:
Sería la instrucción STD en modo directo. Así que el 03 que sigue es el operando. Eso significa que 08 es el siguiente código de operación, de la instrucción INX (modo inherente). Pasemos en limpio:
$C000 7F 00 03         CLR  $0003
$C003 7f 00 04         CLR  $0004
$C006 de 00            LDX  $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B D3 03            ADDD $03
$C00D DD 03            STD  $03
$C00F 08               INX
$C010 
ya nos queda menos:

$C010: 7a 00 02 26 f3 20 fe

El código de operación -CodOp- 7A lo encontramos en DEC para modo extendido. Así que los dos bytes siguientes son la dirección del operando (00 y 02). Por tanto 26 es un CodOp, el de BNE. Dado que los saltos operan solo en modo relativo, F3 es el offset del salto. Eso nos deja como siguiente CodOp 20, que corresponde a BRA. Por lo tanto FE es el offset de este último salto relativo. Completemos el programa:

$C000 7F 00 03         CLR  $0003
$C003 7f 00 04         CLR  $0004
$C006 de 00            LDX  $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B D3 03            ADDD $03
$C00D DD 03            STD  $03
$C00F 08               INX
$C010 7A 00 02         DEC  $0002
$C013 26 F3            BNE  $F3
$C015 20 FE            BRA  $FE
¡Desensamblamos el programa! Ahora nos faltan tres cosas:
  1. Asignar etiquetas de acuerdo a los saltos que pudieran existir
  2. Asignar nombres a las direcciones de memoria de los operandos.
  3. Discernir qué hace el programa 
Hay dos saltos: en $C013 aplica el offset $F3 y en $C015 el offset $FE. Hay al menos dos maneras de determinar a dónde salta. Primero necesitamos interpretar ambos números, vamos primero con F3. Expresémoslo en binario:

 F    3 
1111 0011

De un vistazo nos damos cuenta que es negativo, ya que el bit más significativo (el primero de la izquierda) es un uno. Para determinar su magnitud aplicamos entonces lo que sabemos sobre negativos en complemento a la base. Sumemos los pesos que tienen un uno en su posición, recordando como vimos en Cómo expresar negativos en Cb que el peso del más significativo es negativo. Sería:

       F            3 
    1  1  1  1    0 0 1 1   
-128 64 32 16        2 1

Aunque la cuenta parece horrible, no lo es (tanto). Comience sumando los dos primeros:
-128 + 64 = -64
siga con el tercero:
-64 + 32 = -32
el cuarto:
-32 + 16 = -16
y finalmente le sumamos 2+1
-16 + 3 = -13 
¿No fue tan difícil, no?

El valor que obtuvimos en decimal (-13) es lo que debiéramos aplicar al PC luego de leer la instrucción de salto (C015). Si su primera respuesta fue C002, le cuento que está mal porque C015 es una magnitud hexadecimal. Podemos hacer la cuenta en hexadecimal (-13 sería -D):

C015 - D =  C008. De modo que el salto apunta a la instrucción que hay en C008. Verificamos que en C008 hay un código de operación (porque si hubiera un operando algo hicimos mal):


   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
-> $C008 4F               CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  $F3
   $C015 20 FE            BRA  $FE

De modo que podríamos poner una etiqueta (por ejemplo "bucle" para no ser originales) en la dirección C008 y reemplazar BNE $F3 por BNE bucle:
 
   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
   $C008 4F       bucle   CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  bucle
   $C015 20 FE            BRA  $FE

Si hacer la resta en hexadecimal le resulta algo desafiante, puede contar los bytes, aprovechando que sobre el margen izquierdo está ensamblado. Cuente desde el C015 hacia atrás. En la imagen marqué los bytes de cada línea desde C015 y anoté la cantidad de bytes de cada una. 
Los 13 bytes hacia atrás (el salto F3 = -13) los debemos contar desde el valor que tendrá el PC después de la instrucción de salto. (Este es tal vez el punto más importante a comprender y recordar). Retrocediendo 13 bytes llegamos a C008, que es la posición en la que pusimos la etiqueta.

El segundo salto del programa es la última línea: BRA $FE. ¿Qué valor decimal está representado en Cb como FE? Pasemos FE a binario:
   F    E 
1111 1110

Sumando los valores de los pesos (nuevamente recordando que el MSB es -128), sería:
-128 + 64 +32 + 16 + 8 + 4 + 2 = -2.
Partiendo del valor C017 y restando llegamos a C015. Esto significa que el último salto apunta a sí mismo, es el clásico FIN BRA FIN del programa. Llegamos a esto:


   $C000 7F 00 03         CLR  $0003
   $C003 7f 00 04         CLR  $0004
   $C006 de 00            LDX  $00
   $C008 4F       bucle   CLRA
   $C009 E6 00            LDAB 0,x
   $C00B D3 03            ADDD $03
   $C00D DD 03            STD  $03
   $C00F 08               INX
   $C010 7A 00 02         DEC  $0002
   $C013 26 F3            BNE  bucle
   $C015 20 FE    fin     BRA  fin
En la próxima entrega intentaremos asignar nombres a las posiciones de memoria que usa el programa, intentaremos determinar qué hace, y veremos cómo cargarlo en el simulador.

martes, 24 de mayo de 2016

Desensamblemos: más bajo no podemos caer

Aunque es sabido que las computadoras operan en binario (al menos las que existen masivamente al día de hoy) es raro que uno se tope con unos y ceros en su uso cotidiano. Incluso quienes se dedican a la programación, salvo que operen en el bajo nivel, no suelen palpar mucho este tema. Ahora bien, cuando empezamos a trabajar cada vez más abajo... los unos y ceros nos llegan al cuello.
En otro post explicamos cómo ensamblar un programa, o sea cómo llevar a binario (hexadecimal si lo resumimos de a cuatro bits) lo que programamos en texto. Ahora veremos cómo hacer el paso inverso: partiendo de un programa en binario/hexadecimal, reconstituiremos el fuente en assembler. A esto le llamamos "desensamblar".

Aviso: Si en este punto de su existencia se da cuenta de que su vida social es prácticamente nula (asintóticamente cerca de cero), entonces tenga por seguro que comprenderá este tema y lo dominará sin problemas.

Este tipo de problemas deben plantearse en un contexto. Se nos debe informar el estado de los registros internos de CPU -el PC como mínimo- para saber cómo comenzar el desensamblado. Si acaso se solicita además que indiquemos la evolución de los registros internos o el efecto en una porción de memoria, el estado inicial de los mismos debe conocerse.

Por ejemplo supongamos se nos provee el estado del program counter (PC): $C000 y dos porciones de memoria: 
$C000: 7f 00 03 7f 00 04 de 00 4f e6 00 d3 03 dd 03 08 7a 00 02 26 f3 20 fe
$0000: 00 06 05 01 02 03 04 99 54 65 32
¿Qué hacemos con esto? Empezamos por el dato más importante: el PC. Sabemos que desde la posición de memoria que se indica en él tenemos que buscar el primer código de operación. Aquí toca hacer una búsqueda inversa en el set de instrucciones, en lugar de mirar la columna "mnemónic" buscaremos en la columna Opcode, eso lo encontramos aquí:

 Son dos las cosas que tenemos que utilizar al realizar estas búsquedas inversas:
  • instrucción (mnemonic)
  • modo de direccionamiento empleado (Addressing mode).
La instrucción es CLR y en modo EXTendido. ¿Por qué tan importante? Por el formato del campo de operandos. En este caso es "hh ll", o sea una dirección completa de 16 bits. Junto al código de operación 7f aparecen 00 03. Esos dos bytes componen el operando de 16 bits. 
¡Bien! Hemos logrado desensamblar la primera instrucción, podríamos expresarlo tal como lo hacemos cuando ensamblamos un programa:

$C000 7F 00 03         CLR $0003

por ende la porción de memoria que nos queda por delante es:

$C003: 7f 00 04 de 00 4f e6 00 d3 03 dd 03 08 7a 00 02 26 f3 20 fe

Note que el valor de la dirección en la que inicia "lo que resta" desensamblar la incrementé a $C003, porque la primer instrucción es de 3 bytes. Repetimos el procedimiento, nuevamente el código de operación es 7F, el mismo que antes, así que ya sabemos que se trata de otro CLR y que lo que sigue es la dirección del operando en 16 bits (extendido):

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004

volvemos a expresar lo que nos queda:

$C006: de 00 4f e6 00 d3 03 dd 03 08 7a 00 02 26 f3 20 fe

 Busquemos DE en la columna de "código de operación" del set de instrucciones:

Recuerde los dos datos que buscamos: instrucción -> LDX y modo de direccionamiento -> DIRecto.
Por tanto nos quedamos con el primer byte que sigue al código de operación DE: 00. Ese será el operando en modo directo (o sea la parte baja de la dirección donde la parte alta es la página cero).

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004
$C006 de 00            LDX $00

vamos avanzando, ahora nos queda esto (preste atención al avance de la dirección de inicio del bloque):

$C008: 4f e6 00 d3 03 dd 03 08 7a 00 02 26 f3 20 fe

Repetimos el mismo procedimiento, ahora sigue el código de operación 4F, que corresponde a CLRA. No hay operandos para esta instrucción, ya que solo opera en modo inherente. Eso significa que el byte que sigue también es un código de operación: E6. Este último corresponde a LDAB en modo indexado en IX. 

En este punto un par de detalles importantes:
  • Observe que LDAB en modo IND,X tiene como código de operación E6. Fíjese que en modo IND,Y el código es 18 E6. Es un caso de código de operación de 16 bits. ¡Cuidado! no olvide que el 18  es parte del código de operación, más allá de que aparezca alineado a la izquierda.
  • El modo indexado también tiene operando. Tanto IND,X como IND,Y tienen un operando de 8 bits, representado en el set de instrucciones por las letras ff. Corresponde al offset que se aplica al índice en el cálculo de la dirección real del operando.
Comprendemos entonces que E6 00 equivale a LDAB 0,x. Vayamos completando el programa:

$C000 7F 00 03         CLR $0003
$C003 7f 00 04         CLR $0004
$C006 de 00            LDX $00
$C008 4F               CLRA
$C009 E6 00            LDAB 0,x
$C00B

nos queda la última porción:

$C00B: d3 03 dd 03 08 7a 00 02 26 f3 20 fe

Luego de completar esta etapa del desensamblado todavía tendremos por delante:
  • asignar etiquetas de acuerdo a los saltos que pudieran existir
  • discernir qué hace el programa para en función de ello asignar nombres a las direcciones de memoria de los operandos.
Desafío: complete el desensamblado, trate de avanzar en la consigna final. En el próximo post vemos cómo termina y aprendemos a probar esto en el THRSim.


Si les gusta musicalizar su trabajo, sugiero "El reino del revés" de María Elena Walsh. ¿Alguna otra canción a tono con el desensamblaje?


lunes, 23 de mayo de 2016

Cómo expresar números negativos en complemento a la base sin que se te vuele el peluquín


Existe una razón por la que el sistema decimal nos resulta tán cómodo, después de todo contamos con diez dedos y pasamos los primeros años de nuestra vida midiendo años en término de ellos. Pero como la amiga CPU necesita ver las cosas en binario, más de una vez al programar en bajo nivel debemos transformar un valor a ceros y unos. Hasta ahí es feo, pero después de un poco de práctica se hace más fácil. Sin embargo expresar números negativos en binario es un par de vueltas más feo que eso. ¿Cómo podemos hacerlo -un poco- más fácil? En lugar de las metodologías tradicionales, les propongo este sistema, el cual requiere hacer algunos cálculos mentales, los que estoy seguro de que su cerebro podrá procesar sin levantar temperatura.
Este es el método que aprendí en el libro de Thomas Floyd (Fundamentos de sistemas digitales) para representar números enteros negativos en complemento a la base en 8 bits. 
Cuando representamos enteros sin signo en 8 bits, la vida es fácil, solo debemos recordar los pesos de cada dígito:

128 64 32 16 8 4 2 1

Se trata de las potencias enteras de dos, que justamente por ello tienen una relación fácil de recordar entre valores consecutivos (dependiendo de dónde se empiece, es el doble o la mitad). Para representar números sin signo rápidamente, podemos emplear el método por inspección (manera elegante de decir medio mentalmente, medio a ojo). 
Pongamos un ejemplo cualquiera: el 96.
1. Antes que nada constatamos que el número a representar entre en el rango admitido de 8 bits sin signo (0;255). El 96 cumple la condición, así que continuamos.
2. Luego vemos si alguno de los pesos binarios de cada posición supera al número. 96 es mayor a todos los pesos salvo al más pesado: 128. Por tanto en 128 va un cero:

128 64 32 16 8 4 2 1
   0  ?  ?  ?  ? ? ? ?  

El resto aun no sabemos (por eso el signo de interrogación).
Si hubiera más valores de pesos que superan al número objetivo, podemos completar con ceros.
3. Cuando hallamos el mayor peso que no supera al número objetivo (o que lo iguala) le ponemos un uno. En nuestro ejemplo 64 es ese número:

128 64 32 16 8 4 2 1
   0  1  ?  ?  ? ? ? ?  

Ahora tenemos que restar (mentalmente en un mundo ideal) 96 - 64. El resultado es 32.
4. Repetimos el paso anterior con el siguiente peso. Si el peso supera al número, va un cero. Si no lo supera, va un uno y restamos el peso del resto del objetivo. Casualmente en este ejemplo nos quedaron 32 y el siguiente peso es 32. Así que va un 1 en el peso 32 y la resta nos da cero. Por lo tanto el resto de los pesos son cero (no queda resto para representar):

128 64 32 16 8 4 2 1
   0  1  1  0  0 0 0 0  


Repitamos el procedimiento para un número distinto, por ejemplo el 172. En este caso 128 es menor a 172, por tanto ponemos un 1 en 128:

128 64 32 16 8 4 2 1
   1  ?  ?  ?  ? ? ? ?  

La parte difícil: restamos mentalmente 172-128... es 44. El siguiente peso (64) es mayor a 44, por ende ponemos un cero:
128 64 32 16 8 4 2 1
   1  0  ?  ?  ? ? ? ?   

El siguiente peso (32) es menor a 44, por ende ponemos un uno:
128 64 32 16 8 4 2 1
   1  0  1  ?  ? ? ? ?   
 y volvemos a restar, 44-32 (esta es más fácil!), nos queda 12. Podemos completar los pesos de 8 y 4 con un 1, y el resto de los pesos con ceros:



128 64 32 16 8 4 2 1
 1  0  1  0  1 1 0 0

¿No es tan difícil, no? Ahora veamos cómo haríamos con números negativos. Me animaría a decir que es más fácil. El único cambio con el procedimiento que hicimos hasta ahora es que el peso de mayor significado es negativo, o sea:


-128 64 32 16 8 4 2 1

Obviamente también cambia el rango. Ahora será (-128;127). Deducir el rango a partir de recordar lo anterior (que el peso 128 es negativo) es bien fácil. Para el menor de los negativos (-128) ponemos un 1 en el único peso negativo:


-128 64 32 16 8 4 2 1
 1   0  0  0  0 0 0 0

y para el mayor de los positivos (127) ponemos unos en todos los pesos positivos:


-128 64 32 16 8 4 2 1
 0   1  1  1  1 1 1 1

Ahora el procedimiento para representar un negativo sería así:
1. Verificar que entre en el rango representable. En 8 bits dijimos es -128;127.
2. Colocar un 1 en el único peso negativo (-128)
3. Agregar un 1 en los pesos positivos necesarios para llegar al número objetivo, siguiendo el mismo mecanismo que con los positivos. Veámoslo con un ejemplo, supongamos el número -37.
Ponemos un 1 en el peso negativo:

-128 64 32 16 8 4 2 1
 1   ?  ?  ?  ? ? ? ?

Tenemos el -128 y queremos llegar a -37. Cada uno que completemos suma al -128 y nos acerca al objetivo. Oh casualidad que al sumar 64 a -128 nos da.... -64. Volvemos a sumar 32 a -64 y nos da -32. ¡Momento que soy lento! Nos pasamos. Queríamos el -37 y llegamos a -32. Así que en 32 va un cero:



-128 64 32 16 8 4 2 1
 1   1  0  ?  ? ? ? ?

Vamos en -64, nos dimos cuenta que no podemos sumarle 32 porque es demasiado. Pero sí sumamos 16, entonces nos queda -48 (esta es la parte difícil, sumar/restar mentalmente):


-128 64 32 16 8 4 2 1
 1   1  0  1  ? ? ? ?

Al -48 le sumamos 8 y nos queda -40:



-128 64 32 16 8 4 2 1
 1   1  0  1  1 ? ? ?

Si le sumamos 4 llegamos a -36... nuevamente es demasiado. Así que en el peso 4 va un cero:



-128 64 32 16 8 4 2 1
 1   1  0  1  1 0 ? ?

Teníamos -40, si ponemos un 1 en los últimos dos pesos (2 y 1) le sumamos 3, y llegamos a -37:



-128 64 32 16 8 4 2 1
 1   1  0  1  1 0 1 1

Objetivo alcanzado! La primera vez puede que se complique, pero luego de un poco de práctica se hace más natural. ¿Probaste hacerlo?


Edit (9/9/16): Tenia un error (arrastrado como vibora) que corregí gracias a la aguda observación de un alumno.
Edit (12/12/16): Otro error más! Por canchero. Corregido gracias a Maximiliano.