Как я могу упростить создание кода во время выполнения?
Я работаю над программным обеспечением, которое генерирует код ассемблера во время выполнения. Например,
здесь очень простая функция, которая генерирует код ассемблера для вызова функции GetCurrentProcess
(для Win64 ABI):
void genGetCurrentProcess( char *codePtr, FARPROC addressForGetCurrentProcessFunction )
{
#ifdef _WIN64
// mov rax, addressForGetCurrentProcessFunction
*codePtr++ = 0x48
*codePtr++ = 0xB8;
*((FARPROC *)codePtr)++ = addressForGetCurrentProcessFunction;
// call rax
*codePtr++ = 0xFF;
*codePtr++ = 0xD0;
#else
// mov eax, addressForGetCurrentProcessfunction
*codePtr++ = 0xB8;
*((FARPROC *)codePtr)++ = addressForGetCurrentProcessFunction;
// call eax
*codePtr++ = 0xFF;
*codePtr++ = 0xD0;
#endif
}
Обычно я использую встроенный ассемблер, но, увы - это, похоже, не представляется возможным с 64-битными компиляторами MSVC. Пока я нахожусь - этот код должен работать с MSVC6 до MSVC10, а также с MinGW. Есть еще много функций, таких как genGetCurrentProcess
, все они испускают код ассемблера, и многие из них получают вызовы функций, которые будут вызваны переданы как аргументы.
Досадная вещь об этом заключается в том, что изменение этого кода подвержено ошибкам, и мы должны позаботиться о специфических для ABI вещах вручную (например, резервируя 32-битное пространство стека перед вызовом функций для разлива регистров).
Итак, мой вопрос: могу ли я упростить этот код для генерации кода ассемблера во время выполнения? Я надеялся, что могу как-то написать код ассемблера напрямую (возможно, во внешнем файле, который затем собирается с помощью ml
/ml64
), но мне не ясно, как это будет работать, если некоторые из байтов в собранном коде известны только во время выполнения (например, значение addressForGetcurrentProcessFunction
в приведенном выше примере). Может быть, возможно собрать какой-то код, но назначить "метки" определенным местам в коде, чтобы я мог легко изменить код во время выполнения, а затем скопировать его в мой буфер?
Ответы
Ответ 1
Взгляните на asmjit. Это библиотека С++ для генерации кода времени исполнения. Поддерживает x64 и, возможно, большинство существующих расширений (FPU, MMX, 3dNow, SSE, SSE2, SSE3, SSE4). Его интерфейс напоминает синтаксис сборки, и он правильно кодирует инструкции.
Ответ 2
Вы можете зависеть от реального ассемблера, который сделает для вас работу - тот, который генерирует двоичный вывод, является, пожалуй, лучшим. Рассмотрим yasm или fasm (есть некоторые сообщения на форумах fasm о выполнении DLL-версии, поэтому вам не нужно писать временный файл сборки, запускать внешний процесс и читать выходной файл обратно, но я не знаю, был ли он обновлен для более поздних версий).
Это может быть излишним, если ваши потребности относительно просты. Я бы подумал о создании класса С++ Assembler, поддерживающего только мнемонику, которая вам нужна, а также некоторые вспомогательные функции, такие как GeneratePrologue
, GenerateEpilogue
, InstructionPointerRelativeAddress
и т.д. Это позволит вам писать псевдо-сборку и иметь вспомогательные функции по 32/64-разрядным вопросам.
Ответ 3
Вы можете абстрагироваться от некоторых команд, кодирования, вызова и деталей, связанных с процессором, путем написания некоторых вспомогательных функций и макросов.
Вы даже можете создать небольшой ассемблер, который будет собирать псевдо-asm-код, численно закодированный и содержащийся в массиве, в исполняемый код, например. начиная с ввода следующим образом:
UINT32 blah[] =
{
mov_, ebx_, dwordPtr_, edi_, plus_, eax_, times8_, plus_, const_, 0xFEDCBA98,
call_, dwordPtr_, ebx_,
};
Но это большая работа, чтобы сделать это сделано и сделано правильно. Для чего-то более простого, просто создайте вспомогательные функции/макросы, по существу делая то, что вы уже сделали, но скрываете от пользователя некоторые неприятные данные.
Ответ 4
Очевидной задачей является создание набора абстракций, представляющих собой генерацию элементов интересующих машинных команд, а затем составление вызовов для получения желаемых режимов инструкций/адресации. Если вы создаете большое количество кода, вы можете закончить кодирование всей инструкции таким образом.
Затем, чтобы сгенерировать инструкцию MOV, вы можете написать код, который выглядит так:
ObjectCodeEmitMovRegister32ScaledRegister32OffsetRegister32(EAX,EDX,4,-LowerBound*4,ESP);
Вы можете сказать, что мне нравятся длинные имена. (По крайней мере, я никогда не забываю, что они делают.)
Вот некоторые бит генератора кода, поддерживающие это, что я реализовал в C давным-давно. Это относится к самой сложной части, которая представляет собой создание байтов MOD и SIB. Следуя этому стилю, вы можете реализовать столько наборов команд, сколько нравится. Этот пример предназначен только для x32, поэтому OP придется соответствующим образом расширять и изменять. Определение генератора команд MOV находится в конце.
#define Register32T enum Register32Type
enum Register32Type {EAX=0,ECX=1,EDX=2,EBX=3,ESP=4,EBP=5,ESI=6,EDI=7};
inline
byte ObjectCodeEmitModRM32Register32(Register32T Register32,Register32T BaseRegister32)
// Send ModRM32Bytes for register-register mode to object file
{ byte ModRM32Byte=0xC0+Register32*0x8+BaseRegister32;
ObjectCodeEmitByte(ModRM32Byte);
return ModRM32Byte;
}
inline
byte ObjectCodeEmitModRM32Direct(Register32T Register32)
// Send ModRM32Bytes for direct address mode to object file
{ byte ModRM32Byte=Register32*0x8+0x05;
ObjectCodeEmitByte(ModRM32Byte);
return ModRM32Byte;
}
inline
void ObjectCodeEmitSIB(Register32T ScaledRegister32,
natural Scale,
Register32T BaseRegister32)
// send SIB byte to object file
// Note: Use ESP for ScaledRegister32 to disable scaling; only useful when using ESP for BASE.
{ if (ScaledRegister32==ESP && BaseRegister32!=ESP) CompilerFault(31);
if (Scale==1) ObjectCodeEmitByte((byte)(0x00+ScaledRegister32*0x8+BaseRegister32));
else if (Scale==2) ObjectCodeEmitByte((byte)(0x40+ScaledRegister32*0x8+BaseRegister32));
else if (Scale==4) ObjectCodeEmitByte((byte)(0x80+ScaledRegister32*0x8+BaseRegister32));
else if (Scale==8) ObjectCodeEmitByte((byte)(0xC0+ScaledRegister32*0x8+BaseRegister32));
else CompilerFault(32);
}
inline
byte ObjectCodeEmitModRM32OffsetRegister32(Register32T Register32,
integer Offset,
Register32T BaseRegister32)
// Send ModRM32Bytes for indexed address mode to object file
// Returns 1st byte of ModRM32 for possible use in EmittedPushRM32 peephole optimization
{ byte ModRM32Byte;
if (Offset==0 && BaseRegister32!=EBP)
{ ModRM32Byte=0x00+Register32*0x8+BaseRegister32;
ObjectCodeEmitByte(ModRM32Byte);
if (BaseRegister32==ESP) ObjectCodeEmitSIB(ESP,1,ESP);
}
else if (Offset>=-128 && Offset<=127)
{ ModRM32Byte=0x40+Register32*0x8+BaseRegister32;
ObjectCodeEmitByte(ModRM32Byte);
if (BaseRegister32==ESP) ObjectCodeEmitSIB(ESP,1,ESP);
ObjectCodeEmitByte((byte)Offset);
}
else { // large offset
ModRM32Byte=0x80+Register32*0x8+BaseRegister32;
ObjectCodeEmitByte(ModRM32Byte);
if (BaseRegister32==ESP) ObjectCodeEmitSIB(ESP,1,ESP);
ObjectCodeEmitDword(Offset);
}
return ModRM32Byte;
}
inline
byte ObjectCodeEmitModRM32OffsetScaledRegister32(Register32T Register32,
integer Offset,
Register32T ScaledRegister32,
natural Scale)
// Send ModRM32Bytes for indexing by a scaled register with no base register to object file
// Returns 1st byte of ModRM32 for possible use in EmittedPushRM32 peephole optimization
{ byte ModRM32Byte=0x00+Register32*0x8+ESP;
ObjectCodeEmitByte(ModRM32Byte); // MOD=00 --> SIB does disp32[index]
ObjectCodeEmitSIB(ScaledRegister32,Scale,EBP);
ObjectCodeEmitDword(Offset);
return ModRM32Byte;
}
inline
byte ObjectCodeEmitModRM32ScaledRegister32OffsetRegister32(Register32T Register32,
Register32T ScaledRegister32,
natural Scale,
integer Offset,
Register32T BaseRegister32)
// Send ModRM32Bytes for indexed address mode to object file
// Returns 1st byte of ModRM32 for possible use in EmittedPushRM32 peephole optimization
// If Scale==0, leave scale and scaled register out of the computation
{ byte ModRM32Byte;
if (Scale==0) ObjectCodeEmitModRM32OffsetRegister32(Register32,Offset,BaseRegister32);
else if (Offset==0 && BaseRegister32!=EBP)
{ ModRM32Byte=0x00+Register32*0x8+ESP;
ObjectCodeEmitByte(ModRM32Byte);
ObjectCodeEmitSIB(ScaledRegister32,Scale,BaseRegister32);
}
else if (Offset>=-128 && Offset<=127)
{ ModRM32Byte=0x40+Register32*0x8+ESP;
ObjectCodeEmitByte(ModRM32Byte);
ObjectCodeEmitSIB(ScaledRegister32,Scale,BaseRegister32);
ObjectCodeEmitByte((byte)Offset);
}
else { // large offset
ModRM32Byte=0x80+Register32*0x8+ESP;
ObjectCodeEmitByte(ModRM32Byte);
ObjectCodeEmitSIB(ScaledRegister32,Scale,BaseRegister32);
ObjectCodeEmitDword(Offset);
}
return ModRM32Byte;
}
inline
void ObjectCodeEmitLeaRegister32OffsetRegister32ScaledPlusBase32(
Register32T Register32Destination,
integer Offset,
Register32T Register32Source,
natural Scale, // 1,2,4 or 8
Register32T Base)
// send "LEA Register32,offset[Register32*Scale+Base]" to object file
{ ObjectCodeEmitLeaOpcode();
ObjectCodeEmitModRM32ScaledRegister32OffsetRegister32(
Register32Destination,Register32Source,Scale,Offset,Base);
}
inline
void ObjectCodeEmitMovRegister32ScaledRegister32OffsetRegister32(Register32T DestinationRegister32,
Register32T ScaledRegister32,
natural Scale,
integer Offset,
Register32T BaseRegister32)
// Emit Mov R32 using scaled index addressing
{ ObjectCodeEmitMovRegister32Opcode();
ObjectCodeEmitModRM32ScaledRegister32OffsetRegister32(DestinationRegister32,
ScaledRegister32,
Scale,
Offset,
BaseRegister32);
}