c ++ - Hvordan kan jeg forenkle kodegenerering ved kørsel?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg arbejder på et stykke software, der genererer assembler kode ved kørselstid. For eksempel,
her er en meget enkel funktion, der genererer assembler kode for at kalde funktionen GetCurrentProcess (til Win64 ABI): [13]


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
}


Normalt bruger jeg inline assembler, men desværre synes det ikke at være muligt med 64bit MSVC compilers længere. Mens jeg er ved det - skal denne kode arbejde med MSVC6 op til MSVC10 og også MinGW. Der er mange flere funktioner som genGetCurrentProcess, de alle udsender assembler kode, og mange af dem får funktionspegere, der skal kaldes bestået som argumenter .


Det irriterende ved dette er, at ændring af denne kode er fejlagtigt, og vi skal håndtere ABI-specifikke ting manuelt (for eksempel at reservere 32 bytes stakplads, før der kaldes funktioner til registrering af spil).


Så mit spørgsmål er - kan jeg forenkle denne kode til at generere assembler kode ved kørsel? Jeg håbede, at jeg på en eller anden måde kunne skrive assembler-koden direkte (muligvis i en ekstern fil, som derefter er samlet sammen med ml/ml64), men det er ikke klart for mig, hvordan dette ville fungere, hvis nogle af byterne i den samlede kode kendes kun ved kørselstid (værdien addressForGetcurrentProcessFunction i eksempelet ovenfor. Måske er det muligt at samle nogle kode men tildele 'etiketter' til bestemte steder i koden, så Jeg kan nemt ændre koden ved kørsel og derefter kopiere den til min buffer?

Bedste reference


Tag et kig på asmjit. Det er et C ++-bibliotek til runtime-kodegenerering. Understøtter x64 og sandsynligvis de fleste af de eksisterende udvidelser (FPU, MMX, 3dNow, SSE, SSE2, SSE3, SSE4). Dets grænseflade ligner monteringssyntax, og den koder instruktionerne korrekt for dig. [14]

Andre referencer 1


Du kan stole på en rigtig assembler til at gøre arbejdet for dig - en der genererer binær output er selvfølgelig det bedste. Overvej at se på yasm eller fasm (der er nogle indlæg på fasmfora om at lave en DLL-version, så du behøver ikke at skrive en midlertidig samlingsfil, starte ekstern proces og læse outputfilen tilbage, men det kan jeg ikke om det 'er blevet opdateret til senere versioner). [15] [16]


Dette kan være overkill, hvis dine behov er relativt enkle, selvom. Jeg vil overveje at lave en C ++ Assembler klasse, der understøtter bare de mnemonics du har brug for sammen med nogle hjælperfunktioner som GeneratePrologue, GenerateEpilogue, InstructionPointerRelativeAddress og så. Dette vil give dig mulighed for at skrive pseudo- montering, og at have hjælperfunktionerne tager sig af 32/64bit-problemer.

Andre referencer 2


Du kunne abstrahere nogle instruktionskodning, kalde konvent og CPU-mode-relaterede detaljer ved at skrive nogle hjælperfunktioner og makroer.


Du kan endda oprette en lille assembler, der ville samle pseudo-asm-kode numerisk kodet og indeholdt i en matrix i runnable kode, f.eks. starter med input som dette:


UINT32 blah[] =
{
  mov\_, ebx\_, dwordPtr\_, edi\_, plus\_, eax\_, times8\_, plus\_, const\_, 0xFEDCBA98,
  call\_, dwordPtr\_, ebx\_,
};


Men det er meget arbejde for at få dette gjort færdigt. For noget enklere, skal du bare oprette hjælperfunktioner/makroer, og gør i det væsentlige det, du allerede har gjort, men gemmer nogle ubehagelige detaljer fra brugeren.

Andre referencer 3


Den indlysende ting at gøre er at opbygge et sæt abstraktioner, der repræsenterer genereringen af ​​elementerne i maskinvejledningen af ​​interesse, og lav derefter opkald for at få de ønskede instruktioner/adresseringstilstande. Hvis du genererer en bred vifte af kode, kan du ende med at kode hele instruktionssættet på denne måde.


Derefter for at generere en MOV-instruktion, kan du skrive kode, der ligner:


ObjectCodeEmitMovRegister32ScaledRegister32OffsetRegister32(EAX,EDX,4,-LowerBound*4,ESP);


Du kan fortælle jeg kan lide lange navne. (I det mindste glemmer jeg aldrig, hvad de gør.)


Her er nogle bits af en kodegenerator, der understøtter dette, som jeg implementerede i C for længe siden. Dette dækker slags den sværeste del, som er generering af MOD og SIB bytes. Efter denne stil kan man implementere så meget af instruktionssætet som man kan lide. Dette eksempel er kun for x32, så OP skal udvide og ændre i overensstemmelse hermed. Definitionen af ​​MOV-instruktionsgeneratoren er nede i slutningen.


#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);
}