//: operating directly on a register :(before "End Initialize Op Names") put_new(Name, "01", "add r32 to rm32 (add)"); :(code) void test_add_r32_to_r32() { Reg[EAX].i = 0x10; Reg[EBX].i = 1; run( "== code 0x1\n" // code segment // op ModR/M SIB displacement immediate " 01 d8 \n" // add EBX to EAX // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX) ); CHECK_TRACE_CONTENTS( "run: add EBX to r/m32\n" "run: r/m32 is EAX\n" "run: storing 0x00000011\n" ); } :(before "End Single-Byte Opcodes") case 0x01: { // add r32 to r/m32 uint8_t modrm = next(); uint8_t arg2 = (modrm>>3)&0x7; trace(Callstack_depth+1, "run") << "add " << rname(arg2) << " to r/m32" << end(); int32_t* signed_arg1 = effective_address(modrm); int32_t signed_result = *signed_arg1 + Reg[arg2].i; SF = (signed_result < 0); ZF = (signed_result == 0); int64_t signed_full_result = static_cast(*signed_arg1) + Reg[arg2].i; OF = (signed_result != signed_full_result); // set CF uint32_t unsigned_arg1 = static_cast(*signed_arg1); uint32_t unsigned_result = unsigned_arg1 + Reg[arg2].u; uint64_t unsigned_full_result = static_cast(unsigned_arg1) + Reg[arg2].u; CF = (unsigned_result != unsigned_full_result); trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end(); *signed_arg1 = signed_result; trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end(); break; } :(code) void test_add_r32_to_r32_signed_overflow() { Reg[EAX].i = INT32_MAX; Reg[EBX].i = 1; run( "== code 0x1\n" // code segment // op ModR/M SIB displacement immediate " 01 d8 \n" // add EBX to EAX // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX) ); CHECK_TRACE_CONTENTS( "run: add EBX to r/m32\n" "run: r/m32 is EAX\n" "run: SF=1; ZF=0; CF=0; OF=1\n" "run: storing 0x80000000\n" ); } void test_add_r32_to_r32_unsigned_overflow() { Reg[EAX].u = UINT32_MAX; Reg[EBX].u = 1; run( "== code 0x1\n" // code segment // op ModR/M SIB displacement immediate " 01 d8 \n" // add EBX to EAX // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX) ); CHECK_TRACE_CONTENTS( "run: add EBX to r/m32\n" "run: r/m32 is EAX\n" "run: SF=0; ZF=1; CF=1; OF=0\n" "run: storing 0x00000000\n" ); } void test_add_r32_to_r32_unsigned_and_signed_overflow() { Reg[EAX].i = Reg[EBX].i = INT32_MIN; run( "== code 0x1\n" // code segment // op ModR/M SIB displacement immediate " 01 d8 \n" // add EBX to EAX // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX) ); CHECK_TRACE_CONTENTS( "run: add EBX to r/m32\n" "run: r/m32 is EAX\n" "run: SF=0; ZF=1; CF=1; OF=1\n" "run: storing 0x00000000\n" ); } :(code) // Implement tables 2-2 and 2-3 in the Intel manual, Volume 2. // We return a pointer so that instructions can write to multiple bytes in // 'Mem' at once. // beware: will eventually have side-effects int32_t* effective_address(uint8_t modrm) { const uint8_t mod = (modrm>>6); // ignore middle 3 'reg opcode' bits const uint8_t rm = modrm & 0x7; if (mod == 3) { // mod 3 is just register direct addressing trace(Callstack_depth+1, "run") << "r/m32 is " << rname(rm) << end(); return &Reg[rm].i; } uint32_t addr = effective_address_number(modrm); trace(Callstack_depth+1, "run") << "effective address contains 0x" << HEXWORD << read_mem_i32(addr) << end(); return mem_addr_i32(addr); } // beware: will eventually have side-effects uint32_t effective_address_number(uint8_t modrm) { const uint8_t mod = (modrm>>6); // ignore middle 3 'reg opcode' bits const uint8_t rm = modrm & 0x7; uint32_t addr = 0; switch (mod) { case 3: // mod 3 is just register direct addressing raise << "unexpected direct addressing mode\n" << end(); return 0; // End Mod Special-cases(addr) default: cerr << "unrecognized mod bits: " << NUM(mod) << '\n'; exit(1); } //: other mods are indirect, and they'll set addr appropriately // Found effective_address(addr) return addr; } 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 ""; } } //:: subtract :(before "End Initialize Op Names") put_new(Name, "29", "subtract r32 from rm32 (sub)"); :(code) void test_subtract_r32_from_r32() { Reg[EAX].i = 10; Reg[EBX].i = 1; run( "== code 0x1\n" // code segment // op ModR/M SIB displacement immediate " 29 d8 \n" // subtract EBX from EAX // ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX) ); CHECK_TRACE_CONTENTS( "run: subtract EBX from r/m32\n" "run: r/m32 is EAX\n" "run: storing 0x00000009\n" ); } :(before "End Single-Byte Opcodes") case 0x29: { // subtract r32 from r/m32 const uint8_t modrm = next(); const uint8_t arg2 = (modrm>>3)&0x7; trace(Callstack_depth+1, "run") << "subtract " << rname(arg2) << " from r/m32" << end(); int32_t* signed_arg1 = effective_address(modrm); int32_t signed_result = *signed_arg1 - Reg[arg2].i; SF = (signed_result < 0); ZF = (signed_result == 0); int64_t signed_full_result = static_cast(*signed_arg1) - Reg[arg2].i; OF = (signed_result != signed_full_result); // set CF uint32_t unsigned_arg1 = static_cast(*signed_arg1); uint32_t unsigned_result = unsigned_arg1 - Reg[arg2].u; uint64_t unsigned_full_result = static_cast(unsigned_arg1) - Reg[arg2].u; CF = (unsigned_result != unsigned_full_result); trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end(); *signed_arg1 = signed_result; trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << *signed_arg1 << end(); break; } :(code) void test_subtract_r32_from_r32_signed_overflow() { Reg[EAX].i = INT32_MIN; Reg[EBX].i = INT32_MAX; run( "== code 0x1\n" // code segment // op ModR/M SIB displ
# Wrappers around interaction primitives that take a potentially fake object
# and are thus easier to test.

exclusive-container event [
  text:char
  keycode:num  # keys on keyboard without a unicode representation
  touch:touch-event  # mouse, track ball, etc.
  resize:resize-event
  # update the assume-console handler if you add more variants
]

container touch-event [
  type:num
  row:num
  column:num
]

container resize-event [
  width:num
  height:num
]

container console [
  current-event-index:num
  events:&:@:event
]

def new-fake-console events:&:@:event -> result:&:console [
  local-scope
  load-inputs
  result:&:console <- new console:type
  *result <- put *result, events:offset, events
]

def read-event console:&:console -> result:event, found?:bool, quit?:bool, console:&:console [
  local-scope
  load-inputs
  {
    break-unless console
    current-event-index:num <- get *console, current-event-index:offset
    buf:&:@:event <- get *console, events:offset
    {
      max:num <- length *buf
      done?:bool <- greater-or-equal current-event-index, max
      break-unless done?
      dummy:&:event <- new event:type
      return *dummy, true/found, true/quit
    }
    result <- index *buf, current-event-index
    current-event-index <- add current-event-index, 1
    *console <- put *console, current-event-index:offset, current-event-index
    return result, true/found, false/quit
  }
  switch  # real event source is infrequent; avoid polling it too much
  result:event, found?:bool <- check-for-interaction
  return result, found?, false/quit
]

# variant of read-event for just keyboard events. Discards everything that
# isn't unicode, so no arrow keys, page-up/page-down, etc. But you still get
# newlines, tabs, ctrl-d..
def read-key console:&:console -> result:char, found?:bool, quit?:bool, console:&:console [
  local-scope
  load-inputs
  x:event, found?:bool, quit?:bool, console <- read-event console
  return-if quit?, 0, found?, quit?
  return-unless found?, 0, found?, quit?
  c:char, converted?:bool <- maybe-convert x, text:variant
  return-unless converted?, 0, false/found, false/quit
  return c, true/found, false/quit
]

def send-keys-to-channel console:&:console, chan:&:sink:char, screen:&:screen -> console:&:console, chan:&:sink:char, screen:&:screen [
  local-scope
  load-inputs
  {
    c:char, found?:bool, quit?:bool, console <- read-key console
    loop-unless found?
    break-if quit?
    assert c, [invalid event, expected text]
    screen <- print screen, c
    chan <- write chan, c
    loop
  }
  chan <- close chan
]

def wait-for-event console:&:console -> console:&:console [
  local-scope
  load-inputs
  {
    _, found?:bool <- read-event console
    break-if found?
    switch
    loop
  }
]

def has-more-events? console:&:console -> result:bool [
  local-scope
  load-inputs
  return-if console, false  # fake events are processed as soon as they arrive
  result <- interactions-left?
]
stack to ESI (pop)"); put_new(Name, "5f", "pop top of stack to EDI (pop)"); :(code) void test_pop_r32() { Mem.push_back(vma(0xbd000000)); // manually allocate memory Reg[ESP].u = 0xbd000008; write_mem_i32(0xbd000008, 0x0000000a); // ..before this write run( "== code 0x1\n" // code segment // op ModR/M SIB displacement immediate " 5b \n" // pop stack to EBX "== data 0x2000\n" // data segment "0a 00 00 00\n" // 0xa ); CHECK_TRACE_CONTENTS( "run: pop into EBX\n" "run: popping value 0x0000000a\n" "run: incrementing ESP to 0xbd00000c\n" ); } :(before "End Single-Byte Opcodes") case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: { // pop stack into r32 const uint8_t reg = op & 0x7; trace(Callstack_depth+1, "run") << "pop into " << rname(reg) << end(); //? cerr << "pop from " << Reg[ESP].u << '\n'; Reg[reg].u = pop(); //? cerr << "=> " << NUM(reg) << ": " << Reg[reg].u << '\n'; break; } :(code) uint32_t pop() { const uint32_t result = read_mem_u32(Reg[ESP].u); trace(Callstack_depth+1, "run") << "popping value 0x" << HEXWORD << result << end(); Reg[ESP].u += 4; trace(Callstack_depth+1, "run") << "incrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end(); assert(Reg[ESP].u < AFTER_STACK); return result; } :(before "End Includes") #include