; ; trace.nasm ; Copyright © 2012-2015 Jeff Parsons ; ; This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines) ; at and . ; ; 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 . ; ; 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