about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/v3ch5/v3ch5.html
diff options
context:
space:
mode:
authorelioat <{ID}+{username}@users.noreply.github.com>2025-07-21 09:29:12 -0400
committerelioat <{ID}+{username}@users.noreply.github.com>2025-07-21 09:29:12 -0400
commit7250bdf5aaa962ba276ee9e3181017a86cba2db2 (patch)
treea3cbee4288c1c1df596c8eb3398390f7f0d04a44 /js/games/nluqo.github.io/~bh/v3ch5/v3ch5.html
parente1662a5405920b6338e342c4e04b48e555dbf6bc (diff)
downloadtour-master.tar.gz
Diffstat (limited to 'js/games/nluqo.github.io/~bh/v3ch5/v3ch5.html')
0 files changed, 0 insertions, 0 deletions
='n93' href='#n93'>93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
//: 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 << NUM(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;
}