pcjs/tests/pcx86/trace/trace.nasm

710 lines
13 KiB
NASM

;
; trace.nasm
; Copyright © 2012-2015 Jeff Parsons <Jeff@pcjs.org>
;
; This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
; at <http://jsmachines.net/> and <http://pcjs.org/>.
;
; PCjs is free software: you can redistribute it and/or modify it under the terms of the
; GNU General Public License as published by the Free Software Foundation, either version 3
; of the License, or (at your option) any later version.
;
; PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
; even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along with PCjs. If not,
; see <http://www.gnu.org/licenses/gpl.html>.
;
; You are required to include the above copyright notice in every source code file of every
; copy or modified version of this work, and to display that copyright notice on every screen
; that loads or runs any version of this software (see Computer.sCopyright).
;
; Some PCjs files also attempt to load external resource files, such as character-image files,
; ROM files, and disk image files. Those external resource files are not considered part of the
; PCjs program for purposes of the GNU General Public License, and the author does not claim
; any copyright as to their contents.
;
; Overview
; --------
; Takes an instruction log, as recorded by the Debugger's traceLog() function, and
; "plays" the instructions back on another machine DOS-compatible 8086 machine, verifying that:
;
; 1) results match the recorded results
; 2) any "modified" flags match the recorded flags
; 3) any "unmodified" flags remain unmodified
;
; The format of an instruction log entry is a series of lines (ASCII characters terminated by an LF),
; where each line looks like:
;
; F000:EEFF SHL(0480,0002,F006) 1200,F006
;
; ie, address, space, instruction, parenthesis, dst operand, comma, src operand, comma,
; input flags, parenthesis, space, result, comma, and output flags.
;
; WARNING: For the shift and rotate tests to pass on a real x86 CPU, we either have to distinguish
; between single-bit shifts and multi-bit shifts (because the latter leaves PS_OF in an "undefined"
; state), or we have to ignore PS_OF altogether. For now, I'm specifying PS_ALL_BUT_OF for those
; instructions, even though we'll be missing OVERFLOW validation for all single-bit shifts and rotates.
;
CPU 8086
;
; Bit masks for all the arithmetic flags we care about
;
PS_NONE equ 0x0000
PS_CF equ 0x0001 ; bit 0: Carry flag
PS_PF equ 0x0004 ; bit 2: Parity flag
PS_AF equ 0x0010 ; bit 4: Auxiliary Carry flag (aka Arithmetic flag)
PS_ZF equ 0x0040 ; bit 6: Zero flag
PS_SF equ 0x0080 ; bit 7: Sign flag
PS_OF equ 0x0800 ; bit 11: Overflow flag
PS_ALL equ PS_CF | PS_PF | PS_AF | PS_ZF | PS_SF | PS_OF
PS_ALL_BUT_AF equ PS_CF | PS_PF | PS_ZF | PS_SF | PS_OF
PS_ALL_BUT_OF equ PS_CF | PS_PF | PS_AF | PS_ZF | PS_SF
%macro openF 1
section .data
%%name: db %1,0
section .text
mov dx,%%name
mov ax,0x3D00
int 0x21
%endmacro
%macro readF 3
%ifnidni %1,bx
mov bx,%1
%endif
%ifnidni %2,dx
mov dx,%2
%endif
%ifnidni %3,cx
mov cx,%3
%endif
mov ah,0x3F
int 0x21
%endmacro
%macro print 1
%ifidni %1,line
push ax
push cx
push dx
push si
mov dx,si
dec cx
add si,cx
mov byte [si],'$'
mov ah,0x09
int 0x21
mov dx,strCRLF
mov ah,0x09
int 0x21
pop si
pop dx
pop cx
pop ax
%else
%ifstr %1
section .data
%%str: db %1,'$'
section .text
push dx
mov dx,%%str
%elifnidni %1,dx
push dx
mov dx,%1
%endif
push ax
mov ah,0x09
int 0x21
pop ax
%ifnidni %1,dx
pop dx
%endif
%endif
%endmacro
%macro exit 0-2
%ifstr %1
section .data
%%msg: db %1,'$'
section .text
%ifidni %2,oncarry
jnc %%ok
%endif
mov dx,%%msg
mov ah,0x09
int 0x21
%endif
int 0x20
%%ok:
%endmacro
%macro break 0
;
; "int3" generates the 1-byte breakpoint instruction; "int 3" generates a 2-byte software interrupt
;
int3
%endmacro
org 0x100
section .text
openF "TRACE.TXT"
exit "unable to open file",oncarry
mov di,file_buffer
readF ax,di,file_buffer_len
m1: exit "unable to read file",oncarry
test ax,ax ; AX contains how many bytes were actually read
jnz m2
exit "processing complete"
m2: cld
mov si,file_buffer
add di,ax
;
; At this point, DS:SI is the current line pointer, and DI is the end-of-buffer
; position. getLine() will update CX to the length of the current line (including
; the terminating LF).
;
m3: call getLine
jnc m4
;
; Oops, carry is set, so we're missing part or all of the next line. Move the
; partial line to the top of the file buffer and then fill the rest of the buffer.
;
mov di,file_buffer
rep movsb
mov cx,file_buffer_end
sub cx,di
readF bx,di,cx
jmp m1
;
; OK, we now have a complete line at DS:SI, guaranteed to end with an LF, with a
; length of CX (although CX will soon be overwritten by calls to getHex).
;
m4:
; print line
mov ah,' '
call skipTo
m4err: exit "missing space",oncarry
inc si
push di
mov di,ins_name
m5: lodsb
cmp al,'('
je m6
stosb
jmp m5
m6: mov al,'$'
stosb
pop di
print ins_name
print strColon
call getHex
mov [dst_operand],ax
mov [dst_operand+2],dx
mov [dst_size],cx
call printHex
print strComma
inc si
call getHex
mov [src_operand],ax
mov [src_operand+2],dx
mov [src_size],cx
call printHex
inc si
call getHex
and ax,PS_ALL
mov [operand_flags],ax
jcxz m6b
print strComma
call printHex
m6b: mov ah,' '
call skipTo
jc m4err
inc si
call getHex
mov [result_operand],ax
mov [result_operand+2],dx
mov [result_size],cx
print strEquals
call printHex
print strComma
inc si
call getHex
and ax,PS_ALL
mov [result_flags],ax
call printHex
print strCRLF
;
; Now that we know operand sizes, it's time to look up the instruction function
;
push si
mov si,ins_table
push di
m7a: mov di,ins_name
m7b: lodsb
test al,al
jz m8
mov ah,[es:di]
inc di
cmp al,ah
je m7b
m7c: lodsb
test al,al
jnz m7c
add si,8 ; after then name, each ins_table entry is 4 words long
cmp byte [si],0
jne m7a
print "missing function: "
print ins_name
exit
m8: mov ax,[si]
mov cx,[result_size]
mov dx,compare8
cmp cl,4
jb m8a
mov ax,[si+2]
mov dx,compare16
cmp cl,8
jb m8a
mov ax,[si+4]
mov dx,compare32
m8a: test ax,ax
jnz m8b
print "missing "
print ins_name
xchg ax,cx
call printHex
exit
m8b: mov [ins_function],ax
mov [ins_compare],dx
mov ax,[si+6]
mov [relevant_flags],ax
;
; Let's call the instruction function now, loading the PS_ALL flags with the same values
; that the emulator recorded (operand_flags).
;
pushf
pop cx ; CX == current flags
mov ax,PS_ALL
not ax
and cx,ax ; CX == current flags, with PS_ALL flags cleared
or cx,[operand_flags]
push cx ; CX == current flags, with PS_ALL flags from operand_flags included
popf
call [ins_function]
pushf
pop cx
call [ins_compare]
;
; If we're still here, the instruction passed, so restore the line pointer and move to the next line
;
pop di
pop si
;
; When we finished reading the current line, DS:SI should have been left pointing at the terminating LF;
; however, if we used "print" to display it, that LF would have replaced with a '$'. In any case, we don't
; really need to call skipTo, if we know we're at the end of the current line.
;
; mov ah,0x0A
; call skipTo
inc si ; step over the LF (or '$', in case we printed the line before processing it)
jmp m3
compare8:
mov ah,[result_operand]
cmp al,ah
jne c8err
jmp compareFlags
c8err: print "byte mismatch:"
print strActual
mov cx,2
call printHex
print strRecorded
mov al,ah
call printHex
print strCRLF
exit
compare16:
mov dx,[result_operand]
cmp ax,dx
jne c16err
jmp compareFlags
c16err: print "word mismatch:"
print strActual
mov cx,4
call printHex
print strRecorded
xchg ax,dx
call printHex
print strCRLF
exit
compare32:
cmp ax,[result_operand]
jne c32err
cmp dx,[result_operand+2]
je compareFlags
c32err: print "dword mismatch:"
print strActual
mov cx,8
call printHex
print strRecorded
mov ax,[result_operand]
mov dx,[result_operand+2]
call printHex
print strCRLF
exit
compareFlags:
mov dx,[result_flags]
and cx,[relevant_flags]
and dx,[relevant_flags]
cmp cx,dx
je cfret
print "flag mismatch:"
print strActual
xchg ax,cx
mov cx,4
call printHex
print strRecorded
xchg ax,dx
call printHex
print strCRLF
exit
cfret: ret
testROL8:
mov al,[dst_operand]
mov cl,[src_operand]
rol al,cl
ret
testROL16:
mov ax,[dst_operand]
mov cl,[src_operand]
rol ax,cl
ret
testROR8:
mov al,[dst_operand]
mov cl,[src_operand]
ror al,cl
ret
testROR16:
mov ax,[dst_operand]
mov cl,[src_operand]
ror ax,cl
ret
testRCL8:
mov al,[dst_operand]
mov cl,[src_operand]
rcl al,cl
ret
testRCL16:
mov ax,[dst_operand]
mov cl,[src_operand]
rcl ax,cl
ret
testRCR8:
mov al,[dst_operand]
mov cl,[src_operand]
rcr al,cl
ret
testRCR16:
mov ax,[dst_operand]
mov cl,[src_operand]
rcr ax,cl
ret
testSHL8:
mov al,[dst_operand]
mov cl,[src_operand]
shl al,cl
ret
testSHL16:
mov ax,[dst_operand]
mov cl,[src_operand]
shl ax,cl
ret
testMUL16:
mov al,[dst_operand]
mov cl,[src_operand]
mul cl
ret
testMUL32:
mov ax,[dst_operand]
mov cx,[src_operand]
mul cx
ret
testIMUL16:
mov al,[dst_operand]
mov cl,[src_operand]
imul cl
ret
testIMUL32:
mov ax,[dst_operand]
mov cx,[src_operand]
imul cx
ret
testDIV16:
mov ax,[dst_operand]
mov cl,[src_operand]
div cl
ret
testDIV32:
mov ax,[dst_operand]
mov dx,[dst_operand+2]
mov cx,[src_operand]
div cx
ret
testIDIV16:
mov ax,[dst_operand]
mov cl,[src_operand]
idiv cl
ret
testIDIV32:
mov ax,[dst_operand]
mov dx,[dst_operand+2]
mov cx,[src_operand]
idiv cx
ret
;
; getHex: get value of hex string
;
; Inputs
; DS:SI -> hex string
;
; Outputs
; CX == number of characters
; DX:AX == corresponding value
; DS:SI -> next non-hex character
;
; Uses
; AX, CX, DX, SI, Flags
;
; Notes
; Supports upper-case alpha chars only, with no prefixes (eg, "0x") or suffixes (eg, "h");
; if there are more than 8 hex characters, the value will represent only the last 8 characters.
;
getHex:
push bx
sub bx,bx ; BX holds the low 16 bits
sub dx,dx ; DX holds the high 16 bits
sub cx,cx ; CX holds the character count
gh1: lodsb
cmp al,'0'
jb gh9
cmp al,'9'
ja gh3
sub al,'0'
gh2: shl bx,1
rcl dx,1
shl bx,1
rcl dx,1
shl bx,1
rcl dx,1
shl bx,1
rcl dx,1
or bl,al
inc cx
jmp gh1
gh3: cmp al,'A'
jb gh9
cmp al,'F'
ja gh9
sub al,'A'-10
jmp gh2
gh9: dec si
xchg ax,bx ; DX:AX now holds the final 32-bit result
pop bx
ret
;
; printHex: print value in hex
;
; Inputs
; DX:AX == value
; CX == # of characters
;
; Outputs
; None
;
; Uses
; Flags
;
printHex:
push ax
push bx
push cx
push dx
push di
mov bx,ax ; DX:BX now holds the value to print
mov di,hex_buffer_end - 1
mov al,'$'
std
stosb
ph1: jcxz ph3
mov al,bl
and al,0x0F
add al,'0'
cmp al,'9'
jbe ph2
add al,'A'-'0'-10
ph2: stosb
dec cx
shr dx,1
rcr bx,1
shr dx,1
rcr bx,1
shr dx,1
rcr bx,1
shr dx,1
rcr bx,1
jmp ph1
ph3: cld
inc di
print di
pop di
pop dx
pop cx
pop bx
pop ax
ret
;
; getLine: find the length of the current line
;
; Inputs
; DS:SI -> start of line
; DS:DI -> first byte past end of line buffer
;
; Outputs
; CX == length of line, including the terminating LF (or partial length)
; Carry clear if line complete, carry set if line incomplete (SI reached DI)
;
; Uses
; AL, CX, Flags
;
getLine:
push si
sub cx,cx
gl1: cmp si,di
jb gl2
stc
jmp gl9
gl2: lodsb
inc cx
cmp al,0x0A
jne gl1
gl9: pop si
ret
;
; skipTo: skip to the character in AH
;
; Inputs
; AH == specified character
; DS:SI -> LF-terminated line
;
; Outputs
; DS:SI -> specified character if carry clear, or LF if carry set
;
; Uses
; AL, SI, Flags
;
skipTo:
lodsb
cmp al,ah
je st9
cmp al,0x0A
jne skipTo
stc
st9: dec si
ret
;
; The following is "const" (read-only) data...
;
section .data
ins_table db "ROL",0
dw testROL8, testROL16, 0, PS_ALL_BUT_OF
db "ROR",0
dw testROR8, testROR16, 0, PS_ALL_BUT_OF
db "RCL",0
dw testRCL8, testRCL16, 0, PS_ALL_BUT_OF
db "RCR",0
dw testRCR8, testRCR16, 0, PS_ALL_BUT_OF
db "SHL",0
dw testSHL8, testSHL16, 0, PS_ALL_BUT_AF
db "MUL",0
dw 0, testMUL16, testMUL32, PS_CF | PS_OF
db "IMUL",0
dw 0, testIMUL16, testIMUL32, PS_CF | PS_OF
db "DIV",0
dw 0, testDIV16, testDIV32, PS_NONE
db "IMUL",0
dw 0, testIDIV16, testIDIV32, PS_NONE
db 0 ; end of instruction table
strCRLF db 0x0D,0x0A,'$'
strColon db ":$"
strEquals db "=$"
strComma db ",$"
strActual db " actual=$"
strRecorded db " recorded=$"
;
; We end with all the unitialized data (ie, data that doesn't need to be stored in the binary)
;
section .bss
ins_name resb 6
ins_function resw 1
ins_compare resw 1
dst_operand resw 2
dst_size resw 1
src_operand resw 2
src_size resw 1
operand_flags resw 1
relevant_flags resw 1
result_operand resw 2
result_size resw 1
result_flags resw 1
hex_buffer resb 9
hex_buffer_end equ $
hex_buffer_len equ hex_buffer_end - hex_buffer
file_buffer resb 0x1000
file_buffer_end equ $
file_buffer_len equ file_buffer_end - file_buffer