about summary refs log blame commit diff stats
path: root/subx/021byte_addressing.cc
blob: 6510e2a6ea9ce0bb72614d6045e94f13a15bf818 (plain) (tree)
























                                                                                
                                                                             











                                                                                 
 

                                               
 




                                   
                     


                                                                                           
                        








                                                    




                                      
                                                                                                
                                                       


                                                                            
                                                                                    




        

                                               
 




                                                                                                                      
                     


                                                                                                
                        









                                                                




                                       
                                                                                           
                                                       

                                                                                 
                                                                                   

                                          
                                                                                                                        

        
 




                                                 
                     











                                                                                                                





                                                       



                                     
                     


                                                                                        
                        








                                                    




                                   

                                                                             







                                                                                                           
                                                                                    

        
//: SubX mostly deals with instructions operating on 32-bit operands, but we
//: still need to deal with raw bytes for strings and so on.

//: Unfortunately the register encodings when dealing with bytes are a mess.
//: We need a special case for them.
:(code)
string rname_8bit(uint8_t r) {
  switch (r) {
  case 0: return "AL";  // lowest byte of EAX
  case 1: return "CL";  // lowest byte of ECX
  case 2: return "DL";  // lowest byte of EDX
  case 3: return "BL";  // lowest byte of EBX
  case 4: return "AH";  // second lowest byte of EAX
  case 5: return "CH";  // second lowest byte of ECX
  case 6: return "DH";  // second lowest byte of EDX
  case 7: return "BH";  // second lowest byte of EBX
  default: raise << "invalid 8-bit register " << r << '\n' << end();  return "";
  }
}

uint8_t* effective_byte_address(uint8_t modrm) {
  uint8_t mod = (modrm>>6);
  uint8_t rm = modrm & 0x7;
  if (mod == 3) {
    // select an 8-bit register
    trace(Callstack_depth+1, "run") << "r/m8 is " << rname_8bit(rm) << end();
    return reg_8bit(rm);
  }
  // the rest is as usual
  return mem_addr_u8(effective_address_number(modrm));
}

uint8_t* reg_8bit(uint8_t rm) {
  uint8_t* result = reinterpret_cast<uint8_t*>(&Reg[rm & 0x3].i);  // _L register
  if (rm & 0x4)
    ++result;  // _H register;  assumes host is little-endian
  return result;
}

:(before "End Initialize Op Names")
put_new(Name, "88", "copy r8 to r8/m8-at-r32");

:(code)
void test_copy_r8_to_mem_at_r32() {
  Reg[EBX].i = 0x224488ab;
  Reg[EAX].i = 0x2000;
  run(
      "== code 0x1\n"
      // op     ModR/M  SIB   displacement  immediate
      "  88     18                                      \n"  // copy BL to the byte at *EAX
      // ModR/M in binary: 00 (indirect mode) 011 (src BL) 000 (dest EAX)
      "== data 0x2000\n"
      "f0 cc bb aa\n"
  );
  CHECK_TRACE_CONTENTS(
      "run: copy BL to r8/m8-at-r32\n"
      "run: effective address is 0x00002000 (EAX)\n"
      "run: storing 0xab\n"
  );
  CHECK_EQ(0xaabbccab, read_mem_u32(0x2000));
}

:(before "End Single-Byte Opcodes")
case 0x88: {  // copy r8 to r/m8
  const uint8_t modrm = next();
  const uint8_t rsrc = (modrm>>3)&0x7;
  trace(Callstack_depth+1, "run") << "copy " << rname_8bit(rsrc) << " to r8/m8-at-r32" << end();
  // use unsigned to zero-extend 8-bit value to 32 bits
  uint8_t* dest = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
  const uint8_t* src = reg_8bit(rsrc);
  *dest = *src;
  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
  break;
}

//:

:(before "End Initialize Op Names")
put_new(Name, "8a", "copy r8/m8-at-r32 to r8");

:(code)
void test_copy_mem_at_r32_to_r8() {
  Reg[EBX].i = 0xaabbcc0f;  // one nibble each of lowest byte set to all 0s and all 1s, to maximize value of this test
  Reg[EAX].i = 0x2000;
  run(
      "== code 0x1\n"
      // op     ModR/M  SIB   displacement  immediate
      "  8a     18                                      \n"  // copy just the byte at *EAX to BL
      // ModR/M in binary: 00 (indirect mode) 011 (dest EBX) 000 (src EAX)
      "== data 0x2000\n"
      "ab ff ff ff\n"  // 0xab with more data in following bytes
  );
  CHECK_TRACE_CONTENTS(
      "run: copy r8/m8-at-r32 to BL\n"
      "run: effective address is 0x00002000 (EAX)\n"
      "run: storing 0xab\n"
      // remaining bytes of EBX are *not* cleared
      "run: EBX now contains 0xaabbccab\n"
  );
}

:(before "End Single-Byte Opcodes")
case 0x8a: {  // copy r/m8 to r8
  const uint8_t modrm = next();
  const uint8_t rdest = (modrm>>3)&0x7;
  trace(Callstack_depth+1, "run") << "copy r8/m8-at-r32 to " << rname_8bit(rdest) << end();
  // use unsigned to zero-extend 8-bit value to 32 bits
  const uint8_t* src = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
  uint8_t* dest = reg_8bit(rdest);
  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*src) << end();
  *dest = *src;
  const uint8_t rdest_32bit = rdest & 0x3;
  trace(Callstack_depth+1, "run") << rname(rdest_32bit) << " now contains 0x" << HEXWORD << Reg[rdest_32bit].u << end();
  break;
}

:(code)
void test_cannot_copy_byte_to_ESP_EBP_ESI_EDI() {
  Reg[ESI].u = 0xaabbccdd;
  Reg[EBX].u = 0x11223344;
  run(
      "== code 0x1\n"
      // op     ModR/M  SIB   displacement  immediate
      "  8a     f3                                      \n"  // copy just the byte at *EBX to 8-bit register '6'
      // ModR/M in binary: 11 (direct mode) 110 (dest 8-bit 'register 6') 011 (src EBX)
  );
  CHECK_TRACE_CONTENTS(
      // ensure 8-bit register '6' is DH, not ESI
      "run: copy r8/m8-at-r32 to DH\n"
      "run: storing 0x44\n"
  );
  // ensure ESI is unchanged
  CHECK_EQ(Reg[ESI].u, 0xaabbccdd);
}

//:

:(before "End Initialize Op Names")
put_new(Name, "c6", "copy imm8 to r8/m8-at-r32 (mov)");

:(code)
void test_copy_imm8_to_mem_at_r32() {
  Reg[EAX].i = 0x2000;
  run(
      "== code 0x1\n"
      // op     ModR/M  SIB   displacement  immediate
      "  c6     00                          dd          \n"  // copy to the byte at *EAX
      // ModR/M in binary: 00 (indirect mode) 000 (unused) 000 (dest EAX)
      "== data 0x2000\n"
      "f0 cc bb aa\n"
  );
  CHECK_TRACE_CONTENTS(
      "run: copy imm8 to r8/m8-at-r32\n"
      "run: effective address is 0x00002000 (EAX)\n"
      "run: storing 0xdd\n"
  );
  CHECK_EQ(0xaabbccdd, read_mem_u32(0x2000));
}

:(before "End Single-Byte Opcodes")
case 0xc6: {  // copy imm8 to r/m8
  const uint8_t modrm = next();
  const uint8_t src = next();
  trace(Callstack_depth+1, "run") << "copy imm8 to r8/m8-at-r32" << end();
  trace(Callstack_depth+1, "run") << "imm8 is 0x" << HEXWORD << src << end();
  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
  if (subop != 0) {
    cerr << "unrecognized subop for opcode c6: " << NUM(subop) << " (only 0/copy currently implemented)\n";
    exit(1);
  }
  // use unsigned to zero-extend 8-bit value to 32 bits
  uint8_t* dest = reinterpret_cast<uint8_t*>(effective_byte_address(modrm));
  *dest = src;
  trace(Callstack_depth+1, "run") << "storing 0x" << HEXBYTE << NUM(*dest) << end();
  break;
}