Guía · RISC-V

Del código máquina al ensamblador

Traducimos un volcado de ocho palabras a una rutina legible: decodificar, convertir desplazamientos en etiquetas, reconocer pseudoinstrucciones y deducir qué hace.

Revisada el con las herramientas del sitio.

Cuando depuras un programa compilado, examinas un volcado de memoria o corriges un examen, a menudo solo tienes palabras hexadecimales. Pasar de ahí a ensamblador legible exige algo más que decodificar cada palabra por separado: hay que reconstruir las etiquetas y reconocer las pseudoinstrucciones. Lo hacemos con una rutina de ocho instrucciones escrita para esta guía; cada palabra se ha generado y comprobado con el decodificador del sitio.

Paso 1: decodificar cada palabra

Partimos de ocho palabras consecutivas que empiezan en la dirección 0x00. Aplicando el método de la guía de decodificación a cada una se obtiene:

DirecciónPalabraFormatoDecodificada
0x0000000293Iaddi t0, zero, 0
0x0400050A63Bbeq a0, zero, 20
0x0800157313Iandi t1, a0, 1
0x0C006282B3Radd t0, t0, t1
0x1000155513Isrli a0, a0, 1
0x14FF1FF06FJjal zero, -16
0x1800028513Iaddi a0, t0, 0
0x1C00008067Ijalr zero, 0(ra)

Esto ya es ensamblador correcto, pero cuesta leerlo: los saltos aparecen como desplazamientos numéricos y hay instrucciones con registros «raros», como sumar cero o saltar guardando el retorno en zero.

Paso 2: convertir desplazamientos en etiquetas

Los desplazamientos de beq y jal son relativos a la dirección de la propia instrucción. El beq de 0x04 salta +20, es decir, a 0x04 + 20 = 0x18. El jal de 0x14 salta −16, a 0x14 − 16 = 0x04. Esas dos direcciones merecen un nombre: llamamos loop a 0x04 (el destino del salto hacia atrás) y done a 0x18.

Paso 3: reconocer las pseudoinstrucciones

Las pseudoinstrucciones son abreviaturas que el ensamblador traduce a instrucciones reales. Al leer código máquina hay que hacer el camino inverso:

  • addi rd, zero, imm es li rd, imm, y addi rd, rs, 0 es mv rd, rs.
  • beq rs, zero, destino es beqz rs, destino.
  • jal zero, destino es j destino: un salto que no guarda dirección de retorno.
  • jalr zero, 0(ra) es ret: vuelve a la dirección guardada en ra.
DirecciónInstrucción baseForma legible
0x00addi t0, zero, 0li t0, 0
0x04beq a0, zero, 20 → 0x18beqz a0, done
0x08andi t1, a0, 1andi t1, a0, 1
0x0Cadd t0, t0, t1add t0, t0, t1
0x10srli a0, a0, 1srli a0, a0, 1
0x14jal zero, -16 → 0x04j loop
0x18addi a0, t0, 0mv a0, t0
0x1Cjalr zero, 0(ra)ret

Paso 4: entender qué hace

popcount:            # a0 = número de entrada
  li   t0, 0         # t0 = contador de unos
loop:
  beqz a0, done      # sin bits pendientes: terminar
  andi t1, a0, 1     # t1 = bit más bajo de a0
  add  t0, t0, t1    # sumar ese bit al contador
  srli a0, a0, 1     # descartar el bit más bajo
  j    loop
done:
  mv   a0, t0        # devolver el contador en a0
  ret

Con nombres y comentarios la intención aparece sola. En cada vuelta, andi t1, a0, 1 aísla el bit más bajo, se suma al contador y srli desplaza a0 a la derecha. Cuando a0 llega a 0 ya no quedan unos. La rutina cuenta cuántos bits valen 1 en a0 (en inglés, population count) y devuelve el resultado en a0, siguiendo la convención de llamada: argumento y resultado en a0, dirección de retorno en ra.

Paso 5: comprobarlo ejecutándolo

Para ejecutarla hace falta un programa que la llame. Este carga 13 (0b1101) en a0, llama a la rutina con jal y termina:

li   a0, 13
jal  ra, popcount
j    end
popcount:
  li   t0, 0
loop:
  beqz a0, done
  andi t1, a0, 1
  add  t0, t0, t1
  srli a0, a0, 1
  j    loop
done:
  mv   a0, t0
  ret
end:
  nop

En el depurador el programa ejecuta 28 instrucciones y termina con a0 = 3, porque 1101 tiene tres unos. Prueba otros valores, por ejemplo 255 (ocho unos) o 0 (el bucle no se ejecuta ninguna vez).

Resumen del procedimiento

  1. Decodifica cada palabra y anota su dirección.
  2. Calcula el destino de cada salto (dirección + desplazamiento) y ponle una etiqueta.
  3. Sustituye las instrucciones base por sus pseudoinstrucciones cuando encajen.
  4. Busca la estructura: bucles (saltos hacia atrás), condiciones (saltos hacia delante) y llamadas (jal/ret).
  5. Comprueba tu interpretación ejecutando el código con valores conocidos.

Practica lo que has leído

Sigue la ejecución de un bucle

¿Cuánto vale a0 al salir del bucle? ¿Cuántas instrucciones se ejecutan en total (contando cada vuelta)?

li   a0, 1
li   t0, 4
loop:
  add  a0, a0, a0
  addi t0, t0, -1
  bnez t0, loop
Abrir en la herramienta

Llamar a una función con jal y volver con ret

La rutina triple recibe un número en a0 y devuelve el resultado en a0. ¿Qué valen a0 y ra al terminar?

li   a0, 6
jal  ra, triple
j    end
triple:
  add  a1, a0, a0
  add  a0, a1, a0
  ret
end:
  nop
Abrir en la herramienta

Referencias