Pregunta ¿Cómo escribir un _86_64 _assembler_?


Objetivo: Quiero escribir un ensamblador X86_64. Nota: marcado como wiki de la comunidad

Antecedentes: Estoy familiarizado con C. He escrito el ensamblaje MIPS antes. He escrito algunos ensamblados x86. Sin embargo, quiero escribir un ensamblador x86_64; debería generar un código de máquina al que pueda saltar y comenzar a ejecutar (como en un JIT).

La pregunta es: ¿cuál es la mejor manera de abordar esto? Me doy cuenta de que este problema parece bastante grande para abordar. Quiero empezar con un conjunto mínimo básico:

  • Cargar en el registro
  • Arithmetric ops on registers (solo enteros está bien, no hay necesidad de meterse con FPU todavía)
  • Condicionales
  • Saltos

Solo un set básico para hacerlo turing completo. ¿Alguien hizo esto? Sugerencias / recursos?


5
2018-06-19 01:43


origen


Respuestas:


Un ensamblador, como cualquier otro "compilador", se escribe mejor como un analizador léxico que alimenta un procesador de gramática lingüística.

El lenguaje ensamblador suele ser más fácil que los lenguajes compilados normales, ya que no necesita preocuparse por las construcciones que cruzan los límites de las líneas y el formato suele ser fijo.

Escribí un ensamblador para una CPU (ficticia) hace dos años con fines educativos y básicamente trataba cada línea como:

  • etiqueta opcional (por ejemplo, :loop)
  • operación (por ejemplo, mov)
  • operandos (por ejemplo, ax,$1)

La forma más fácil de hacerlo es garantizar que los tokens sean fácilmente distinguibles.

Es por eso que hice la regla de que las etiquetas tenían que comenzar con : - Facilitó mucho el análisis de la línea. El proceso para manejar una línea fue:

  • quitar los comentarios (primero ; fuera de una cadena al final de la línea).
  • extraer la etiqueta si está presente.
  • primera palabra es entonces la operación.
  • el resto son los operandos.

Puede insistir fácilmente en que los diferentes operandos también tienen marcadores especiales para facilitar su vida. Todo esto asumiendo que usted tiene control sobre el formato de entrada. Si se requiere que uses el formato Intel o AT&T, es un poco más difícil.

La forma en que lo abordé es que había una función simple por operación que se llamó (por ejemplo, doJmp, doCall, doRet) y esa función decidía sobre lo que estaba permitido en los operandos.

Por ejemplo, doCall Solo permite un numérico o etiqueta, doRet no permite nada

Por ejemplo, aquí hay un segmento de código de la encInstr función:

private static MultiRet encInstr(
    boolean ignoreVars,
    String opcode,
    String operands)
{
    if (opcode.length() == 0) return hlprNone(ignoreVars);
    if (opcode.equals("defb"))  return hlprByte(ignoreVars,operands);
    if (opcode.equals("defbr")) return hlprByteR(ignoreVars,operands);
    if (opcode.equals("defs"))  return hlprString(ignoreVars,operands);
    if (opcode.equals("defw"))  return hlprWord(ignoreVars,operands);
    if (opcode.equals("defwr")) return hlprWordR(ignoreVars,operands);
    if (opcode.equals("equ"))   return hlprNone(ignoreVars);
    if (opcode.equals("org"))   return hlprNone(ignoreVars);

    if (opcode.equals("adc"))   return hlprTwoReg(ignoreVars,0x0a,operands);
    if (opcode.equals("add"))   return hlprTwoReg(ignoreVars,0x09,operands);
    if (opcode.equals("and"))   return hlprTwoReg(ignoreVars,0x0d,operands);

los hlpr... las funciones simplemente tomaron los operandos y devolvieron una matriz de bytes que contiene las instrucciones. Son útiles cuando muchas operaciones tienen requisitos de operandos similares, como adc,añadirandy `todos requieren dos operandos de registro en el caso anterior (el segundo parámetro controlaba qué código de operación se devolvió para la instrucción).

Al hacer que los tipos de operandos sean fácilmente distinguibles, puede verificar qué operandos se proporcionan, si son legales y qué secuencias de bytes generar. La separación de operaciones en sus propias funciones proporciona una estructura lógica agradable.

Además, la mayoría de las CPU siguen una traducción razonablemente lógica de opcode a operación (para facilitar la vida de los diseñadores de chips), por lo que habrá cálculos muy similares en todos los opcodes que permiten, por ejemplo, direccionamiento indexado.

Para crear correctamente el código en una CPU que permita instrucciones de longitud variable, es mejor hacerlo en dos pases.

En la primera pasada, no genere código, solo genere la longitud de las instrucciones. Esto le permite asignar valores a todas las etiquetas a medida que las encuentre. La segunda pasada generará el código y puede completar referencias a esas etiquetas ya que sus valores son conocidos. los ignoreVars en ese código se usó el segmento anterior para este propósito (se devolvieron las secuencias de bytes del código para que pudiéramos saber la longitud, pero cualquier referencia a los símbolos que solo se usan con 0).


8
2018-06-19 01:52



No para desanimarte, pero ya hay muchos montadores Con varias campanas y silbidos. Por favor, considere contribuir a un proyecto de código abierto existente como elftoolchain.


6
2018-06-19 02:49