驴Qu茅 hay dentro de un archivo .wasm? Introduciendo wasm-decompile

Tenemos muchos compiladores y otras herramientas a nuestra disposici贸n para crear y trabajar con archivos .wasm. El n煤mero de estas herramientas est谩 en constante crecimiento. A veces es necesario examinar el archivo .wasm y descubrir qu茅 hay dentro de 茅l. Tal vez usted sea el desarrollador de una de las herramientas de Wasm, o tal vez sea un programador que escriba c贸digo dise帽ado para convertirse a Wasm y est茅 interesado en c贸mo se ve en qu茅 se convertir谩 su c贸digo. Tal inter茅s puede ser provocado, por ejemplo, por consideraciones de rendimiento.



El problema es que los archivos .wasm contienen un c贸digo de nivel bastante bajo que se parece mucho al c贸digo de ensamblador real. En particular, a diferencia de, por ejemplo, la JVM, todas las estructuras de datos se compilan en conjuntos de operaciones de carga / almacenamiento, y no en algo que tenga nombres claros de clase y campo. Los compiladores, como LLVM, pueden cambiar el c贸digo de entrada de tal manera que lo que obtienen no se parezca. 

驴Qu茅 pasa con el que quiere, tomando un archivo .wasm, para averiguar qu茅 est谩 sucediendo en 茅l?

Desmontaje o ... 驴descompilaci贸n?


Para convertir archivos .wasm en archivos .wat que contengan una representaci贸n textual est谩ndar del c贸digo Wasm, puede usar herramientas como wasm2wat (esto es parte del kit de herramientas WABT ). Los resultados de esta conversi贸n son muy precisos, pero leer el c贸digo resultante no es particularmente conveniente.

Aqu铆, por ejemplo, hay una funci贸n simple escrita en C:

typedef struct { float x, y, z; } vec3;

float dot(const vec3 *a, const vec3 *b) {
    return a->x * b->x +
           a->y * b->y +
           a->z * b->z;
}

El c贸digo se almacena en un archivo dot.c.

Usamos el siguiente comando:

clang dot.c -c -target wasm32 -O2

Luego, para convertir lo que sucedi贸 en un archivo .wat, aplicamos el siguiente comando:

wasm2wat -f dot.o

Esto es lo que nos dar谩:

(func $dot (type 0) (param i32 i32) (result f32)
  (f32.add
    (f32.add
      (f32.mul
        (f32.load
          (local.get 0))
        (f32.load
          (local.get 1)))
      (f32.mul
        (f32.load offset=4
          (local.get 0))
        (f32.load offset=4
          (local.get 1))))
    (f32.mul
      (f32.load offset=8
        (local.get 0))
      (f32.load offset=8
        (local.get 1))))))

El c贸digo es peque帽o, pero por muchas razones, es extremadamente dif铆cil de leer. Adem谩s del hecho de que las expresiones no se usan aqu铆, y el hecho de que, en general, parece lo suficientemente detallado, no es f谩cil entender las estructuras de datos presentadas en forma de comandos para cargar datos desde la memoria. Ahora imagine que necesita analizar dicho c贸digo de un tama帽o mucho mayor. Tal an谩lisis ser谩 una tarea muy dif铆cil.

Probemos, en lugar de usar wasm2wat, ejecute el siguiente comando:

wasm-decompile dot.o

Esto es lo que ella nos dar谩:

function dot(a:{ a:float, b:float, c:float },
             b:{ a:float, b:float, c:float }):float {
  return a.a * b.a + a.b * b.b + a.c * b.c
}

Ya se ve mucho mejor. Adem谩s de usar expresiones que recuerdan un lenguaje de programaci贸n ya conocido por usted, el descompilador analiza los comandos destinados a trabajar con la memoria e intenta recrear las estructuras de datos representadas por estos comandos. Luego, el sistema anota cada variable, que se utiliza como un puntero con una declaraci贸n de estructura "incorporada". El descompilador no crea una declaraci贸n de estructura con nombre, ya que no sabe si hay algo en com煤n entre las estructuras que usan 3 valores flotantes cada una.

Como puede ver, los resultados de la descompilaci贸n resultaron ser m谩s comprensibles que los resultados del desmontaje.

驴En qu茅 idioma est谩 escrito el c贸digo escrito por el descompilador?


La herramienta wasm-decompile genera el c贸digo, tratando de hacer que este c贸digo se vea como una especie de lenguaje de programaci贸n "promedio". Al mismo tiempo, esta herramienta intenta no ir demasiado lejos de Wasm.

El primer objetivo de wasm-decompiler era crear c贸digo legible. Es decir, un c贸digo que permitir谩 a su lector comprender f谩cilmente lo que est谩 sucediendo en el archivo descompilado .wasm. El segundo prop贸sito de esta herramienta es proporcionar la representaci贸n m谩s precisa del archivo .wasm, generando c贸digo que represente completamente lo que est谩 sucediendo en el archivo fuente. Obviamente, estos objetivos est谩n lejos de estar siempre de acuerdo entre s铆.

Las salidas de wasm-decompiler no se concibieron originalmente como c贸digo que representa alg煤n lenguaje de programaci贸n real. Actualmente no hay forma de compilar este c贸digo en Wasm.

Comandos para cargar y guardar datos


Como se muestra arriba, wasm-decompile busca cargar y guardar comandos asociados con un puntero particular. Si estos comandos forman una secuencia continua, el descompilador muestra una de las declaraciones de estructura de datos "incorporadas".

Si no se accedi贸 a todos los "campos", el descompilador no puede distinguir de manera confiable la estructura de una determinada secuencia de operaciones al trabajar con memoria. En este caso, wasm-decompile usa la opci贸n alternativa, usando tipos m谩s simples como float_ptr(si los tipos son iguales) o, en el peor de los casos, genera c贸digo que ilustra c贸mo trabajar con una matriz, como o[2]:int. Tal c贸digo nos dice que oapunta a elementos del tipo int, y pasamos al tercer elemento.

Esta 煤ltima situaci贸n surge mucho m谩s a menudo de lo que podr铆a pensar, ya que las funciones locales de Wasm est谩n m谩s enfocadas en usar registros en lugar de variables. Como resultado, en el c贸digo optimizado, se puede utilizar el mismo puntero para trabajar con objetos completamente no relacionados.

El descompilador busca un enfoque inteligente para la indexaci贸n y es capaz de identificar patrones similares (base + (index << 2))[0]:int. La fuente de tales patrones son las operaciones de indexaci贸n habituales para C, base[index]como d贸nde baseapunta a un tipo de 4 bytes. Esto es muy com煤n en el c贸digo, ya que Wasm, en los comandos de cargar y guardar datos, solo admite desplazamientos definidos como constantes. En el c贸digo generado por wasm-decompile, tales construcciones se convierten a tipo base[index]:int.

Adem谩s, el descompilador sabe cu谩ndo las direcciones absolutas apuntan a una secci贸n de datos.

Programa de control de flujo


Si hablamos de construcciones de control, la m谩s famosa entre ellas es la construcci贸n de Wasm si-entonces, que se convierte en if (cond) { A } else { B }, con la adici贸n del hecho de que dicha construcci贸n en Wasm puede devolver un valor, por lo que tambi茅n puede representar un operador ternario, como cond ? A : B, en algunos idiomas

Otras estructuras de control de Wasm est谩n basadas en bloques blocky loop, as铆 como en transiciones br, br_ify br_table. El descompilador intenta mantenerse lo m谩s cerca posible de estas estructuras. No busca recrear mientras / para / cambiar construcciones que podr铆an servir de base para ellos. El hecho es que este enfoque se muestra mejor al procesar c贸digo optimizado. Por ejemplo, un dise帽o convencional.loop podr铆a verse en el c贸digo devuelto por wasm-decompile as铆:

loop A {
  //    .
  if (cond) continue A;
}

Aqu铆 Ahay una etiqueta que le permite construir estructuras anidadas entre s铆 loop. El hecho de que haya comandos ify se continueusen para controlar el ciclo puede parecer algo extra帽o para los bucles while, pero corresponden a la construcci贸n Wasm br_if.

Los bloques se dibujan de manera similar, pero aqu铆 las condiciones son al principio y no al final:

block {
  if (cond) break;
  //    .
}

El resultado de descompilar la construcci贸n if-then se muestra aqu铆. En futuras versiones del descompilador, probablemente, en lugar de dicho c贸digo, cuando sea posible, se formar谩 una construcci贸n if-then m谩s familiar.

La herramienta Wasm m谩s inusual utilizada para controlar el flujo de un programa es br_table. Esta herramienta es una especie de declaraci贸n de cambio, excepto que utiliza bloques en l铆nea. Todo esto complica la lectura del c贸digo. El descompilador simplifica la estructura de tales estructuras y se esfuerza por facilitar su percepci贸n:

br_table[A, B, C, ..D](a);
label A:
return 0;
label B:
return 1;
label C:
return 2;
label D:

Esto recuerda el uso switchpara el an谩lisis acuando la opci贸n predeterminada es D.

Otras caracter铆sticas interesantes


Aqu铆 hay algunas caracter铆sticas m谩s de wasm-decompile:

  • , . C++-.
  • , , , . . , .
  • .
  • Wasm-, . , wasm-decompile , , , .
  • , ( , C- ).

 


Descompilar el c贸digo Wasm es una tarea mucho m谩s complicada que, por ejemplo, descompilar el c贸digo de bytes JVM.

El bytecode no est谩 optimizado, es decir, reproduce la estructura del c贸digo fuente con bastante precisi贸n. Al mismo tiempo, a pesar de que dicho c贸digo puede no contener los nombres originales, el c贸digo de bytes utiliza referencias a clases 煤nicas y no a 谩reas de memoria.

A diferencia del c贸digo de bytes JVM, LLVM optimiza mucho el c贸digo que ingresa a los archivos .wasm. Como resultado, dicho c贸digo a menudo pierde la mayor parte de su estructura original. El c贸digo de salida es muy diferente de lo que escribir铆a el programador. Esto complica enormemente la tarea de descompilar el c贸digo Wasm con la salida de resultados que pueden traer beneficios reales a los programadores. Sin embargo, esto no significa que no debamos esforzarnos por resolver este problema.

Resumen


Si est谩 interesado en el tema de la descompilaci贸n de c贸digo Wasm, entonces quiz谩s la mejor manera de entender este tema es tomar y descompilar su propio proyecto .wasm. Adem谩s, aqu铆 puede encontrar una gu铆a m谩s detallada sobre wasm-decompile. El c贸digo del descompilador se puede encontrar en los archivos de este repositorio, cuyos nombres comienzan con decompile(si lo desea, 煤nase al trabajo en el descompilador). Aqu铆 puede encontrar pruebas que muestran ejemplos adicionales de diferencias entre archivos .wat y resultados de descompilaci贸n.

驴Y con qu茅 herramientas investigas los archivos .wasm?

, , iPhone. , .


All Articles