Use .nasm to distinguish NASM source from MASM source
This commit is contained in:
parent
5575d6e3fa
commit
af2a2c8ac1
4 changed files with 714 additions and 692 deletions
|
|
@ -12,14 +12,10 @@ indent_style = space
|
|||
indent_size = 8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.ASM,*.INC}]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.asm]
|
||||
[{*.ASM,*.INC,*.asm,*.inc,*.nasm}]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[modules/**.js]
|
||||
indent_style = space
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@
|
|||
|
||||
all: trace.com
|
||||
|
||||
trace.com: trace.asm
|
||||
nasm -f bin trace.asm -l trace.lst -o trace.com
|
||||
trace.com: trace.nasm
|
||||
nasm -f bin trace.nasm -l trace.lst -o trace.com
|
||||
|
|
|
|||
|
|
@ -1,684 +0,0 @@
|
|||
;
|
||||
; trace.asm
|
||||
;
|
||||
; Purpose: take an instruction log, as recorded by the Debugger's traceLog() function, and
|
||||
; "play" 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
|
||||
710
tests/pc/trace/trace.nasm
Normal file
710
tests/pc/trace/trace.nasm
Normal file
|
|
@ -0,0 +1,710 @@
|
|||
;
|
||||
; 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
|
||||
Loading…
Reference in a new issue