PCjs can now connect a serial port to the console

This commit is contained in:
Jeff Parsons 2015-10-15 12:56:07 -07:00
commit 464219a4c1
6 changed files with 367 additions and 117 deletions

View file

@ -1,12 +1,15 @@
{ {
"machine": {
"id": "ibm5150"
},
"computer": { "computer": {
"id": "ibm5150.pc-mda-64k", "id": "pc-mda-64k",
"name": "IBM PC", "name": "IBM PC",
"resume": "1", "resume": "1",
"state": "" "state": ""
}, },
"ram": [ "ram": [
{ "id": "ibm5150.ramLow", { "id": "ramLow",
"name": "", "name": "",
"addr": 0x00000, "addr": 0x00000,
"size": 0, "size": 0,
@ -14,14 +17,14 @@
} }
], ],
"rom": [ "rom": [
{ "id": "ibm5150.romBASIC", { "id": "romBASIC",
"name": "", "name": "",
"addr": 0xf6000, "addr": 0xf6000,
"size": 0x08000, "size": 0x08000,
"file": "/devices/pc/basic/ibm-basic-1.00.json", "file": "/devices/pc/basic/ibm-basic-1.00.json",
"notify": "" "notify": ""
}, },
{ "id": "ibm5150.romBIOS", { "id": "romBIOS",
"name": "", "name": "",
"addr": 0xfe000, "addr": 0xfe000,
"size": 0x02000, "size": 0x02000,
@ -30,7 +33,7 @@
} }
], ],
"video": [ "video": [
{ "id": "ibm5150.videoMDA", { "id": "videoMDA",
"name": "Monochrome Display", "name": "Monochrome Display",
"model": "", "model": "",
"mode": 7, "mode": 7,
@ -45,7 +48,7 @@
} }
], ],
"cpu": { "cpu": {
"id": "ibm5150.cpu8088", "id": "cpu8088",
"name": "", "name": "",
"model": 8088, "model": 8088,
"clock": 0, "clock": 0,
@ -56,12 +59,12 @@
"csStop": -1 "csStop": -1
}, },
"keyboard": { "keyboard": {
"id": "ibm5150.keyboard", "id": "keyboard",
"name": "", "name": "",
"model": "" "model": ""
}, },
"fdc": { "fdc": {
"id": "ibm5150.fdcNEC", "id": "fdcNEC",
"name": "", "name": "",
"autoMount": { "autoMount": {
"A": { "A": {
@ -75,7 +78,7 @@
} }
}, },
"chipset": { "chipset": {
"id": "ibm5150.chipset", "id": "chipset",
"name": "", "name": "",
"model": "5150", "model": "5150",
"sw1": "01000001", "sw1": "01000001",
@ -83,7 +86,7 @@
"sound": true "sound": true
}, },
"debugger": { "debugger": {
"id": "ibm5150.debugger", "id": "debugger",
"name": "", "name": "",
"messages": "" "messages": ""
}, },

View file

@ -217,15 +217,32 @@ function loadMachine(sFile)
* in the browser, by walking the list of PCjs components we loaded above and looking for * in the browser, by walking the list of PCjs components we loaded above and looking for
* matches. * matches.
*/ */
var idMachine = "";
/*
* 'machine' is a pseudo-component that is only used to define an ID for the entire machine;
* if it exists, then that ID is prepended to every component ID, just as our XSLT code would
* do for a machine XML file. This relieves the JSON file from having to manually prepend
* a machine ID to every component ID itself.
*
* This doesn't mean I anticipate a Node environment running multiple machines, as we do in
* a browser; it only means that I'm trying to make both environments operate similarly.
*/
if (machine['machine']) {
idMachine = machine['machine']['id'];
}
for (i = 0; i < aComponents.length; i++) { for (i = 0; i < aComponents.length; i++) {
var parms = machine[aComponents[i].name];
var component = aComponents[i];
var parms = machine[component.name];
/* /*
* If parms is undefined, it means there is no component with that name defined in the * If parms is undefined, it means there is no component with that name defined in the
* machine object (NOT that the component has no parms), and therefore we should skip it. * machine object (NOT that the component has no parms), and therefore we should skip it.
*/ */
if (parms === undefined) continue; if (parms === undefined) continue;
/* /*
* If parms is an Array, then we must create an object for each parms element; and yes, * If parms is an Array, then we must create an object for each parms element; and yes,
* I'm relying on the fact that none of my parm objects use a "length" property, as a quick * I'm relying on the fact that none of my parm objects use a "length" property, as a quick
* and dirty way of differentiating objects from arrays. * and dirty way of differentiating objects from arrays.
*/ */
@ -233,22 +250,27 @@ function loadMachine(sFile)
for (j = 0; j < aParms.length; j++) { for (j = 0; j < aParms.length; j++) {
var obj; var obj;
if (fDebug) console.log("creating " + aComponents[i].name + "..."); var parmsObj = aParms[j];
if (fDebug) console.log(aParms[j]); if (idMachine) parmsObj['id'] = idMachine + '.' + parmsObj['id'];
if (aComponents[i].name == "cpu") { if (fDebug) {
aParms[j]['autoStart'] = false; console.log("creating " + component.name + "...");
console.log(parmsObj);
}
if (component.name == "cpu") {
parmsObj['autoStart'] = false;
} }
try { try {
obj = new aComponents[i].Create(aParms[j]); obj = new component.Create(parmsObj);
} catch(err) { } catch(err) {
console.log("error creating " + aComponents[i].name + ": " + err.message); console.log("error creating " + component.name + ": " + err.message);
continue; continue;
} }
console.log(obj['id'] + " object created"); console.log(obj['id'] + " object created");
aComponents[i].objects.push(obj); component.objects.push(obj);
if (obj.type == "Debugger") { if (obj.type == "Debugger") {
dbg = obj; dbg = obj;

View file

@ -1,13 +1,16 @@
{ {
"machine": {
"id": "pc386"
},
"computer": { "computer": {
"id": "pc386.computer", "id": "computer",
"name": "Compaq DeskPro 386", "name": "Compaq DeskPro 386",
"resume": 0, "resume": 0,
"state": "", "state": "",
"busWidth": 32 "busWidth": 32
}, },
"ram": [ "ram": [
{ "id": "pc386.ramLow", { "id": "ramLow",
"name": "", "name": "",
"addr": 0, "addr": 0,
"size": 655360, "size": 655360,
@ -15,17 +18,17 @@
} }
], ],
"rom": [ "rom": [
{ "id": "pc386.romTests", { "id": "test386",
"name": "", "name": "",
"addr": 983296, "addr": 983296,
"size": 65280, "size": 65280,
"alias": 4294902016, "alias": 4294902016,
"file": "/tests/pc/80386/tests.json", "file": "/tests/pc/80386/test386.json",
"notify": "" "notify": ""
} }
], ],
"video": [ "video": [
{ "id": "pc386.videoMDA", { "id": "videoMDA",
"name": "Monochrome Display", "name": "Monochrome Display",
"model": "", "model": "",
"mode": 7, "mode": 7,
@ -40,7 +43,7 @@
} }
], ],
"cpu": { "cpu": {
"id": "pc386.cpu", "id": "cpu",
"name": "", "name": "",
"model": 80386, "model": 80386,
"clock": 0, "clock": 0,
@ -51,12 +54,12 @@
"csStop": -1 "csStop": -1
}, },
"keyboard": { "keyboard": {
"id": "pc386.keyboard", "id": "keyboard",
"name": "", "name": "",
"model": "" "model": ""
}, },
"fdc": { "fdc": {
"id": "pc386.fdcNEC", "id": "fdcNEC",
"name": "", "name": "",
"autoMount": { "autoMount": {
"A": { "A": {
@ -70,13 +73,18 @@
} }
}, },
"chipset": { "chipset": {
"id": "pc386.chipset", "id": "chipset",
"name": "", "name": "",
"model": "deskpro386", "model": "deskpro386",
"sound": false "sound": false
},
"serialport": {
"id": "com2",
"adapter": 2,
"binding": "console"
}, },
"debugger": { "debugger": {
"id": "pc386.debugger", "id": "debugger",
"name": "", "name": "",
"commands": "", "commands": "",
"messages": "" "messages": ""

View file

@ -33,6 +33,7 @@
"use strict"; "use strict";
if (NODE) { if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib"); var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component"); var Component = require("../../shared/lib/component");
var Messages = require("./messages"); var Messages = require("./messages");
@ -79,9 +80,17 @@ function SerialPort(parmsSerial) {
Component.warning("Unrecognized serial adapter #" + this.iAdapter); Component.warning("Unrecognized serial adapter #" + this.iAdapter);
return; return;
} }
/**
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
* or the string length reaches a threshold (currently, 1024 characters).
*
* @type {string|null}
*/
this.consoleOutput = null;
/** /**
* controlIOBuffer is a DOM element, if any, bound to the port (currently for output purposes only; see echoByte()) * controlIOBuffer is a DOM element, if any, bound to the port (currently used for output only; see echoByte()).
* *
* @type {Object} * @type {Object}
*/ */
@ -89,7 +98,15 @@ function SerialPort(parmsSerial) {
Component.call(this, "SerialPort", parmsSerial, SerialPort, Messages.SERIAL); Component.call(this, "SerialPort", parmsSerial, SerialPort, Messages.SERIAL);
Component.bindExternalControl(this, parmsSerial['binding'], SerialPort.sIOBuffer); var sBinding = parmsSerial['binding'];
if (sBinding == "console") {
this.consoleOutput = "";
} else {
/*
* NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing.
*/
Component.bindExternalControl(this, sBinding, SerialPort.sIOBuffer);
}
} }
/* /*
@ -787,6 +804,14 @@ SerialPort.prototype.echoByte = function(b)
} }
return true; return true;
} }
if (this.consoleOutput != null) {
this.consoleOutput += String.fromCharCode(b);
if (b == 0x0A || this.consoleOutput.length >= 1024) {
this.println(str.trim(this.consoleOutput));
this.consoleOutput = "";
}
return true;
}
return false; return false;
}; };

View file

@ -1,7 +1,7 @@
all: tests.json all: test386.json
tests.com: tests.nasm ../inc/dos.inc ../inc/misc.inc ../inc/x86.inc test386.com: test386.nasm ../inc/dos.inc ../inc/misc.inc ../inc/x86.inc
nasm -i../inc/ -f bin tests.nasm -l tests.lst -o tests.com nasm -i../inc/ -f bin test386.nasm -l test386.lst -o test386.com
tests.json: tests.com test386.json: test386.com
node ../../../modules/filedump/bin/filedump --file=tests.com --output=tests.json --overwrite node ../../../modules/filedump/bin/filedump --file=test386.com --output=test386.json --overwrite

View file

@ -1,40 +1,68 @@
; ;
; This file is designed to run both as a test ROM and as a DOS COM file (hence the "org 0x100"), ; test386.nasm
; which is why it has a ".com" extension instead of the more typical ".rom" extension. ; Copyright © 2012-2015 Jeff Parsons <Jeff@pcjs.org>
; ;
; When used as a ROM, it should be installed at physical address 983296 (0xf0100) and aliased at ; This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
; physical address 4294902016 (0xffff0100). The jump at jmpStart should align with the CPU reset ; at <http://jsmachines.net/> and <http://pcjs.org/>.
; address (%0xfffffff0), which will transfer control to 0xf000:0x0100. From that point on,
; all memory accesses should remain within the first 1Mb.
; ;
; The code which attempts to update myGDT and addrGDT will have no effect when installed as a ROM, ; PCjs is free software: you can redistribute it and/or modify it under the terms of the
; which is fine, because those data structures are predefined with appropriate ROM-based addresses. ; GNU General Public License as published by the Free Software Foundation, either version 3
; of the License, or (at your option) any later version.
; ;
; See the machine definition file in /modules/pcjs/bin/romtests.json for a configuration that can ; PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
; load this file as a ROM image. ; even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
; ;
; REAL32 Notes ; 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>.
; REAL32 is NOT enabled by default, because based on what I've seen in VirtualBox (and notes at
; http://geezer.osdevbrasil.net/johnfine/segments.htm), if CS is loaded with a 32-bit code segment
; while in protected-mode and we then return to real-mode, even if we immediately perform a FAR jump
; with a real-mode CS, the base of CS will be updated, but all the other segment attributes, like
; the 32-bit EXT_BIG attribute, remain unchanged. As a result, the processor will crash as soon as
; it starts executing 16-bit real-mode code, because it's being misinterpreted as 32-bit code, and
; there doesn't appear to be anything you can do about it from real-mode.
; ;
; The work-around: you MUST load CS with a 16-bit code segment BEFORE returning to real-mode. ; 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).
; ;
; "Unreal mode" works by setting OTHER segment registers, like DS and ES, to 32-bit segments before ; Some PCjs files also attempt to load external resource files, such as character-image files,
; returning to real-mode -- just not CS. SS probably shouldn't be set to a 32-bit segment either, ; ROM files, and disk image files. Those external resource files are not considered part of the
; because that causes implicit pushes to use ESP instead of SP, even in real-mode. ; PCjs program for purposes of the GNU General Public License, and the author does not claim
; any copyright as to their contents.
; ;
; The code below ensures that, before returning to real-mode, all of CS, DS, ES, and SS contain ; Overview
; 16-bit protected-mode selectors; note, however, that my 16-bit protected-mode data descriptor uses ; --------
; a full 20-bit limit, so DS, ES, and SS will still have a limit of 1Mb instead of the usual 64Kb, ; This file is designed to run both as a test ROM and as a DOS COM file (hence the "org 0x100"),
; even after returning to real-mode. I use the larger limit because it's convenient to have access ; which is why it has a ".com" extension instead of the more typical ".rom" extension.
; to the first 1Mb in protected-mode, with or without a 32-bit data segment, and the larger data ;
; segment limit shouldn't affect any 16-bit real-mode operations. ; When used as a ROM, it should be installed at physical address 983296 (0xf0100) and aliased at
; physical address 4294902016 (0xffff0100). The jump at jmpStart should align with the CPU reset
; address (%0xfffffff0), which will transfer control to 0xf000:0x0100. From that point on,
; all memory accesses should remain within the first 1Mb.
;
; The code which attempts to update myGDT and addrGDT will have no effect when installed as a ROM,
; which is fine, because those data structures are predefined with appropriate ROM-based addresses.
;
; See the machine definition file in /modules/pcjs/bin/romtests.json for a configuration that can
; load this file as a ROM image.
;
; REAL32 Notes
; ------------
; REAL32 is NOT enabled by default, because based on what I've seen in VirtualBox (and notes at
; http://geezer.osdevbrasil.net/johnfine/segments.htm), if CS is loaded with a 32-bit code segment
; while in protected-mode and we then return to real-mode, even if we immediately perform a FAR jump
; with a real-mode CS, the base of CS will be updated, but all the other segment attributes, like
; the 32-bit EXT_BIG attribute, remain unchanged. As a result, the processor will crash as soon as
; it starts executing 16-bit real-mode code, because it's being misinterpreted as 32-bit code, and
; there doesn't appear to be anything you can do about it from real-mode.
;
; The work-around: you MUST load CS with a 16-bit code segment BEFORE returning to real-mode.
;
; "Unreal mode" works by setting OTHER segment registers, like DS and ES, to 32-bit segments before
; returning to real-mode -- just not CS. SS probably shouldn't be set to a 32-bit segment either,
; because that causes implicit pushes to use ESP instead of SP, even in real-mode.
;
; The code below ensures that, before returning to real-mode, all of CS, DS, ES, and SS contain
; 16-bit protected-mode selectors; note, however, that my 16-bit protected-mode data descriptor uses
; a full 20-bit limit, so DS, ES, and SS will still have a limit of 1Mb instead of the usual 64Kb,
; even after returning to real-mode. I use the larger limit because it's convenient to have access
; to the first 1Mb in protected-mode, with or without a 32-bit data segment, and the larger data
; segment limit shouldn't affect any 16-bit real-mode operations.
; ;
cpu 386 cpu 386
org 0x100 org 0x100
@ -49,7 +77,7 @@
PAGING equ 1 PAGING equ 1
; ;
; If we built our data structures in RAM, we might use the first page of RAM (0x0000-0x0fff) like so: ; If we built our data structures in RAM, we might use the first page of RAM (0x0000-0x0fff) like so:
; ;
; 0x0000-0x03ff Real-mode IDT (256*4) ; 0x0000-0x03ff Real-mode IDT (256*4)
; 0x0400-0x0bff Prot-mode IDT (256*8) ; 0x0400-0x0bff Prot-mode IDT (256*8)
@ -59,8 +87,8 @@ PAGING equ 1
; 0x0d10-0x0d13 RAM_RETF (Real-mode return address) ; 0x0d10-0x0d13 RAM_RETF (Real-mode return address)
; 0x0d14-0x0fff reserved ; 0x0d14-0x0fff reserved
; ;
; And in the second page (0x1000-0x1fff), we might build a page directory, followed by a single page table ; And in the second page (0x1000-0x1fff), we might build a page directory, followed by a single page table
; that allows us to map up to 4Mb (although we'd likely only create PTEs for the first 1Mb). ; that allows us to map up to 4Mb (although we'd likely only create PTEs for the first 1Mb).
; ;
;RAM_GDT equ 0x0c00 ;RAM_GDT equ 0x0c00
;RAM_IDTR equ 0x0d00 ;RAM_IDTR equ 0x0d00
@ -75,7 +103,7 @@ DSEG_PROT32 equ 0x0020
SSEG_PROT32 equ 0x0028 SSEG_PROT32 equ 0x0028
; ;
; The "defDesc" macro defines a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) ; The "defDesc" macro defines a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5)
; ;
%assign selDesc 0 %assign selDesc 0
@ -93,7 +121,7 @@ SSEG_PROT32 equ 0x0028
%endmacro %endmacro
; ;
; The "setDesc" macro creates a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) ; The "setDesc" macro creates a descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5)
; ;
%macro setDesc 1-5 0,0,0,0 %macro setDesc 1-5 0,0,0,0
%assign %1 selDesc %assign %1 selDesc
@ -107,7 +135,7 @@ SSEG_PROT32 equ 0x0028
start: nop start: nop
; ;
; Quick test of unsigned 32-bit multiplication and division ; Quick test of unsigned 32-bit multiplication and division
; ;
mov eax,0x44332211 mov eax,0x44332211
mov ebx,eax mov ebx,eax
@ -120,7 +148,7 @@ start: nop
xor dx,dx xor dx,dx
mov ds,dx ; DS -> 0x0000 mov ds,dx ; DS -> 0x0000
; ;
; Quick test of moving a segment register to a 32-bit register ; Quick test of moving a segment register to a 32-bit register
; ;
mov eax,ds mov eax,ds
test eax,eax test eax,eax
@ -130,7 +158,7 @@ start: nop
times 32768 nop ; lots of NOPs to force a 16-bit conditional jump times 32768 nop ; lots of NOPs to force a 16-bit conditional jump
; ;
; storeDesc(EBX=base, ECX=limit, DX=type, AX=ext, DI=address of descriptor) ; storeDesc(EBX=base, ECX=limit, DX=type, AX=ext, DI=address of descriptor)
; ;
storeDesc: storeDesc:
cld cld
@ -183,7 +211,7 @@ initGDT:
mov word [RAM_RETF+2],cs mov word [RAM_RETF+2],cs
%else %else
; ;
; This code fixes the GDT and all our FAR jumps if we're running in RAM ; This code fixes the GDT and all our FAR jumps if we're running in RAM
; ;
xor eax,eax xor eax,eax
mov ax,cs mov ax,cs
@ -208,9 +236,9 @@ initGDT:
mov [cs:jmpStart+3],ax ; ditto for the FAR jump that returns us to the start of the image mov [cs:jmpStart+3],ax ; ditto for the FAR jump that returns us to the start of the image
%endif %endif
; ;
; Now we want to build a page directory and a page table, but we need two pages of ; Now we want to build a page directory and a page table, but we need two pages of
; 4K-aligned physical memory. We can use a hard-coded address (segment 0x100, corresponding ; 4K-aligned physical memory. We can use a hard-coded address (segment 0x100, corresponding
; to physical address 0x1000) if we're running in ROM; otherwise, we ask DOS for some memory. ; to physical address 0x1000) if we're running in ROM; otherwise, we ask DOS for some memory.
; ;
cmp ax,CSEG_REAL cmp ax,CSEG_REAL
mov ax,0x100 ; default to the 2nd physical page in low memory mov ax,0x100 ; default to the 2nd physical page in low memory
@ -238,7 +266,7 @@ allocPages:
int INT_DOS int INT_DOS
jc errDOSMem jc errDOSMem
; ;
; AX == segment of 64K memory block ; AX == segment of 64K memory block
; ;
initPages: initPages:
movzx eax,ax movzx eax,ax
@ -250,8 +278,8 @@ initPages:
mov es,ax mov es,ax
xor edi,edi xor edi,edi
; ;
; Build a page directory at ES:EDI with only 1 valid PDE (the first one), ; Build a page directory at ES:EDI with only 1 valid PDE (the first one),
; because we're not going to access any memory outside the first 1Mb (of the first 4Mb). ; because we're not going to access any memory outside the first 1Mb (of the first 4Mb).
; ;
cld cld
mov eax,esi mov eax,esi
@ -262,8 +290,8 @@ initPages:
sub eax,eax sub eax,eax
rep stosd rep stosd
; ;
; Build a page table at EDI with 256 (out of 1024) valid PTEs, mapping the first 1Mb of the ; Build a page table at EDI with 256 (out of 1024) valid PTEs, mapping the first 1Mb of the
; first 4Mb as linear == physical. ; first 4Mb as linear == physical.
; ;
mov eax,PTE_USER | PTE_READWRITE | PTE_PRESENT mov eax,PTE_USER | PTE_READWRITE | PTE_PRESENT
mov ecx,256 ; ECX == number of PTEs to write mov ecx,256 ; ECX == number of PTEs to write
@ -294,13 +322,13 @@ toProt32:
mov ds,ax mov ds,ax
mov es,ax mov es,ax
; ;
; Of the 128Kb of scratch memory we allocated, we may have lost as much as 4Kb-1 rounding ; Of the 128Kb of scratch memory we allocated, we may have lost as much as 4Kb-1 rounding
; up to the first physical 4Kb page; the next 8Kb (0x2000) was used for a page directory and a ; up to the first physical 4Kb page; the next 8Kb (0x2000) was used for a page directory and a
; single page table, leaving us with a minimum of 116Kb to play with, starting at ESI+0x2000. ; single page table, leaving us with a minimum of 116Kb to play with, starting at ESI+0x2000.
; ;
; We'll set the top of our stack to ESI+0xe000. This guarantees an ESP greater than 0xffff, ; We'll set the top of our stack to ESI+0xe000. This guarantees an ESP greater than 0xffff,
; and so for the next few tests, with a 16-bit data segment in SS, we expect all pushes/pops ; and so for the next few tests, with a 16-bit data segment in SS, we expect all pushes/pops
; will occur at SP rather than ESP. ; will occur at SP rather than ESP.
; ;
add esi,0x2000 ; ESI -> bottom of scratch memory add esi,0x2000 ; ESI -> bottom of scratch memory
mov ss,ax mov ss,ax
@ -333,7 +361,7 @@ toProt32:
jne near error jne near error
pop ax pop ax
; ;
; Test moving a segment register to a 32-bit memory location ; Test moving a segment register to a 32-bit memory location
; ;
mov edx,[0x0000] ; save the DWORD at 0x0000:0x0000 in EDX mov edx,[0x0000] ; save the DWORD at 0x0000:0x0000 in EDX
or eax,-1 or eax,-1
@ -348,31 +376,31 @@ toProt32:
jne near error jne near error
mov [0x0000],edx ; restore the DWORD at 0x0000:0x0000 from EDX mov [0x0000],edx ; restore the DWORD at 0x0000:0x0000 from EDX
; ;
; Test moving a byte to a 32-bit register with sign-extension ; Test moving a byte to a 32-bit register with sign-extension
; ;
movsx eax,byte [cs:0xffff] movsx eax,byte [cs:0xffff]
cmp eax,0xffffff80 cmp eax,0xffffff80
jne near error jne near error
; ;
; Test moving a word to a 32-bit register with sign-extension ; Test moving a word to a 32-bit register with sign-extension
; ;
movsx eax,word [cs:0xfffe] movsx eax,word [cs:0xfffe]
cmp eax,0xffff80fc cmp eax,0xffff80fc
jne near error jne near error
; ;
; Test moving a byte to a 32-bit register with zero-extension ; Test moving a byte to a 32-bit register with zero-extension
; ;
movzx eax,byte [cs:0xffff] movzx eax,byte [cs:0xffff]
cmp eax,0x00000080 cmp eax,0x00000080
jne near error jne near error
; ;
; Test moving a word to a 32-bit register with zero-extension ; Test moving a word to a 32-bit register with zero-extension
; ;
movzx eax,word [cs:0xfffe] movzx eax,word [cs:0xfffe]
cmp eax,0x000080fc cmp eax,0x000080fc
jne near error jne near error
; ;
; More assorted ZX and SX tests ; More assorted ZX and SX tests
; ;
mov esp,0x40000 mov esp,0x40000
mov edx,[esp] ; save word at scratch address 0x40000 mov edx,[esp] ; save word at scratch address 0x40000
@ -425,7 +453,7 @@ toProt32:
cmp ebx,0xFFFFFF80 cmp ebx,0xFFFFFF80
jne near error jne near error
; ;
; Test assorted 32-bit addressing modes ; Test assorted 32-bit addressing modes
; ;
mov ax,SSEG_PROT32 ; we want SS != DS for the next tests mov ax,SSEG_PROT32 ; we want SS != DS for the next tests
mov ss,ax mov ss,ax
@ -462,8 +490,10 @@ toProt32:
mov [0x40000],edx ; restore word at scratch address 0x40000 mov [0x40000],edx ; restore word at scratch address 0x40000
; ;
; Now run a series of unverified opcode tests (verification will happen later, by comparing the output of the tests) ; Now run a series of unverified opcode tests (verification will happen later, by comparing the output of the tests)
; ;
int3
cld
mov esi,tableOps ; ESI -> tableOps entry mov esi,tableOps ; ESI -> tableOps entry
testOps: testOps:
movzx ecx,byte [cs:esi] ; ECX == length of instruction sequence movzx ecx,byte [cs:esi] ; ECX == length of instruction sequence
@ -472,8 +502,12 @@ testOps:
shl ebx,5 ; EBX == type * 32 shl ebx,5 ; EBX == type * 32
movzx edx,byte [cs:esi+2] ; EDX == SIZE movzx edx,byte [cs:esi+2] ; EDX == SIZE
lea ebx,[cs:typeValues+ebx+edx*8] ; EBX -> values for type lea ebx,[cs:typeValues+ebx+edx*8] ; EBX -> values for type
add esi,3 ; ESI -> instruction sequence to test add esi,3 ; ESI -> instruction mnemonic
skipOp: cs lodsb
test al,al
jnz skipOp
xor eax,eax ; set flags to known values prior tests
push ecx push ecx
mov ecx,[cs:ebx] ; ECX == count of values for dst mov ecx,[cs:ebx] ; ECX == count of values for dst
mov ebx,[cs:ebx+4] ; EBX -> values for dst mov ebx,[cs:ebx+4] ; EBX -> values for dst
@ -482,17 +516,29 @@ testOps:
testDst: testDst:
push ebp push ebp
push edi push edi
pushfd
testSrc: testSrc:
mov eax,[cs:ebx] ; EAX == dst mov eax,[cs:ebx] ; EAX == dst
mov edx,[cs:edi] ; EDX == src mov edx,[cs:edi] ; EDX == src
call esi popfd
call printOp
call printEAX
call printEDX
call printPS
call esi ; execute the instruction sequence
call printEAX
call printEDX
call printPS
call printEOL
pushfd
add edi,4 ; EDI -> next src add edi,4 ; EDI -> next src
dec ebp ; decrement src count dec ebp ; decrement src count
jnz testSrc jnz testSrc
popfd
pop edi ; ESI -> restored values for src pop edi ; ESI -> restored values for src
pop ebp ; EBP == restored count of values for src pop ebp ; EBP == restored count of values for src
add ebx,4 ; EBX -> next dst lea ebx,[ebx+4] ; EBX -> next dst (without modifying flags)
loop testDst loop testDst
pop ecx pop ecx
@ -502,32 +548,178 @@ testSrc:
doneOps: doneOps:
jmp doneProt jmp doneProt
;
; printOp(ESI -> instruction sequence)
;
; Rewinds ESI to the start of the mnemonic preceding the instruction sequence and prints the mnemonic
;
; Uses: None
;
printOp:
pushfd
pushad
findOp: dec esi
mov al,[cs:esi-1]
cmp al,32
jae findOp
call printStr
mov al,' '
call printChar
popad
popfd
ret
;
; printEAX()
;
; Uses: None
;
printEAX:
pushfd
pushad
mov esi,strEAX
call printStr
mov cl,8
call printVal
popad
popfd
ret
;
; printEDX()
;
; Uses: None
;
printEDX:
pushfd
pushad
mov esi,strEDX
call printStr
mov cl,8
mov eax,edx
call printVal
popad
popfd
ret
;
; printPS()
;
; Uses: None
;
printPS:
pushfd
pushad
pushfd
pop eax
mov esi,strPS
call printStr
mov cl,4
call printVal
popad
popfd
ret
;
; printEOL()
;
; Uses: None
;
printEOL:
pushfd
pushad
mov al,0x0d
call printChar
mov al,0x0a
call printChar
popad
popfd
ret
;
; printChar(AL)
;
; Uses: None
;
printChar:
push edx
mov dx,0x2F8 ; EDX -> COM2 I/O port
out dx,al
pop edx
ret
;
; printStr(ESI -> zero-terminated string)
;
; Uses: ESI, Flags
;
printStr:
push eax
psLoop: cs lodsb
test al,al
jz psDone
call printChar
jmp psLoop
psDone: pop eax
ret
;
; printVal(EAX == value, CL == number of hex digits)
;
; Uses: EAX, ECX, Flags
;
printVal:
shl cl,2 ; CL == number of bits (4 times the number of hex digits)
jz pvDone
pvLoop: sub cl,4
push eax
shr eax,cl
and al,0x0f
add al,'0'
cmp al,'9'
jbe pvOK
add al,'A'-'0'-10
pvOK: call printChar
pop eax
test cl,cl
jnz pvLoop
pvDone: mov al,' '
call printChar
ret
TYPE_ARITH equ 0 TYPE_ARITH equ 0
SIZE_BYTE equ 0 SIZE_BYTE equ 0
SIZE_SHORT equ 1 SIZE_SHORT equ 1
SIZE_LONG equ 2 SIZE_LONG equ 2
%macro defOp 4 %macro defOp 5
%ifidn %2,al %ifidni %3,al
%assign size SIZE_BYTE %assign size SIZE_BYTE
%elifidn %2,ax %elifidni %3,ax
%assign size SIZE_SHORT %assign size SIZE_SHORT
%else %else
%assign size SIZE_LONG %assign size SIZE_LONG
%endif %endif
db %%end-%%beg,%4,size db %%end-%%beg,%5,size
%%beg: %1 %2,%3 %%name: db %1,0
%%beg: %2 %3,%4
ret ret
%%end: %%end:
%endmacro %endmacro
strEAX: db "EAX=",0
strEDX: db "EDX=",0
strPS: db "PS=",0
tableOps: tableOps:
defOp add,al,dl,TYPE_ARITH defOp "ADD",ADD,AL,DL,TYPE_ARITH
defOp add,ax,dx,TYPE_ARITH defOp "ADD",ADD,AX,DX,TYPE_ARITH
defOp add,eax,edx,TYPE_ARITH defOp "ADD",ADD,EAX,EDX,TYPE_ARITH
db 0 db 0
align 4
typeValues: typeValues:
dd 9,arithValues,18,arithValues,27,arithValues,0,0 dd 9,arithValues,18,arithValues,27,arithValues,0,0
@ -546,7 +738,7 @@ doneProt:
%ifndef REAL32 %ifndef REAL32
; ;
; Return to real-mode now, after first loading CS with a 16-bit code segment ; Return to real-mode now, after first loading CS with a 16-bit code segment
; ;
jmp CSEG_PROT16:toProt16 jmp CSEG_PROT16:toProt16
toProt16: toProt16:
@ -571,8 +763,8 @@ toReal:
int INT_DOSEXIT ; no, so assume we're running under DOS and exit int INT_DOSEXIT ; no, so assume we're running under DOS and exit
; ;
; Fill the remaining space with NOPs until we get to target offset 0xFFF0. ; Fill the remaining space with NOPs until we get to target offset 0xFFF0.
; Note that we subtract 0x100 from the target offset because we're ORG'ed at 0x100. ; Note that we subtract 0x100 from the target offset because we're ORG'ed at 0x100.
; ;
times 0xfff0-0x100-($-$$) nop times 0xfff0-0x100-($-$$) nop