about summary refs log tree commit diff stats
path: root/subx
diff options
context:
space:
mode:
Diffstat (limited to 'subx')
-rw-r--r--subx/010core.cc14
-rw-r--r--subx/011direct_addressing.cc99
-rw-r--r--subx/012indirect_addressing.cc140
-rw-r--r--subx/013immediate_addressing.cc114
-rw-r--r--subx/014index_addressing.cc16
-rw-r--r--subx/017functions.cc8
6 files changed, 203 insertions, 188 deletions
diff --git a/subx/010core.cc b/subx/010core.cc
index a23418db..050ac932 100644
--- a/subx/010core.cc
+++ b/subx/010core.cc
@@ -224,6 +224,20 @@ int32_t imm32() {
   return result;
 }
 
+string rname(uint8_t r) {
+  switch (r) {
+  case 0: return "EAX";
+  case 1: return "ECX";
+  case 2: return "EDX";
+  case 3: return "EBX";
+  case 4: return "ESP";
+  case 5: return "EBP";
+  case 6: return "ESI";
+  case 7: return "EDI";
+  default: raise << "invalid register " << r << '\n' << end();  return "";
+  }
+}
+
 :(before "End Includes")
 #include <iomanip>
 #define HEXBYTE  std::hex << std::setw(2) << std::setfill('0')
diff --git a/subx/011direct_addressing.cc b/subx/011direct_addressing.cc
index 00e0e877..46c24828 100644
--- a/subx/011direct_addressing.cc
+++ b/subx/011direct_addressing.cc
@@ -4,16 +4,17 @@
 % Reg[0].i = 0x10;
 % Reg[3].i = 1;
 # op  ModR/M  SIB   displacement  immediate
-  01  d8                                      # add EBX (reg 3) to EAX (reg 0)
-+run: add reg 3 to effective address
-+run: effective address is reg 0
+  01  d8                                      # add EBX to EAX
+# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
++run: add EBX to effective address
++run: effective address is EAX
 +run: storing 0x00000011
 
 :(before "End Single-Byte Opcodes")
 case 0x01: {  // add r32 to r/m32
   uint8_t modrm = next();
   uint8_t arg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "add reg " << NUM(arg2) << " to effective address" << end();
+  trace(2, "run") << "add " << rname(arg2) << " to effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(+, *arg1, Reg[arg2].i);
   break;
@@ -31,7 +32,7 @@ int32_t* effective_address(uint8_t modrm) {
   switch (mod) {
   case 3:
     // mod 3 is just register direct addressing
-    trace(2, "run") << "effective address is reg " << NUM(rm) << end();
+    trace(2, "run") << "effective address is " << rname(rm) << end();
     result = &Reg[rm].i;
     break;
   // End Mod Special-cases
@@ -48,16 +49,16 @@ int32_t* effective_address(uint8_t modrm) {
 % Reg[0].i = 10;
 % Reg[3].i = 1;
 # op  ModR/M  SIB   displacement  immediate
-  29  d8                                      # subtract EBX (reg 3) from EAX (reg 0)
-+run: subtract reg 3 from effective address
-+run: effective address is reg 0
+  29  d8                                      # subtract EBX from EAX
++run: subtract EBX from effective address
++run: effective address is EAX
 +run: storing 0x00000009
 
 :(before "End Single-Byte Opcodes")
 case 0x29: {  // subtract r32 from r/m32
   uint8_t modrm = next();
   uint8_t arg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "subtract reg " << NUM(arg2) << " from effective address" << end();
+  trace(2, "run") << "subtract " << rname(arg2) << " from effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(-, *arg1, Reg[arg2].i);
   break;
@@ -69,16 +70,16 @@ case 0x29: {  // subtract r32 from r/m32
 % Reg[0].i = 0x0a0b0c0d;
 % Reg[3].i = 0x000000ff;
 # op  ModR/M  SIB   displacement  immediate
-  21  d8                                      # and EBX (reg 3) with destination EAX (reg 0)
-+run: and reg 3 with effective address
-+run: effective address is reg 0
+  21  d8                                      # and EBX with destination EAX
++run: and EBX with effective address
++run: effective address is EAX
 +run: storing 0x0000000d
 
 :(before "End Single-Byte Opcodes")
 case 0x21: {  // and r32 with r/m32
   uint8_t modrm = next();
   uint8_t arg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "and reg " << NUM(arg2) << " with effective address" << end();
+  trace(2, "run") << "and " << rname(arg2) << " with effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_BITWISE_OP(&, *arg1, Reg[arg2].u);
   break;
@@ -90,16 +91,16 @@ case 0x21: {  // and r32 with r/m32
 % Reg[0].i = 0x0a0b0c0d;
 % Reg[3].i = 0xa0b0c0d0;
 # op  ModR/M  SIB   displacement  immediate
-  09  d8                                      # or EBX (reg 3) with destination EAX (reg 0)
-+run: or reg 3 with effective address
-+run: effective address is reg 0
+  09  d8                                      # or EBX with destination EAX
++run: or EBX with effective address
++run: effective address is EAX
 +run: storing 0xaabbccdd
 
 :(before "End Single-Byte Opcodes")
 case 0x09: {  // or r32 with r/m32
   uint8_t modrm = next();
   uint8_t arg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "or reg " << NUM(arg2) << " with effective address" << end();
+  trace(2, "run") << "or " << rname(arg2) << " with effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_BITWISE_OP(|, *arg1, Reg[arg2].u);
   break;
@@ -111,16 +112,16 @@ case 0x09: {  // or r32 with r/m32
 % Reg[0].i = 0x0a0b0c0d;
 % Reg[3].i = 0xaabbc0d0;
 # op  ModR/M  SIB   displacement  immediate
-  31  d8                                      # xor EBX (reg 3) with destination EAX (reg 0)
-+run: xor reg 3 with effective address
-+run: effective address is reg 0
+  31  d8                                      # xor EBX with destination EAX
++run: xor EBX with effective address
++run: effective address is EAX
 +run: storing 0xa0b0ccdd
 
 :(before "End Single-Byte Opcodes")
 case 0x31: {  // xor r32 with r/m32
   uint8_t modrm = next();
   uint8_t arg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "xor reg " << NUM(arg2) << " with effective address" << end();
+  trace(2, "run") << "xor " << rname(arg2) << " with effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   BINARY_BITWISE_OP(^, *arg1, Reg[arg2].u);
   break;
@@ -131,9 +132,9 @@ case 0x31: {  // xor r32 with r/m32
 :(scenario not_r32)
 % Reg[3].i = 0x0f0f00ff;
 # op  ModR/M  SIB   displacement  immediate
-  f7  c3                                      # not EBX (reg 3)
+  f7  c3                                      # not EBX
 +run: 'not' of effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: storing 0xf0f0ff00
 
 :(before "End Single-Byte Opcodes")
@@ -155,16 +156,16 @@ case 0xf7: {  // xor r32 with r/m32
 % Reg[0].i = 0x0a0b0c0d;
 % Reg[3].i = 0x0a0b0c07;
 # op  ModRM   SIB   displacement  immediate
-  39  d8                                      # compare EBX (reg 3) with EAX (reg 0)
-+run: compare reg 3 with effective address
-+run: effective address is reg 0
+  39  d8                                      # compare EBX with EAX
++run: compare EBX with effective address
++run: effective address is EAX
 +run: SF=0; ZF=0; OF=0
 
 :(before "End Single-Byte Opcodes")
 case 0x39: {  // set SF if r/m32 < r32
   uint8_t modrm = next();
   uint8_t reg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "compare reg " << NUM(reg2) << " with effective address" << end();
+  trace(2, "run") << "compare " << rname(reg2) << " with effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   int32_t arg2 = Reg[reg2].i;
   int32_t tmp1 = *arg1 - arg2;
@@ -180,18 +181,18 @@ case 0x39: {  // set SF if r/m32 < r32
 % Reg[0].i = 0x0a0b0c07;
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  39  d8                                      # compare EBX (reg 3) with EAX (reg 0)
-+run: compare reg 3 with effective address
-+run: effective address is reg 0
+  39  d8                                      # compare EBX with EAX
++run: compare EBX with effective address
++run: effective address is EAX
 +run: SF=1; ZF=0; OF=0
 
 :(scenario compare_r32_with_r32_equal)
 % Reg[0].i = 0x0a0b0c0d;
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  39  d8                                      # compare EBX (reg 3) with EAX (reg 0)
-+run: compare reg 3 with effective address
-+run: effective address is reg 0
+  39  d8                                      # compare EBX with EAX
++run: compare EBX with effective address
++run: effective address is EAX
 +run: SF=0; ZF=1; OF=0
 
 //:: copy (mov)
@@ -199,16 +200,16 @@ case 0x39: {  // set SF if r/m32 < r32
 :(scenario copy_r32_to_r32)
 % Reg[3].i = 0xaf;
 # op  ModRM   SIB   displacement  immediate
-  89  d8                                      # copy EBX (reg 3) to EAX (reg 0)
-+run: copy reg 3 to effective address
-+run: effective address is reg 0
+  89  d8                                      # copy EBX to EAX
++run: copy EBX to effective address
++run: effective address is EAX
 +run: storing 0x000000af
 
 :(before "End Single-Byte Opcodes")
 case 0x89: {  // copy r32 to r/m32
   uint8_t modrm = next();
   uint8_t reg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "copy reg " << NUM(reg2) << " to effective address" << end();
+  trace(2, "run") << "copy " << rname(reg2) << " to effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   *arg1 = Reg[reg2].i;
   trace(2, "run") << "storing 0x" << HEXWORD << *arg1 << end();
@@ -221,23 +222,23 @@ case 0x89: {  // copy r32 to r/m32
 % Reg[3].i = 0xaf;
 % Reg[0].i = 0x2e;
 # op  ModRM   SIB   displacement  immediate
-  87  d8                                      # exchange EBX (reg 3) with EAX (reg 0)
-+run: exchange reg 3 with effective address
-+run: effective address is reg 0
+  87  d8                                      # exchange EBX with EAX
++run: exchange EBX with effective address
++run: effective address is EAX
 +run: storing 0x000000af in effective address
-+run: storing 0x0000002e in reg 3
++run: storing 0x0000002e in EBX
 
 :(before "End Single-Byte Opcodes")
 case 0x87: {  // exchange r32 with r/m32
   uint8_t modrm = next();
   uint8_t reg2 = (modrm>>3)&0x7;
-  trace(2, "run") << "exchange reg " << NUM(reg2) << " with effective address" << end();
+  trace(2, "run") << "exchange " << rname(reg2) << " with effective address" << end();
   int32_t* arg1 = effective_address(modrm);
   int32_t tmp = *arg1;
   *arg1 = Reg[reg2].i;
   Reg[reg2].i = tmp;
   trace(2, "run") << "storing 0x" << HEXWORD << *arg1 << " in effective address" << end();
-  trace(2, "run") << "storing 0x" << HEXWORD << Reg[reg2].i << " in reg " << NUM(reg2) << end();
+  trace(2, "run") << "storing 0x" << HEXWORD << Reg[reg2].i << " in " << rname(reg2) << end();
   break;
 }
 
@@ -247,8 +248,8 @@ case 0x87: {  // exchange r32 with r/m32
 % Reg[ESP].u = 0x64;
 % Reg[EBX].i = 0x0000000a;
 # op  ModRM   SIB   displacement  immediate
-  53                                          # push EBX (reg 3) to stack
-+run: push reg 3
+  53                                          # push EBX to stack
++run: push EBX
 +run: decrementing ESP to 0x00000060
 +run: pushing value 0x0000000a
 
@@ -262,7 +263,7 @@ case 0x55:
 case 0x56:
 case 0x57: {  // push r32 to stack
   uint8_t reg = op & 0x7;
-  trace(2, "run") << "push reg " << NUM(reg) << end();
+  trace(2, "run") << "push " << rname(reg) << end();
   push(Reg[reg].u);
   break;
 }
@@ -280,8 +281,8 @@ void push(uint32_t val) {
 % Reg[ESP].u = 0x60;
 % SET_WORD_IN_MEM(0x60, 0x0000000a);
 # op  ModRM   SIB   displacement  immediate
-  5b                                          # pop stack to EBX (reg 3)
-+run: pop into reg 3
+  5b                                          # pop stack to EBX
++run: pop into EBX
 +run: popping value 0x0000000a
 +run: incrementing ESP to 0x00000064
 
@@ -295,7 +296,7 @@ case 0x5d:
 case 0x5e:
 case 0x5f: {  // pop stack into r32
   uint8_t reg = op & 0x7;
-  trace(2, "run") << "pop into reg " << NUM(reg) << end();
+  trace(2, "run") << "pop into " << rname(reg) << end();
   Reg[reg].u = pop();
   break;
 }
diff --git a/subx/012indirect_addressing.cc b/subx/012indirect_addressing.cc
index 0acd7a1e..f41bdfef 100644
--- a/subx/012indirect_addressing.cc
+++ b/subx/012indirect_addressing.cc
@@ -5,9 +5,9 @@
 % Reg[0].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 1);
 # op  ModR/M  SIB   displacement  immediate
-  01  18                                     # add EBX (reg 3) to *EAX (reg 0)
-+run: add reg 3 to effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  01  18                                     # add EBX to *EAX
++run: add EBX to effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x00000011
 
 :(before "End Mod Special-cases")
@@ -15,7 +15,7 @@ case 0:
   // mod 0 is usually indirect addressing
   switch (rm) {
   default:
-    trace(2, "run") << "effective address is mem at address 0x" << std::hex << Reg[rm].u << " (reg " << NUM(rm) << ")" << end();
+    trace(2, "run") << "effective address is mem at address 0x" << std::hex << Reg[rm].u << " (" << rname(rm) << ")" << end();
     assert(Reg[rm].u + sizeof(int32_t) <= Mem.size());
     result = reinterpret_cast<int32_t*>(&Mem.at(Reg[rm].u));  // rely on the host itself being in little-endian order
     break;
@@ -30,16 +30,16 @@ case 0:
 % Reg[3].i = 0x10;
 % SET_WORD_IN_MEM(0x60, 1);
 # op  ModR/M  SIB   displacement  immediate
-  03  18                                      # add *EAX (reg 0) to EBX (reg 3)
-+run: add effective address to reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  03  18                                      # add *EAX to EBX
++run: add effective address to EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x00000011
 
 :(before "End Single-Byte Opcodes")
 case 0x03: {  // add r/m32 to r32
   uint8_t modrm = next();
   uint8_t arg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "add effective address to reg " << NUM(arg1) << end();
+  trace(2, "run") << "add effective address to " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(+, Reg[arg1].i, *arg2);
   break;
@@ -52,9 +52,9 @@ case 0x03: {  // add r/m32 to r32
 % SET_WORD_IN_MEM(0x60, 10);
 % Reg[3].i = 1;
 # op  ModRM   SIB   displacement  immediate
-  29  18                                      # subtract EBX (reg 3) from *EAX (reg 0)
-+run: subtract reg 3 from effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  29  18                                      # subtract EBX from *EAX
++run: subtract EBX from effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x00000009
 
 //:
@@ -64,16 +64,16 @@ case 0x03: {  // add r/m32 to r32
 % SET_WORD_IN_MEM(0x60, 1);
 % Reg[3].i = 10;
 # op  ModRM   SIB   displacement  immediate
-  2b  18                                      # subtract *EAX (reg 0) from EBX (reg 3)
-+run: subtract effective address from reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  2b  18                                      # subtract *EAX from EBX
++run: subtract effective address from EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x00000009
 
 :(before "End Single-Byte Opcodes")
 case 0x2b: {  // subtract r/m32 from r32
   uint8_t modrm = next();
   uint8_t arg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "subtract effective address from reg " << NUM(arg1) << end();
+  trace(2, "run") << "subtract effective address from " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_ARITHMETIC_OP(-, Reg[arg1].i, *arg2);
   break;
@@ -86,9 +86,9 @@ case 0x2b: {  // subtract r/m32 from r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0xff;
 # op  ModRM   SIB   displacement  immediate
-  21  18                                      # and EBX (reg 3) with *EAX (reg 0)
-+run: and reg 3 with effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  21  18                                      # and EBX with *EAX
++run: and EBX with effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x0000000d
 
 //:
@@ -98,16 +98,16 @@ case 0x2b: {  // subtract r/m32 from r32
 % SET_WORD_IN_MEM(0x60, 0x000000ff);
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  23  18                                      # and *EAX (reg 0) with EBX (reg 3)
-+run: and effective address with reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  23  18                                      # and *EAX with EBX
++run: and effective address with EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x0000000d
 
 :(before "End Single-Byte Opcodes")
 case 0x23: {  // and r/m32 with r32
   uint8_t modrm = next();
   uint8_t arg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "and effective address with reg " << NUM(arg1) << end();
+  trace(2, "run") << "and effective address with " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_BITWISE_OP(&, Reg[arg1].u, *arg2);
   break;
@@ -120,9 +120,9 @@ case 0x23: {  // and r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0xa0b0c0d0;
 # op  ModRM   SIB   displacement  immediate
-  09  18                                      # or EBX (reg 3) with *EAX (reg 0)
-+run: or reg 3 with effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  09  18                                      # or EBX with *EAX
++run: or EBX with effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0xaabbccdd
 
 //:
@@ -132,16 +132,16 @@ case 0x23: {  // and r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0xa0b0c0d0;
 # op  ModRM   SIB   displacement  immediate
-  0b  18                                      # or *EAX (reg 0) with EBX (reg 3)
-+run: or effective address with reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  0b  18                                      # or *EAX with EBX
++run: or effective address with EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0xaabbccdd
 
 :(before "End Single-Byte Opcodes")
 case 0x0b: {  // or r/m32 with r32
   uint8_t modrm = next();
   uint8_t arg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "or effective address with reg " << NUM(arg1) << end();
+  trace(2, "run") << "or effective address with " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_BITWISE_OP(|, Reg[arg1].u, *arg2);
   break;
@@ -154,9 +154,9 @@ case 0x0b: {  // or r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0xaabb0c0d);
 % Reg[3].i = 0xa0b0c0d0;
 # op  ModRM   SIB   displacement  immediate
-  31  18                                      # xor EBX (reg 3) with *EAX (reg 0)
-+run: xor reg 3 with effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  31  18                                      # xor EBX with *EAX
++run: xor EBX with effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x0a0bccdd
 
 //:
@@ -166,16 +166,16 @@ case 0x0b: {  // or r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0xa0b0c0d0;
 # op  ModRM   SIB   displacement  immediate
-  33  18                                      # xor *EAX (reg 0) with EBX (reg 3)
-+run: xor effective address with reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  33  18                                      # xor *EAX with EBX
++run: xor effective address with EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0xaabbccdd
 
 :(before "End Single-Byte Opcodes")
 case 0x33: {  // xor r/m32 with r32
   uint8_t modrm = next();
   uint8_t arg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "xor effective address with reg " << NUM(arg1) << end();
+  trace(2, "run") << "xor effective address with " << rname(arg1) << end();
   const int32_t* arg2 = effective_address(modrm);
   BINARY_BITWISE_OP(|, Reg[arg1].u, *arg2);
   break;
@@ -188,9 +188,9 @@ case 0x33: {  // xor r/m32 with r32
 # word at 0x60 is 0x0f0f00ff
 % SET_WORD_IN_MEM(0x60, 0x0f0f00ff);
 # op  ModRM   SIB   displacement  immediate
-  f7  03                                      # negate *EBX (reg 3)
+  f7  03                                      # negate *EBX
 +run: 'not' of effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: storing 0xf0f0ff00
 
 //:: compare (cmp)
@@ -200,9 +200,9 @@ case 0x33: {  // xor r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0x0a0b0c07;
 # op  ModRM   SIB   displacement  immediate
-  39  18                                      # compare EBX (reg 3) with *EAX (reg 0)
-+run: compare reg 3 with effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  39  18                                      # compare EBX with *EAX
++run: compare EBX with effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: SF=0; ZF=0; OF=0
 
 :(scenario compare_mem_at_r32_with_r32_lesser)
@@ -210,9 +210,9 @@ case 0x33: {  // xor r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c07);
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  39  18                                      # compare EBX (reg 3) with *EAX (reg 0)
-+run: compare reg 3 with effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  39  18                                      # compare EBX with *EAX
++run: compare EBX with effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: SF=1; ZF=0; OF=0
 
 :(scenario compare_mem_at_r32_with_r32_equal)
@@ -220,9 +220,9 @@ case 0x33: {  // xor r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  39  18                                      # compare EBX (reg 3) with *EAX (reg 0)
-+run: compare reg 3 with effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  39  18                                      # compare EBX with *EAX
++run: compare EBX with effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: SF=0; ZF=1; OF=0
 
 //:
@@ -232,16 +232,16 @@ case 0x33: {  // xor r/m32 with r32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c07);
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  3b  18                                      # compare *EAX (reg 0) with EBX (reg 3)
-+run: compare effective address with reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  3b  18                                      # compare *EAX with EBX
++run: compare effective address with EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: SF=0; ZF=0; OF=0
 
 :(before "End Single-Byte Opcodes")
 case 0x3b: {  // set SF if r32 < r/m32
   uint8_t modrm = next();
   uint8_t reg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "compare effective address with reg " << NUM(reg1) << end();
+  trace(2, "run") << "compare effective address with " << rname(reg1) << end();
   int32_t arg1 = Reg[reg1].i;
   int32_t* arg2 = effective_address(modrm);
   int32_t tmp1 = arg1 - *arg2;
@@ -258,9 +258,9 @@ case 0x3b: {  // set SF if r32 < r/m32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0x0a0b0c07;
 # op  ModRM   SIB   displacement  immediate
-  3b  18                                      # compare *EAX (reg 0) with EBX (reg 3)
-+run: compare effective address with reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  3b  18                                      # compare *EAX with EBX
++run: compare effective address with EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: SF=1; ZF=0; OF=0
 
 :(scenario compare_r32_with_mem_at_r32_equal)
@@ -268,9 +268,9 @@ case 0x3b: {  // set SF if r32 < r/m32
 % SET_WORD_IN_MEM(0x60, 0x0a0b0c0d);
 % Reg[3].i = 0x0a0b0c0d;
 # op  ModRM   SIB   displacement  immediate
-  3b  18                                      # compare *EAX (reg 0) with EBX (reg 3)
-+run: compare effective address with reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  3b  18                                      # compare *EAX with EBX
++run: compare effective address with EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: SF=0; ZF=1; OF=0
 
 //:: copy (mov)
@@ -279,9 +279,9 @@ case 0x3b: {  // set SF if r32 < r/m32
 % Reg[3].i = 0xaf;
 % Reg[0].i = 0x60;
 # op  ModRM   SIB   displacement  immediate
-  89  18                                      # copy EBX (reg 3) to *EAX (reg 0)
-+run: copy reg 3 to effective address
-+run: effective address is mem at address 0x60 (reg 0)
+  89  18                                      # copy EBX to *EAX
++run: copy EBX to effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x000000af
 
 //:
@@ -290,16 +290,16 @@ case 0x3b: {  // set SF if r32 < r/m32
 % Reg[0].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0x000000af);
 # op  ModRM   SIB   displacement  immediate
-  8b  18                                      # copy *EAX (reg 0) to EBX (reg 3)
-+run: copy effective address to reg 3
-+run: effective address is mem at address 0x60 (reg 0)
+  8b  18                                      # copy *EAX to EBX
++run: copy effective address to EBX
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x000000af
 
 :(before "End Single-Byte Opcodes")
 case 0x8b: {  // copy r32 to r/m32
   uint8_t modrm = next();
   uint8_t reg1 = (modrm>>3)&0x7;
-  trace(2, "run") << "copy effective address to reg " << NUM(reg1) << end();
+  trace(2, "run") << "copy effective address to " << rname(reg1) << end();
   int32_t* arg2 = effective_address(modrm);
   Reg[reg1].i = *arg2;
   trace(2, "run") << "storing 0x" << HEXWORD << *arg2 << end();
@@ -312,12 +312,12 @@ case 0x8b: {  // copy r32 to r/m32
 % Reg[0].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 8);
 # op  ModRM   SIB   displacement  immediate
-  ff  20                                      # jump to *EAX (reg 0)
+  ff  20                                      # jump to *EAX
   05                              00 00 00 01
   05                              00 00 00 02
 +run: inst: 0x00000001
 +run: jump to effective address
-+run: effective address is mem at address 0x60 (reg 0)
++run: effective address is mem at address 0x60 (EAX)
 +run: jumping to 0x00000008
 +run: inst: 0x00000008
 -run: inst: 0x00000003
@@ -346,9 +346,9 @@ case 0xff: {
 % SET_WORD_IN_MEM(0x60, 0x000000af);
 % Reg[ESP].u = 0x14;
 # op  ModRM   SIB   displacement  immediate
-  ff  30                                      # push *EAX (reg 0) to stack
+  ff  30                                      # push *EAX to stack
 +run: push effective address
-+run: effective address is mem at address 0x60 (reg 0)
++run: effective address is mem at address 0x60 (EAX)
 +run: decrementing ESP to 0x00000010
 +run: pushing value 0x000000af
 
@@ -367,9 +367,9 @@ case 6: {  // push r/m32 to stack
 % Reg[ESP].u = 0x10;
 % SET_WORD_IN_MEM(0x10, 0x00000030);
 # op  ModRM   SIB   displacement  immediate
-  8f  00                                      # pop stack into *EAX (reg 0)
+  8f  00                                      # pop stack into *EAX
 +run: pop into effective address
-+run: effective address is mem at address 0x60 (reg 0)
++run: effective address is mem at address 0x60 (EAX)
 +run: popping value 0x00000030
 +run: incrementing ESP to 0x00000014
 
diff --git a/subx/013immediate_addressing.cc b/subx/013immediate_addressing.cc
index 4773e75f..3021617f 100644
--- a/subx/013immediate_addressing.cc
+++ b/subx/013immediate_addressing.cc
@@ -3,9 +3,9 @@
 :(scenario add_imm32_to_r32)
 % Reg[3].i = 1;
 # op  ModRM   SIB   displacement  immediate
-  81  c3                          0a 0b 0c 0d  # add 0x0d0c0b0a to EBX (reg 3)
+  81  c3                          0a 0b 0c 0d  # add 0x0d0c0b0a to EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: subop add
 +run: storing 0x0d0c0b0b
 
@@ -35,9 +35,9 @@ case 0x81: {  // combine imm32 with r/m32
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 1);
 # op  ModR/M  SIB   displacement  immediate
-  81  03                          0a 0b 0c 0d  # add 0x0d0c0b0a to *EBX (reg 3)
+  81  03                          0a 0b 0c 0d  # add 0x0d0c0b0a to *EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: subop add
 +run: storing 0x0d0c0b0b
 
@@ -46,14 +46,14 @@ case 0x81: {  // combine imm32 with r/m32
 :(scenario subtract_imm32_from_eax)
 % Reg[EAX].i = 0x0d0c0baa;
 # op  ModR/M  SIB   displacement  immediate
-  2d                              0a 0b 0c 0d  # subtract 0x0d0c0b0a from EAX (reg 0)
-+run: subtract imm32 0x0d0c0b0a from reg EAX
+  2d                              0a 0b 0c 0d  # subtract 0x0d0c0b0a from EAX
++run: subtract imm32 0x0d0c0b0a from EAX
 +run: storing 0x000000a0
 
 :(before "End Single-Byte Opcodes")
 case 0x2d: {  // subtract imm32 from EAX
   int32_t arg2 = imm32();
-  trace(2, "run") << "subtract imm32 0x" << HEXWORD << arg2 << " from reg EAX" << end();
+  trace(2, "run") << "subtract imm32 0x" << HEXWORD << arg2 << " from EAX" << end();
   BINARY_ARITHMETIC_OP(-, Reg[EAX].i, arg2);
   break;
 }
@@ -64,9 +64,9 @@ case 0x2d: {  // subtract imm32 from EAX
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 10);
 # op  ModRM   SIB   displacement  immediate
-  81  2b                          01 00 00 00  # subtract 1 from *EBX (reg 3)
+  81  2b                          01 00 00 00  # subtract 1 from *EBX
 +run: combine imm32 0x00000001 with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: subop subtract
 +run: storing 0x00000009
 
@@ -75,9 +75,9 @@ case 0x2d: {  // subtract imm32 from EAX
 :(scenario subtract_imm32_from_r32)
 % Reg[3].i = 10;
 # op  ModRM   SIB   displacement  immediate
-  81  eb                          01 00 00 00  # subtract 1 from EBX (reg 3)
+  81  eb                          01 00 00 00  # subtract 1 from EBX
 +run: combine imm32 0x00000001 with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: subop subtract
 +run: storing 0x00000009
 
@@ -93,14 +93,14 @@ case 5: {
 :(scenario and_imm32_with_eax)
 % Reg[EAX].i = 0xff;
 # op  ModR/M  SIB   displacement  immediate
-  25                              0a 0b 0c 0d  # and 0x0d0c0b0a with EAX (reg 0)
-+run: and imm32 0x0d0c0b0a with reg EAX
+  25                              0a 0b 0c 0d  # and 0x0d0c0b0a with EAX
++run: and imm32 0x0d0c0b0a with EAX
 +run: storing 0x0000000a
 
 :(before "End Single-Byte Opcodes")
 case 0x25: {  // and imm32 with EAX
   int32_t arg2 = imm32();
-  trace(2, "run") << "and imm32 0x" << HEXWORD << arg2 << " with reg EAX" << end();
+  trace(2, "run") << "and imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
   BINARY_BITWISE_OP(&, Reg[EAX].i, arg2);
   break;
 }
@@ -111,9 +111,9 @@ case 0x25: {  // and imm32 with EAX
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0x000000ff);
 # op  ModRM   SIB   displacement  immediate
-  81  23                          0a 0b 0c 0d  # and 0x0d0c0b0a with *EBX (reg 3)
+  81  23                          0a 0b 0c 0d  # and 0x0d0c0b0a with *EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: subop and
 +run: storing 0x0000000a
 
@@ -122,9 +122,9 @@ case 0x25: {  // and imm32 with EAX
 :(scenario and_imm32_with_r32)
 % Reg[3].i = 0xff;
 # op  ModRM   SIB   displacement  immediate
-  81  e3                          0a 0b 0c 0d  # and 0x0d0c0b0a with EBX (reg 3)
+  81  e3                          0a 0b 0c 0d  # and 0x0d0c0b0a with EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: subop and
 +run: storing 0x0000000a
 
@@ -140,14 +140,14 @@ case 4: {
 :(scenario or_imm32_with_eax)
 % Reg[EAX].i = 0xd0c0b0a0;
 # op  ModR/M  SIB   displacement  immediate
-  0d                              0a 0b 0c 0d  # or 0x0d0c0b0a with EAX (reg 0)
-+run: or imm32 0x0d0c0b0a with reg EAX
+  0d                              0a 0b 0c 0d  # or 0x0d0c0b0a with EAX
++run: or imm32 0x0d0c0b0a with EAX
 +run: storing 0xddccbbaa
 
 :(before "End Single-Byte Opcodes")
 case 0x0d: {  // or imm32 with EAX
   int32_t arg2 = imm32();
-  trace(2, "run") << "or imm32 0x" << HEXWORD << arg2 << " with reg EAX" << end();
+  trace(2, "run") << "or imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
   BINARY_BITWISE_OP(|, Reg[EAX].i, arg2);
   break;
 }
@@ -158,9 +158,9 @@ case 0x0d: {  // or imm32 with EAX
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0xd0c0b0a0);
 # op  ModRM   SIB   displacement  immediate
-  81  0b                          0a 0b 0c 0d  # or 0x0d0c0b0a with *EBX (reg 3)
+  81  0b                          0a 0b 0c 0d  # or 0x0d0c0b0a with *EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: subop or
 +run: storing 0xddccbbaa
 
@@ -174,9 +174,9 @@ case 1: {
 :(scenario or_imm32_with_r32)
 % Reg[3].i = 0xd0c0b0a0;
 # op  ModRM   SIB   displacement  immediate
-  81  cb                          0a 0b 0c 0d  # or 0x0d0c0b0a with EBX (reg 3)
+  81  cb                          0a 0b 0c 0d  # or 0x0d0c0b0a with EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: subop or
 +run: storing 0xddccbbaa
 
@@ -185,14 +185,14 @@ case 1: {
 :(scenario xor_imm32_with_eax)
 % Reg[EAX].i = 0xddccb0a0;
 # op  ModR/M  SIB   displacement  immediate
-  35                              0a 0b 0c 0d  # xor 0x0d0c0b0a with EAX (reg 0)
-+run: xor imm32 0x0d0c0b0a with reg EAX
+  35                              0a 0b 0c 0d  # xor 0x0d0c0b0a with EAX
++run: xor imm32 0x0d0c0b0a with EAX
 +run: storing 0xd0c0bbaa
 
 :(before "End Single-Byte Opcodes")
 case 0x35: {  // xor imm32 with EAX
   int32_t arg2 = imm32();
-  trace(2, "run") << "xor imm32 0x" << HEXWORD << arg2 << " with reg EAX" << end();
+  trace(2, "run") << "xor imm32 0x" << HEXWORD << arg2 << " with EAX" << end();
   BINARY_BITWISE_OP(^, Reg[EAX].i, arg2);
   break;
 }
@@ -203,9 +203,9 @@ case 0x35: {  // xor imm32 with EAX
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0xd0c0b0a0);
 # op  ModRM   SIB   displacement  immediate
-  81  33                          0a 0b 0c 0d  # xor 0x0d0c0b0a with *EBX (reg 3)
+  81  33                          0a 0b 0c 0d  # xor 0x0d0c0b0a with *EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: subop xor
 +run: storing 0xddccbbaa
 
@@ -219,9 +219,9 @@ case 6: {
 :(scenario xor_imm32_with_r32)
 % Reg[3].i = 0xd0c0b0a0;
 # op  ModRM   SIB   displacement  immediate
-  81  f3                          0a 0b 0c 0d  # xor 0x0d0c0b0a with EBX (reg 3)
+  81  f3                          0a 0b 0c 0d  # xor 0x0d0c0b0a with EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: subop xor
 +run: storing 0xddccbbaa
 
@@ -230,15 +230,15 @@ case 6: {
 :(scenario compare_imm32_with_eax_greater)
 % Reg[0].i = 0x0d0c0b0a;
 # op  ModRM   SIB   displacement  immediate
-  3d                              07 0b 0c 0d  # compare 0x0d0c0b07 with EAX (reg 0)
-+run: compare reg EAX and imm32 0x0d0c0b07
+  3d                              07 0b 0c 0d  # compare 0x0d0c0b07 with EAX
++run: compare EAX and imm32 0x0d0c0b07
 +run: SF=0; ZF=0; OF=0
 
 :(before "End Single-Byte Opcodes")
 case 0x3d: {  // subtract imm32 from EAX
   int32_t arg1 = Reg[EAX].i;
   int32_t arg2 = imm32();
-  trace(2, "run") << "compare reg EAX and imm32 0x" << HEXWORD << arg2 << end();
+  trace(2, "run") << "compare EAX and imm32 0x" << HEXWORD << arg2 << end();
   int32_t tmp1 = arg1 - arg2;
   SF = (tmp1 < 0);
   ZF = (tmp1 == 0);
@@ -251,15 +251,15 @@ case 0x3d: {  // subtract imm32 from EAX
 :(scenario compare_imm32_with_eax_lesser)
 % Reg[0].i = 0x0d0c0b07;
 # op  ModRM   SIB   displacement  immediate
-  3d                              0a 0b 0c 0d  # compare 0x0d0c0b0a with EAX (reg 0)
-+run: compare reg EAX and imm32 0x0d0c0b0a
+  3d                              0a 0b 0c 0d  # compare 0x0d0c0b0a with EAX
++run: compare EAX and imm32 0x0d0c0b0a
 +run: SF=1; ZF=0; OF=0
 
 :(scenario compare_imm32_with_eax_equal)
 % Reg[0].i = 0x0d0c0b0a;
 # op  ModRM   SIB   displacement  immediate
-  3d                              0a 0b 0c 0d  # compare 0x0d0c0b0a with EAX (reg 0)
-+run: compare reg EAX and imm32 0x0d0c0b0a
+  3d                              0a 0b 0c 0d  # compare 0x0d0c0b0a with EAX
++run: compare EAX and imm32 0x0d0c0b0a
 +run: SF=0; ZF=1; OF=0
 
 //:
@@ -267,9 +267,9 @@ case 0x3d: {  // subtract imm32 from EAX
 :(scenario compare_imm32_with_r32_greater)
 % Reg[3].i = 0x0d0c0b0a;
 # op  ModRM   SIB   displacement  immediate
-  81  fb                          07 0b 0c 0d  # compare 0x0d0c0b07 with EBX (reg 3)
+  81  fb                          07 0b 0c 0d  # compare 0x0d0c0b07 with EBX
 +run: combine imm32 0x0d0c0b07 with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: SF=0; ZF=0; OF=0
 
 :(before "End Op 81 Subops")
@@ -287,35 +287,35 @@ case 7: {
 :(scenario compare_imm32_with_r32_lesser)
 % Reg[3].i = 0x0d0c0b07;
 # op  ModRM   SIB   displacement  immediate
-  81  fb                          0a 0b 0c 0d  # compare 0x0d0c0b0a with EBX (reg 3)
+  81  fb                          0a 0b 0c 0d  # compare 0x0d0c0b0a with EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: SF=1; ZF=0; OF=0
 
 :(scenario compare_imm32_with_r32_equal)
 % Reg[3].i = 0x0d0c0b0a;
 # op  ModRM   SIB   displacement  immediate
-  81  fb                          0a 0b 0c 0d  # compare 0x0d0c0b0a with EBX (reg 3)
+  81  fb                          0a 0b 0c 0d  # compare 0x0d0c0b0a with EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: SF=0; ZF=1; OF=0
 
 :(scenario compare_imm32_with_mem_at_r32_greater)
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0x0d0c0b0a);
 # op  ModRM   SIB   displacement  immediate
-  81  3b                          07 0b 0c 0d  # compare 0x0d0c0b07 with *EBX (reg 3)
+  81  3b                          07 0b 0c 0d  # compare 0x0d0c0b07 with *EBX
 +run: combine imm32 0x0d0c0b07 with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: SF=0; ZF=0; OF=0
 
 :(scenario compare_imm32_with_mem_at_r32_lesser)
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0x0d0c0b07);
 # op  ModRM   SIB   displacement  immediate
-  81  3b                          0a 0b 0c 0d  # compare 0x0d0c0b0a with *EBX (reg 3)
+  81  3b                          0a 0b 0c 0d  # compare 0x0d0c0b0a with *EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: SF=1; ZF=0; OF=0
 
 :(scenario compare_imm32_with_mem_at_r32_equal)
@@ -323,17 +323,17 @@ case 7: {
 % Reg[3].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 0x0d0c0b0a);
 # op  ModRM   SIB   displacement  immediate
-  81  3b                          0a 0b 0c 0d  # compare 0x0d0c0b0a with *EBX (reg 3)
+  81  3b                          0a 0b 0c 0d  # compare 0x0d0c0b0a with *EBX
 +run: combine imm32 0x0d0c0b0a with effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 +run: SF=0; ZF=1; OF=0
 
 //:: copy (mov)
 
 :(scenario copy_imm32_to_r32)
 # op  ModRM   SIB   displacement  immediate
-  bb                              0a 0b 0c 0d  # copy 0x0d0c0b0a to EBX (reg 3)
-+run: copy imm32 0x0d0c0b0a to reg 3
+  bb                              0a 0b 0c 0d  # copy 0x0d0c0b0a to EBX
++run: copy imm32 0x0d0c0b0a to EBX
 
 :(before "End Single-Byte Opcodes")
 case 0xb8:
@@ -346,7 +346,7 @@ case 0xbe:
 case 0xbf: {  // copy imm32 to r32
   uint8_t reg1 = op & 0x7;
   int32_t arg2 = imm32();
-  trace(2, "run") << "copy imm32 0x" << HEXWORD << arg2 << " to reg " << NUM(reg1) << end();
+  trace(2, "run") << "copy imm32 0x" << HEXWORD << arg2 << " to " << rname(reg1) << end();
   Reg[reg1].i = arg2;
   break;
 }
@@ -356,9 +356,9 @@ case 0xbf: {  // copy imm32 to r32
 :(scenario copy_imm32_to_mem_at_r32)
 % Reg[3].i = 0x60;
 # op  ModRM   SIB   displacement  immediate
-  c7  03                          0a 0b 0c 0d  # copy 0x0d0c0b0a to *EBX (reg 3)
+  c7  03                          0a 0b 0c 0d  # copy 0x0d0c0b0a to *EBX
 +run: copy imm32 0x0d0c0b0a to effective address
-+run: effective address is mem at address 0x60 (reg 3)
++run: effective address is mem at address 0x60 (EBX)
 
 :(before "End Single-Byte Opcodes")
 case 0xc7: {  // copy imm32 to r32
@@ -375,7 +375,7 @@ case 0xc7: {  // copy imm32 to r32
 :(scenario push_imm32)
 % Reg[ESP].u = 0x14;
 # op  ModRM   SIB   displacement  immediate
-  68                              af 00 00 00  # push *EAX (reg 0) to stack
+  68                              af 00 00 00  # push *EAX to stack
 +run: push imm32 0x000000af
 +run: ESP is now 0x00000010
 +run: contents at ESP: 0x000000af
diff --git a/subx/014index_addressing.cc b/subx/014index_addressing.cc
index 1615e652..e489dba1 100644
--- a/subx/014index_addressing.cc
+++ b/subx/014index_addressing.cc
@@ -5,11 +5,11 @@
 % Reg[0].i = 0x60;
 % SET_WORD_IN_MEM(0x60, 1);
 # op  ModR/M  SIB   displacement  immediate
-  01  1c      20                             # add EBX (reg 3) to *EAX (reg 0)
+  01  1c      20                             # add EBX to *EAX
 # SIB in binary: 00 (scale 1) 100 (no index) 000 (base EAX)
 # See Table 2-3 of the Intel programming manual.
-+run: add reg 3 to effective address
-+run: effective address is mem at address 0x60 (reg 0)
++run: add EBX to effective address
++run: effective address is mem at address 0x60 (EAX)
 +run: storing 0x00000011
 
 :(before "End Mod 0 Special-cases")
@@ -20,13 +20,13 @@ case 4:
   uint8_t index = (sib>>3)&0x7;
   if (index == ESP) {
     // ignore index and scale
-    trace(2, "run") << "effective address is mem at address 0x" << std::hex << Reg[base].u << " (reg " << NUM(base) << ")" << end();
+    trace(2, "run") << "effective address is mem at address 0x" << std::hex << Reg[base].u << " (" << rname(base) << ")" << end();
     result = reinterpret_cast<int32_t*>(&Mem.at(Reg[base].u));
   }
   else {
     uint8_t scale = (1 << (sib>>6));
     uint32_t addr = Reg[base].u + Reg[index].u*scale;
-    trace(2, "run") << "effective address is mem at address 0x" << std::hex << addr << " (reg " << NUM(base) << " + reg " << NUM(index) << " * " << NUM(scale) << ")" << end();
+    trace(2, "run") << "effective address is mem at address 0x" << std::hex << addr << " (" << rname(base) << " + " << rname(index) << "*" << NUM(scale) << ")" << end();
     result = reinterpret_cast<int32_t*>(&Mem.at(addr));
   }
   break;
@@ -37,9 +37,9 @@ case 4:
 % Reg[1].i = 0x2;  // dest index
 % SET_WORD_IN_MEM(0x60, 1);
 # op  ModR/M  SIB   displacement  immediate
-  01  1c      08                             # add EBX (reg 3) to *(EAX+ECX)
+  01  1c      08                             # add EBX to *(EAX+ECX)
 # SIB in binary: 00 (scale 1) 001 (index ECX) 000 (base EAX)
 # See Table 2-3 of the Intel programming manual.
-+run: add reg 3 to effective address
-+run: effective address is mem at address 0x60 (reg 0 + reg 1 * 1)
++run: add EBX to effective address
++run: effective address is mem at address 0x60 (EAX + ECX*1)
 +run: storing 0x00000011
diff --git a/subx/017functions.cc b/subx/017functions.cc
index 7837f080..774bbf42 100644
--- a/subx/017functions.cc
+++ b/subx/017functions.cc
@@ -26,10 +26,10 @@ case 0xe8: {  // call imm32 relative to next EIP
 % Reg[ESP].u = 0x64;
 % Reg[EBX].u = 0x000000a0;
 # op  ModRM   SIB   displacement  immediate
-  ff  d3                                       # call function offset at EBX (reg 3)
+  ff  d3                                       # call function offset at EBX
   # next EIP is 3
 +run: call to effective address
-+run: effective address is reg 3
++run: effective address is EBX
 +run: decrementing ESP to 0x00000060
 +run: pushing value 0x00000003
 +run: jumping to 0x000000a3
@@ -49,10 +49,10 @@ case 2: {  // call function pointer at r/m32
 % Reg[EBX].u = 0x10;
 % SET_WORD_IN_MEM(0x10, 0x000000a0);
 # op  ModRM   SIB   displacement  immediate
-  ff  13                                       # call function offset at *EBX (reg 3)
+  ff  13                                       # call function offset at *EBX
   # next EIP is 3
 +run: call to effective address
-+run: effective address is mem at address 0x10 (reg 3)
++run: effective address is mem at address 0x10 (EBX)
 +run: decrementing ESP to 0x00000060
 +run: pushing value 0x00000003
 +run: jumping to 0x000000a3