Написание эмиттера кода x64 для создания полноценного ассемблера, такого как NASM

Как следует из названия, я пишу эмиттер кода x64. Прямо сейчас я закодировал только 1 инструкцию (Инструкция добавления). Я хочу знать, можно ли вообще улучшить этот API.

Вот как я его использую.

int main() {
    // The Operand type contains the type of the operand and a union with the contained value.
    // There are 4 types right now, NONE, REGISTER, MEMORY, and CONSTANT.
    Operand imm42 = (Operand) { OPERAND_CONSTANT, .Constant = 42 };
    Operand imm256 = (Operand) { OPERAND_CONSTANT, .Constant = 256 };
        
    // rax and r8 are constants defined in another file.
    // the emitAdd function for now simply prints the result to stdout.
    emitAdd(rax, imm42); // -> 48 83 C0 2A
    emitAdd(rax, imm256); // -> 48 05 00 01 00 00 
    emitAdd(r8, imm42); // -> 49 83 C0 2A
    emitAdd(r8, imm256); // -> 49 81 C0 00 01 00 00
}

Я очень доволен тем, что сделал до сих пор, и я протестировал все варианты этой инструкции. т.е. (добавить reg, imm добавить reg, reg) Я еще не закодировал варианты памяти.

Это основной код.

// Instruction.c
// forward declaration for emitAdd
#include "Instruction.h"

#include <stdio.h>
#include <stdbool.h>
#include <stddef.h>
#include "types.h"
#include "Encoding.h"

unsigned int registerToIndex(Register reg) {
    switch (reg) {
        case REGISTER_AL:
        case REGISTER_AX:
        case REGISTER_EAX:
        case REGISTER_RAX: return 0;
        case REGISTER_CL:
        case REGISTER_CX:
        case REGISTER_ECX:
        case REGISTER_RCX: return 1;
        case REGISTER_DL:
        case REGISTER_DX:
        case REGISTER_EDX:
        case REGISTER_RDX: return 2;
        case REGISTER_BL:
        case REGISTER_BX:
        case REGISTER_EBX:
        case REGISTER_RBX: return 3;
        case REGISTER_AH:
        case REGISTER_SPL:
        case REGISTER_SP:
        case REGISTER_ESP:
        case REGISTER_RSP: return 4;
        case REGISTER_CH:
        case REGISTER_BPL:
        case REGISTER_BP:
        case REGISTER_EBP:
        case REGISTER_RBP: return 5;
        case REGISTER_DH:
        case REGISTER_SIL:
        case REGISTER_SI:
        case REGISTER_ESI:
        case REGISTER_RSI: return 6;
        case REGISTER_BH:
        case REGISTER_DIL:
        case REGISTER_DI:
        case REGISTER_EDI:
        case REGISTER_RDI: return 7;
        case REGISTER_R8B:
        case REGISTER_R8W:
        case REGISTER_R8D:
        case REGISTER_R8: return 8;
        case REGISTER_R9B:
        case REGISTER_R9W:
        case REGISTER_R9D:
        case REGISTER_R9: return 9;
        case REGISTER_R10B:
        case REGISTER_R10W:
        case REGISTER_R10D:
        case REGISTER_R10: return 10;
        case REGISTER_R11B:
        case REGISTER_R11W:
        case REGISTER_R11D:
        case REGISTER_R11: return 11;
        case REGISTER_R12B:
        case REGISTER_R12W:
        case REGISTER_R12D:
        case REGISTER_R12: return 12;
        case REGISTER_R13B:
        case REGISTER_R13W:
        case REGISTER_R13D:
        case REGISTER_R13: return 13;
        case REGISTER_R14B:
        case REGISTER_R14W:
        case REGISTER_R14D:
        case REGISTER_R14: return 14;
        case REGISTER_R15B:
        case REGISTER_R15W:
        case REGISTER_R15D:
        case REGISTER_R15: return 15;
    }
}

static bool is8BitRegister(Register reg) {
    return reg >= REGISTER_AL && reg <= REGISTER_R15B;
}
static bool is16BitRegister(Register reg) {
    return reg >= REGISTER_AX && reg <= REGISTER_R15W;
}
static bool is32BitRegister(Register reg) {
    return reg >= REGISTER_EAX && reg <= REGISTER_R15D;
}
static bool is64BitRegister(Register reg) {
    return reg >= REGISTER_RAX && reg <= REGISTER_R15;
}

static bool needsREX(Register reg) {
    switch (reg) {
        case REGISTER_SPL:
        case REGISTER_BPL:
        case REGISTER_SIL:
        case REGISTER_DIL:
        case REGISTER_R8B:
        case REGISTER_R9B:
        case REGISTER_R10B:
        case REGISTER_R11B:
        case REGISTER_R12B:
        case REGISTER_R13B:
        case REGISTER_R14B:
        case REGISTER_R15B:
        case REGISTER_R8W:
        case REGISTER_R9W:
        case REGISTER_R10W:
        case REGISTER_R11W:
        case REGISTER_R12W:
        case REGISTER_R13W:
        case REGISTER_R14W:
        case REGISTER_R15W:
        case REGISTER_R8D:
        case REGISTER_R9D:
        case REGISTER_R10D:
        case REGISTER_R11D:
        case REGISTER_R12D:
        case REGISTER_R13D:
        case REGISTER_R14D:
        case REGISTER_R15D:
        case REGISTER_R8:
        case REGISTER_R9:
        case REGISTER_R10:
        case REGISTER_R11:
        case REGISTER_R12:
        case REGISTER_R13:
        case REGISTER_R14:
        case REGISTER_R15: return true;
    }

    return false;
}

static void printMemory(byte* memory, size_t length) {
    /*
        byte arr[] = {0x3, 0x4, 0x6, 0x5};

        printMemory(arr, 4); -> 03 04 06 05
    */
    for (size_t i = 0; i < length; i++) {
        byte b = memory[i];
        printf("%X%X ", (b & 0xF0) >> 4, b & 0x0F);
    }
    printf("n");
}

/*
    This function optimizes for instruction size, so instructions are encoded using the
    least amount of bytes possible.
*/
void emitAdd(Operand destination, Operand source) {
    // for now just assume the largest an x64 instruction can be.
    // I think it can be bigger, I haven't checked.
    byte instruction[14] = {};
    unsigned int index = 0;

    if (destination.Type == OPERAND_REGISTER && source.Type == OPERAND_CONSTANT) {
        Register reg = destination.Register;
        unsigned int registerCode = registerToIndex(reg);
        qword constant = source.Constant;

        if (reg == REGISTER_AL || reg == REGISTER_AX || reg == REGISTER_EAX || reg == REGISTER_RAX) {
            switch (reg) {
                case REGISTER_AL: {
                    instruction[index++] = 0x4;
                    *(byte*)(instruction + index++) = constant;
                } break;
                case REGISTER_AX: {
                    instruction[index++] = 0x66;
                    instruction[index++] = 0x5;
                    *(word*)(instruction + index) = constant, index += 2;
                } break;
                case REGISTER_EAX:
                case REGISTER_RAX: {
                    if (reg == REGISTER_RAX)
                        instruction[index++] = REX_W;
                    
                    if (constant <= 0xFF) {
                        instruction[index++] = 0x83;
                        instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                        *(byte*)(instruction + index++) = constant;
                    } else {
                        instruction[index++] = 0x5;
                        *(dword*)(instruction + index) = constant, index += 4;
                    }
                } break;
            }
        } else if (is8BitRegister(reg)) {
            if (needsREX(reg))
                instruction[index++] = (reg < REGISTER_R8B ? REX : REX_B);

            instruction[index++] = 0x80;
            instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
            *(byte*)(instruction + index++) = constant; 
        } else if (is16BitRegister(reg)) {
            instruction[index++] = 0x66;
            if (constant <= 0xFF) {
                if (needsREX(reg))
                    instruction[index++] = REX_B;
                instruction[index++] = 0x83;
                instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                *(byte*)(instruction + index++) = constant;
            } else {
                if (needsREX(reg))
                    instruction[index++] = REX_B;

                instruction[index++] = 0x81;
                instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                *(word*)(instruction + index) = constant, index += 2;
            }
        } else if (is32BitRegister(reg)) {
            if (constant <= 0xFF) {
                if (needsREX(reg))
                    instruction[index++] = REX_B;
                instruction[index++] = 0x83;
                instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                *(byte*)(instruction + index++) = constant;
            } else {
                if (needsREX(reg))
                    instruction[index++] = REX_B;

                instruction[index++] = 0x81;
                instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                *(dword*)(instruction + index) = constant, index += 4;
            }
        } else { // must be a 64 bit register
            instruction[index++] = (reg < REGISTER_R8 ? REX_W : REX_W | REX_B);
            if (constant <= 0xFF) {
                instruction[index++] = 0x83;
                instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                *(byte*)(instruction + index++) = constant;
            } else {
                instruction[index++] = 0x81;
                instruction[index++] = encodeModRM(REGISTER_ADDRESSING, 0, registerCode);
                *(dword*)(instruction + index) = constant, index += 4;
            }
        }
    } else if (destination.Type == OPERAND_REGISTER && source.Type == OPERAND_REGISTER) {
        Register dst = destination.Register;
        Register src = source.Register;
        unsigned int dstRegisterCode = registerToIndex(dst);
        unsigned int srcRegisterCode = registerToIndex(src);

        if (is8BitRegister(dst) && is8BitRegister(src)) {
            if (needsREX(dst) || needsREX(src)) {
                unsigned int rexPrefixIndex = index++;
                instruction[rexPrefixIndex] = REX;
                instruction[rexPrefixIndex] |= (dst >= REGISTER_R8B ? REX_B : 0);
                instruction[rexPrefixIndex] |= (src >= REGISTER_R8B ? REX_R : 0);
            }

            instruction[index++] = 0x00;
            instruction[index++] = encodeModRM(REGISTER_ADDRESSING, srcRegisterCode, dstRegisterCode);
        } else if (is16BitRegister(dst) && is16BitRegister(src)) {
            instruction[index++] = 0x66;
            if (needsREX(dst) || needsREX(src)) {
                unsigned int rexPrexfixIndex = index++;
                instruction[rexPrexfixIndex] = REX;
                instruction[rexPrexfixIndex] |= (dst >= REGISTER_R8W ? REX_B : 0);
                instruction[rexPrexfixIndex] |= (src >= REGISTER_R8W ? REX_R : 0);
            }
            instruction[index++] = 0x1;
            instruction[index++] = encodeModRM(REGISTER_ADDRESSING, srcRegisterCode, dstRegisterCode);
        } else if (is32BitRegister(dst) && is32BitRegister(src)) {
            if (needsREX(dst) || needsREX(src)) {
                unsigned int rexPrefixIndex = index++;
                instruction[rexPrefixIndex] = REX;
                instruction[rexPrefixIndex] |= (dst >= REGISTER_R8D ? REX_B : 0);
                instruction[rexPrefixIndex] |= (src >= REGISTER_R8D ? REX_R : 0);
            }

            instruction[index++] = 0x1;
            instruction[index++] = encodeModRM(REGISTER_ADDRESSING, srcRegisterCode, dstRegisterCode);
        } else if (is64BitRegister(dst) && is64BitRegister(src)) {
            unsigned int rexPrefixIndex = index++;
            instruction[rexPrefixIndex] = REX_W;
            instruction[rexPrefixIndex] |= (dst >= REGISTER_R8 ? REX_B : 0);
            instruction[rexPrefixIndex] |= (src >= REGISTER_R8 ? REX_R : 0);

            instruction[index++] = 0x1;
            instruction[index++] = encodeModRM(REGISTER_ADDRESSING, srcRegisterCode, dstRegisterCode);
        }
    }

    printMemory(instruction, index);
}

Здесь есть все исходники, в основном они определяют константы и форвардные объявления.

types.h: https://hastebin.com/pipuqezoxe.cpp
Encoding.h: https://hastebin.com/esiciniwex.cpp
Instruction.h: https://hastebin.com/refokaluka.cpp
Instruction.c (where all the code emitting happens): https://hastebin.com/fuboqijedi.cpp

1 ответ
1

// forward declaration for emitAdd
#include "Instruction.h"

Здесь нет декларации. Вы имеете в виду, что это в Instruction.h? Думаю, вам не нужен этот комментарий. Когда вы включаете заголовок с тем же именем, что и исходный файл, каждый, читающий ваш код, будет считать, что он содержит объявления экспортируемых функций. Если это не так, это заслуживает комментария.

unsigned int registerToIndex(Register reg) {
    switch (reg) {
        case REGISTER_AL:
        case REGISTER_AX:
        case REGISTER_EAX:
        case REGISTER_RAX: return 0;
        case REGISTER_CL:
        case REGISTER_CX:
        case REGISTER_ECX:
        case REGISTER_RCX: return 1;
        [...]

Это много шаблонов, и ошибки могут легко закрасться. Кроме того, наличие неупорядоченного перечисления для регистров неудобно для пользователей библиотеки, которые, вероятно, захотят упоминать их по номеру, а не по имени, в большинстве случаев ( при написании распределителя регистров, например).

Я бы либо отделил тип регистра от индекса регистра, сохранив последний в каком-нибудь другом поле Operand struct, или переставьте перечисление так, чтобы эта функция могла быть чем-то вроде

unsigned int registerToIndex(Register reg) {
    return (unsigned)reg % 16u;
}

и задокументируйте это.

static bool needsREX(Register reg) { [...] }

Это возвращает false для таких регистров, как rax которые обычно нужны REX.W. Кажется, что он действительно проверяет все потребности в REX, кроме REX.W, что трудно уловить в имени функции.

Мой совет – обращаться с префиксами по-другому. Вместо того, чтобы иметь настраиваемую логику для определения того, какие префиксы вам понадобятся заранее, отслеживайте их по мере продвижения и выдавайте префиксы в конце. encodeModRM должен установить REX.RXB на основе аргументов.

printf("%X%X ", (b & 0xF0) >> 4, b & 0x0F);

Это может быть просто printf("%02X ", b);.

void emitAdd(Operand destination, Operand source) { [...] }

Меня беспокоит длина этой функции и количество дублированного кода. У тебя есть длинный дорога впереди вас. Вы определенно не хотите копировать и вставлять весь этот код только для того, чтобы вы могли изменить байт кода операции для других инструкций.

16-битный, 32-битный и 64-битный варианты почти идентичны, и я бы их унифицировал.

unsigned int registerCode = registerToIndex(reg);

Я бы не стал использовать «код» и «индекс» для одного и того же свойства. Назовите это просто «индекс».

if (reg == REGISTER_AL || reg == REGISTER_AX || reg == REGISTER_EAX || reg == REGISTER_RAX) {

Вы также можете изменить это на if (registerCode == 0) {.

*(byte*)(instruction + index++) = constant;

instruction это массив byte, поэтому я бы просто сказал instruction[index++] = constant;

*(word*)(instruction + index) = constant, index += 2;
*(dword*)(instruction + index) = constant, index += 4;

Они не переносятся; они предполагают, что машина работает с прямым порядком байтов и поддерживает невыровненные хранилища. Даже если вы не сделаете их переносными прямо сейчас, вам следует переместить их в отдельные функции, чтобы они не дублировались повсюду и могли быть перенесены позже без поиска и замены.

if (constant <= 0xFF) {

Этот тест неверен, потому что байты непосредственного значения расширяются по знаку. Должно получиться что-то вроде ((dword)constant <= 0x7Fu || (dword)constant >= 0xFFFFFF80u) (с очевидными изменениями в 16-битном регистре). Конечно, этот тест также должен быть отдельной функцией.

if (is8BitRegister(dst) && is8BitRegister(src)) {
    if (needsREX(dst) || needsREX(src)) {

Некоторые комбинации 8-битных регистров не кодируются, например ah,r8b. В этом случае вы выдаете неверный код.

Если вы последуете моему совету по отработке префиксов на ходу, у вас должен быть флаг «недействителен с REX», который вы устанавливаете всякий раз, когда вы видите ah / ch / dh / bh, и проверяйте его в конце и выводите ошибку при необходимости.

} else if (is64BitRegister(dst) && is64BitRegister(src)) { [...] }

В этой цепочке if-else нет регистра по умолчанию, поэтому, когда типы регистров не совпадают, вы просто молча не выдадите код. То же самое верно, если типы операндов недействительны или вы просто еще не реализовали их.

В общем, с подозрением относитесь к цепочкам if-else без финального слова else и к операторам switch без регистра по умолчанию. Вы можете добавить явный // do nothing случай, если ничего не делать – действительно правильное действие.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *