Merge branch 'wasm' into master

This commit is contained in:
Fabian 2021-01-03 00:51:29 -06:00
commit 497f618cab
376 changed files with 104819 additions and 23662 deletions

2
.cargo/config Normal file
View file

@ -0,0 +1,2 @@
[build]
target-dir = "build"

14
.gitignore vendored
View file

@ -1,17 +1,27 @@
*.swp
*.swo
deploy.sh
screenshots/
tests/qemu/test-i386
tests/jit-paging/test-jit
*.map
build/
closure-compiler/
images/
*.bak
*.orig
*.wasm
*.o
*.bin
*.img
*.fixture
*.fuse_hidden*
*.DS_Store
node_modules/
Cargo.lock
build-head
src/rust/gen/interpreter.rs
src/rust/gen/interpreter0f.rs
src/rust/gen/analyzer.rs
src/rust/gen/analyzer0f.rs
src/rust/gen/jit.rs
src/rust/gen/jit0f.rs
bios/seabios

25
.jshint.json Normal file
View file

@ -0,0 +1,25 @@
{
"esversion": 8,
"globalstrict": true,
"sub": true,
"expr": true,
"-W058": true,
"-W080": true,
"-W082": true,
"-W079": true,
"-W117": true,
"-W054": true,
"-W027": true,
"-W040": true,
"-W087": true,
"-W008": true,
"loopfunc": true,
"shadow": true,
"funcscope": true,
"globals": {
"Blob": false,
"alert": false,
"console": false
},
"browser": true
}

9
.rustfmt.toml Normal file
View file

@ -0,0 +1,9 @@
use_field_init_shorthand = true
match_block_trailing_comma = true
fn_single_line = true
imports_indent = "Block"
control_brace_style = "ClosingNextLine"
single_line_if_else_max_width = 92
ignore = [
"src/rust/gen"
]

View file

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -e
mkdir -p images
(cd images && curl --compressed -OOOOOOOOOO https://copy.sh/v86/images/{linux.iso,linux3.iso,kolibri.img,windows101.img,os8.dsk,freedos722.img,openbsd.img,oberon.dsk,oberon-boot.dsk})
make build/libv86.js useacpi=true
tests/full/run.js

View file

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -e
mkdir -p images
(cd images && curl --compressed -OOOOOOOOOO https://copy.sh/v86/images/{linux.iso,linux3.iso,kolibri.img,windows101.img,os8.dsk,freedos722.img,openbsd.img,oberon.dsk,oberon-boot.dsk})
make build/libv86.js
tests/full/run.js

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
set -e
make nasmtests

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -e
mkdir -p images
(cd images && curl --compressed -O https://copy.sh/v86/images/linux3.iso)
make qemutests

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -e
make build/libv86.js
(cd tests/kvm-unit-tests && ./configure && make)
tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat

View file

@ -1,20 +0,0 @@
language: node_js
node_js:
- "6.10.2"
dist: trusty
before_install:
- sudo dpkg --add-architecture i386
- sudo apt-get update -qq
install:
- sudo apt-get install -y gcc-multilib nasm gdb
script:
- "./.travis-run-$TEST_SUITE.sh"
env:
- TEST_SUITE=unit
- TEST_SUITE=integration
- TEST_SUITE=integration-acpi
- TEST_SUITE=unit-qemu
- TEST_SUITE=nasm
matrix:
allow_failures:
- env: TEST_SUITE=unit-qemu

27
Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "v86"
version = "0.1.0"
publish = false
[dev-dependencies]
quickcheck = "0.6.2"
[features]
default = []
profiler = []
[lib]
crate-type = ["cdylib"]
path = "src/rust/lib.rs"
[profile.dev]
lto = false
opt-level = 2
panic = "abort"
overflow-checks = false
[profile.release]
lto = true
opt-level = 3
incremental = false
panic = "abort"

12
LICENSE
View file

@ -1,14 +1,14 @@
Copyright (c) 2012-2018, Fabian Hemmer
Copyright (c) 2012, The v86 contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -20,7 +20,3 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.

291
Makefile
View file

@ -1,44 +1,40 @@
CLOSURE_DIR=closure-compiler
CLOSURE=$(CLOSURE_DIR)/compiler.jar
BROWSER=chromium
NASM_TEST_DIR=./tests/nasm
all: build/v86_all.js
browser: build/v86_all.js
INSTRUCTION_TABLES=src/rust/gen/jit.rs src/rust/gen/jit0f.rs \
src/rust/gen/interpreter.rs src/rust/gen/interpreter0f.rs \
src/rust/gen/analyzer.rs src/rust/gen/analyzer0f.rs \
ACPI=false
ifeq ($(useacpi),true)
ACPI=true
# Only the dependencies common to both generate_{jit,interpreter}.js
GEN_DEPENDENCIES=$(filter-out gen/generate_interpreter.js gen/generate_jit.js gen/generate_analyzer.js, $(wildcard gen/*.js))
JIT_DEPENDENCIES=$(GEN_DEPENDENCIES) gen/generate_jit.js
INTERPRETER_DEPENDENCIES=$(GEN_DEPENDENCIES) gen/generate_interpreter.js
ANALYZER_DEPENDENCIES=$(GEN_DEPENDENCIES) gen/generate_analyzer.js
STRIP_DEBUG_FLAG=
ifeq ($(STRIP_DEBUG),true)
STRIP_DEBUG_FLAG=--v86-strip-debug
endif
default: build/v86-debug.wasm
all: build/v86_all.js build/libv86.js build/v86.wasm
all-debug: build/libv86-debug.js build/v86-debug.wasm
browser: build/v86_all.js
# Used for nodejs builds and in order to profile code.
# `debug` gives identifiers a readable name, make sure it doesn't have any side effects.
CLOSURE_READABLE=--formatting PRETTY_PRINT --debug
CLOSURE_SOURCE_MAP=\
--source_map_format V3\
--source_map_include_content\
--create_source_map '%outname%.map'
#--jscomp_error reportUnknownTypes\
#--jscomp_error unusedLocalVariables\
#--jscomp_error unusedPrivateMembers\
#--new_type_inf\
# Easily breaks code:
#--assume_function_wrapper\
# implies new type inferrence
#--jscomp_error newCheckTypes\
CLOSURE_FLAGS=\
--js lib/closure-base.js\
--generate_exports\
--externs src/externs.js\
--warning_level VERBOSE\
--jscomp_error accessControls\
--jscomp_error ambiguousFunctionDecl\
--jscomp_error checkEventfulObjectDisposal\
--jscomp_error checkRegExp\
--jscomp_error checkTypes\
--jscomp_error checkVars\
@ -48,15 +44,11 @@ CLOSURE_FLAGS=\
--jscomp_error deprecated\
--jscomp_error deprecatedAnnotations\
--jscomp_error duplicateMessage\
--jscomp_error es3\
--jscomp_error es5Strict\
--jscomp_error externsValidation\
--jscomp_error fileoverviewTags\
--jscomp_error globalThis\
--jscomp_error internetExplorerChecks\
--jscomp_error invalidCasts\
--jscomp_error misplacedTypeAnnotation\
--jscomp_error missingGetCssName\
--jscomp_error missingProperties\
--jscomp_error missingReturn\
--jscomp_error msgDescriptions\
@ -70,22 +62,35 @@ CLOSURE_FLAGS=\
--jscomp_error visibility\
--use_types_for_optimization\
--summary_detail_level 3\
--language_in ECMASCRIPT5_STRICT
--language_in ECMASCRIPT_2017\
--language_out ECMASCRIPT_2017
TRANSPILE_ES6_FLAGS=\
--language_in ECMASCRIPT6_STRICT\
--language_out ECMASCRIPT5_STRICT\
CARGO_FLAGS_SAFE=\
--target wasm32-unknown-unknown \
-- \
-C linker=tools/rust-lld-wrapper \
-C link-args="--import-table --global-base=262144 $(STRIP_DEBUG_FLAG)" \
-C link-args="build/softfloat.o" \
-C link-args="build/zstddeclib.o" \
--verbose
CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory
CORE_FILES=const.js config.js io.js main.js lib.js fpu.js ide.js pci.js floppy.js memory.js \
dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js acpi.js apic.js ioapic.js \
CORE_FILES=const.js config.js io.js main.js lib.js ide.js pci.js floppy.js \
memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js \
acpi.js apic.js ioapic.js \
state.js ne2k.js sb16.js virtio.js bus.js log.js \
cpu.js translate.js modrm.js string.js arith.js misc_instr.js instructions.js debug.js \
elf.js
cpu.js debug.js \
elf.js kernel.js
LIB_FILES=9p.js filesystem.js jor1k.js marshall.js utf8.js
BROWSER_FILES=screen.js \
keyboard.js mouse.js speaker.js serial.js \
network.js lib.js starter.js worker_bus.js dummy_screen.js
BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \
network.js lib.js starter.js worker_bus.js dummy_screen.js \
print_stats.js filestorage.js
RUST_FILES=$(shell find src/rust/ -name '*.rs') \
src/rust/gen/interpreter.rs src/rust/gen/interpreter0f.rs \
src/rust/gen/jit.rs src/rust/gen/jit0f.rs \
src/rust/gen/analyzer.rs src/rust/gen/analyzer0f.rs
CORE_FILES:=$(addprefix src/,$(CORE_FILES))
LIB_FILES:=$(addprefix lib/,$(LIB_FILES))
@ -97,20 +102,27 @@ build/v86_all.js: $(CLOSURE) src/*.js src/browser/*.js lib/*.js
java -jar $(CLOSURE) \
--js_output_file build/v86_all.js\
--define=DEBUG=false\
--define=ENABLE_ACPI=$(ACPI)\
$(CLOSURE_SOURCE_MAP)\
$(CLOSURE_FLAGS)\
--compilation_level ADVANCED\
$(TRANSPILE_ES6_FLAGS)\
--js $(CORE_FILES)\
--js $(LIB_FILES)\
--js $(BROWSER_FILES)\
--js src/browser/main.js
echo '//# sourceMappingURL=v86_all.js.map' >> build/v86_all.js
ls -lh build/v86_all.js
build/v86_all_debug.js: $(CLOSURE) src/*.js src/browser/*.js lib/*.js
mkdir -p build
java -jar $(CLOSURE) \
--js_output_file build/v86_all_debug.js\
--define=DEBUG=true\
$(CLOSURE_SOURCE_MAP)\
$(CLOSURE_FLAGS)\
--compilation_level ADVANCED\
--js $(CORE_FILES)\
--js $(LIB_FILES)\
--js $(BROWSER_FILES)\
--js src/browser/main.js
build/libv86.js: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
mkdir -p build
@ -118,62 +130,209 @@ build/libv86.js: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
java -jar $(CLOSURE) \
--js_output_file build/libv86.js\
--define=DEBUG=false\
--define=ENABLE_ACPI=$(ACPI)\
$(CLOSURE_SOURCE_MAP)\
$(CLOSURE_FLAGS)\
--compilation_level SIMPLE\
$(TRANSPILE_ES6_FLAGS)\
--output_wrapper ';(function(){%output%}).call(this);'\
--js $(CORE_FILES)\
--js $(BROWSER_FILES)\
--js $(LIB_FILES)
ls -lh build/libv86.js
build/libv86-debug.js: $(CLOSURE) src/*.js lib/*.js src/browser/*.js
mkdir -p build
java -jar $(CLOSURE) \
--js_output_file build/libv86-debug.js\
--define=DEBUG=true\
$(CLOSURE_FLAGS)\
$(CLOSURE_READABLE)\
--compilation_level SIMPLE\
--output_wrapper ';(function(){%output%}).call(this);'\
--js $(CORE_FILES)\
--js $(BROWSER_FILES)\
--js $(LIB_FILES)
echo '//# sourceMappingURL=libv86.js.map' >> build/libv86.js
src/rust/gen/jit.rs: $(JIT_DEPENDENCIES)
./gen/generate_jit.js --output-dir build/ --table jit
src/rust/gen/jit0f.rs: $(JIT_DEPENDENCIES)
./gen/generate_jit.js --output-dir build/ --table jit0f
ls -lh build/libv86.js
src/rust/gen/interpreter.rs: $(INTERPRETER_DEPENDENCIES)
./gen/generate_interpreter.js --output-dir build/ --table interpreter
src/rust/gen/interpreter0f.rs: $(INTERPRETER_DEPENDENCIES)
./gen/generate_interpreter.js --output-dir build/ --table interpreter0f
src/rust/gen/analyzer.rs: $(ANALYZER_DEPENDENCIES)
./gen/generate_analyzer.js --output-dir build/ --table analyzer
src/rust/gen/analyzer0f.rs: $(ANALYZER_DEPENDENCIES)
./gen/generate_analyzer.js --output-dir build/ --table analyzer0f
build/v86.wasm: $(RUST_FILES) build/softfloat.o build/zstddeclib.o Cargo.toml
mkdir -p build/
-ls -lh build/v86.wasm
cargo +nightly rustc --release $(CARGO_FLAGS)
mv build/wasm32-unknown-unknown/release/v86.wasm build/v86.wasm
ls -lh build/v86.wasm
build/v86-debug.wasm: $(RUST_FILES) build/softfloat.o build/zstddeclib.o Cargo.toml
mkdir -p build/
-ls -lh build/v86-debug.wasm
cargo +nightly rustc $(CARGO_FLAGS)
mv build/wasm32-unknown-unknown/debug/v86.wasm build/v86-debug.wasm
ls -lh build/v86-debug.wasm
build/v86-fallback.wasm: $(RUST_FILES) build/softfloat.o build/zstddeclib.o Cargo.toml
mkdir -p build/
cargo +nightly rustc --release $(CARGO_FLAGS_SAFE)
mv build/wasm32-unknown-unknown/release/v86.wasm build/v86-fallback.wasm || true
debug-with-profiler: $(RUST_FILES) build/softfloat.o build/zstddeclib.o Cargo.toml
mkdir -p build/
cargo +nightly rustc --features profiler $(CARGO_FLAGS)
mv build/wasm32-unknown-unknown/debug/v86.wasm build/v86-debug.wasm || true
with-profiler: $(RUST_FILES) build/softfloat.o build/zstddeclib.o Cargo.toml
mkdir -p build/
cargo +nightly rustc --release --features profiler $(CARGO_FLAGS)
mv build/wasm32-unknown-unknown/release/v86.wasm build/v86.wasm || true
build/softfloat.o: lib/softfloat/softfloat.c
mkdir -p build
clang -c -Wall \
--target=wasm32 -O3 -flto -nostdlib -fvisibility=hidden -ffunction-sections -fdata-sections \
-DSOFTFLOAT_FAST_INT64 -DINLINE_LEVEL=5 -DSOFTFLOAT_FAST_DIV32TO16 -DSOFTFLOAT_FAST_DIV64TO32 \
-o build/softfloat.o \
lib/softfloat/softfloat.c
build/zstddeclib.o: lib/zstd/zstddeclib.c
mkdir -p build
clang -c -Wall \
--target=wasm32 -O3 -flto -nostdlib -fvisibility=hidden -ffunction-sections -fdata-sections \
-I /usr/include \
-o build/zstddeclib.o \
lib/zstd/zstddeclib.c
clean:
-rm build/libv86.js
-rm build/libv86-debug.js
-rm build/v86_all.js
-rm build/libv86.js.map
-rm build/v86_all.js.map
-rm build/v86.wasm
-rm build/v86-debug.wasm
-rm $(INSTRUCTION_TABLES)
-rm build/*.map
-rm build/*.wast
-rm build/*.o
$(MAKE) -C $(NASM_TEST_DIR) clean
run:
python3 -m http.server 2> /dev/null
#sleep 1
#$(BROWSER) http://localhost:8000/index.html &
update_version:
set -e ;\
COMMIT=`git log --format="%h" -n 1` ;\
DATE=`git log --date="format:%b %e, %Y %H:%m" --format="%cd" -n 1` ;\
SEARCH='<code>Version: <a href="https://github.com/copy/v86/commits/[a-f0-9]\+">[a-f0-9]\+</a> ([^(]\+)</a></code>' ;\
REPLACE='<code>Version: <a href="https://github.com/copy/v86/commits/'$$COMMIT'">'$$COMMIT'</a> ('$$DATE')</a></code>' ;\
SEARCH='<code>Version: <a href="https://github.com/copy/v86/commits/[a-f0-9]\+">[a-f0-9]\+</a> ([^(]\+)</code>' ;\
REPLACE='<code>Version: <a href="https://github.com/copy/v86/commits/'$$COMMIT'">'$$COMMIT'</a> ('$$DATE')</code>' ;\
sed -i "s@$$SEARCH@$$REPLACE@g" index.html ;\
grep $$COMMIT index.html
$(CLOSURE):
wget -P $(CLOSURE_DIR) https://dl.google.com/closure-compiler/compiler-20190709.zip
unzip -d closure-compiler $(CLOSURE_DIR)/compiler-20190709.zip \*.jar
mv $(CLOSURE_DIR)/*.jar $(CLOSURE)
rm $(CLOSURE_DIR)/compiler-20190709.zip
mkdir -p $(CLOSURE_DIR)
wget -nv -O $(CLOSURE) https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v20201207/closure-compiler-v20201207.jar
tests: build/libv86.js
build/integration-test-fs/fs.json:
mkdir -p build/integration-test-fs/flat
cp images/buildroot-bzimage.bin build/integration-test-fs/bzImage
touch build/integration-test-fs/initrd
cd build/integration-test-fs && tar cfv fs.tar bzImage initrd
./tools/fs2json.py build/integration-test-fs/fs.tar --out build/integration-test-fs/fs.json
./tools/copy-to-sha256.py build/integration-test-fs/fs.tar build/integration-test-fs/flat
rm build/integration-test-fs/fs.tar build/integration-test-fs/bzImage build/integration-test-fs/initrd
tests: all-debug build/integration-test-fs/fs.json
./tests/full/run.js
nasmtests: build/libv86.js
tests-release: all build/integration-test-fs/fs.json
TEST_RELEASE_BUILD=1 ./tests/full/run.js
nasmtests: all-debug
$(MAKE) -C $(NASM_TEST_DIR) all
$(NASM_TEST_DIR)/gen_fixtures.js
$(NASM_TEST_DIR)/run.js
qemutests: build/libv86.js
make -C tests/qemu test-i386
./tests/qemu/run.js > result
./tests/qemu/test-i386 > reference
diff result reference
nasmtests-force-jit: all-debug
$(MAKE) -C $(NASM_TEST_DIR) all
$(NASM_TEST_DIR)/gen_fixtures.js
$(NASM_TEST_DIR)/run.js --force-jit
kvm-unit-test: build/libv86.js
(cd tests/kvm-unit-tests && ./configure)
make -C tests/kvm-unit-tests
jitpagingtests: all-debug
$(MAKE) -C tests/jit-paging test-jit
./tests/jit-paging/run.js
qemutests: all-debug
$(MAKE) -C tests/qemu test-i386
./tests/qemu/run.js > build/qemu-test-result
./tests/qemu/run-qemu.js > build/qemu-test-reference
diff build/qemu-test-result build/qemu-test-reference
qemutests-release: all
$(MAKE) -C tests/qemu test-i386
time TEST_RELEASE_BUILD=1 ./tests/qemu/run.js > build/qemu-test-result
./tests/qemu/run-qemu.js > build/qemu-test-reference
diff build/qemu-test-result build/qemu-test-reference
kvm-unit-test: all-debug
(cd tests/kvm-unit-tests && ./configure && make)
tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
kvm-unit-test-release: all
(cd tests/kvm-unit-tests && ./configure && make)
TEST_RELEASE_BUILD=1 tests/kvm-unit-tests/run.js tests/kvm-unit-tests/x86/realmode.flat
expect-tests: all-debug build/libwabt.js
make -C tests/expect/tests
./tests/expect/run.js
devices-test: all-debug
./tests/devices/virtio_9p.js
rust-test: $(RUST_FILES)
# RUSTFLAGS="-D warnings"
env RUST_BACKTRACE=full RUST_TEST_THREADS=1 cargo +nightly test -- --nocapture
./tests/rust/verify-wasmgen-dummy-output.js
rust-no-warnings:
RUSTFLAGS="-D warnings" make all all-debug
rust-test-intensive:
QUICKCHECK_TESTS=100000000 make rust-test
api-tests: all-debug
./tests/api/clean-shutdown.js
./tests/api/state.js
./tests/api/reset.js
all-tests: jshint kvm-unit-test qemutests qemutests-release jitpagingtests api-tests nasmtests nasmtests-force-jit tests expect-tests
# Skipping:
# - devices-test (hangs)
jshint:
jshint --config=./.jshint.json src tests gen lib --exclude lib/closure-base.js
rustfmt: $(RUST_FILES)
cargo +nightly fmt --all -- --check
build/capstone-x86.min.js:
mkdir -p build
wget -P build https://github.com/AlexAltea/capstone.js/releases/download/v3.0.5-rc1/capstone-x86.min.js
build/libwabt.js:
mkdir -p build
wget -P build https://github.com/WebAssembly/wabt/archive/1.0.6.zip
unzip -j -d build/ build/1.0.6.zip wabt-1.0.6/demo/libwabt.js
rm build/1.0.6.zip
build/xterm.js:
curl https://cdn.jsdelivr.net/npm/xterm@4.2.0-vscode1/lib/xterm.js > build/xterm.js
curl https://cdn.jsdelivr.net/npm/xterm@4.2.0-vscode1/lib/xterm.js.map > build/xterm.js.map
curl https://cdn.jsdelivr.net/npm/xterm@4.2.0-vscode1/css/xterm.css > build/xterm.css

255
Readme.md
View file

@ -1,16 +1,126 @@
[![Join the chat at https://gitter.im/copy/v86](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/copy/v86)
v86 emulates an x86-compatible CPU and hardware. Machine code is translated to
WebAssembly modules at runtime in order to achieve decent performance. Here's a
list of emulated hardware:
- An x86-compatible CPU. The instruction set is around Pentium III level,
including full SSE2 support. Some features are missing, in particular:
- Task gates, far calls in protected mode
- Some 16 bit protected mode features
- Single stepping (trap flag, debug registers)
- Some exceptions, especially floating point and SSE
- Multicore
- PAE
- 64-bit extensions
- A floating point unit (FPU). Calculations are done using the Berkeley
SoftFloat library and therefore should be precise (but slow). Trigonometric
and log functions are emulated using 64-bit floats and may be less precise.
Not all FPU excpetions are supported.
- A floppy disk controller (8272A).
- An 8042 Keyboard Controller, PS2. With mouse support.
- An 8254 Programmable Interval Timer (PIT).
- An 8259 Programmable Interrupt Controller (PIC).
- Partial APIC support.
- A CMOS Real Time Clock (RTC).
- A generic VGA card with SVGA support and Bochs VBE Extensions.
- A PCI bus. This one is partly incomplete and not used by every device.
- An IDE disk controller.
- An NE2000 (8390) PCI network card.
- A virtio filesystem.
- A SoundBlaster 16 sound card.
Demos
-
- [Windows 98](https://copy.sh/v86/?profile=windows98)
- [Linux](https://copy.sh/v86/?profile=linux26)
- [Linux 3](https://copy.sh/v86/?profile=linux3)
- [KolibriOS](https://copy.sh/v86/?profile=kolibrios)
- [FreeDOS](https://copy.sh/v86/?profile=freedos)
- [Windows 1.01](https://copy.sh/v86/?profile=windows1)
- [Archlinux](https://copy.sh/v86/?profile=archlinux)
[Arch Linux](https://copy.sh/v86/?profile=archlinux) —
[Damn Small Linux](https://copy.sh/v86/?profile=dsl) —
[Buildroot Linux](https://copy.sh/v86/?profile=buildroot) —
[ReactOS](https://copy.sh/v86/?profile=reactos) —
[Windows 98](https://copy.sh/v86/?profile=windows98) —
[Windows 95](https://copy.sh/v86/?profile=windows95) —
[Windows 1.01](https://copy.sh/v86/?profile=windows1) —
[MS-DOS](https://copy.sh/v86/?profile=msdos) —
[FreeDOS](https://copy.sh/v86/?profile=freedos) —
[FreeBSD](https://copy.sh/v86/?profile=freebsd) —
[OpenBSD](https://copy.sh/v86/?profile=openbsd) —
[9front](https://copy.sh/v86/?profile=9front) —
[Haiku](https://copy.sh/v86/?profile=haiku) —
[Oberon](https://copy.sh/v86/?profile=oberon) —
[KolibriOS](https://copy.sh/v86/?profile=kolibrios) —
[QNX](https://copy.sh/v86/?profile=qnx)
Compatibility
-
Here's an overview of the operating systems supported in v86:
- Linux works pretty well. Neither 64-bit nor PAE kernels are supported.
- Damn Small Linux (2.4 Kernel) works.
- All tested versions of TinyCore work.
- [BuildRoot](https://buildroot.uclibc.org) can be used to build a minimal
image. [humphd/browser-vm](https://github.com/humphd/browser-vm) has some
useful scripts for building one.
- Archlinux works. See [archlinux.md](docs/archlinux.md) for building an image.
- Debian works. An image can be built from a Dockerfile, see [tools/docker/debian/](tools/docker/debian/).
- Alpine Linux works.
- ReactOS works.
- FreeDOS, Windows 1.01 and MS-DOS run very well.
- KolibriOS works.
- Haiku works.
- Android x86 1.6-r2 works if one selects VESA mode at the boot prompt. Newer
versions haven't been tested.
- Windows 1, 3.0, 95, 98 and ME work. Other versions currently don't (see #86, #208).
- Many hobby operating systems work.
- 9front works.
- Plan 9 doesn't work.
- QNX works.
- OS/2 doesn't work.
- FreeBSD works.
- OpenBSD works with a specific boot configuration. At the `boot>` prompt type
`boot -c`, then at the `UKC>` prompt `disable mpbios` and `exit`.
- NetBSD works only with a custom kernel, see #350.
- SerenityOS doesn't work due to missing PAE support.
You can get some infos on the disk images here: https://github.com/copy/images.
How to build, run and embed?
-
You need:
- java (for Closure Compiler, not necessary when using `debug.html`)
- make
- gcc and libc-i386 for building some of the test binaries
- nasm, gdb and qemu-system (for running tests)
- rust-nightly with the wasm32-unknown-unknown target
- A version of clang compatible with rust-nightly
- nodejs (a recent version is required, 10.11.0 is known to be working)
See `tools/docker/test-image/Dockerfile` for a full setup on Debian.
- Run `make` to build the debug build (at `debug.html`).
- Run `make all` to build the optimized build (at `index.html`).
- ROM and disk images are loaded via XHR, so if you want to try out `index.html`
locally, make sure to serve it from a local webserver. You can use `make run`
to serve the files using Python's http module.
- If you only want to embed v86 in a webpage you can use libv86.js. For
usage, check out the [examples](examples/).
Testing
-
The disk images for testing are not included in this repository. You can
download them directly from the website using:
`wget -P images/ https://k.copy.sh/{linux.iso,linux4.iso,buildroot-bzimage.bin,openbsd-floppy.img,kolibri.img,windows101.img,os8.img,freedos722.img}`
Run all tests: `make jshint rustfmt kvm-unit-test nasmtests nasmtests-force-jit expect-tests jitpagingtests qemutests rust-test tests`
See [tests/Readme.md](tests/Readme.md) for more infos.
API examples
@ -40,131 +150,29 @@ var emulator = new V86Starter({
});
```
See [API](docs/api.md).
See [starter.js](src/browser/starter.js).
How does it work?
-
v86 emulates an x86-compatible CPU and hardware. Here's a list of emulated hardware:
- An x86 compatible CPU. The instruction set is around Pentium 1 level. Some
features are missing, more specifically:
- Task gates, far calls in protected mode
- 16 bit protected mode features
- Single stepping
- MMX, SSE
- A bunch of FPU instructions
- Some exceptions
- A floating point unit (FPU). Calculations are done with JavaScript's double
precision numbers (64 bit), so they are not as precise as calculations on a
real FPU (80 bit).
- A floppy disk controller (8272A).
- An 8042 Keyboard Controller, PS2. With mouse support.
- An 8254 Programmable Interval Timer (PIT).
- An 8259 Programmable Interrupt Controller (PIC).
- A CMOS Real Time Clock (RTC).
- A generic VGA card with SVGA support and Bochs VBE Extensions.
- A PCI bus. This one is partly incomplete and not used by every device.
- An IDE disk controller.
- An NE2000 (8390) PCI network card.
- A virtio filesystem.
- A SoundBlaster 16 sound card.
Testing
-
The disk images are not included in this repository. You can download them
directly from the website using:
`wget -P images/ https://copy.sh/v86/images/{linux.iso,linux3.iso,kolibri.img,windows101.img,os8.dsk,freedos722.img,openbsd.img}`.
A testsuite is available in `tests/full/`. Run it using `node tests/full/run.js`.
How to build, run and embed?
-
- Building is only necessary for releases, open debug.html and everything should load out of the box
- If you want a compressed and fast (i.e. with debug code removed) version, you
need Closure Compiler. Download it as shown below and run `make build/v86_all.js`.
- ROM and disk images are loaded via XHR, so if you want to try out `index.html`
locally, make sure to serve it from a local webserver. You can use `make run`
to serve the files using Python's SimpleHTTPServer.
- If you only want to embed v86 in a webpage you can use libv86.js. For
usage, check out the [API](docs/api.md) and [examples](examples/).
- A couple of disk images are provided for testing. You can check them out
using `wget -P images/ https://copy.sh/v86/images/{linux.iso,linux3.iso,kolibri.img,windows101.img,os8.dsk,freedos722.img,openbsd.img}`.
**Short summary:**
```bash
# grab the main repo
git clone https://github.com/copy/v86.git && cd v86
# grab the disk images
wget -P images/ https://copy.sh/v86/images/{linux.iso,linux3.iso,kolibri.img,windows101.img,os8.dsk,freedos722.img,openbsd.img}
# grab closure compiler
wget -P closure-compiler https://dl.google.com/closure-compiler/compiler-latest.zip
unzip -d closure-compiler closure-compiler/compiler-latest.zip *.jar
# build the library
make build/libv86.js
# run the tests
./tests/full/run.js
```
Compatibility
-
Here's an overview of the operating systems supported in v86:
- Linux works pretty well.
- Tinycore (3.16, 4.8 kernel): Works.
- Nanolinux works.
- Archlinux works with some caveats. See [archlinux.md](docs/archlinux.md).
- Damn Small Linux (2.4 Kernel): Doesn't work.
- ReactOS works.
- FreeDOS, Windows 1.01 and MS-DOS run very well.
- KolibriOS works. A few applications need SSE.
- Haiku boots, but takes very long (around 30 minutes).
- No Android version seems to work, you still get a shell.
- Windows 1, 3, 95 and 98 work. Windows XP is unstable, but can work with some
tweaks ([see this issue](https://github.com/copy/v86/issues/86)). Other
versions might work but haven't been tested.
- Many hobby operating systems work.
- FreeBSD works.
- OS/2 doesn't work.
You can get some infos on the disk images here: https://github.com/copy/images.
The Windows images are from [WinWorld](https://winworldpc.com/).
How can I contribute?
-
- Add new features (hardware devices, fill holes in the CPU), fix bugs. Check
out the issues section and contact me if you need help.
- Report bugs.
- If you want to donate, let me know.
License
-
- Source code and most tests: Simplified BSD License, see [LICENSE](LICENSE).
- QEMU test suite: See [tests/qemu/LICENSE](LICENSE).
v86 is distributed under the terms of the Simplified BSD License, see
[LICENSE](LICENSE). The following third-party dependencies are included in the
repository under their own licenses:
- [`lib/softfloat/softfloat.c`](lib/softfloat/softfloat.c)
- [`lib/zstd/zstddeclib.c`](lib/zstd/zstddeclib.c)
- [`tests/kvm-unit-tests/`](tests/kvm-unit-tests)
- [`tests/qemutests/`](tests/qemutests)
Credits
-
- CPU test cases via QEMU, http://wiki.qemu.org/Main_Page
- CPU test cases via [QEMU](https://wiki.qemu.org/Main_Page)
- More tests via [kvm-unit-tests](https://www.linux-kvm.org/page/KVM-unit-tests)
- [Disk Images](https://github.com/copy/images)
- [zstd](https://github.com/facebook/zstd) support is included for better compression of state images
- [Berkeley SoftFloat](http://www.jhauser.us/arithmetic/SoftFloat.html) is included to precisely emulate 80-bit floating point numbers
- [The jor1k project](https://github.com/s-macke/jor1k) for 9p, filesystem and uart drivers
- [WinWorld](https://winworldpc.com/) sources of some old operating systems
@ -172,11 +180,10 @@ Credits
More questions?
-
Shoot me an email to `copy@copy.sh`. Please don't tell about bugs via mail,
create a bug report on GitHub instead.
Shoot me an email to `copy@copy.sh`. Please report bugs on GitHub.
Author
-
Fabian Hemmer (http://copy.sh/, `copy@copy.sh`)
Fabian Hemmer (https://copy.sh/, `copy@copy.sh`)

13
bios/fetch-and-build-seabios.sh Executable file
View file

@ -0,0 +1,13 @@
set -e
git clone https://git.seabios.org/seabios.git || true
(cd seabios && git checkout rel-1.12.1)
cp seabios.config seabios/.config
make -C seabios
cp seabios/out/bios.bin seabios.bin
cp seabios/out/vgabios.bin vgabios.bin
cp seabios-debug.config seabios/.config
make -C seabios
cp seabios/out/bios.bin seabios-debug.bin
cp seabios/out/vgabios.bin vgabios-debug.bin

Binary file not shown.

117
bios/seabios-debug.config Normal file
View file

@ -0,0 +1,117 @@
#
# Automatically generated file; DO NOT EDIT.
# SeaBIOS Configuration
#
#
# General Features
#
# CONFIG_COREBOOT is not set
CONFIG_QEMU=y
# CONFIG_CSM is not set
CONFIG_QEMU_HARDWARE=y
CONFIG_XEN=y
CONFIG_THREADS=y
# CONFIG_RELOCATE_INIT is not set
# CONFIG_BOOTMENU is not set
CONFIG_BOOTORDER=y
CONFIG_MULTIBOOT=y
CONFIG_ENTRY_EXTRASTACK=y
CONFIG_MALLOC_UPPERMEMORY=y
CONFIG_ROM_SIZE=0
#
# Hardware support
#
CONFIG_ATA=y
CONFIG_ATA_DMA=y
CONFIG_ATA_PIO32=y
CONFIG_AHCI=y
CONFIG_SDCARD=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_SCSI=y
CONFIG_PVSCSI=y
CONFIG_ESP_SCSI=y
CONFIG_LSI_SCSI=y
CONFIG_MEGASAS=y
CONFIG_MPT_SCSI=y
CONFIG_FLOPPY=y
CONFIG_FLASH_FLOPPY=y
# CONFIG_NVME is not set
CONFIG_PS2PORT=y
# CONFIG_USB is not set
CONFIG_SERIAL=y
# CONFIG_SERCON is not set
CONFIG_LPT=y
CONFIG_RTC_TIMER=y
CONFIG_HARDWARE_IRQ=y
CONFIG_USE_SMM=y
CONFIG_CALL32_SMM=y
CONFIG_MTRR_INIT=y
CONFIG_PMTIMER=y
CONFIG_TSC_TIMER=y
#
# BIOS interfaces
#
CONFIG_DRIVES=y
CONFIG_CDROM_BOOT=y
CONFIG_CDROM_EMU=y
CONFIG_PCIBIOS=y
CONFIG_APMBIOS=y
CONFIG_PNPBIOS=y
CONFIG_OPTIONROMS=y
CONFIG_PMM=y
CONFIG_BOOT=y
CONFIG_KEYBOARD=y
CONFIG_KBD_CALL_INT15_4F=y
CONFIG_MOUSE=y
CONFIG_S3_RESUME=y
CONFIG_VGAHOOKS=y
# CONFIG_DISABLE_A20 is not set
# CONFIG_WRITABLE_UPPERMEMORY is not set
CONFIG_TCGBIOS=y
#
# BIOS Tables
#
CONFIG_PIRTABLE=y
CONFIG_MPTABLE=y
# CONFIG_SMBIOS is not set
CONFIG_ACPI=y
CONFIG_ACPI_DSDT=y
CONFIG_FW_ROMFILE_LOAD=y
#
# VGA ROM
#
# CONFIG_NO_VGABIOS is not set
# CONFIG_VGA_STANDARD_VGA is not set
# CONFIG_VGA_CIRRUS is not set
CONFIG_VGA_BOCHS=y
# CONFIG_VGA_GEODEGX2 is not set
# CONFIG_VGA_GEODELX is not set
# CONFIG_DISPLAY_BOCHS is not set
# CONFIG_VGA_RAMFB is not set
CONFIG_VGA_BOCHS_STDVGA=y
# CONFIG_VGA_BOCHS_VMWARE is not set
# CONFIG_VGA_BOCHS_QXL is not set
# CONFIG_VGA_BOCHS_VIRTIO is not set
CONFIG_BUILD_VGABIOS=y
CONFIG_VGA_STDVGA_PORTS=y
CONFIG_VGA_FIXUP_ASM=y
CONFIG_VGA_ALLOCATE_EXTRA_STACK=y
CONFIG_VGA_EXTRA_STACK_SIZE=512
CONFIG_VGA_VBE=y
CONFIG_VGA_PCI=y
CONFIG_OVERRIDE_PCI_ID=y
CONFIG_VGA_VID=0x1234
CONFIG_VGA_DID=0x1111
#
# Debugging
#
CONFIG_DEBUG_LEVEL=8
# CONFIG_DEBUG_SERIAL is not set
# CONFIG_DEBUG_SERIAL_MMIO is not set
CONFIG_DEBUG_IO=y

Binary file not shown.

114
bios/seabios.config Normal file
View file

@ -0,0 +1,114 @@
#
# Automatically generated file; DO NOT EDIT.
# SeaBIOS Configuration
#
#
# General Features
#
# CONFIG_COREBOOT is not set
CONFIG_QEMU=y
# CONFIG_CSM is not set
CONFIG_QEMU_HARDWARE=y
CONFIG_XEN=y
CONFIG_THREADS=y
# CONFIG_RELOCATE_INIT is not set
# CONFIG_BOOTMENU is not set
CONFIG_BOOTORDER=y
CONFIG_MULTIBOOT=y
CONFIG_ENTRY_EXTRASTACK=y
CONFIG_MALLOC_UPPERMEMORY=y
CONFIG_ROM_SIZE=0
#
# Hardware support
#
CONFIG_ATA=y
CONFIG_ATA_DMA=y
CONFIG_ATA_PIO32=y
CONFIG_AHCI=y
CONFIG_SDCARD=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_SCSI=y
CONFIG_PVSCSI=y
CONFIG_ESP_SCSI=y
CONFIG_LSI_SCSI=y
CONFIG_MEGASAS=y
CONFIG_MPT_SCSI=y
CONFIG_FLOPPY=y
CONFIG_FLASH_FLOPPY=y
# CONFIG_NVME is not set
CONFIG_PS2PORT=y
# CONFIG_USB is not set
CONFIG_SERIAL=y
# CONFIG_SERCON is not set
CONFIG_LPT=y
CONFIG_RTC_TIMER=y
CONFIG_HARDWARE_IRQ=y
CONFIG_USE_SMM=y
CONFIG_CALL32_SMM=y
CONFIG_MTRR_INIT=y
CONFIG_PMTIMER=y
CONFIG_TSC_TIMER=y
#
# BIOS interfaces
#
CONFIG_DRIVES=y
CONFIG_CDROM_BOOT=y
CONFIG_CDROM_EMU=y
CONFIG_PCIBIOS=y
CONFIG_APMBIOS=y
CONFIG_PNPBIOS=y
CONFIG_OPTIONROMS=y
CONFIG_PMM=y
CONFIG_BOOT=y
CONFIG_KEYBOARD=y
CONFIG_KBD_CALL_INT15_4F=y
CONFIG_MOUSE=y
CONFIG_S3_RESUME=y
CONFIG_VGAHOOKS=y
# CONFIG_DISABLE_A20 is not set
# CONFIG_WRITABLE_UPPERMEMORY is not set
CONFIG_TCGBIOS=y
#
# BIOS Tables
#
CONFIG_PIRTABLE=y
CONFIG_MPTABLE=y
# CONFIG_SMBIOS is not set
CONFIG_ACPI=y
CONFIG_ACPI_DSDT=y
CONFIG_FW_ROMFILE_LOAD=y
#
# VGA ROM
#
# CONFIG_NO_VGABIOS is not set
# CONFIG_VGA_STANDARD_VGA is not set
# CONFIG_VGA_CIRRUS is not set
CONFIG_VGA_BOCHS=y
# CONFIG_VGA_GEODEGX2 is not set
# CONFIG_VGA_GEODELX is not set
# CONFIG_DISPLAY_BOCHS is not set
# CONFIG_VGA_RAMFB is not set
CONFIG_VGA_BOCHS_STDVGA=y
# CONFIG_VGA_BOCHS_VMWARE is not set
# CONFIG_VGA_BOCHS_QXL is not set
# CONFIG_VGA_BOCHS_VIRTIO is not set
CONFIG_BUILD_VGABIOS=y
CONFIG_VGA_STDVGA_PORTS=y
CONFIG_VGA_FIXUP_ASM=y
CONFIG_VGA_ALLOCATE_EXTRA_STACK=y
CONFIG_VGA_EXTRA_STACK_SIZE=512
CONFIG_VGA_VBE=y
CONFIG_VGA_PCI=y
CONFIG_OVERRIDE_PCI_ID=y
CONFIG_VGA_VID=0x1234
CONFIG_VGA_DID=0x1111
#
# Debugging
#
CONFIG_DEBUG_LEVEL=0

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,7 @@
<!doctype html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Virtual x86</title>
<title>Virtual x86 (debug)</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<script src="loader.js"></script>
@ -11,22 +11,63 @@
<div id="boot_options">
<h4>Debugger</h4>
<input type="button" value="ReactOS" id="start_reactos">
<input type="button" value="Windows ME" id="start_windowsme">
<input type="button" value="ReactOS (boot)" id="start_reactos-boot">
<input type="button" value="Hiren's Boot CD" id="start_hirens">
<br>
<input type="button" value="MS-DOS" id="start_msdos">
<input type="button" value="FreeDOS" id="start_freedos">
<input type="button" value="FreeDOS with FreeGEM" id="start_freegem">
<input type="button" value="FreeDOS CD with games" id="start_fdgame">
<input type="button" value="FreeDOS with QBasic" id="start_qbasic">
<br>
<input type="button" value="Windows 1.01" id="start_windows1">
<input type="button" value="Windows 3.0" id="start_windows30">
<input type="button" value="Windows 3.1" id="start_windows31">
<input type="button" value="Windows 95" id="start_windows95">
<input type="button" value="FreeBSD" id="start_freebsd">
<input type="button" value="Windows 95 (boot)" id="start_windows95-boot">
<br>
<input type="button" value="Windows 98" id="start_windows98">
<input type="button" value="Windows 98 (boot)" id="start_windows98-boot">
<input type="button" value="Windows 2000" id="start_windows2000">
<input type="button" value="Windows Me" id="start_windowsme">
<input type="button" value="Windows Me (2)" id="start_windowsme2">
<br>
<input type="button" value="Linux 2.6 (Buildroot)" id="start_linux26">
<input type="button" value="Linux 3.18 (Buildroot)" id="start_linux3">
<input type="button" value="Linux 4.16 (Buildroot)" id="start_linux4">
<input type="button" value="Linux 5.6 (Buildroot)" id="start_buildroot">
<input type="button" value="Tiny Core" id="start_tinycore">
<input type="button" value="OpenWRT" id="start_openwrt">
<br>
<input type="button" value="Arch Linux" id="start_archlinux">
<input type="button" value="Arch Linux (boot)" id="start_archlinux-9p-cool">
<input type="button" value="Damn Small Linux" id="start_dsl">
<br>
<input type="button" value="FreeBSD" id="start_freebsd">
<input type="button" value="FreeBSD (boot)" id="start_freebsd-boot">
<input type="button" value="OpenBSD" id="start_openbsd">
<input type="button" value="NetBSD" id="start_netbsd">
<br>
<input type="button" value="Haiku" id="start_haiku">
<input type="button" value="Haiku (boot)" id="start_haiku-boot">
<input type="button" value="Minix" id="start_minix">
<input type="button" value="SerenityOS" id="start_serenity">
<input type="button" value="QNX" id="start_qnx">
<input type="button" value="9front" id="start_9front">
<input type="button" value="Plan 9" id="start_plan9">
<br>
<input type="button" value="Oberon" id="start_oberon">
<input type="button" value="KolibriOS" id="start_kolibrios">
<input type="button" value="Linux 2.6" id="start_linux26">
<input type="button" value="Linux 3.18" id="start_linux3">
<input type="button" value="Windows 1.01" id="start_windows1">
<input type="button" value="FreeDOS" id="start_freedos">
<input type="button" value="MS-DOS" id="start_msdos">
<input type="button" value="OpenBSD" id="start_openbsd">
<input type="button" value="Solar OS" id="start_solos">
<input type="button" value="Bootchess" id="start_bootchess">
<input type="button" value="Test" id="start_test">
<input type="button" value="HelenOS" id="start_helenos">
<input type="button" value="MikeOS" id="start_mikeos">
<br>
<!--
@ -39,25 +80,19 @@
<table>
<tr>
<td width="250">CD image</td>
<td width="350"><label for="cd_image">CD image</label></td>
<td>
<!--
<select>
<option>None</option>
<option>Local file</option>
<option>External Server</option>
</select>-->
<input type="file" id="cd_image">
</td>
</tr>
<tr>
<td>Floppy disk image</td>
<td><label for="floppy_image">Floppy disk image</label></td>
<td> <input type="file" id="floppy_image"><br></td>
</tr>
<tr>
<td>Master Hard drive disk image</td>
<td><label for="hda_image">Hard drive disk image</label></td>
<td><input type="file" id="hda_image"><br></td>
</tr>
@ -67,41 +102,59 @@
</tr>
<tr>
<td>Multiboot kernel image</td>
<td><label for="multiboot_image">Multiboot kernel image (experimental)</label></td>
<td><input type="file" id="multiboot_image"><br></td>
</tr>
<tr>
<td colspan="2"><hr></td>
<td colspan="2"><small>Disk images are not uploaded to the server</small><hr></td>
</tr>
<tr>
<td>Memory size</td>
<td><label for="memory_size">Memory size</label></td>
<td>
<input id="memory_size" type="number" value="128" min="16" max="2048" step="16"> MB<br>
</td>
</tr>
<tr>
<td>Video Memory size</td>
<td><label for="video_memory_size">Video Memory size</label></td>
<td>
<input id="video_memory_size" type="number" value="8" min="1" max="128" step="1"> MB<br>
</td>
</tr>
<tr>
<td><label for="networking_proxy">Networking proxy (leave blank to disable)</label></td>
<td>
<input id="networking_proxy" type="text" value="wss://relay.widgetry.org/">
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<!--
<tr>
<td>Execution Cap </td>
<td> <input type="number" value="100" min="5" max="100" step="5"> %<br> </td>
<td><label for="disable_audio">Disable audio</label></td>
<td>
<input id="disable_audio" type="checkbox"><br>
</td>
</tr>
-->
<tr>
<td>Boot order</td>
<td><label for="enable_acpi">Enable ACPI (experimental)</label></td>
<td>
<input id="enable_acpi" type="checkbox"><br>
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<tr>
<td><label for="boot_order">Boot order</label></td>
<td>
<select id="boot_order">
<option value="213">CD / Floppy / Hard Disk</option>
@ -117,25 +170,11 @@
<br>
<button id="start_emulation">Start Emulation</button>
<!--
<br>
<div id="setup_error">Error: Video size must be at least over 9000</div>
-->
<!--
<br>
<br>
Link to this configuration: <a href="" id="config_link">http://copy.sh/v86/?a=b&amp;c=d</a>
-->
<br>
<br>
</div>
<div id="runtime_options" style="display: none">
<input type="button" value="Step" id="step">
<input type="button" value="Run until" id="run_until">
<input type="button" value="Debugger" id="debugger">
<input type="button" value="Dump Instructions" id="dump_instructions">
<input type="button" value="Dump Instructions to file" id="dump_instructions_file">
<input type="button" value="Dump Registers" id="dump_regs">
<input type="button" value="Dump GDT/LDT" id="dump_gdt">
<input type="button" value="Dump IDT" id="dump_idt">
@ -155,7 +194,7 @@
<input type="button" value="Get cdrom image" id="get_cdrom_image">
<input type="button" value="Save State" id="save_state">
<input type="button" value="Load State" id="load_state"> <input type="file" style="display: none" id="load_state_input">
<input type="button" value="Memory Dump (raw)" id="memory_dump">
<input type="button" value="Memory Dump" id="memory_dump">
<input type="button" value="Disable mouse" id="toggle_mouse">
<input type="button" value="Lock mouse" id="lock_mouse">
<input type="button" value="Go fullscreen" id="fullscreen">
@ -180,10 +219,7 @@
</div>
<pre style="margin: 0" id="log_levels"></pre>
<pre style="margin: 3px 0px 0px 0px" id="debug_infos"></pre>
<pre style="display: none" id="loading"></pre>
<br>
</div>
@ -196,10 +232,11 @@
</div>
</div>
<div id="runtime_infos" style="display: none">
Running: <span id="running_time">0s</span> <br>
Speed: <span id="speed">0</span>kIPS<br>
Avg speed: <span id="avg_speed">0</span>kIPS<br>
Speed: <span id="speed">0</span> mIPS<br>
Avg speed: <span id="avg_speed">0</span> mIPS<br>
<br>
<div id="info_storage" style="display: none">
<b>IDE device (HDA or CDROM)</b><br>
@ -214,7 +251,7 @@
<b>9p Filesystem</b><br>
Bytes read: <span id="info_filesystem_bytes_read">0</span><br>
Bytes written: <span id="info_filesystem_bytes_written">0</span><br>
Last file: <span id="info_filesystem_last_file" style="word-wrap: break-word"></span><br>
<div style="white-space: nowrap; overflow-x: hidden">Last file: <span id="info_filesystem_last_file"></span></div>
Status: <span id="info_filesystem_status"></span><br>
<br>
</div>
@ -230,7 +267,6 @@
BPP: <span id="info_bpp">-</span><br>
<br>
Mouse: <span id="info_mouse_enabled">No</span><br>
<!-- Keyboard: <span id="info_keyboard_enabled">-</span><br> -->
</div>
<div id="filesystem_panel" style="display: none">
@ -248,9 +284,11 @@
<div id="debug_panel" style="display: none">
</div>
<br style="clear:both"><br>
<br style="clear: both"><br>
<textarea readonly id="log" style="display:none"></textarea>
<textarea readonly id="log" style="display: none"></textarea>
<textarea spellcheck="false" cols="40" rows="12" id="serial" style="display:none">
<textarea spellcheck="false" cols="40" rows="12" id="serial" style="display: none">
</textarea>
<div id="terminal"></div>

View file

@ -1,279 +0,0 @@
# V86Starter
- [`run()`](#run)
- [`stop()`](#stop)
- [`restart()`](#restart)
- [`add_listener(string event, function(*) listener)`](#add_listenerstring-event-function-listener)
- [`remove_listener(string event, function(*) listener)`](#remove_listenerstring-event-function-listener)
- [`restore_state(ArrayBuffer state)`](#restore_statearraybuffer-state)
- [`save_state(function(Object, ArrayBuffer) callback)`](#save_statefunctionobject-arraybuffer-callback)
- [`get_statistics() -> Object`](#get_statistics---object)
- [`is_running() -> boolean`](#is_running---boolean)
- [`keyboard_send_scancodes(Array.<number> codes)`](#keyboard_send_scancodesarraynumber-codes)
- [`mouse_set_status(boolean enabled)`](#mouse_set_statusboolean-enabled)
- [`keyboard_set_status(boolean enabled)`](#keyboard_set_statusboolean-enabled)
- [`serial0_send(string data)`](#serial0_sendstring-data)
- [`create_file(string file, Uint8Array data, function(Object) callback)`](#create_filestring-file-uint8array-data-functionobject-callback)
- [`read_file(string file, function(Object, Uint8Array) callback)`](#read_filestring-file-functionobject-uint8array-callback)
- [`lock_mouse()`](#lock_mouse)
***
## `V86Starter`
Constructor for emulator instances.
Usage: `var emulator = new V86Starter(options);`
Options can have the following properties (all optional, default in parenthesis):
- `memory_size number` (16 * 1024 * 1024) - The memory size in bytes, should
be a power of 2.
- `vga_memory_size number` (8 * 1024 * 1024) - VGA memory size in bytes.
- `autostart boolean` (false) - If emulation should be started when emulator
is ready.
- `disable_keyboard boolean` (false) - If the keyboard should be disabled.
- `disable_mouse boolean` (false) - If the mouse should be disabled.
- `network_relay_url string` (No network card) - The url of a server running
websockproxy. See [networking.md](networking.md). Setting this will
enable an emulated network card.
- `bios Object` (No bios) - Either a url pointing to a bios or an
ArrayBuffer, see below.
- `vga_bios Object` (No VGA bios) - VGA bios, see below.
- `hda Object` (No hard drive) - First hard disk, see below.
- `fda Object` (No floppy disk) - First floppy disk, see below.
- `cdrom Object` (No CD) - See below.
- `initial_state Object` (Normal boot) - An initial state to load, see
[`restore_state`](#restore_statearraybuffer-state) and below.
- `filesystem Object` (No 9p filesystem) - A 9p filesystem, see
[filesystem.md](filesystem.md).
- `serial_container HTMLTextAreaElement` (No serial terminal) - A textarea
that will receive and send data to the emulated serial terminal.
Alternatively the serial terminal can also be accessed programatically,
see [serial.html](../examples/serial.html).
- `screen_container HTMLElement` (No screen) - An HTMLElement. This should
have a certain structure, see [basic.html](../examples/basic.html).
***
There are two ways to load images (`bios`, `vga_bios`, `cdrom`, `hda`, ...):
- Pass an object that has a url. Optionally, `async: true` and `size:
size_in_bytes` can be added to the object, so that sectors of the image
are loaded on demand instead of being loaded before boot (slower, but
strongly recommended for big files). In that case, the `Range: bytes=...`
header must be supported on the server. Note: the python SimpleHTTPServer
does not support this, so it won't work with the default webserver used
by `make run`.
```javascript
// download file before boot
bios: {
url: "bios/seabios.bin"
}
// download file sectors as requested, size is required
hda: {
url: "disk/linux.iso",
async: true,
size: 16 * 1024 * 1024
}
```
- Pass an `ArrayBuffer` or `File` object as `buffer` property.
```javascript
// use <input type=file>
bios: {
buffer: document.all.hd_image.files[0]
}
// start with empty hard drive
hda: {
buffer: new ArrayBuffer(16 * 1024 * 1024)
}
```
***
**Parameters:**
1. **`Object`** options Options to initialize the emulator with.
***
#### `run()`
Start emulation. Do nothing if emulator is running already. Can be
asynchronous.
***
#### `stop()`
Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
***
#### `restart()`
Restart (force a reboot).
***
#### `lock_mouse()`
Locks the mouse to the canvas the emulator was initated with
***
#### `add_listener(string event, function(*) listener)`
Add an event listener (the emulator is an event emitter). A list of events
can be found at [events.md](events.md).
The callback function gets a single argument which depends on the event.
**Parameters:**
1. **`string`** event Name of the event.
2. **`function(*)`** listener The callback function.
***
#### `remove_listener(string event, function(*) listener)`
Remove an event listener.
**Parameters:**
1. **`string`** event
2. **`function(*)`** listener
***
#### `restore_state(ArrayBuffer state)`
Restore the emulator state from the given state, which must be an
ArrayBuffer returned by
[`save_state`](#save_statefunctionobject-arraybuffer-callback).
Note that the state can only be restored correctly if this constructor has
been created with the same options as the original instance (e.g., same disk
images, memory size, etc.).
Different versions of the emulator might use a different format for the
state buffer.
**Parameters:**
1. **`ArrayBuffer`** state
***
#### `save_state(function(Object, ArrayBuffer) callback)`
Asynchronously save the current state of the emulator. The first argument to
the callback is an Error object if something went wrong and is null
otherwise.
**Parameters:**
1. **`function(Object, ArrayBuffer)`** callback
***
#### `get_statistics() -> Object`
**Deprecated - Might be removed in a later release.**
Return an object with several statistics. Return value looks similar to
(but can be subject to change in future versions or different
configurations, so use defensively):
```javascript
{
"cpu": {
"instruction_counter": 2821610069
},
"hda": {
"sectors_read": 95240,
"sectors_written": 952,
"bytes_read": 48762880,
"bytes_written": 487424,
"loading": false
},
"cdrom": {
"sectors_read": 0,
"sectors_written": 0,
"bytes_read": 0,
"bytes_written": 0,
"loading": false
},
"mouse": {
"enabled": true
},
"vga": {
"is_graphical": true,
"res_x": 800,
"res_y": 600,
"bpp": 32
}
}
```
**Returns:**
* **`Object`**
***
#### `is_running() -> boolean`
**Returns:**
* **`boolean`**
***
#### `keyboard_send_scancodes(Array.<number> codes)`
Send a sequence of scan codes to the emulated PS2 controller. A list of
codes can be found at http://stanislavs.org/helppc/make_codes.html.
Do nothing if there is no keyboard controller.
**Parameters:**
1. **`Array.<number>`** codes
***
#### `mouse_set_status(boolean enabled)`
Enable or disable sending mouse events to the emulated PS2 controller.
**Parameters:**
1. **`boolean`** enabled
***
#### `keyboard_set_status(boolean enabled)`
Enable or disable sending keyboard events to the emulated PS2 controller.
**Parameters:**
1. **`boolean`** enabled
***
#### `serial0_send(string data)`
Send a string to the first emulated serial terminal.
**Parameters:**
1. **`string`** data
***
#### `create_file(string file, Uint8Array data, function(Object) callback)`
Write to a file in the 9p filesystem. Nothing happens if no filesystem has
been initialized. First argument to the callback is an error object if
something went wrong and null otherwise.
**Parameters:**
1. **`string`** file
2. **`Uint8Array`** data
3. **`function(Object)`** (optional) callback
***
#### `read_file(string file, function(Object, Uint8Array) callback)`
Read a file in the 9p filesystem. Nothing happens if no filesystem has been
initialized.
**Parameters:**
1. **`string`** file
2. **`function(Object, Uint8Array)`** callback
<!-- ../src/browser/starter.js-->
<!-- vim: set tabstop=2 shiftwidth=2 softtabstop=2: -->

View file

@ -1,16 +0,0 @@
#!/usr/bin/env node
var markdox = require("markdox");
var options = {
output: __dirname + "/api.md",
template: __dirname + "/template.md.ejs"
};
var files = [
__dirname + "/../src/browser/starter.js",
];
markdox.process(files, options, function() {
console.log("Ok. %s written.", options.output);
});

View file

@ -1,32 +0,0 @@
Here is a list of events that can be listened to using
[`add_listener`](api.md#add_listenerstring-event-function-listener). These
can be used to programmatically control the emulator. Events cannot be sent to
the emulator (although it is internally implemented that way), use the
[API](api.md) methods for that.
### Serial terminal
See also: [serial.js](../src/browser/serial.js).
- `serial0-output-char` - `string chr`
### Network
See also: [network.js](../src/browser/network.js).
- `net0-receive` - `Uint8Array buffer`
### Screen
See also: [screen.js](../src/browser/screen.js).
- `screen-set-mode` - `boolean is_graphic`
- `screen-put-char` - `[number row, number col, number chr, number bg_color, number fg_color]`
- `screen-put-pixel-linear` - `[number addr, number value]`
- `screen-put-pixel-linear32` - `[number addr, number value]`
- `screen-set-size-text` - `[number cols_count, number rows_count]`
- `screen-set-size-graphical` - `[number width, number height, number virtual_width, number virtual_height, number bpp]`
- `screen-update-cursor` - `[number row, number col]`
- `screen-update-cursor-scanline` - `[number cursor_scanline_start, number cursor_scanline_end]`

39
docs/sse-shifts.txt Normal file
View file

@ -0,0 +1,39 @@
0F F1 PSLLW mm mm/m64 mmx Shift Packed Data Left Logical
66 0F F1 PSLLW xmm xmm/m128 sse2 Shift Packed Data Left Logical
0F F2 PSLLD mm mm/m64 mmx Shift Packed Data Left Logical
66 0F F2 PSLLD xmm xmm/m128 sse2 Shift Packed Data Left Logical
0F F3 PSLLQ mm mm/m64 mmx Shift Packed Data Left Logical
66 0F F3 PSLLQ xmm xmm/m128 sse2 Shift Packed Data Left Logical
0F E1 PSRAW mm mm/m64 mmx Shift Packed Data Right Arithmetic
66 0F E1 PSRAW xmm xmm/m128 sse2 Shift Packed Data Right Arithmetic
0F E2 PSRAD mm mm/m64 mmx Shift Packed Data Right Arithmetic
66 0F E2 PSRAD xmm xmm/m128 sse2 Shift Packed Data Right Arithmetic
0F D1 PSRLW mm mm/m64 mmx Shift Packed Data Right Logical
66 0F D1 PSRLW xmm xmm/m128 sse2 Shift Packed Data Right Logical
0F D2 PSRLD mm mm/m64 mmx Shift Packed Data Right Logical
66 0F D2 PSRLD xmm xmm/m128 sse2 Shift Packed Data Right Logical
0F D3 PSRLQ mm mm/m64 mmx Shift Packed Data Right Logical
66 0F D3 PSRLQ xmm xmm/m128 sse2 Shift Packed Data Right Logical
0F 71 PSRLW mm imm8 mmx Shift Packed Data Right Logical
66 0F 71 PSRLW xmm imm8 sse2 Shift Packed Data Right Logical
0F 71 PSRAW mm imm8 mmx Shift Packed Data Right Arithmetic
66 0F 71 PSRAW xmm imm8 sse2 Shift Packed Data Right Arithmetic
0F 71 PSLLW mm imm8 mmx Shift Packed Data Left Logical
66 0F 71 PSLLW xmm imm8 sse2 Shift Packed Data Left Logical
0F 72 PSRLD mm imm8 mmx Shift Double Quadword Right Logical
66 0F 72 PSRLD xmm imm8 sse2 Shift Double Quadword Right Logical
0F 72 PSRAD mm imm8 mmx Shift Packed Data Right Arithmetic
66 0F 72 PSRAD xmm imm8 sse2 Shift Packed Data Right Arithmetic
0F 72 PSLLD mm imm8 mmx Shift Packed Data Left Logical
66 0F 72 PSLLD xmm imm8 sse2 Shift Packed Data Left Logical
0F 73 PSRLQ mm imm8 mmx Shift Packed Data Right Logical
66 0F 73 PSRLQ xmm imm8 sse2 Shift Packed Data Right Logical
66 0F 73 PSRLDQ xmm imm8 sse2 Shift Double Quadword Right Logical
0F 73 PSLLQ mm imm8 mmx Shift Packed Data Left Logical
66 0F 73 PSLLQ xmm imm8 sse2 Shift Packed Data Left Logical
66 0F 73 PSLLDQ xmm imm8 sse2 Shift Double Quadword Left Logical

View file

@ -1,107 +0,0 @@
<?
function anchorify(str)
{
// how github creates the name attribute for anchors from headlines
str = str.replace(/[()<>.]/g, "");
str = str.replace(/[^a-z0-9_-]+/gi, "-");
str = str.toLowerCase();
return str;
}
docfiles.forEach(function(doc)
{
doc.javadoc.forEach(function(comment)
{
var tags = comment.raw.tags;
comment.tagsByType = comment.raw.tags.reduce(function(result, tag)
{
result[tag.type] = tag;
return result;
}, {});
comment.ignore = "ignore" in comment.tagsByType;
if(comment.name && !comment.ignore)
{
if(comment.isMethod || comment.isFunction)
{
var args = comment.paramTags.map(function(c)
{
return c.joinedTypes + " " + c.name;
}).join(", ");
var returnVal = "";
if(comment.returnTags[0])
{
returnVal = " -> " + comment.returnTags[0].joinedTypes;
}
comment.args = args;
comment.returnVal = returnVal;
comment.longName = comment.name + "(" + comment.args + ")" + comment.returnVal;
?><?= "- [`" + comment.longName + "`](#" + anchorify(comment.longName) + ")\n" ?><?
} else {
?># <?= comment.name + "\n" ?><?
}
}
});
doc.javadoc.forEach(function(comment)
{
if(!comment.ignore)
{
?><?= "\n***\n" ?><?
if(comment.name)
{
if(comment.isMethod || comment.isFunction)
{
?><?= "#### `" + comment.longName + "`\n" ?><?
}
else
{
?><?= "## `" + comment.name + "`\n" ?><?
}
}
if(comment.deprecated)
{
?><?= "\n\n**Deprecated - Might be removed in a later release.**\n\n" ?><?
}
?><?= comment.description + "\n" ?><?
if(comment.paramTags.length)
{
?><?= "**Parameters:**\n\n" ?><?
comment.paramTags.forEach(function(paramTag, i)
{
?><?= (i + 1) + ". **`" + paramTag.joinedTypes + "`** " + (paramTag.optional ? "(optional) " : "") + paramTag.name + " " + (paramTag.description ? " " + paramTag.description : "") + "\n" ?><?
});
}
if(comment.returnTags.length)
{
?><?= "**Returns:**\n\n" ?><?
comment.returnTags.forEach(function(returnTag)
{
?><?= "* **`" + returnTag.joinedTypes + "`** " + returnTag.description + "\n" ?><?
});
}
}
});
?><?= "\n<!-- " + doc.filename + "-->\n" ?><?
});
?>
<!-- vim: set tabstop=2 shiftwidth=2 softtabstop=2: -->

View file

@ -8,7 +8,8 @@
window.onload = function()
{
var emulator = new V86Starter({
memory_size: 128 * 1024 * 1024,
wasm_path: "../build/v86.wasm",
memory_size: 512 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),
bios: {
@ -17,16 +18,17 @@ window.onload = function()
vga_bios: {
url: "../bios/vgabios.bin",
},
hda: {
url: "http://localhost/v86-images/arch3.img",
async: true,
size: 8 * 1024 * 1024 * 1024,
},
filesystem: {
baseurl: "http://localhost/v86-images/arch/",
basefs: "http://localhost/v86-images/fs.json",
baseurl: "../images/arch/",
basefs: "../images/fs.json",
},
autostart: true,
bzimage_initrd_from_filesystem: true,
cmdline: [
"rw",
"root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose",
"init=/usr/bin/init-openrc",
].join(" "),
});
document.getElementById("save_file").onclick = function()

View file

@ -14,6 +14,7 @@ window.onload = function()
// is required if the server is on a different host
var emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
memory_size: 64 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),
@ -24,7 +25,7 @@ window.onload = function()
url: "../bios/vgabios.bin",
},
cdrom: {
url: "https://dl.dropboxusercontent.com/u/61029208/dsl-4.11.rc2.iso",
url: "../images/dsl-4.11.rc2.iso",
async: true,
// size can be determined automatically, but costs an extra request

View file

@ -8,6 +8,7 @@
window.onload = function()
{
var emulator = window.emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
memory_size: 32 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),

View file

@ -8,6 +8,7 @@
window.onload = function()
{
var emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
memory_size: 32 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),

View file

@ -15,7 +15,8 @@ window.onload = function()
}, 999);
var emulator = new V86Starter({
memory_size: 128 * 1024 * 1024,
wasm_path: "../build/v86.wasm",
memory_size: 512 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),
bios: {
@ -24,17 +25,11 @@ window.onload = function()
vga_bios: {
url: "../bios/vgabios.bin",
},
hda: {
url: "http://localhost/v86-images/arch3.img",
size: 8 * 1024 * 1024 * 1024,
async: true,
},
initial_state: {
url: "http://localhost/v86-images/v86state.bin",
url: "../images/arch_state.bin.zst",
},
filesystem: {
baseurl: "http://localhost/v86-images/arch/",
basefs: "http://localhost/v86-images/fs.json",
baseurl: "../images/arch/",
},
autostart: true,
});

View file

@ -1,104 +0,0 @@
<!doctype html>
<title>Interpreter 2</title>
<script src="../build/libv86.js"></script>
<script>
"use strict";
window.onload = function()
{
var start = Date.now();
document.getElementById("status").textContent = "Loading ...";
setInterval(function()
{
document.getElementById("time").textContent = Math.round((Date.now() - start) / 1000);
}, 999);
if(location.host === "localhost")
{
var urlbase = "http://localhost/v86-images/";
}
else
{
var urlbase = "http://104.131.53.7:8086/";
}
var emulator = new V86Starter({
memory_size: 128 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),
bios: {
url: "../bios/seabios.bin",
},
vga_bios: {
url: "../bios/vgabios.bin",
},
hda: {
url: urlbase + "arch3.img",
size: 8 * 1024 * 1024 * 1024,
async: true,
},
initial_state: {
url: urlbase + "v86state-node.bin",
},
filesystem: {
baseurl: urlbase + "arch/",
basefs: urlbase + "fs.json",
},
autostart: true,
});
window.emulator = emulator;
emulator.add_listener("emulator-ready", function()
{
document.getElementById("status").textContent = "Running code ...";
var code = "var fs = require('fs');\n" +
"module.exports = function() {\n" +
" fs.writeFileSync('/root/out.txt', 'The result is: ' + 2 * 3 * 4 * 5 * 6 * 7 * 8);\n" +
"}\n";
var buffer = new Uint8Array(code.length);
buffer.set(code.split("").map(function(chr) { return chr.charCodeAt(0); }));
emulator.create_file("/root/code.js", buffer, function(error)
{
if(error) throw error;
emulator.serial0_send('require("/root/code.js")()\n\n');
});
});
var interval = setInterval(function()
{
emulator.read_file("/root/out.txt", function(error, data)
{
if(error || !data)
{
return;
}
document.getElementById("status").textContent = "Done!";
document.getElementById("output").textContent = String.fromCharCode.apply(this, data);
clearInterval(interval);
});
}, 500);
}
</script>
<pre><span id=time>0</span>s -- <span id=status></span></pre>
<hr>
<pre id=output>
</pre>
<hr>
<!-- A minimal structure for the ScreenAdapter defined in browser/screen.js -->
<div id="screen_container">
<div style="white-space: pre; font: 14px monospace; line-height: 14px"></div>
<canvas style="display: none"></canvas>
</div>

View file

@ -8,6 +8,7 @@
window.onload = function()
{
var emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
memory_size: 32 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
@ -20,8 +21,8 @@ window.onload = function()
vga_bios: {
url: "../bios/vgabios.bin",
},
cdrom: {
url: "../images/linux.iso",
bzimage: {
url: "../images/buildroot-bzimage.bin",
},
autostart: true,
disable_keyboard: true,
@ -36,12 +37,7 @@ window.onload = function()
data += char;
}
if(data.endsWith("login: "))
{
console.log("Do login");
emulator.serial0_send("root\n");
}
else if(data.endsWith("/root% "))
if(data.endsWith("~% "))
{
console.log("Now ready");
document.getElementById("status").textContent = "Ready.\n";

View file

@ -10,15 +10,12 @@ function readfile(path)
}
var bios = readfile(__dirname + "/../bios/seabios.bin");
var linux = readfile(__dirname + "/../images/linux.iso");
var linux = readfile(__dirname + "/../images/linux4.iso");
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding("utf8");
var boot_start = Date.now();
var booted = false;
console.log("Now booting, please stand by ...");
var emulator = new V86Starter({
@ -29,14 +26,10 @@ var emulator = new V86Starter({
emulator.add_listener("serial0-output-char", function(chr)
{
if(!booted)
if(chr <= "~")
{
var now = Date.now();
console.log("Took %dms to boot", now - boot_start);
booted = true;
process.stdout.write(chr);
}
process.stdout.write(chr);
});
process.stdin.on("data", function(c)

View file

@ -12,7 +12,7 @@ function readfile(path)
console.log("Use F2 to save the state and F3 to restore.");
var bios = readfile(__dirname + "/../bios/seabios.bin");
var linux = readfile(__dirname + "/../images/linux.iso");
var linux = readfile(__dirname + "/../images/linux4.iso");
process.stdin.setRawMode(true);
process.stdin.resume();
@ -28,7 +28,10 @@ var emulator = new V86Starter({
emulator.add_listener("serial0-output-char", function(chr)
{
process.stdout.write(chr);
if(chr <= "~")
{
process.stdout.write(chr);
}
});
var state;

View file

@ -8,6 +8,7 @@
window.onload = function()
{
var emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
memory_size: 32 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
screen_container: document.getElementById("screen_container"),

View file

@ -8,6 +8,8 @@
window.onload = function()
{
var emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
// Uncomment to see what's going on
//screen_container: document.getElementById("screen_container"),
@ -18,7 +20,7 @@ window.onload = function()
url: "../bios/vgabios.bin",
},
cdrom: {
url: "../images/linux.iso",
url: "../images/linux4.iso",
},
autostart: true,
disable_keyboard: true,

View file

@ -11,6 +11,7 @@ window.onload = function()
var container2 = document.getElementById("screen_container2");
var emulator1 = new V86Starter({
wasm_path: "../build/v86.wasm",
screen_container: container1,
bios: {
url: "../bios/seabios.bin",
@ -25,6 +26,7 @@ window.onload = function()
});
var emulator2 = new V86Starter({
wasm_path: "../build/v86.wasm",
screen_container: container2,
bios: {
url: "../bios/seabios.bin",

View file

@ -1,6 +1,7 @@
importScripts("../build/libv86.js");
var emulator = new V86Starter({
wasm_path: "../build/v86.wasm",
memory_size: 32 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
bios: {
@ -10,7 +11,7 @@ var emulator = new V86Starter({
url: "../bios/vgabios.bin",
},
cdrom: {
url: "../images/linux.iso",
url: "../images/linux4.iso",
},
autostart: true,
});

486
gen/generate_analyzer.js Executable file
View file

@ -0,0 +1,486 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/");
mkdirpSync(OUT_DIR);
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");
const to_generate = {
analyzer: gen_all || table_arg === "analyzer",
analyzer0f: gen_all || table_arg === "analyzer0f",
};
assert(
Object.keys(to_generate).some(k => to_generate[k]),
"Pass --table [analyzer|analyzer0f] or --all to pick which tables to generate"
);
gen_table();
function gen_read_imm_call(op, size_variant)
{
let size = (op.os || op.opcode % 2 === 1) ? size_variant : 8;
if(op.imm8 || op.imm8s || op.imm16 || op.imm1632 || op.imm32 || op.immaddr)
{
if(op.imm8)
{
return "cpu.read_imm8()";
}
else if(op.imm8s)
{
return "cpu.read_imm8s()";
}
else
{
if(op.immaddr)
{
// immaddr: depends on address size
return "cpu.read_moffs()";
}
else
{
assert(op.imm1632 || op.imm16 || op.imm32);
if(op.imm1632 && size === 16 || op.imm16)
{
return "cpu.read_imm16()";
}
else
{
assert(op.imm1632 && size === 32 || op.imm32);
return "cpu.read_imm32()";
}
}
}
}
else
{
return undefined;
}
}
function gen_call(name, args)
{
args = args || [];
return `${name}(${args.join(", ")});`;
}
/*
* Current naming scheme:
* instr(16|32|)_(66|F2|F3)?0F?[0-9a-f]{2}(_[0-7])?(_mem|_reg|)
*/
function make_instruction_name(encoding, size)
{
const suffix = encoding.os ? String(size) : "";
const opcode_hex = hex(encoding.opcode & 0xFF, 2);
const first_prefix = (encoding.opcode & 0xFF00) === 0 ? "" : hex(encoding.opcode >> 8 & 0xFF, 2);
const second_prefix = (encoding.opcode & 0xFF0000) === 0 ? "" : hex(encoding.opcode >> 16 & 0xFF, 2);
const fixed_g_suffix = encoding.fixed_g === undefined ? "" : `_${encoding.fixed_g}`;
assert(first_prefix === "" || first_prefix === "0F" || first_prefix === "F2" || first_prefix === "F3");
assert(second_prefix === "" || second_prefix === "66" || second_prefix === "F2" || second_prefix === "F3");
return `instr${suffix}_${second_prefix}${first_prefix}${opcode_hex}${fixed_g_suffix}`;
}
function gen_instruction_body(encodings, size)
{
const encoding = encodings[0];
let has_66 = [];
let has_F2 = [];
let has_F3 = [];
let no_prefix = [];
for(let e of encodings)
{
if((e.opcode >>> 16) === 0x66) has_66.push(e);
else if((e.opcode >>> 8 & 0xFF) === 0xF2 || (e.opcode >>> 16) === 0xF2) has_F2.push(e);
else if((e.opcode >>> 8 & 0xFF) === 0xF3 || (e.opcode >>> 16) === 0xF3) has_F3.push(e);
else no_prefix.push(e);
}
if(has_F2.length || has_F3.length)
{
assert((encoding.opcode & 0xFF0000) === 0 || (encoding.opcode & 0xFF00) === 0x0F00);
}
if(has_66.length)
{
assert((encoding.opcode & 0xFF00) === 0x0F00);
}
const code = [];
if(encoding.e)
{
code.push("let modrm_byte = cpu.read_imm8();");
}
if(has_66.length || has_F2.length || has_F3.length)
{
const if_blocks = [];
if(has_66.length) {
const body = gen_instruction_body_after_prefix(has_66, size);
if_blocks.push({ condition: "cpu.prefixes & ::prefix::PREFIX_66 != 0", body, });
}
if(has_F2.length) {
const body = gen_instruction_body_after_prefix(has_F2, size);
if_blocks.push({ condition: "cpu.prefixes & ::prefix::PREFIX_F2 != 0", body, });
}
if(has_F3.length) {
const body = gen_instruction_body_after_prefix(has_F3, size);
if_blocks.push({ condition: "cpu.prefixes & ::prefix::PREFIX_F3 != 0", body, });
}
const else_block = {
body: gen_instruction_body_after_prefix(no_prefix, size),
};
return [].concat(
code,
{
type: "if-else",
if_blocks,
else_block,
}
);
}
else {
return [].concat(
code,
gen_instruction_body_after_prefix(encodings, size)
);
}
}
function gen_instruction_body_after_prefix(encodings, size)
{
const encoding = encodings[0];
if(encoding.fixed_g !== undefined)
{
assert(encoding.e);
// instruction with modrm byte where the middle 3 bits encode the instruction
// group by opcode without prefix plus middle bits of modrm byte
let cases = encodings.reduce((cases_by_opcode, case_) => {
assert(typeof case_.fixed_g === "number");
cases_by_opcode[case_.opcode & 0xFFFF | case_.fixed_g << 16] = case_;
return cases_by_opcode;
}, Object.create(null));
cases = Object.values(cases).sort((e1, e2) => e1.fixed_g - e2.fixed_g);
return [
{
type: "switch",
condition: "modrm_byte >> 3 & 7",
cases: cases.map(case_ => {
const fixed_g = case_.fixed_g;
const body = gen_instruction_body_after_fixed_g(case_, size);
return {
conditions: [fixed_g],
body,
};
}),
default_case: {
body: [
"analysis.ty = ::analysis::AnalysisType::BlockBoundary;",
"analysis.no_next_instruction = true;",
],
}
},
];
}
else {
assert(encodings.length === 1);
return gen_instruction_body_after_fixed_g(encodings[0], size);
}
}
function gen_instruction_body_after_fixed_g(encoding, size)
{
const imm_read = gen_read_imm_call(encoding, size);
const instruction_postfix = [];
if(encoding.custom_sti) {
instruction_postfix.push("analysis.ty = ::analysis::AnalysisType::STI;");
}
else if(
encoding.block_boundary &&
// jump_offset_imm: Is a block boundary, but gets a different type (Jump) below
!encoding.jump_offset_imm || (!encoding.custom && encoding.e))
{
instruction_postfix.push("analysis.ty = ::analysis::AnalysisType::BlockBoundary;");
}
if(encoding.no_next_instruction)
{
instruction_postfix.push("analysis.no_next_instruction = true;");
}
if(encoding.absolute_jump)
{
instruction_postfix.push("analysis.absolute_jump = true;");
}
if(encoding.prefix)
{
const instruction_name = "::analysis::" + make_instruction_name(encoding, size) + "_analyze";
const args = ["cpu", "analysis"];
assert(!imm_read);
return [].concat(
gen_call(instruction_name, args),
instruction_postfix
);
}
else if(encoding.e)
{
// instruction with modrm byte where the middle 3 bits encode a register
const reg_postfix = [];
const mem_postfix = [];
if(encoding.mem_ud)
{
mem_postfix.push(
"analysis.ty = ::analysis::AnalysisType::BlockBoundary;"
);
}
if(encoding.reg_ud)
{
reg_postfix.push(
"analysis.ty = ::analysis::AnalysisType::BlockBoundary;"
);
}
if(encoding.ignore_mod)
{
assert(!imm_read, "Unexpected instruction (ignore mod with immediate value)");
// Has modrm byte, but the 2 mod bits are ignored and both
// operands are always registers (0f20-0f24)
return instruction_postfix;
}
else
{
return [].concat(
{
type: "if-else",
if_blocks: [{
condition: "modrm_byte < 0xC0",
body: [].concat(
gen_call("::analysis::modrm_analyze", ["cpu", "modrm_byte"]),
mem_postfix,
),
}],
else_block: {
body: reg_postfix,
},
},
imm_read ? [imm_read + ";"] : [],
instruction_postfix
);
}
}
else
{
// instruction without modrm byte or prefix
const body = [];
if(imm_read)
{
if(encoding.jump_offset_imm)
{
body.push("let jump_offset = " + imm_read + ";");
if(encoding.conditional_jump)
{
assert(
(encoding.opcode & ~0xF) === 0x70 ||
(encoding.opcode & ~0xF) === 0x0F80 ||
(encoding.opcode & ~0x3) === 0xE0
);
const condition_index = encoding.opcode & 0xFF;
body.push(`analysis.ty = ::analysis::AnalysisType::Jump { offset: jump_offset as i32, condition: Some(0x${hex(condition_index, 2)}), is_32: cpu.osize_32() };`);
}
else
{
body.push(`analysis.ty = ::analysis::AnalysisType::Jump { offset: jump_offset as i32, condition: None, is_32: cpu.osize_32() };`);
}
}
else
{
body.push(imm_read + ";");
}
}
if(encoding.extra_imm16)
{
assert(imm_read);
body.push(gen_call("cpu.read_imm16"));
}
else if(encoding.extra_imm8)
{
assert(imm_read);
body.push(gen_call("cpu.read_imm8"));
}
return [].concat(
body,
instruction_postfix
);
}
}
function gen_table()
{
let by_opcode = Object.create(null);
let by_opcode0f = Object.create(null);
for(let o of x86_table)
{
let opcode = o.opcode;
if((opcode & 0xFF00) === 0x0F00)
{
opcode &= 0xFF;
by_opcode0f[opcode] = by_opcode0f[opcode] || [];
by_opcode0f[opcode].push(o);
}
else
{
opcode &= 0xFF;
by_opcode[opcode] = by_opcode[opcode] || [];
by_opcode[opcode].push(o);
}
}
let cases = [];
for(let opcode = 0; opcode < 0x100; opcode++)
{
let encoding = by_opcode[opcode];
assert(encoding && encoding.length);
let opcode_hex = hex(opcode, 2);
let opcode_high_hex = hex(opcode | 0x100, 2);
if(encoding[0].os)
{
cases.push({
conditions: [`0x${opcode_hex}`],
body: gen_instruction_body(encoding, 16),
});
cases.push({
conditions: [`0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, 32),
});
}
else
{
cases.push({
conditions: [`0x${opcode_hex}`, `0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, undefined),
});
}
}
const table = {
type: "switch",
condition: "opcode",
cases,
default_case: {
body: ["dbg_assert!(false);"]
},
};
if(to_generate.analyzer)
{
const code = [
"#[cfg_attr(rustfmt, rustfmt_skip)]",
"pub fn analyzer(opcode: u32, cpu: &mut ::cpu_context::CpuContext, analysis: &mut ::analysis::Analysis) {",
table,
"}",
];
finalize_table_rust(
OUT_DIR,
"analyzer.rs",
rust_ast.print_syntax_tree([].concat(code)).join("\n") + "\n"
);
}
const cases0f = [];
for(let opcode = 0; opcode < 0x100; opcode++)
{
let encoding = by_opcode0f[opcode];
assert(encoding && encoding.length);
let opcode_hex = hex(opcode, 2);
let opcode_high_hex = hex(opcode | 0x100, 2);
if(encoding[0].os)
{
cases0f.push({
conditions: [`0x${opcode_hex}`],
body: gen_instruction_body(encoding, 16),
});
cases0f.push({
conditions: [`0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, 32),
});
}
else
{
let block = {
conditions: [`0x${opcode_hex}`, `0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, undefined),
};
cases0f.push(block);
}
}
const table0f = {
type: "switch",
condition: "opcode",
cases: cases0f,
default_case: {
body: ["dbg_assert!(false);"]
},
};
if(to_generate.analyzer0f)
{
const code = [
"#![allow(unused)]",
"#[cfg_attr(rustfmt, rustfmt_skip)]",
"pub fn analyzer(opcode: u32, cpu: &mut ::cpu_context::CpuContext, analysis: &mut ::analysis::Analysis) {",
table0f,
"}"
];
finalize_table_rust(
OUT_DIR,
"analyzer0f.rs",
rust_ast.print_syntax_tree([].concat(code)).join("\n") + "\n"
);
}
}

492
gen/generate_interpreter.js Executable file
View file

@ -0,0 +1,492 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/");
mkdirpSync(OUT_DIR);
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");
const to_generate = {
interpreter: gen_all || table_arg === "interpreter",
interpreter0f: gen_all || table_arg === "interpreter0f",
};
assert(
Object.keys(to_generate).some(k => to_generate[k]),
"Pass --table [interpreter|interpreter0f] or --all to pick which tables to generate"
);
gen_table();
function wrap_imm_call(imm)
{
return `match ${imm} { Ok(o) => o, Err(()) => return }`;
}
function gen_read_imm_call(op, size_variant)
{
let size = (op.os || op.opcode % 2 === 1) ? size_variant : 8;
if(op.imm8 || op.imm8s || op.imm16 || op.imm1632 || op.imm32 || op.immaddr)
{
if(op.imm8)
{
return wrap_imm_call("read_imm8()");
}
else if(op.imm8s)
{
return wrap_imm_call("read_imm8s()");
}
else
{
if(op.immaddr)
{
// immaddr: depends on address size
return wrap_imm_call("read_moffs()");
}
else
{
assert(op.imm1632 || op.imm16 || op.imm32);
if(op.imm1632 && size === 16 || op.imm16)
{
return wrap_imm_call("read_imm16()");
}
else
{
assert(op.imm1632 && size === 32 || op.imm32);
return wrap_imm_call("read_imm32s()");
}
}
}
}
else
{
return undefined;
}
}
function gen_call(name, args)
{
args = args || [];
return `${name}(${args.join(", ")});`;
}
/*
* Current naming scheme:
* instr(16|32|)_(66|F2|F3)?0F?[0-9a-f]{2}(_[0-7])?(_mem|_reg|)
*/
function make_instruction_name(encoding, size)
{
const suffix = encoding.os ? String(size) : "";
const opcode_hex = hex(encoding.opcode & 0xFF, 2);
const first_prefix = (encoding.opcode & 0xFF00) === 0 ? "" : hex(encoding.opcode >> 8 & 0xFF, 2);
const second_prefix = (encoding.opcode & 0xFF0000) === 0 ? "" : hex(encoding.opcode >> 16 & 0xFF, 2);
const fixed_g_suffix = encoding.fixed_g === undefined ? "" : `_${encoding.fixed_g}`;
const module = first_prefix === "0F" || second_prefix === "0F" ? "instructions_0f" : "instructions";
assert(first_prefix === "" || first_prefix === "0F" || first_prefix === "F2" || first_prefix === "F3");
assert(second_prefix === "" || second_prefix === "66" || second_prefix === "F2" || second_prefix === "F3");
return `${module}::instr${suffix}_${second_prefix}${first_prefix}${opcode_hex}${fixed_g_suffix}`;
}
function gen_instruction_body(encodings, size)
{
const encoding = encodings[0];
let has_66 = [];
let has_F2 = [];
let has_F3 = [];
let no_prefix = [];
for(let e of encodings)
{
if((e.opcode >>> 16) === 0x66) has_66.push(e);
else if((e.opcode >>> 8 & 0xFF) === 0xF2 || (e.opcode >>> 16) === 0xF2) has_F2.push(e);
else if((e.opcode >>> 8 & 0xFF) === 0xF3 || (e.opcode >>> 16) === 0xF3) has_F3.push(e);
else no_prefix.push(e);
}
if(has_F2.length || has_F3.length)
{
assert((encoding.opcode & 0xFF0000) === 0 || (encoding.opcode & 0xFF00) === 0x0F00);
}
if(has_66.length)
{
assert((encoding.opcode & 0xFF00) === 0x0F00);
}
const code = [];
if(encoding.e)
{
code.push(`let modrm_byte = ${wrap_imm_call("read_imm8()")};`);
}
if(has_66.length || has_F2.length || has_F3.length)
{
const if_blocks = [];
if(has_66.length) {
const body = gen_instruction_body_after_prefix(has_66, size);
if_blocks.push({ condition: "prefixes_ & PREFIX_66 != 0", body, });
}
if(has_F2.length) {
const body = gen_instruction_body_after_prefix(has_F2, size);
if_blocks.push({ condition: "prefixes_ & PREFIX_F2 != 0", body, });
}
if(has_F3.length) {
const body = gen_instruction_body_after_prefix(has_F3, size);
if_blocks.push({ condition: "prefixes_ & PREFIX_F3 != 0", body, });
}
const check_prefixes = encoding.sse ? "(PREFIX_66 | PREFIX_F2 | PREFIX_F3)" : "(PREFIX_F2 | PREFIX_F3)";
const else_block = {
body: [].concat(
"dbg_assert!((prefixes_ & " + check_prefixes + ") == 0);",
gen_instruction_body_after_prefix(no_prefix, size)
)
};
return [].concat(
"let prefixes_ = *prefixes as i32;",
code,
{
type: "if-else",
if_blocks,
else_block,
}
);
}
else {
return [].concat(
code,
gen_instruction_body_after_prefix(encodings, size)
);
}
}
function gen_instruction_body_after_prefix(encodings, size)
{
const encoding = encodings[0];
if(encoding.fixed_g !== undefined)
{
assert(encoding.e);
// instruction with modrm byte where the middle 3 bits encode the instruction
// group by opcode without prefix plus middle bits of modrm byte
let cases = encodings.reduce((cases_by_opcode, case_) => {
assert(typeof case_.fixed_g === "number");
cases_by_opcode[case_.opcode & 0xFFFF | case_.fixed_g << 16] = case_;
return cases_by_opcode;
}, Object.create(null));
cases = Object.values(cases).sort((e1, e2) => e1.fixed_g - e2.fixed_g);
return [
{
type: "switch",
condition: "modrm_byte >> 3 & 7",
cases: cases.map(case_ => {
const fixed_g = case_.fixed_g;
const body = gen_instruction_body_after_fixed_g(case_, size);
return {
conditions: [fixed_g],
body,
};
}),
default_case: {
body: [
`if DEBUG { panic!("Bad instruction at {:x}", *instruction_pointer); }`,
"trigger_ud();",
],
}
},
];
}
else {
assert(encodings.length === 1);
return gen_instruction_body_after_fixed_g(encodings[0], size);
}
}
function gen_instruction_body_after_fixed_g(encoding, size)
{
const instruction_prefix = [];
const instruction_postfix =
(encoding.block_boundary && !encoding.no_block_boundary_in_interpreted) ||
(!encoding.custom && encoding.e) ?
["after_block_boundary();"] : [];
if(encoding.task_switch_test || encoding.sse)
{
instruction_prefix.push(
{
type: "if-else",
if_blocks: [
{
condition: encoding.sse ? "!task_switch_test_mmx()" : "!task_switch_test()",
body: ["return;"],
}
],
});
}
const imm_read = gen_read_imm_call(encoding, size);
const instruction_name = make_instruction_name(encoding, size);
if(encoding.e)
{
// instruction with modrm byte
const imm_read = gen_read_imm_call(encoding, size);
if(encoding.ignore_mod)
{
assert(!imm_read, "Unexpected instruction (ignore mod with immediate value)");
// Has modrm byte, but the 2 mod bits are ignored and both
// operands are always registers (0f20-0f24)
return [].concat(
instruction_prefix,
gen_call(instruction_name, ["modrm_byte & 7", "modrm_byte >> 3 & 7"]),
instruction_postfix
);
}
else
{
let mem_args;
if(encoding.custom_modrm_resolve)
{
// requires special handling around modrm_resolve
mem_args = ["modrm_byte"];
}
else
{
mem_args = ["match modrm_resolve(modrm_byte) { Ok(a) => a, Err(()) => return }"];
}
const reg_args = ["modrm_byte & 7"];
if(encoding.fixed_g === undefined)
{
mem_args.push("modrm_byte >> 3 & 7");
reg_args.push("modrm_byte >> 3 & 7");
}
if(imm_read)
{
mem_args.push(imm_read);
reg_args.push(imm_read);
}
return [].concat(
instruction_prefix,
{
type: "if-else",
if_blocks: [
{
condition: "modrm_byte < 0xC0",
body: [].concat(
gen_call(`${instruction_name}_mem`, mem_args)
),
}
],
else_block: {
body: [gen_call(`${instruction_name}_reg`, reg_args)],
},
},
instruction_postfix
);
}
}
else
{
const args = [];
if(imm_read)
{
args.push(imm_read);
}
if(encoding.extra_imm16)
{
assert(imm_read);
args.push(wrap_imm_call("read_imm16()"));
}
else if(encoding.extra_imm8)
{
assert(imm_read);
args.push(wrap_imm_call("read_imm8()"));
}
return [].concat(
instruction_prefix,
gen_call(instruction_name, args),
instruction_postfix
);
}
}
function gen_table()
{
let by_opcode = Object.create(null);
let by_opcode0f = Object.create(null);
for(let o of x86_table)
{
let opcode = o.opcode;
if((opcode & 0xFF00) === 0x0F00)
{
opcode &= 0xFF;
by_opcode0f[opcode] = by_opcode0f[opcode] || [];
by_opcode0f[opcode].push(o);
}
else
{
opcode &= 0xFF;
by_opcode[opcode] = by_opcode[opcode] || [];
by_opcode[opcode].push(o);
}
}
let cases = [];
for(let opcode = 0; opcode < 0x100; opcode++)
{
let encoding = by_opcode[opcode];
assert(encoding && encoding.length);
let opcode_hex = hex(opcode, 2);
let opcode_high_hex = hex(opcode | 0x100, 2);
if(encoding[0].os)
{
cases.push({
conditions: [`0x${opcode_hex}`],
body: gen_instruction_body(encoding, 16),
});
cases.push({
conditions: [`0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, 32),
});
}
else
{
cases.push({
conditions: [`0x${opcode_hex}`, `0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, undefined),
});
}
}
const table = {
type: "switch",
condition: "opcode",
cases,
default_case: {
body: ["assert!(false);"]
},
};
if(to_generate.interpreter)
{
const code = [
"#![cfg_attr(rustfmt, rustfmt_skip)]",
"use cpu::cpu::{after_block_boundary, modrm_resolve};",
"use cpu::cpu::{read_imm8, read_imm8s, read_imm16, read_imm32s, read_moffs};",
"use cpu::cpu::{task_switch_test, trigger_ud, DEBUG, PREFIX_F2, PREFIX_F3};",
"use cpu::instructions;",
"use cpu::global_pointers::{instruction_pointer, prefixes};",
"pub unsafe fn run(opcode: u32) {",
table,
"}",
];
finalize_table_rust(
OUT_DIR,
"interpreter.rs",
rust_ast.print_syntax_tree([].concat(code)).join("\n") + "\n"
);
}
const cases0f = [];
for(let opcode = 0; opcode < 0x100; opcode++)
{
let encoding = by_opcode0f[opcode];
assert(encoding && encoding.length);
let opcode_hex = hex(opcode, 2);
let opcode_high_hex = hex(opcode | 0x100, 2);
if(encoding[0].os)
{
cases0f.push({
conditions: [`0x${opcode_hex}`],
body: gen_instruction_body(encoding, 16),
});
cases0f.push({
conditions: [`0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, 32),
});
}
else
{
let block = {
conditions: [`0x${opcode_hex}`, `0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, undefined),
};
cases0f.push(block);
}
}
const table0f = {
type: "switch",
condition: "opcode",
cases: cases0f,
default_case: {
body: ["assert!(false);"]
},
};
if(to_generate.interpreter0f)
{
const code = [
"#![cfg_attr(rustfmt, rustfmt_skip)]",
"use cpu::cpu::{after_block_boundary, modrm_resolve};",
"use cpu::cpu::{read_imm8, read_imm16, read_imm32s};",
"use cpu::cpu::{task_switch_test, task_switch_test_mmx, trigger_ud};",
"use cpu::cpu::{DEBUG, PREFIX_66, PREFIX_F2, PREFIX_F3};",
"use cpu::instructions_0f;",
"use cpu::global_pointers::{instruction_pointer, prefixes};",
"pub unsafe fn run(opcode: u32) {",
table0f,
"}",
];
finalize_table_rust(
OUT_DIR,
"interpreter0f.rs",
rust_ast.print_syntax_tree([].concat(code)).join("\n") + "\n"
);
}
}

563
gen/generate_jit.js Executable file
View file

@ -0,0 +1,563 @@
#!/usr/bin/env node
"use strict";
const assert = require("assert").strict;
const fs = require("fs");
const path = require("path");
const x86_table = require("./x86_table");
const rust_ast = require("./rust_ast");
const { hex, mkdirpSync, get_switch_value, get_switch_exist, finalize_table_rust } = require("./util");
const OUT_DIR = path.join(__dirname, "..", "src/rust/gen/");
mkdirpSync(OUT_DIR);
const table_arg = get_switch_value("--table");
const gen_all = get_switch_exist("--all");
const to_generate = {
jit: gen_all || table_arg === "jit",
jit0f: gen_all || table_arg === "jit0f",
};
assert(
Object.keys(to_generate).some(k => to_generate[k]),
"Pass --table [jit|jit0f] or --all to pick which tables to generate"
);
gen_table();
function gen_read_imm_call(op, size_variant)
{
let size = (op.os || op.opcode % 2 === 1) ? size_variant : 8;
if(op.imm8 || op.imm8s || op.imm16 || op.imm1632 || op.imm32 || op.immaddr)
{
if(op.imm8)
{
return "ctx.cpu.read_imm8()";
}
else if(op.imm8s)
{
return "ctx.cpu.read_imm8s()";
}
else
{
if(op.immaddr)
{
// immaddr: depends on address size
return "ctx.cpu.read_moffs()";
}
else
{
assert(op.imm1632 || op.imm16 || op.imm32);
if(op.imm1632 && size === 16 || op.imm16)
{
return "ctx.cpu.read_imm16()";
}
else
{
assert(op.imm1632 && size === 32 || op.imm32);
return "ctx.cpu.read_imm32()";
}
}
}
}
else
{
return undefined;
}
}
function gen_call(name, args)
{
args = args || [];
return `${name}(${args.join(", ")});`;
}
/*
* Current naming scheme:
* instr(16|32|)_(66|F2|F3)?0F?[0-9a-f]{2}(_[0-7])?(_mem|_reg|)
*/
function make_instruction_name(encoding, size)
{
const suffix = encoding.os ? String(size) : "";
const opcode_hex = hex(encoding.opcode & 0xFF, 2);
const first_prefix = (encoding.opcode & 0xFF00) === 0 ? "" : hex(encoding.opcode >> 8 & 0xFF, 2);
const second_prefix = (encoding.opcode & 0xFF0000) === 0 ? "" : hex(encoding.opcode >> 16 & 0xFF, 2);
const fixed_g_suffix = encoding.fixed_g === undefined ? "" : `_${encoding.fixed_g}`;
assert(first_prefix === "" || first_prefix === "0F" || first_prefix === "F2" || first_prefix === "F3");
assert(second_prefix === "" || second_prefix === "66" || second_prefix === "F2" || second_prefix === "F3");
return `instr${suffix}_${second_prefix}${first_prefix}${opcode_hex}${fixed_g_suffix}`;
}
function gen_instruction_body(encodings, size)
{
const encoding = encodings[0];
let has_66 = [];
let has_F2 = [];
let has_F3 = [];
let no_prefix = [];
for(let e of encodings)
{
if((e.opcode >>> 16) === 0x66) has_66.push(e);
else if((e.opcode >>> 8 & 0xFF) === 0xF2 || (e.opcode >>> 16) === 0xF2) has_F2.push(e);
else if((e.opcode >>> 8 & 0xFF) === 0xF3 || (e.opcode >>> 16) === 0xF3) has_F3.push(e);
else no_prefix.push(e);
}
if(has_F2.length || has_F3.length)
{
assert((encoding.opcode & 0xFF0000) === 0 || (encoding.opcode & 0xFF00) === 0x0F00);
}
if(has_66.length)
{
assert((encoding.opcode & 0xFF00) === 0x0F00);
}
const code = [];
if(encoding.e)
{
code.push("let modrm_byte = ctx.cpu.read_imm8();");
}
if(has_66.length || has_F2.length || has_F3.length)
{
const if_blocks = [];
if(has_66.length) {
const body = gen_instruction_body_after_prefix(has_66, size);
if_blocks.push({ condition: "ctx.cpu.prefixes & ::prefix::PREFIX_66 != 0", body, });
}
if(has_F2.length) {
const body = gen_instruction_body_after_prefix(has_F2, size);
if_blocks.push({ condition: "ctx.cpu.prefixes & ::prefix::PREFIX_F2 != 0", body, });
}
if(has_F3.length) {
const body = gen_instruction_body_after_prefix(has_F3, size);
if_blocks.push({ condition: "ctx.cpu.prefixes & ::prefix::PREFIX_F3 != 0", body, });
}
const else_block = {
body: gen_instruction_body_after_prefix(no_prefix, size),
};
return [].concat(
code,
{
type: "if-else",
if_blocks,
else_block,
}
);
}
else {
return [].concat(
code,
gen_instruction_body_after_prefix(encodings, size)
);
}
}
function gen_instruction_body_after_prefix(encodings, size)
{
const encoding = encodings[0];
if(encoding.fixed_g !== undefined)
{
assert(encoding.e);
// instruction with modrm byte where the middle 3 bits encode the instruction
// group by opcode without prefix plus middle bits of modrm byte
let cases = encodings.reduce((cases_by_opcode, case_) => {
assert(typeof case_.fixed_g === "number");
cases_by_opcode[case_.opcode & 0xFFFF | case_.fixed_g << 16] = case_;
return cases_by_opcode;
}, Object.create(null));
cases = Object.values(cases).sort((e1, e2) => e1.fixed_g - e2.fixed_g);
return [
{
type: "switch",
condition: "modrm_byte >> 3 & 7",
cases: cases.map(case_ => {
const fixed_g = case_.fixed_g;
const body = gen_instruction_body_after_fixed_g(case_, size);
return {
conditions: [fixed_g],
body,
};
}),
default_case: {
body: [].concat(
gen_call(`::codegen::gen_trigger_ud`, ["ctx"]),
"*instr_flags |= ::jit::JIT_INSTR_BLOCK_BOUNDARY_FLAG;"
),
}
},
];
}
else {
assert(encodings.length === 1);
return gen_instruction_body_after_fixed_g(encodings[0], size);
}
}
function gen_instruction_body_after_fixed_g(encoding, size)
{
const instruction_postfix = [];
if(encoding.block_boundary || (!encoding.custom && encoding.e))
{
instruction_postfix.push("*instr_flags |= ::jit::JIT_INSTR_BLOCK_BOUNDARY_FLAG;");
}
const instruction_prefix = [];
if(encoding.task_switch_test || encoding.sse)
{
instruction_prefix.push(
gen_call(encoding.sse ? "::codegen::gen_task_switch_test_mmx" : "::codegen::gen_task_switch_test", ["ctx"])
);
}
const imm_read = gen_read_imm_call(encoding, size);
const imm_read_bindings = [];
if(imm_read)
{
imm_read_bindings.push(`let imm = ${imm_read} as u32;`);
}
const instruction_name = make_instruction_name(encoding, size);
if(!encoding.prefix)
{
if(encoding.custom)
{
}
else
{
instruction_prefix.push(
gen_call("::codegen::gen_move_registers_from_locals_to_memory", ["ctx"])
);
instruction_postfix.push(
gen_call("::codegen::gen_move_registers_from_memory_to_locals", ["ctx"])
);
}
}
if(encoding.e)
{
const reg_postfix = [];
const mem_postfix = [];
if(encoding.mem_ud)
{
mem_postfix.push(
"*instr_flags |= ::jit::JIT_INSTR_BLOCK_BOUNDARY_FLAG;"
);
}
if(encoding.reg_ud)
{
reg_postfix.push(
"*instr_flags |= ::jit::JIT_INSTR_BLOCK_BOUNDARY_FLAG;"
);
}
if(encoding.ignore_mod)
{
assert(!imm_read, "Unexpected instruction (ignore mod with immediate value)");
// Has modrm byte, but the 2 mod bits are ignored and both
// operands are always registers (0f20-0f24)
const args = ["ctx.builder", `"${instruction_name}"`, "(modrm_byte & 7) as u32", "(modrm_byte >> 3 & 7) as u32"];
return [].concat(
instruction_prefix,
gen_call(`::codegen::gen_fn${args.length - 2}_const`, args),
reg_postfix,
instruction_postfix
);
}
else if(encoding.custom)
{
const mem_args = ["ctx", "addr"];
const reg_args = ["ctx", "(modrm_byte & 7) as u32"];
if(encoding.fixed_g === undefined)
{
mem_args.push("(modrm_byte >> 3 & 7) as u32");
reg_args.push("(modrm_byte >> 3 & 7) as u32");
}
if(imm_read)
{
mem_args.push("imm");
reg_args.push("imm");
}
return [].concat(
instruction_prefix,
{
type: "if-else",
if_blocks: [{
condition: "modrm_byte < 0xC0",
body: [].concat(
"let addr = ::modrm::decode(ctx.cpu, modrm_byte);",
imm_read_bindings,
gen_call(`::jit_instructions::${instruction_name}_mem_jit`, mem_args),
mem_postfix
),
}],
else_block: {
body: [].concat(
imm_read_bindings,
gen_call(`::jit_instructions::${instruction_name}_reg_jit`, reg_args),
reg_postfix
),
},
},
instruction_postfix
);
}
else
{
const mem_args = ["ctx.builder", `"${instruction_name}_mem"`];
const reg_args = ["ctx.builder", `"${instruction_name}_reg"`, "(modrm_byte & 7) as u32"];
if(encoding.fixed_g === undefined)
{
mem_args.push("(modrm_byte >> 3 & 7) as u32");
reg_args.push("(modrm_byte >> 3 & 7) as u32");
}
if(imm_read)
{
mem_args.push("imm");
reg_args.push("imm");
}
return [].concat(
instruction_prefix,
{
type: "if-else",
if_blocks: [{
condition: "modrm_byte < 0xC0",
body: [].concat(
"let addr = ::modrm::decode(ctx.cpu, modrm_byte);",
gen_call(`::codegen::gen_modrm_resolve`, ["ctx", "addr"]),
imm_read_bindings,
gen_call(`::codegen::gen_modrm_fn${mem_args.length - 2}`, mem_args),
mem_postfix
),
}],
else_block: {
body: [].concat(
imm_read_bindings,
gen_call(`::codegen::gen_fn${reg_args.length - 2}_const`, reg_args),
reg_postfix
),
},
},
instruction_postfix
);
}
}
else if(encoding.prefix || encoding.custom)
{
// custom, but not modrm
const args = ["ctx"];
if(imm_read)
{
args.push("imm");
}
if(encoding.prefix)
{
args.push("instr_flags");
}
return [].concat(
instruction_prefix,
imm_read_bindings,
gen_call(`::jit_instructions::${instruction_name}_jit`, args),
instruction_postfix
);
}
else
{
// instruction without modrm byte or prefix
const args = ["ctx.builder", `"${instruction_name}"`];
if(imm_read)
{
args.push("imm");
}
if(encoding.extra_imm16)
{
assert(imm_read);
imm_read_bindings.push(`let imm2 = ctx.cpu.read_imm16() as u32;`);
args.push("imm2");
}
else if(encoding.extra_imm8)
{
assert(imm_read);
imm_read_bindings.push(`let imm2 = ctx.cpu.read_imm8() as u32;`);
args.push("imm2");
}
return [].concat(
instruction_prefix,
imm_read_bindings,
gen_call(`::codegen::gen_fn${args.length - 2}_const`, args),
instruction_postfix
);
}
}
function gen_table()
{
let by_opcode = Object.create(null);
let by_opcode0f = Object.create(null);
for(let o of x86_table)
{
let opcode = o.opcode;
if((opcode & 0xFF00) === 0x0F00)
{
opcode &= 0xFF;
by_opcode0f[opcode] = by_opcode0f[opcode] || [];
by_opcode0f[opcode].push(o);
}
else
{
opcode &= 0xFF;
by_opcode[opcode] = by_opcode[opcode] || [];
by_opcode[opcode].push(o);
}
}
let cases = [];
for(let opcode = 0; opcode < 0x100; opcode++)
{
let encoding = by_opcode[opcode];
assert(encoding && encoding.length);
let opcode_hex = hex(opcode, 2);
let opcode_high_hex = hex(opcode | 0x100, 2);
if(encoding[0].os)
{
cases.push({
conditions: [`0x${opcode_hex}`],
body: gen_instruction_body(encoding, 16),
});
cases.push({
conditions: [`0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, 32),
});
}
else
{
cases.push({
conditions: [`0x${opcode_hex}`, `0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, undefined),
});
}
}
const table = {
type: "switch",
condition: "opcode",
cases,
default_case: {
body: ["assert!(false);"]
},
};
if(to_generate.jit)
{
const code = [
"#[cfg_attr(rustfmt, rustfmt_skip)]",
"pub fn jit(opcode: u32, ctx: &mut ::jit::JitContext, instr_flags: &mut u32) {",
table,
"}",
];
finalize_table_rust(
OUT_DIR,
"jit.rs",
rust_ast.print_syntax_tree([].concat(code)).join("\n") + "\n"
);
}
const cases0f = [];
for(let opcode = 0; opcode < 0x100; opcode++)
{
let encoding = by_opcode0f[opcode];
assert(encoding && encoding.length);
let opcode_hex = hex(opcode, 2);
let opcode_high_hex = hex(opcode | 0x100, 2);
if(encoding[0].os)
{
cases0f.push({
conditions: [`0x${opcode_hex}`],
body: gen_instruction_body(encoding, 16),
});
cases0f.push({
conditions: [`0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, 32),
});
}
else
{
let block = {
conditions: [`0x${opcode_hex}`, `0x${opcode_high_hex}`],
body: gen_instruction_body(encoding, undefined),
};
cases0f.push(block);
}
}
const table0f = {
type: "switch",
condition: "opcode",
cases: cases0f,
default_case: {
body: ["assert!(false);"]
},
};
if(to_generate.jit0f)
{
const code = [
"#[cfg_attr(rustfmt, rustfmt_skip)]",
"pub fn jit(opcode: u32, ctx: &mut ::jit::JitContext, instr_flags: &mut u32) {",
table0f,
"}",
];
finalize_table_rust(
OUT_DIR,
"jit0f.rs",
rust_ast.print_syntax_tree([].concat(code)).join("\n") + "\n"
);
}
}

90
gen/rust_ast.js Normal file
View file

@ -0,0 +1,90 @@
"use strict";
const assert = require("assert").strict;
function repeat(s, n)
{
let out = "";
for(let i = 0; i < n; i++) out += s;
return out;
}
function indent(lines, how_much)
{
return lines.map(line => repeat(" ", how_much) + line);
}
function print_syntax_tree(statements)
{
let code = [];
for(let statement of statements)
{
if(typeof statement === "string")
{
code.push(statement);
}
else if(statement.type === "switch")
{
assert(statement.condition);
const cases = [];
for(let case_ of statement.cases)
{
assert(case_.conditions.length >= 1);
cases.push(case_.conditions.join(" | ") + " => {");
cases.push.apply(cases, indent(print_syntax_tree(case_.body), 4));
cases.push(`},`);
}
if(statement.default_case)
{
cases.push(`_ => {`);
cases.push.apply(cases, indent(print_syntax_tree(statement.default_case.body), 4));
cases.push(`}`);
}
code.push(`match ${statement.condition} {`);
code.push.apply(code, indent(cases, 4));
code.push(`}`);
}
else if(statement.type === "if-else")
{
assert(statement.if_blocks.length >= 1);
let first_if_block = statement.if_blocks[0];
code.push(`if ${first_if_block.condition} {`);
code.push.apply(code, indent(print_syntax_tree(first_if_block.body), 4));
code.push(`}`);
for(let i = 1; i < statement.if_blocks.length; i++)
{
let if_block = statement.if_blocks[i];
code.push(`else if ${if_block.condition} {`);
code.push.apply(code, indent(print_syntax_tree(if_block.body), 4));
code.push(`}`);
}
if(statement.else_block)
{
code.push(`else {`);
code.push.apply(code, indent(print_syntax_tree(statement.else_block.body), 4));
code.push(`}`);
}
}
else
{
assert(false, "Unexpected type: " + statement.type, "In:", statement);
}
}
return code;
}
module.exports = {
print_syntax_tree,
};

54
gen/util.js Normal file
View file

@ -0,0 +1,54 @@
"use strict";
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const process = require("process");
const child_process = require("child_process");
const CYAN_FMT = "\x1b[36m%s\x1b[0m";
function hex(n, pad)
{
pad = pad || 0;
let s = n.toString(16).toUpperCase();
while(s.length < pad) s = "0" + s;
return s;
}
function mkdirpSync(dir)
{
fs.mkdirSync(dir, { recursive: true });
}
function get_switch_value(arg_switch)
{
const argv = process.argv;
const switch_i = argv.indexOf(arg_switch);
const val_i = switch_i + 1;
if(switch_i > -1 && val_i < argv.length)
{
return argv[switch_i + 1];
}
return null;
}
function get_switch_exist(arg_switch)
{
return process.argv.includes(arg_switch);
}
function finalize_table_rust(out_dir, name, contents)
{
const file_path = path.join(out_dir, name);
fs.writeFileSync(file_path, contents);
console.log(CYAN_FMT, `[+] Wrote table ${name}.`);
}
module.exports = {
hex,
mkdirpSync,
get_switch_value,
get_switch_exist,
finalize_table_rust,
};

977
gen/x86_table.js Normal file
View file

@ -0,0 +1,977 @@
"use strict";
const { hex } = require("./util");
// http://ref.x86asm.net/coder32.html
const zf = 1 << 6;
const of = 1 << 11;
const cf = 1 << 0;
const af = 1 << 4;
const pf = 1 << 2;
const sf = 1 << 7;
// === Types of instructions
//
// create entry | check for compiled code | instruction
// -------------+-------------------------+-----------------------------------------------------------
// 1 | optional | pop ds (may change cpu state)
// | | trigger_ud, div (exception that doesn't generate conditional return from BB)
// | | port io, popf, sti (may call interrupt or continue at next instruction)
// | | hlt
// -------------+-------------------------+-----------------------------------------------------------
// 1 | 1 | call [eax], jmp [eax], int, iret, ret, jmpf, callf, sysenter, sysexit
// | | Special case: normal instruction with fallthough to next page
// | | Special case: after execution of compiled code
// | | -> may create redundant entry points depending on last instruction?
// -------------+-------------------------+-----------------------------------------------------------
// 1 | 0 | rep movs, rep lods, rep stos, rep cmps, rep scas
// | | -> Executed as follows:
// | | - Upto including the first call in compiled mode
// | | - Back to main loop and repeated in interpreted mode (as entry point is after instruction, not on)
// | | - When finished entry pointer *after* instruction is hit and execution continues in compiled mode
// -------------+-------------------------+-----------------------------------------------------------
// 0 | optional | jmp foo, jnz foo
// | | (foo is in the same page as the instruction)
// -------------+-------------------------+-----------------------------------------------------------
// 1 | 1 | call foo
// | | (foo is in the same page as the instruction)
// | | -> The entry point is not created for jumps within
// | | this page, but speculatively for calls from
// | | other pages to the function in this page
// -------------+-------------------------+-----------------------------------------------------------
// 1 | 1 | call foo, jmp foo, jnz foo
// | | (foo is in a different page than the instruction)
// e: a modrm byte follows the operand
// os: the instruction behaves differently depending on the operand size
// fixed_g: the reg field of the modrm byte selects an instruction
// skip: skip automatically generated tests (nasmtests)
// mask_flags: flags bits to mask in generated tests
// prefix: is a prefix instruction
// imm8, imm8s, imm16, imm1632, immaddr, extra_imm8, extra_imm16: one or two immediate bytes follows the instruction
// custom: will callback jit to generate custom code
// block_boundary: may change eip in a way not handled by the jit
// no_next_instruction: jit will stop analysing after instruction (e.g., unconditional jump, ret)
const encodings = [
{ opcode: 0x00, custom: 1, e: 1, },
{ opcode: 0x01, custom: 1, os: 1, e: 1, },
{ opcode: 0x02, custom: 1, e: 1, },
{ opcode: 0x03, custom: 1, os: 1, e: 1, },
{ opcode: 0x08, custom: 1, e: 1, },
{ opcode: 0x09, custom: 1, os: 1, e: 1, },
{ opcode: 0x0A, custom: 1, e: 1, },
{ opcode: 0x0B, custom: 1, os: 1, e: 1, },
{ opcode: 0x10, custom: 1, e: 1, },
{ opcode: 0x11, custom: 1, os: 1, e: 1, },
{ opcode: 0x12, custom: 1, e: 1, },
{ opcode: 0x13, custom: 1, os: 1, e: 1, },
{ opcode: 0x18, custom: 1, e: 1, },
{ opcode: 0x19, custom: 1, os: 1, e: 1, },
{ opcode: 0x1A, custom: 1, e: 1, },
{ opcode: 0x1B, custom: 1, os: 1, e: 1, },
{ opcode: 0x20, custom: 1, e: 1, },
{ opcode: 0x21, custom: 1, os: 1, e: 1, },
{ opcode: 0x22, custom: 1, e: 1, },
{ opcode: 0x23, custom: 1, os: 1, e: 1, },
{ opcode: 0x28, custom: 1, e: 1, },
{ opcode: 0x29, custom: 1, os: 1, e: 1, },
{ opcode: 0x2A, custom: 1, e: 1, },
{ opcode: 0x2B, custom: 1, os: 1, e: 1, },
{ opcode: 0x30, custom: 1, e: 1, },
{ opcode: 0x31, custom: 1, os: 1, e: 1, },
{ opcode: 0x32, custom: 1, e: 1, },
{ opcode: 0x33, custom: 1, os: 1, e: 1, },
{ opcode: 0x38, custom: 1, e: 1, },
{ opcode: 0x39, custom: 1, os: 1, e: 1, },
{ opcode: 0x3A, custom: 1, e: 1, },
{ opcode: 0x3B, custom: 1, os: 1, e: 1, },
{ opcode: 0x06, os: 1, custom: 1 },
{ opcode: 0x07, os: 1, skip: 1, block_boundary: 1, }, // pop es: block_boundary since it uses non-raising cpu exceptions
{ opcode: 0x0E, os: 1, custom: 1 },
{ opcode: 0x0F, os: 1, prefix: 1, },
{ opcode: 0x16, os: 1, custom: 1 },
{ opcode: 0x17, block_boundary: 1, os: 1, skip: 1, }, // pop ss
{ opcode: 0x1E, os: 1, custom: 1 },
{ opcode: 0x1F, block_boundary: 1, os: 1, skip: 1, }, // pop ds
{ opcode: 0x26, prefix: 1, },
{ opcode: 0x27, mask_flags: of, },
{ opcode: 0x2E, prefix: 1, },
{ opcode: 0x2F, mask_flags: of, },
{ opcode: 0x36, prefix: 1, },
{ opcode: 0x37, mask_flags: of | sf | pf | zf, },
{ opcode: 0x3E, prefix: 1, },
{ opcode: 0x3F, mask_flags: of | sf | pf | zf, },
{ opcode: 0x40, os: 1, custom: 1 },
{ opcode: 0x41, os: 1, custom: 1 },
{ opcode: 0x42, os: 1, custom: 1 },
{ opcode: 0x43, os: 1, custom: 1 },
{ opcode: 0x44, os: 1, custom: 1 },
{ opcode: 0x45, os: 1, custom: 1 },
{ opcode: 0x46, os: 1, custom: 1 },
{ opcode: 0x47, os: 1, custom: 1 },
{ opcode: 0x48, os: 1, custom: 1 },
{ opcode: 0x49, os: 1, custom: 1 },
{ opcode: 0x4A, os: 1, custom: 1 },
{ opcode: 0x4B, os: 1, custom: 1 },
{ opcode: 0x4C, os: 1, custom: 1 },
{ opcode: 0x4D, os: 1, custom: 1 },
{ opcode: 0x4E, os: 1, custom: 1 },
{ opcode: 0x4F, os: 1, custom: 1 },
{ opcode: 0x50, custom: 1, os: 1 },
{ opcode: 0x51, custom: 1, os: 1 },
{ opcode: 0x52, custom: 1, os: 1 },
{ opcode: 0x53, custom: 1, os: 1 },
{ opcode: 0x54, custom: 1, os: 1 },
{ opcode: 0x55, custom: 1, os: 1 },
{ opcode: 0x56, custom: 1, os: 1 },
{ opcode: 0x57, custom: 1, os: 1 },
{ opcode: 0x58, custom: 1, os: 1, },
{ opcode: 0x59, custom: 1, os: 1, },
{ opcode: 0x5A, custom: 1, os: 1, },
{ opcode: 0x5B, custom: 1, os: 1, },
{ opcode: 0x5C, custom: 1, os: 1, },
{ opcode: 0x5D, custom: 1, os: 1, },
{ opcode: 0x5E, custom: 1, os: 1, },
{ opcode: 0x5F, custom: 1, os: 1, },
{ opcode: 0x60, os: 1, },
{ opcode: 0x61, os: 1, },
{ opcode: 0x62, e: 1, skip: 1, },
{ opcode: 0x63, e: 1, block_boundary: 1, }, // arpl
{ opcode: 0x64, prefix: 1, },
{ opcode: 0x65, prefix: 1, },
{ opcode: 0x66, prefix: 1, },
{ opcode: 0x67, prefix: 1, },
{ opcode: 0x68, custom: 1, os: 1, imm1632: 1 },
{ opcode: 0x69, os: 1, e: 1, custom: 1, imm1632: 1, mask_flags: af, }, // zf?
{ opcode: 0x6A, custom: 1, os: 1, imm8s: 1 },
{ opcode: 0x6B, os: 1, e: 1, custom: 1, imm8s: 1, mask_flags: af, }, // zf?
{ opcode: 0x6C, block_boundary: 1, custom: 1, is_string: 1, skip: 1, }, // ins
{ opcode: 0xF26C, block_boundary: 1, custom: 1, is_string: 1, skip: 1, },
{ opcode: 0xF36C, block_boundary: 1, custom: 1, is_string: 1, skip: 1, },
{ opcode: 0x6D, block_boundary: 1, custom: 1, is_string: 1, os: 1, skip: 1, },
{ opcode: 0xF26D, block_boundary: 1, custom: 1, is_string: 1, os: 1, skip: 1, },
{ opcode: 0xF36D, block_boundary: 1, custom: 1, is_string: 1, os: 1, skip: 1, },
{ opcode: 0x6E, block_boundary: 1, custom: 1, is_string: 1, skip: 1, }, // outs
{ opcode: 0xF26E, block_boundary: 1, custom: 1, is_string: 1, skip: 1, },
{ opcode: 0xF36E, block_boundary: 1, custom: 1, is_string: 1, skip: 1, },
{ opcode: 0x6F, block_boundary: 1, custom: 1, is_string: 1, os: 1, skip: 1, },
{ opcode: 0xF26F, block_boundary: 1, custom: 1, is_string: 1, os: 1, skip: 1, },
{ opcode: 0xF36F, block_boundary: 1, custom: 1, is_string: 1, os: 1, skip: 1, },
{ opcode: 0x84, custom: 1, e: 1, },
{ opcode: 0x85, custom: 1, e: 1, os: 1, },
{ opcode: 0x86, custom: 1, e: 1, },
{ opcode: 0x87, custom: 1, os: 1, e: 1, },
{ opcode: 0x88, custom: 1, e: 1, },
{ opcode: 0x89, custom: 1, os: 1, e: 1, },
{ opcode: 0x8A, custom: 1, e: 1, },
{ opcode: 0x8B, custom: 1, os: 1, e: 1, },
{ opcode: 0x8C, os: 1, e: 1, custom: 1 }, // mov reg, sreg
{ opcode: 0x8D, reg_ud: 1, os: 1, e: 1, custom_modrm_resolve: 1, custom: 1, }, // lea
{ opcode: 0x8E, block_boundary: 1, e: 1, skip: 1, }, // mov sreg
{ opcode: 0x8F, os: 1, e: 1, fixed_g: 0, custom_modrm_resolve: 1, custom: 1, block_boundary: 1, }, // pop r/m
{ opcode: 0x90, custom: 1, },
{ opcode: 0x91, custom: 1, os: 1, },
{ opcode: 0x92, custom: 1, os: 1, },
{ opcode: 0x93, custom: 1, os: 1, },
{ opcode: 0x94, custom: 1, os: 1, },
{ opcode: 0x95, custom: 1, os: 1, },
{ opcode: 0x96, custom: 1, os: 1, },
{ opcode: 0x97, custom: 1, os: 1, },
{ opcode: 0x98, os: 1, custom: 1 },
{ opcode: 0x99, os: 1, custom: 1 },
{ opcode: 0x9A, os: 1, imm1632: 1, extra_imm16: 1, skip: 1, block_boundary: 1, }, // callf
{ opcode: 0x9B, block_boundary: 1, skip: 1, }, // fwait: block_boundary since it uses non-raising cpu exceptions
{ opcode: 0x9C, os: 1, custom: 1 },
{ opcode: 0x9D, os: 1, skip: 1, custom: 1, },
{ opcode: 0x9E, custom: 1 },
{ opcode: 0x9F, custom: 1 },
{ opcode: 0xA0, custom: 1, immaddr: 1 },
{ opcode: 0xA1, custom: 1, os: 1, immaddr: 1 },
{ opcode: 0xA2, custom: 1, immaddr: 1 },
{ opcode: 0xA3, custom: 1, os: 1, immaddr: 1 },
// string instructions aren't jumps, but they modify eip due to how they're implemented
// TODO: The block_boundary on the non-rep instructions can be removed once they're custom
{ opcode: 0xA4, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF2A4, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF3A4, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xA5, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF2A5, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF3A5, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xA6, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF2A6, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF3A6, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xA7, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF2A7, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF3A7, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xA8, custom: 1, imm8: 1, },
{ opcode: 0xA9, custom: 1, os: 1, imm1632: 1, },
{ opcode: 0xAA, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF2AA, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF3AA, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xAB, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF2AB, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF3AB, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xAC, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF2AC, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF3AC, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xAD, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF2AD, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF3AD, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xAE, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF2AE, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xF3AE, block_boundary: 1, custom: 1, is_string: 1, },
{ opcode: 0xAF, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF2AF, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xF3AF, block_boundary: 1, custom: 1, is_string: 1, os: 1, },
{ opcode: 0xC2, custom: 1, block_boundary: 1, no_next_instruction: 1, os: 1, absolute_jump: 1, imm16: 1, skip: 1, }, // ret
{ opcode: 0xC3, custom: 1, block_boundary: 1, no_next_instruction: 1, os: 1, absolute_jump: 1, skip: 1, },
{ opcode: 0xC4, block_boundary: 1, os: 1, e: 1, skip: 1, }, // les
{ opcode: 0xC5, block_boundary: 1, os: 1, e: 1, skip: 1, }, // lds
{ opcode: 0xC6, custom: 1, e: 1, fixed_g: 0, imm8: 1 },
{ opcode: 0xC7, custom: 1, os: 1, e: 1, fixed_g: 0, imm1632: 1 },
// XXX: Temporary block boundary
{ opcode: 0xC8, os: 1, imm16: 1, extra_imm8: 1, block_boundary: 1, }, // enter
{ opcode: 0xC9, custom: 1, os: 1, skip: 1 }, // leave
{ opcode: 0xCA, block_boundary: 1, no_next_instruction: 1, os: 1, imm16: 1, skip: 1, }, // retf
{ opcode: 0xCB, block_boundary: 1, no_next_instruction: 1, os: 1, skip: 1, },
{ opcode: 0xCC, block_boundary: 1, skip: 1, }, // int
{ opcode: 0xCD, block_boundary: 1, skip: 1, imm8: 1, },
{ opcode: 0xCE, block_boundary: 1, skip: 1, },
{ opcode: 0xCF, block_boundary: 1, no_next_instruction: 1, os: 1, skip: 1, }, // iret
{ opcode: 0xD4, imm8: 1, block_boundary: 1, }, // aam, may trigger #de
{ opcode: 0xD5, imm8: 1, mask_flags: of | cf | af, },
{ opcode: 0xD6, },
{ opcode: 0xD7, skip: 1, custom: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 0, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 1, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 4, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 6, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD8, e: 1, fixed_g: 7, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 0, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 1, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 4, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, skip_mem: 1, }, // fldenv (mem)
{ opcode: 0xD9, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xD9, e: 1, fixed_g: 6, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, skip: 1, }, // fstenv (mem), fprem (reg)
{ opcode: 0xD9, e: 1, fixed_g: 7, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, skip_reg: 1, }, // fprem, fyl2xp1 (precision issues)
{ opcode: 0xDA, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 2, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 3, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDA, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 0, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, skip_mem: 1, }, // unimplemented: fisttp (sse3)
{ opcode: 0xDB, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 6, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDB, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 0, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 1, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 4, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 6, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDC, e: 1, fixed_g: 7, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 0, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, os: 1, skip_mem: 1, }, // unimplemented: fisttp (sse3)
{ opcode: 0xDD, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 4, custom: 0, is_fpu: 1, task_switch_test: 1, os: 1, skip_mem: 1 }, // frstor
{ opcode: 0xDD, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xDD, e: 1, fixed_g: 6, custom: 0, is_fpu: 1, task_switch_test: 1, os: 1, skip_mem: 1 }, // fsave
{ opcode: 0xDD, e: 1, fixed_g: 7, custom: 0, is_fpu: 1, task_switch_test: 1, os: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 0, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 1, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 4, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 6, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDE, e: 1, fixed_g: 7, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDF, e: 1, fixed_g: 0, custom: 0, is_fpu: 1, task_switch_test: 1 },
{ opcode: 0xDF, e: 1, fixed_g: 1, custom: 0, is_fpu: 1, task_switch_test: 1, skip_mem: 1 }, // unimplemented: fisttp (sse3)
{ opcode: 0xDF, e: 1, fixed_g: 2, custom: 1, is_fpu: 1, task_switch_test: 1 },
{ opcode: 0xDF, e: 1, fixed_g: 3, custom: 1, is_fpu: 1, task_switch_test: 1 },
{ opcode: 0xDF, e: 1, fixed_g: 4, custom: 1, is_fpu: 1, task_switch_test: 1, skip_mem: 1 }, // unimplemented: Binary Coded Decimals
{ opcode: 0xDF, e: 1, fixed_g: 5, custom: 1, is_fpu: 1, task_switch_test: 1, },
{ opcode: 0xDF, e: 1, fixed_g: 6, custom: 1, is_fpu: 1, task_switch_test: 1, skip_mem: 1 }, // unimplemented: Binary Coded Decimals
{ opcode: 0xDF, e: 1, fixed_g: 7, custom: 1, is_fpu: 1, task_switch_test: 1, },
// loop, jcxz, etc.
{ opcode: 0xE0, os: 1, imm8s: 1, no_block_boundary_in_interpreted: 1, skip: 1, block_boundary: 1, jump_offset_imm: 1, custom: 1, conditional_jump: 1, },
{ opcode: 0xE1, os: 1, imm8s: 1, no_block_boundary_in_interpreted: 1, skip: 1, block_boundary: 1, jump_offset_imm: 1, custom: 1, conditional_jump: 1, },
{ opcode: 0xE2, os: 1, imm8s: 1, no_block_boundary_in_interpreted: 1, skip: 1, block_boundary: 1, jump_offset_imm: 1, custom: 1, conditional_jump: 1, },
{ opcode: 0xE3, os: 1, imm8s: 1, no_block_boundary_in_interpreted: 1, skip: 1, block_boundary: 1, jump_offset_imm: 1, custom: 1, conditional_jump: 1, },
// port functions aren't jumps, but they may modify eip due to how they are implemented
{ opcode: 0xE4, block_boundary: 1, imm8: 1, skip: 1, }, // in
{ opcode: 0xE5, block_boundary: 1, os: 1, imm8: 1, skip: 1, },
{ opcode: 0xE6, block_boundary: 1, imm8: 1, skip: 1, }, // out
{ opcode: 0xE7, block_boundary: 1, os: 1, imm8: 1, skip: 1, },
{ opcode: 0xE8, block_boundary: 1, jump_offset_imm: 1, os: 1, imm1632: 1, custom: 1, skip: 1, }, // call
{ opcode: 0xE9, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, no_next_instruction: 1, os: 1, imm1632: 1, custom: 1, skip: 1, },
{ opcode: 0xEA, block_boundary: 1, no_next_instruction: 1, os: 1, imm1632: 1, extra_imm16: 1, skip: 1, }, // jmpf
{ opcode: 0xEB, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, no_next_instruction: 1, os: 1, imm8s: 1, custom: 1, skip: 1, },
{ opcode: 0xEC, block_boundary: 1, skip: 1, }, // in
{ opcode: 0xED, block_boundary: 1, os: 1, skip: 1, },
{ opcode: 0xEE, block_boundary: 1, skip: 1, }, // out
{ opcode: 0xEF, block_boundary: 1, os: 1, skip: 1, },
{ opcode: 0xF0, prefix: 1, },
{ opcode: 0xF1, skip: 1, },
{ opcode: 0xF2, prefix: 1, },
{ opcode: 0xF3, prefix: 1, },
{ opcode: 0xF4, block_boundary: 1, no_next_instruction: 1, skip: 1, }, // hlt
{ opcode: 0xF5, },
{ opcode: 0xF6, e: 1, fixed_g: 0, imm8: 1, custom: 1 },
{ opcode: 0xF6, e: 1, fixed_g: 1, imm8: 1, custom: 1 },
{ opcode: 0xF6, e: 1, fixed_g: 2, },
{ opcode: 0xF6, e: 1, fixed_g: 3, },
{ opcode: 0xF6, e: 1, fixed_g: 4, mask_flags: af | zf, },
{ opcode: 0xF6, e: 1, fixed_g: 5, mask_flags: af | zf, },
{ opcode: 0xF6, e: 1, fixed_g: 6, block_boundary: 1, }, // div/idiv: Not a block boundary, but doesn't use control flow exceptions
{ opcode: 0xF6, e: 1, fixed_g: 7, block_boundary: 1, },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 0, imm1632: 1, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 1, imm1632: 1, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 2, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 3, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 4, mask_flags: zf | af, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 5, mask_flags: zf | af, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 6, custom: 1 },
{ opcode: 0xF7, os: 1, e: 1, fixed_g: 7, custom: 1 },
{ opcode: 0xF8, custom: 1 },
{ opcode: 0xF9, custom: 1 },
{ opcode: 0xFA, custom: 1, skip: 1 },
// STI: Note: Has special handling in jit in order to call handle_irqs safely
{ opcode: 0xFB, custom: 1, custom_sti: 1, skip: 1, },
{ opcode: 0xFC, custom: 1, },
{ opcode: 0xFD, custom: 1, },
{ opcode: 0xFE, e: 1, fixed_g: 0, custom: 1 },
{ opcode: 0xFE, e: 1, fixed_g: 1, custom: 1 },
{ opcode: 0xFF, os: 1, e: 1, fixed_g: 0, custom: 1, },
{ opcode: 0xFF, os: 1, e: 1, fixed_g: 1, custom: 1, },
{ opcode: 0xFF, os: 1, e: 1, fixed_g: 2, custom: 1, block_boundary: 1, absolute_jump: 1, skip: 1, },
{ opcode: 0xFF, os: 1, e: 1, fixed_g: 3, block_boundary: 1, skip: 1, },
{ opcode: 0xFF, os: 1, e: 1, fixed_g: 4, custom: 1, block_boundary: 1, absolute_jump: 1, no_next_instruction: 1, skip: 1, },
{ opcode: 0xFF, os: 1, e: 1, fixed_g: 5, block_boundary: 1, no_next_instruction: 1, skip: 1, },
{ opcode: 0xFF, custom: 1, os: 1, e: 1, fixed_g: 6, },
{ opcode: 0x0F00, fixed_g: 0, e: 1, skip: 1, block_boundary: 1, os: 1, }, // sldt, ...
{ opcode: 0x0F00, fixed_g: 1, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F00, fixed_g: 2, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F00, fixed_g: 3, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F00, fixed_g: 4, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F00, fixed_g: 5, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F01, fixed_g: 0, e: 1, skip: 1, block_boundary: 1, os: 1, }, // sgdt, ...
{ opcode: 0x0F01, fixed_g: 1, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F01, fixed_g: 2, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F01, fixed_g: 3, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F01, fixed_g: 4, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F01, fixed_g: 6, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F01, fixed_g: 7, e: 1, skip: 1, block_boundary: 1, os: 1, },
{ opcode: 0x0F02, os: 1, e: 1, skip: 1, block_boundary: 1, }, // lar
{ opcode: 0x0F03, os: 1, e: 1, skip: 1, block_boundary: 1, }, // lsl
{ opcode: 0x0F04, skip: 1, block_boundary: 1, },
{ opcode: 0x0F05, skip: 1, block_boundary: 1, },
{ opcode: 0x0F06, skip: 1, block_boundary: 1, }, // clts
{ opcode: 0x0F07, skip: 1, block_boundary: 1, },
{ opcode: 0x0F08, skip: 1, block_boundary: 1, },
{ opcode: 0x0F09, skip: 1, block_boundary: 1, }, // wbinvd
{ opcode: 0x0F0A, skip: 1, block_boundary: 1, },
// ud2
// Technically has a next instruction, but Linux uses this for assertions
// and embeds the assertion message after this instruction, which is likely
// the most common use case of ud2
{ opcode: 0x0F0B, skip: 1, block_boundary: 1, no_next_instruction: 1, },
{ opcode: 0x0F0C, skip: 1, block_boundary: 1, },
{ opcode: 0x0F0D, skip: 1, block_boundary: 1, },
{ opcode: 0x0F0E, skip: 1, block_boundary: 1, },
{ opcode: 0x0F0F, skip: 1, block_boundary: 1, },
{ opcode: 0x0F18, e: 1, custom: 1 },
{ opcode: 0x0F19, custom: 1, e: 1, },
{ opcode: 0x0F1A, skip: 1, block_boundary: 1, },
{ opcode: 0x0F1B, skip: 1, block_boundary: 1, },
{ opcode: 0x0F1C, custom: 1, e: 1, },
{ opcode: 0x0F1D, custom: 1, e: 1, },
{ opcode: 0x0F1E, custom: 1, e: 1, },
{ opcode: 0x0F1F, custom: 1, e: 1, },
{ opcode: 0x0F20, ignore_mod: 1, e: 1, skip: 1, block_boundary: 1, }, // mov reg, creg
{ opcode: 0x0F21, ignore_mod: 1, e: 1, skip: 1, block_boundary: 1, }, // mov reg, dreg
{ opcode: 0x0F22, ignore_mod: 1, e: 1, skip: 1, block_boundary: 1, }, // mov creg, reg
{ opcode: 0x0F23, ignore_mod: 1, e: 1, skip: 1, block_boundary: 1, }, // mov dreg, reg
{ opcode: 0x0F24, skip: 1, block_boundary: 1, },
{ opcode: 0x0F25, skip: 1, block_boundary: 1, },
{ opcode: 0x0F26, skip: 1, block_boundary: 1, },
{ opcode: 0x0F27, skip: 1, block_boundary: 1, },
{ opcode: 0x0F30, skip: 1, block_boundary: 1, }, // wrmsr
{ opcode: 0x0F31, skip: 1, custom: 1, }, // rdtsc
{ opcode: 0x0F32, skip: 1, block_boundary: 1, }, // rdmsr
{ opcode: 0x0F33, skip: 1, block_boundary: 1, }, // rdpmc
{ opcode: 0x0F34, skip: 1, block_boundary: 1, no_next_instruction: 1, }, // sysenter
{ opcode: 0x0F35, skip: 1, block_boundary: 1, no_next_instruction: 1, }, // sysexit
{ opcode: 0x0F36, skip: 1, block_boundary: 1, }, // ud
{ opcode: 0x0F37, skip: 1, block_boundary: 1, }, // getsec
// sse3+
{ opcode: 0x0F38, skip: 1, block_boundary: 1, },
{ opcode: 0x0F39, skip: 1, block_boundary: 1, },
{ opcode: 0x0F3A, skip: 1, block_boundary: 1, },
{ opcode: 0x0F3B, skip: 1, block_boundary: 1, },
{ opcode: 0x0F3C, skip: 1, block_boundary: 1, },
{ opcode: 0x0F3D, skip: 1, block_boundary: 1, },
{ opcode: 0x0F3E, skip: 1, block_boundary: 1, },
{ opcode: 0x0F3F, skip: 1, block_boundary: 1, },
{ opcode: 0x0F40, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F41, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F42, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F43, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F44, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F45, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F46, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F47, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F48, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F49, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F4A, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F4B, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F4C, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F4D, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F4E, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F4F, e: 1, os: 1, custom: 1, },
{ opcode: 0x0F80, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F81, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F82, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F83, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F84, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F85, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F86, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F87, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F88, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F89, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F8A, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F8B, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F8C, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F8D, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F8E, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F8F, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, imm1632: 1, os: 1, custom: 1, skip: 1, },
{ opcode: 0x0F90, e: 1, custom: 1, },
{ opcode: 0x0F91, e: 1, custom: 1, },
{ opcode: 0x0F92, e: 1, custom: 1, },
{ opcode: 0x0F93, e: 1, custom: 1, },
{ opcode: 0x0F94, e: 1, custom: 1, },
{ opcode: 0x0F95, e: 1, custom: 1, },
{ opcode: 0x0F96, e: 1, custom: 1, },
{ opcode: 0x0F97, e: 1, custom: 1, },
{ opcode: 0x0F98, e: 1, custom: 1, },
{ opcode: 0x0F99, e: 1, custom: 1, },
{ opcode: 0x0F9A, e: 1, custom: 1, },
{ opcode: 0x0F9B, e: 1, custom: 1, },
{ opcode: 0x0F9C, e: 1, custom: 1, },
{ opcode: 0x0F9D, e: 1, custom: 1, },
{ opcode: 0x0F9E, e: 1, custom: 1, },
{ opcode: 0x0F9F, e: 1, custom: 1, },
{ opcode: 0x0FA0, os: 1, custom: 1, },
{ opcode: 0x0FA1, os: 1, block_boundary: 1, skip: 1, }, // pop fs: block_boundary since it uses non-raising cpu exceptions
{ opcode: 0x0FA2, skip: 1, },
{ opcode: 0x0FA8, os: 1, custom: 1, },
{ opcode: 0x0FA9, os: 1, block_boundary: 1, skip: 1, }, // pop gs
{ opcode: 0x0FA3, os: 1, e: 1, custom: 1, skip_mem: 1 }, // bt (can also index memory, but not supported by test right now)
{ opcode: 0x0FAB, os: 1, e: 1, custom: 1, skip_mem: 1 },
{ opcode: 0x0FB3, os: 1, e: 1, custom: 1, skip_mem: 1 },
{ opcode: 0x0FBB, os: 1, e: 1, custom: 1, skip_mem: 1 },
{ opcode: 0x0FBA, os: 1, e: 1, fixed_g: 4, imm8: 1, custom: 1 }, // bt
{ opcode: 0x0FBA, os: 1, e: 1, fixed_g: 5, imm8: 1, custom: 1 },
{ opcode: 0x0FBA, os: 1, e: 1, fixed_g: 6, imm8: 1, custom: 1 },
{ opcode: 0x0FBA, os: 1, e: 1, fixed_g: 7, imm8: 1, custom: 1 },
{ opcode: 0x0FBC, os: 1, e: 1, mask_flags: af, custom: 1 }, // bsf
{ opcode: 0x0FBD, os: 1, e: 1, mask_flags: af, custom: 1 },
// note: overflow flag only undefined if shift is > 1
{ opcode: 0x0FA4, os: 1, e: 1, custom: 1, imm8: 1, mask_flags: af | of, }, // shld
{ opcode: 0x0FA5, os: 1, e: 1, custom: 1, mask_flags: af | of, },
{ opcode: 0x0FAC, os: 1, e: 1, custom: 1, imm8: 1, mask_flags: af | of, },
{ opcode: 0x0FAD, os: 1, e: 1, custom: 1, mask_flags: af | of, },
{ opcode: 0x0FA6, skip: 1, block_boundary: 1, }, // ud
{ opcode: 0x0FA7, skip: 1, block_boundary: 1, }, // ud
{ opcode: 0x0FAA, skip: 1 },
{ opcode: 0x0FAE, e: 1, fixed_g: 0, reg_ud: 1, task_switch_test: 1, skip: 1, block_boundary: 1, }, // fxsave
{ opcode: 0x0FAE, e: 1, fixed_g: 1, reg_ud: 1, task_switch_test: 1, skip: 1, block_boundary: 1, }, // fxrstor
{ opcode: 0x0FAE, e: 1, fixed_g: 2, reg_ud: 1, sse: 1, skip: 1, block_boundary: 1, }, // ldmxcsr
{ opcode: 0x0FAE, e: 1, fixed_g: 3, reg_ud: 1, sse: 1, skip: 1, block_boundary: 1, }, // stmxcsr
{ opcode: 0x0FAE, e: 1, fixed_g: 4, reg_ud: 1, skip: 1, block_boundary: 1, }, // xsave (mem, not implemented)
{ opcode: 0x0FAE, e: 1, fixed_g: 5, skip: 1, custom: 1 }, // lfence (reg, only 0), xrstor (mem, not implemented)
{ opcode: 0x0FAE, e: 1, fixed_g: 6, skip: 1, block_boundary: 1, }, // mfence (reg, only 0), xsaveopt (mem, not implemented)
{ opcode: 0x0FAE, e: 1, fixed_g: 7, skip: 1, block_boundary: 1, }, // sfence (reg, only 0), clflush (mem)
{ opcode: 0x0FAF, os: 1, e: 1, mask_flags: af | zf, custom: 1, }, // imul
{ opcode: 0x0FB0, e: 1 }, // cmxchg
{ opcode: 0x0FB1, os: 1, e: 1, custom: 1 },
{ opcode: 0x0FC7, e: 1, fixed_g: 1, os: 1, reg_ud: 1, custom: 1 }, // cmpxchg8b (memory)
{ opcode: 0x0FC7, e: 1, fixed_g: 6, os: 1, mem_ud: 1, skip: 1, }, // rdrand
{ opcode: 0x0FB2, block_boundary: 1, os: 1, e: 1, skip: 1, }, // lss
{ opcode: 0x0FB4, block_boundary: 1, os: 1, e: 1, skip: 1, }, // lfs
{ opcode: 0x0FB5, block_boundary: 1, os: 1, e: 1, skip: 1, }, // lgs
{ opcode: 0x0FB6, os: 1, e: 1, custom: 1 }, // movzx
{ opcode: 0x0FB7, os: 1, e: 1, custom: 1 },
{ opcode: 0xF30FB8, os: 1, e: 1, custom: 1 }, // popcnt
{ opcode: 0x0FB8, os: 1, e: 1, block_boundary: 1, }, // ud
{ opcode: 0x0FB9, block_boundary: 1, }, // ud2
{ opcode: 0x0FBE, os: 1, e: 1, custom: 1 }, // movsx
{ opcode: 0x0FBF, os: 1, e: 1, custom: 1 },
{ opcode: 0x0FC0, e: 1, }, // xadd
{ opcode: 0x0FC1, os: 1, e: 1, custom: 1 },
{ opcode: 0x0FC8, custom: 1 }, // bswap
{ opcode: 0x0FC9, custom: 1 },
{ opcode: 0x0FCA, custom: 1 },
{ opcode: 0x0FCB, custom: 1 },
{ opcode: 0x0FCC, custom: 1 },
{ opcode: 0x0FCD, custom: 1 },
{ opcode: 0x0FCE, custom: 1 },
{ opcode: 0x0FCF, custom: 1 },
// mmx, sse
// - skipped or missing are sse3+
{ sse: 1, opcode: 0x0F10, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F10, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F10, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF20F10, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F11, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F11, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F11, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF20F11, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F12, e: 1 },
{ sse: 1, opcode: 0x660F12, reg_ud: 1, e: 1 },
{ sse: 1, opcode: 0xF20F12, e: 1, skip: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0xF30F12, e: 1, skip: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0x0F13, reg_ud: 1, e: 1 },
{ sse: 1, opcode: 0x660F13, reg_ud: 1, e: 1 },
{ sse: 1, opcode: 0x0F14, e: 1 },
{ sse: 1, opcode: 0x660F14, e: 1 },
{ sse: 1, opcode: 0x0F15, e: 1 },
{ sse: 1, opcode: 0x660F15, e: 1 },
{ sse: 1, opcode: 0x0F16, e: 1 },
{ sse: 1, opcode: 0x660F16, reg_ud: 1, e: 1 },
{ sse: 1, opcode: 0xF30F16, skip: 1, e: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0x0F17, reg_ud: 1, e: 1 },
{ sse: 1, opcode: 0x660F17, reg_ud: 1, e: 1 },
{ sse: 1, opcode: 0x0F28, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F28, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F29, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F29, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F2A, e: 1, },
{ sse: 1, opcode: 0x660F2A, e: 1, },
{ sse: 1, opcode: 0xF20F2A, e: 1, },
{ sse: 1, opcode: 0xF30F2A, e: 1, },
{ sse: 1, opcode: 0x0F2B, reg_ud: 1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F2B, reg_ud: 1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F2C, e: 1, },
{ sse: 1, opcode: 0x660F2C, e: 1, },
{ sse: 1, opcode: 0xF20F2C, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F2C, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F2D, e: 1, },
{ sse: 1, opcode: 0x660F2D, e: 1, },
{ sse: 1, opcode: 0xF20F2D, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F2D, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F2E, e: 1 },
{ sse: 1, opcode: 0x660F2E, e: 1 },
{ sse: 1, opcode: 0x0F2F, e: 1 },
{ sse: 1, opcode: 0x660F2F, e: 1 },
{ sse: 1, opcode: 0x0F50, mem_ud: 1, e: 1 },
{ sse: 1, opcode: 0x660F50, mem_ud: 1, e: 1 },
{ sse: 1, opcode: 0x0F51, e: 1 },
{ sse: 1, opcode: 0x660F51, e: 1 },
{ sse: 1, opcode: 0xF20F51, e: 1 },
{ sse: 1, opcode: 0xF30F51, e: 1 },
// approximation of 1/sqrt(x). Skipped because our approximation doesn't match intel's
{ sse: 1, opcode: 0x0F52, e: 1, skip: 1, },
{ sse: 1, opcode: 0xF30F52, e: 1, skip: 1, },
// reciprocal: approximation of 1/x. Skipped because our approximation doesn't match intel's
{ sse: 1, opcode: 0x0F53, e: 1, skip: 1, },
{ sse: 1, opcode: 0xF30F53, e: 1, skip: 1, },
{ sse: 1, opcode: 0x0F54, e: 1 },
{ sse: 1, opcode: 0x660F54, e: 1 },
{ sse: 1, opcode: 0x0F55, e: 1 },
{ sse: 1, opcode: 0x660F55, e: 1 },
{ sse: 1, opcode: 0x0F56, e: 1 },
{ sse: 1, opcode: 0x660F56, e: 1 },
{ sse: 1, opcode: 0x0F57, e: 1 },
{ sse: 1, opcode: 0x660F57, e: 1 },
{ sse: 1, opcode: 0x0F58, e: 1, },
{ sse: 1, opcode: 0x660F58, e: 1, },
{ sse: 1, opcode: 0xF20F58, e: 1, },
{ sse: 1, opcode: 0xF30F58, e: 1, },
{ sse: 1, opcode: 0x0F59, e: 1, },
{ sse: 1, opcode: 0x660F59, e: 1, },
{ sse: 1, opcode: 0xF20F59, e: 1, },
{ sse: 1, opcode: 0xF30F59, e: 1, },
{ sse: 1, opcode: 0x0F5A, e: 1, },
{ sse: 1, opcode: 0x660F5A, e: 1, },
{ sse: 1, opcode: 0xF20F5A, e: 1, },
{ sse: 1, opcode: 0xF30F5A, e: 1, },
{ sse: 1, opcode: 0x0F5B, e: 1, },
{ sse: 1, opcode: 0x660F5B, e: 1, },
// no F2 variant
{ sse: 1, opcode: 0xF30F5B, e: 1, },
{ sse: 1, opcode: 0x0F5C, e: 1, },
{ sse: 1, opcode: 0x660F5C, e: 1, },
{ sse: 1, opcode: 0xF20F5C, e: 1, },
{ sse: 1, opcode: 0xF30F5C, e: 1, },
{ sse: 1, opcode: 0x0F5D, e: 1, },
{ sse: 1, opcode: 0x660F5D, e: 1, },
{ sse: 1, opcode: 0xF20F5D, e: 1, },
{ sse: 1, opcode: 0xF30F5D, e: 1, },
{ sse: 1, opcode: 0x0F5E, e: 1, },
{ sse: 1, opcode: 0x660F5E, e: 1, },
{ sse: 1, opcode: 0xF20F5E, e: 1, },
{ sse: 1, opcode: 0xF30F5E, e: 1, },
{ sse: 1, opcode: 0x0F5F, e: 1, },
{ sse: 1, opcode: 0x660F5F, e: 1, },
{ sse: 1, opcode: 0xF20F5F, e: 1, },
{ sse: 1, opcode: 0xF30F5F, e: 1, },
{ sse: 1, opcode: 0x660F60, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F60, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F61, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F61, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F62, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F62, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F63, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F63, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F64, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F64, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F65, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F65, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F66, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F66, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F67, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F67, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F68, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F68, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F69, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F69, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F6A, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F6A, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F6B, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F6B, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F6C, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F6C, e: 1, block_boundary: 1, }, // ud
{ sse: 1, opcode: 0x660F6D, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F6D, e: 1, block_boundary: 1, }, // ud
{ sse: 1, opcode: 0x660F6E, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F6E, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F6F, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F6F, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F6F, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F70, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0x660F70, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0xF20F70, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F70, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0x0F71, e: 1, fixed_g: 2, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F71, e: 1, fixed_g: 2, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F71, e: 1, fixed_g: 4, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F71, e: 1, fixed_g: 4, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F71, e: 1, fixed_g: 6, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F71, e: 1, fixed_g: 6, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F72, e: 1, fixed_g: 2, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F72, e: 1, fixed_g: 2, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F72, e: 1, fixed_g: 4, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F72, e: 1, fixed_g: 4, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F72, e: 1, fixed_g: 6, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F72, e: 1, fixed_g: 6, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F73, e: 1, fixed_g: 2, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F73, e: 1, fixed_g: 2, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F73, e: 1, fixed_g: 3, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F73, e: 1, fixed_g: 6, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F73, e: 1, fixed_g: 6, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660F73, e: 1, fixed_g: 7, imm8: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0F74, e: 1, },
{ sse: 1, opcode: 0x660F74, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F75, e: 1, },
{ sse: 1, opcode: 0x660F75, e: 1, },
{ sse: 1, opcode: 0x0F76, e: 1, },
{ sse: 1, opcode: 0x660F76, e: 1, },
{ sse: 1, opcode: 0x0F77, skip: 1 }, // emms (skip as it breaks gdb printing of float registers)
// vmx instructions
{ opcode: 0x0F78, skip: 1, block_boundary: 1, },
{ opcode: 0x0F79, skip: 1, block_boundary: 1, },
{ opcode: 0x0F7A, skip: 1, block_boundary: 1, }, // ud
{ opcode: 0x0F7B, skip: 1, block_boundary: 1, }, // ud
{ sse: 1, opcode: 0x0F7C, skip: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0x0F7D, skip: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0x0F7E, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F7E, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F7E, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0F7F, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660F7F, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30F7F, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FC2, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0x660FC2, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0xF20FC2, e: 1, imm8: 1 },
{ sse: 1, opcode: 0xF30FC2, e: 1, imm8: 1 },
{ opcode: 0x0FC3, e: 1, custom: 1, reg_ud: 1, }, // movnti: Uses normal registers, hence not marked as sse
{ sse: 1, opcode: 0x0FC4, e: 1, imm8: 1 },
{ sse: 1, opcode: 0x660FC4, e: 1, imm8: 1 },
{ sse: 1, opcode: 0x0FC5, e: 1, mem_ud: 1, imm8: 1 },
{ sse: 1, opcode: 0x660FC5, e: 1, mem_ud: 1, imm8: 1, },
{ sse: 1, opcode: 0x0FC6, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0x660FC6, e: 1, imm8: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD0, skip: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0x0FD1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD2, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD2, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD3, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD3, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD4, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD4, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD5, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD5, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD6, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF20FD6, mem_ud: 1, e: 1 },
{ sse: 1, opcode: 0xF30FD6, mem_ud: 1, e: 1 },
{ sse: 1, opcode: 0x0FD6, e: 1, block_boundary: 1, }, // ud
{ sse: 1, opcode: 0x0FD7, e: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD7, e: 1, mem_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD8, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD8, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FD9, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FD9, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FDA, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FDA, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FDB, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FDB, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FDC, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FDC, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FDD, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FDD, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FDE, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FDE, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FDF, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FDF, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE0, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE0, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE2, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE2, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE3, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE3, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE4, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE4, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE5, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE5, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE6, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF20FE6, e: 1, custom: 1 },
{ sse: 1, opcode: 0xF30FE6, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE6, e: 1, block_boundary: 1, }, // ud
{ sse: 1, opcode: 0x0FE7, e: 1, reg_ud: 1 },
{ sse: 1, opcode: 0x660FE7, e: 1, reg_ud: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE8, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE8, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FE9, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FE9, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FEA, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FEA, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FEB, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FEB, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FEC, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FEC, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FED, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FED, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FEE, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FEE, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FEF, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FEF, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FF0, skip: 1, block_boundary: 1, }, // sse3
{ sse: 1, opcode: 0x0FF1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF1, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FF2, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF2, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FF3, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF3, e: 1, custom: 1, },
{ sse: 1, opcode: 0x0FF4, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF4, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FF5, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF5, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FF6, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF6, e: 1, custom: 1 },
// maskmovq (0FF7), maskmovdqu (660FF7) tested manually
// Generated tests don't setup EDI as required (yet)
{ sse: 1, opcode: 0x0FF7, mem_ud: 1, e: 1, custom: 1, skip: 1, },
{ sse: 1, opcode: 0x660FF7, mem_ud: 1, e: 1, custom: 1, skip: 1, },
{ sse: 1, opcode: 0x0FF8, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF8, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FF9, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FF9, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FFA, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FFA, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FFB, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FFB, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FFC, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FFC, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FFD, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FFD, e: 1, custom: 1 },
{ sse: 1, opcode: 0x0FFE, e: 1, custom: 1 },
{ sse: 1, opcode: 0x660FFE, e: 1, custom: 1 },
{ opcode: 0x0FFF, block_boundary: 1, }, // ud
];
for(let i = 0; i < 8; i++)
{
encodings.push.apply(encodings, [
{ opcode: 0x04 | i << 3, custom: 1, imm8: 1, },
{ opcode: 0x05 | i << 3, custom: 1, os: 1, imm1632: 1, },
{ opcode: 0x70 | i, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, os: 1, imm8s: 1, custom: 1, skip: 1, },
{ opcode: 0x78 | i, block_boundary: 1, no_block_boundary_in_interpreted: 1, jump_offset_imm: 1, conditional_jump: 1, os: 1, imm8s: 1, custom: 1, skip: 1, },
{ opcode: 0x80, e: 1, fixed_g: i, imm8: 1, custom: 1, },
{ opcode: 0x81, os: 1, e: 1, fixed_g: i, imm1632: 1, custom: 1, },
{ opcode: 0x82, e: 1, fixed_g: i, imm8: 1, custom: 1, },
{ opcode: 0x83, os: 1, e: 1, fixed_g: i, imm8s: 1, custom: 1, },
{ opcode: 0xB0 | i, custom: 1, imm8: 1, },
{ opcode: 0xB8 | i, custom: 1, os: 1, imm1632: 1, },
// note: overflow flag only undefined if shift is > 1
// note: the adjust flag is undefined for shifts > 0 and unaffected by rotates
{ opcode: 0xC0, e: 1, fixed_g: i, imm8: 1, mask_flags: of | af, custom: 1, },
{ opcode: 0xC1, os: 1, e: 1, fixed_g: i, imm8: 1, mask_flags: of | af, custom: 1, },
{ opcode: 0xD0, e: 1, fixed_g: i, mask_flags: af, custom: 1 },
{ opcode: 0xD1, os: 1, e: 1, fixed_g: i, mask_flags: af, custom: 1, },
{ opcode: 0xD2, e: 1, fixed_g: i, mask_flags: of | af, custom: 1 },
{ opcode: 0xD3, os: 1, e: 1, fixed_g: i, mask_flags: of | af, custom: 1, },
]);
}
encodings.sort((e1, e2) => {
let o1 = (e1.opcode & 0xFF00) === 0x0F00 ? e1.opcode & 0xFFFF : e1.opcode & 0xFF;
let o2 = (e2.opcode & 0xFF00) === 0x0F00 ? e2.opcode & 0xFFFF : e2.opcode & 0xFF;
return o1 - o2 || e1.fixed_g - e2.fixed_g;
});
module.exports = Object.freeze(encodings.map(entry => Object.freeze(entry)));

View file

@ -3,97 +3,133 @@
<title>Virtual x86</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta name="description" content="Run KolibriOS, Linux or Windows 98 in your browser">
<script src="build/v86_all.js"></script>
<link rel="stylesheet" href="v86.css">
<div>
<div id="boot_options">
<h4>Quickstart</h4>
<input type="button" value="ReactOS (32 MB)" id="start_reactos">
- Restored from snapshot<br>
<input type="button" value="Windows 95 (6.7 MB)" id="start_windows95">
- Restored from snapshot<br>
<input type="button" value="FreeBSD 10.2 (13.0 MB)" id="start_freebsd">
- Restored from snapshot<br>
<input type="button" value="Oberon (16.0 MB)" id="start_oberon">
- Native Oberon 2.3.6 (<a href="https://lists.inf.ethz.ch/pipermail/oberon/2013/006844.html">via</a>)<br>
<input type="button" value="Windows 98 (12.0 MB)" id="start_windows98">
- Including Minesweeper and audio, additional sectors are loaded as needed<br>
<input type="button" value="Arch Linux (10.1 MB)" id="start_archlinux">
- A complete Arch Linux restored from a snapshot, additional files are loaded as needed<br>
<input type="button" value="KolibriOS (1.4 MB)" id="start_kolibrios">
- Graphical OS, takes about 60 seconds to boot<br>
<input type="button" value="Linux 2.6 (5.4 MB)" id="start_linux26">
- With busybox, Lua interpreter and test cases, takes about 20 seconds to boot<br>
<input type="button" value="Linux 3.18 (8.3 MB)" id="start_linux3">
- With internet access, telnet, ping, wget and links. Takes about 60 seconds to boot. Run <code>udhcpc</code> for networking. Exchange files through <code>/mnt/</code>.<br>
<input type="button" value="Windows 1.01 (1.4 MB)" id="start_windows1">
- Takes 1 second to boot<br>
<input type="button" value="MS-DOS 6.22 (3.4 MB)" id="start_msdos">
- Takes 10 seconds to boot. With Enhanced Tools, QBasic and everything from the FreeDOS image<br>
<input type="button" value="FreeDOS (0.7 MB)" id="start_freedos">
- With nasm, vim, debug.com, some games and demos, takes 1 second to boot<br>
<input type="button" value="OpenBSD (1.4 MB)" id="start_openbsd">
- Random boot floppy, takes about 60 seconds<br>
<input type="button" value="Solar OS (1.4 MB)" id="start_solos">
- Simple graphical OS<br>
<input type="button" value="Bootchess (0.2 MB)" id="start_bootchess">
- A tiny chess program written in the boot sector
<h4>Select profile</h4>
<table id="oses">
<tr id="start_archlinux"><td><a href="?profile=archlinux">Arch Linux</a> <small>12 MB</small></td><td>
A complete Arch Linux restored from a snapshot, additional files are loaded as needed</td></tr>
<tr id="start_dsl"><td><a href="?profile=dsl">Damn Small Linux</a> <small>50 MB</small></td><td>
Graphical Linux with 2.4 kernel, Firefox 2.0 and more. Takes 1 minute to boot.</td></tr>
<tr id="start_buildroot"><td><a href="?profile=buildroot">Buildroot Linux</a> <small>5.0 MB</small></td><td>
Minimal Linux with busybox, Lua, tests, internet access, ping, telnet and curl. Exchange files through <code>/mnt/</code>.</td></tr>
<tr id="start_reactos"><td><a href="?profile=reactos">ReactOS</a> <small>18 MB</small></td><td>
Windows-compatible OS. Restored from snapshot</td></tr>
<tr id="start_windows98"><td><a href="?profile=windows98">Windows 98</a> <small>9.7 MB</small></td><td>
Including Minesweeper and Internet Explorer with internet access. Additional sectors are loaded as needed.</td></tr>
<tr id="start_windows95"><td><a href="?profile=windows95">Windows 95</a> <small>4.6 MB</small></td><td>
Restored from snapshot</td></tr>
<tr id="start_windows1"><td><a href="?profile=windows1">Windows 1.01</a> <small>0.6 MB</small></td><td>
Takes 1 second to boot</td></tr>
<tr id="start_msdos"><td><a href="?profile=msdos">MS-DOS 6.22</a> <small>4.4 MB</small></td><td>
With Enhanced Tools, QBasic, vim, games and demos.</td></tr>
<tr id="start_freedos"><td><a href="?profile=freedos">FreeDOS</a> <small>0.5 MB</small></td><td>
With nasm, vim, debug.com, Rogue, some games and demos.</td></tr>
<tr id="start_freebsd"><td><a href="?profile=freebsd">FreeBSD</a> <small>17 MB</small></td><td>
FreeBSD 12.0 base install. Restored from snapshot.</td></tr>
<tr id="start_openbsd"><td><a href="?profile=openbsd">OpenBSD</a> <small>12 MB</small></td><td>
OpenBSD 6.6 base install. Restored from snapshot.</td></tr>
<tr id="start_9front"><td><a href="?profile=9front">9front</a> <small>4.4 MB</small></td><td>
A Plan 9 fork.</td></tr>
<tr id="start_haiku"><td><a href="?profile=haiku">Haiku</a> <small>46 MB</small></td><td>
Restored from snapshot. Includes network support.</td></tr>
<tr id="start_oberon"><td><a href="?profile=oberon">Oberon</a> <small>1.2 MB</small></td><td>
Native Oberon 2.3.6</td></tr>
<tr id="start_kolibrios"><td><a href="?profile=kolibrios">KolibriOS</a> <small>1.4 MB</small></td><td>
Fast graphical OS written in Assembly</td></tr>
<tr id="start_qnx"><td><a href="?profile=qnx">QNX</a> <small>1.3 MB</small></td><td>
QNX 4.05 Demo disk (no networking)</td></tr>
<tr id="start_solos"><td><a href="?profile=solos">Solar OS</a> <small>0.3 MB</small></td><td>
Simple graphical OS</td></tr>
<tr id="start_bootchess"><td><a href="?profile=bootchess">Bootchess</a> <small>0.1 MB</small></td><td>
A tiny chess program written in the boot sector</td></tr>
</table>
<br>
<hr>
<h4>Setup</h4>
<table>
<tr>
<td width="350">CD image</td>
<td width="350"><label for="cd_image">CD image</label></td>
<td>
<input type="file" id="cd_image">
</td>
</tr>
<tr>
<td>Floppy disk image</td>
<td><label for="floppy_image">Floppy disk image</label></td>
<td> <input type="file" id="floppy_image"><br></td>
</tr>
<tr>
<td>Hard drive disk image</td>
<td><label for="hda_image">Hard drive disk image</label></td>
<td><input type="file" id="hda_image"><br></td>
</tr>
<!--
<tr>
<td>Multiboot kernel image (experimental)</td>
<td><label for="multiboot_image">Multiboot kernel image (experimental)</td>
<td><input type="file" id="multiboot_image"><br></td>
</tr>
-->
<tr>
<td colspan="2"><small><small>Disk images are not uploaded to the server</small></small><hr></td>
<td colspan="2"><small>Disk images are not uploaded to the server</small><hr></td>
</tr>
<tr>
<td>Memory size</td>
<td><label for="memory_size">Memory size</label></td>
<td>
<input id="memory_size" type="number" value="128" min="16" max="2048" step="16"> MB<br>
</td>
</tr>
<tr>
<td>Video Memory size</td>
<td><label for="video_memory_size">Video Memory size</label></td>
<td>
<input id="video_memory_size" type="number" value="8" min="1" max="128" step="1"> MB<br>
</td>
</tr>
<tr>
<td><label for="networking_proxy">Networking proxy (leave blank to disable)</label></td>
<td>
<input id="networking_proxy" type="text" value="wss://relay.widgetry.org/">
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<tr>
<td>Boot order</td>
<td><label for="disable_audio">Disable audio</label></td>
<td>
<input id="disable_audio" type="checkbox"><br>
</td>
</tr>
<tr>
<td><label for="enable_acpi">Enable ACPI (experimental)</label></td>
<td>
<input id="enable_acpi" type="checkbox"><br>
</td>
</tr>
<tr>
<td colspan="2"><hr></td>
</tr>
<tr>
<td><label for="boot_order">Boot order</label></td>
<td>
<select id="boot_order">
<option value="213">CD / Floppy / Hard Disk</option>
@ -152,7 +188,6 @@
<pre style="display: none" id="loading"></pre>
</div>
<div id="screen_container" style="display: none">
<div id="screen"></div>
<canvas id="vga"></canvas>
@ -164,8 +199,8 @@
<div id="runtime_infos" style="display: none">
Running: <span id="running_time">0s</span> <br>
Speed: <span id="speed">0</span>kIPS<br>
Avg speed: <span id="avg_speed">0</span>kIPS<br>
Speed: <span id="speed">0</span> mIPS<br>
Avg speed: <span id="avg_speed">0</span> mIPS<br>
<br>
<div id="info_storage" style="display: none">
<b>IDE device (HDA or CDROM)</b><br>
@ -180,7 +215,7 @@
<b>9p Filesystem</b><br>
Bytes read: <span id="info_filesystem_bytes_read">0</span><br>
Bytes written: <span id="info_filesystem_bytes_written">0</span><br>
Last file: <span id="info_filesystem_last_file" style="word-wrap: break-word"></span><br>
<div style="white-space: nowrap; overflow-x: hidden">Last file: <span id="info_filesystem_last_file"></span></div>
Status: <span id="info_filesystem_status"></span><br>
<br>
</div>
@ -196,13 +231,12 @@
BPP: <span id="info_bpp">-</span><br>
<br>
Mouse: <span id="info_mouse_enabled">No</span><br>
<!-- Keyboard: <span id="info_keyboard_enabled">-</span><br> -->
<div id="description" style="display: none"></div>
<div id="description" style="display: none"><br></div>
</div>
<div id="filesystem_panel" style="display: none">
<label title="Files will appear in / of the 9p filesystem">
<label>
Send files to emulator<br>
<input type="file" id="filesystem_send_file" multiple>
</label>
@ -213,15 +247,12 @@
</label>
</div>
<br style="clear:both"><br>
<br style="clear: both"><br>
<textarea cols="40" rows="12" id="serial" style="display: none">This is the serial console. Whatever you type or paste here will be sent to COM1.
<div id="terminal"></div>
In Linux it can be accessed with `cat /dev/ttyS0`
</textarea>
<br style="clear:both">
<code>Version: <a href="https://github.com/copy/v86/commits/53caefc">53caefc</a> (Jan 22, 2016 23:01)</a></code>
<br style="clear: both">
<code>Version: <a href="https://github.com/copy/v86/commits/b40c8ad2">b40c8ad2</a> (Apr 23, 2020 01:04)</code>
<hr>
<a href="debug.html">Enable debug</a>
@ -231,7 +262,3 @@ In Linux it can be accessed with `cat /dev/ttyS0`
<a href="https://github.com/copy/v86">Project on Github</a>
&mdash;
<a href="https://github.com/copy/v86#compatibility">Compatibility</a>
&mdash;
<a href="./screenshots/">Screenshots</a>

546
lib/9p.js
View file

@ -1,22 +1,26 @@
// -------------------------------------------------
// --------------------- 9P ------------------------
// -------------------------------------------------
// Implementation of the 9p filesystem device following the
// Implementation of the 9p filesystem device following the
// 9P2000.L protocol ( https://code.google.com/p/diod/wiki/protocol )
"use strict";
// Feature bit (bit position) for mount tag.
const VIRTIO_9P_F_MOUNT_TAG = 0;
// Assumed max tag length in bytes.
const VIRTIO_9P_MAX_TAGLEN = 254;
// TODO
// flush
// lock?
// correct hard links
var EPERM = 1; /* Operation not permitted */
var ENOENT = 2; /* No such file or directory */
var EEXIST = 17; /* File exists */
var EINVAL = 22; /* Invalid argument */
var ENOTSUPP = 524; /* Operation is not supported */
var EOPNOTSUPP = 95; /* Operation is not supported */
var ENOTEMPTY = 39; /* Directory not empty */
var EPROTO = 71 /* Protocol error */
var EPROTO = 71; /* Protocol error */
var P9_SETATTR_MODE = 0x00000001;
var P9_SETATTR_UID = 0x00000002;
@ -43,29 +47,40 @@ var P9_STAT_MODE_SETUID = 0x00080000;
var P9_STAT_MODE_SETGID = 0x00040000;
var P9_STAT_MODE_SETVTX = 0x00010000;
const P9_LOCK_TYPE_RDLCK = 0;
const P9_LOCK_TYPE_WRLCK = 1;
const P9_LOCK_TYPE_UNLCK = 2;
const P9_LOCK_TYPES = Object.freeze(["shared", "exclusive", "unlock"]);
const P9_LOCK_FLAGS_BLOCK = 1;
const P9_LOCK_FLAGS_RECLAIM = 2;
const P9_LOCK_SUCCESS = 0;
const P9_LOCK_BLOCKED = 1;
const P9_LOCK_ERROR = 2;
const P9_LOCK_GRACE = 3;
var FID_NONE = -1;
var FID_INODE = 1;
var FID_XATTR = 2;
/**
* @constructor
/**
* @constructor
*
* @param {FS} filesystem
* @param {CPU} cpu
*/
function Virtio9p(filesystem, bus) {
/** @const @type {FS} */
function Virtio9p(filesystem, cpu, bus) {
/** @type {FS} */
this.fs = filesystem;
/** @const @type {BusConnector} */
this.bus = bus;
this.SendReply = function(x, y) {};
this.deviceid = 0x9; // 9p filesystem
this.hostfeature = 0x1; // mountpoint
//this.configspace = [0x0, 0x4, 0x68, 0x6F, 0x73, 0x74]; // length of string and "host" string
//this.configspace = [0x0, 0x9, 0x2F, 0x64, 0x65, 0x76, 0x2F, 0x72, 0x6F, 0x6F, 0x74 ]; // length of string and "/dev/root" string
this.configspace = new Uint8Array([0x6, 0x0, 0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]); // length of string and "host9p" string
this.configspace_tagname = [0x68, 0x6F, 0x73, 0x74, 0x39, 0x70]; // "host9p" string
this.configspace_taglen = this.configspace_tagname.length; // num bytes
this.VERSION = "9P2000.L";
this.BLOCKSIZE = 8192; // Let's define one page.
this.msize = 8192; // maximum message size
@ -73,48 +88,145 @@ function Virtio9p(filesystem, bus) {
this.replybuffersize = 0;
this.fids = [];
/** @type {VirtIO} */
this.virtio = new VirtIO(cpu,
{
name: "virtio-9p",
pci_id: 0x06 << 3,
device_id: 0x1049,
subsystem_device_id: 9,
common:
{
initial_port: 0xA800,
queues:
[
{
size_supported: 32,
notify_offset: 0,
},
],
features:
[
VIRTIO_9P_F_MOUNT_TAG,
VIRTIO_F_VERSION_1,
VIRTIO_F_RING_EVENT_IDX,
VIRTIO_F_RING_INDIRECT_DESC,
],
on_driver_ok: () => {},
},
notification:
{
initial_port: 0xA900,
single_handler: false,
handlers:
[
(queue_id) =>
{
if(queue_id !== 0)
{
dbg_assert(false, "Virtio9P Notified for non-existent queue: " + queue_id +
" (expected queue_id of 0)");
return;
}
while(this.virtqueue.has_request())
{
const bufchain = this.virtqueue.pop_request();
this.ReceiveRequest(bufchain);
}
this.virtqueue.notify_me_after(0);
// Don't flush replies here: async replies are not completed yet.
},
],
},
isr_status:
{
initial_port: 0xA700,
},
device_specific:
{
initial_port: 0xA600,
struct:
[
{
bytes: 2,
name: "mount tag length",
read: () => this.configspace_taglen,
write: data => { /* read only */ },
},
].concat(v86util.range(VIRTIO_9P_MAX_TAGLEN).map(index =>
({
bytes: 1,
name: "mount tag name " + index,
// Note: configspace_tagname may have changed after set_state
read: () => this.configspace_tagname[index] || 0,
write: data => { /* read only */ },
})
)),
},
});
this.virtqueue = this.virtio.queues[0];
}
Virtio9p.prototype.get_state = function()
{
var state = [];
state[0] = this.deviceid;
state[1] = this.hostfeature;
state[2] = this.configspace;
state[0] = this.configspace_tagname;
state[1] = this.configspace_taglen;
state[2] = this.virtio;
state[3] = this.VERSION;
state[4] = this.BLOCKSIZE;
state[5] = this.msize;
state[6] = this.replybuffer;
state[7] = this.replybuffersize;
state[8] = this.fids.map(function(f) { return [f.inodeid, f.type, f.uid] });
state[8] = this.fids.map(function(f) { return [f.inodeid, f.type, f.uid, f.dbg_name]; });
state[9] = this.fs;
return state;
};
Virtio9p.prototype.set_state = function(state)
{
this.deviceid = state[0];
this.hostfeature = state[1];
this.configspace = state[2];
this.configspace_tagname = state[0];
this.configspace_taglen = state[1];
this.virtio.set_state(state[2]);
this.virtqueue = this.virtio.queues[0];
this.VERSION = state[3];
this.BLOCKSIZE = state[4];
this.msize = state[5];
this.replybuffer = state[6];
this.replybuffersize = state[7];
this.fids = state[8].map(function(f) { return { inodeid: f[0], type: f[1], uid: f[2] } });
this.fids = state[8].map(function(f)
{
return { inodeid: f[0], type: f[1], uid: f[2], dbg_name: f[3] };
});
this.fs.set_state(state[9]);
};
Virtio9p.prototype.Createfid = function(inode, type, uid) {
return {inodeid: inode, type: type, uid: uid};
}
// Note: dbg_name is only used for debugging messages and may not be the same as the filename,
// since it is not synchronised with renames done outside of 9p. Hard-links, linking and unlinking
// operations also mean that having a single filename no longer makes sense.
// Set TRACK_FILENAMES = true (in config.js) to sync dbg_name during 9p renames.
Virtio9p.prototype.Createfid = function(inodeid, type, uid, dbg_name) {
return {inodeid, type, uid, dbg_name};
};
Virtio9p.prototype.update_dbg_name = function(idx, newname)
{
for(const fid of this.fids)
{
if(fid.inodeid === idx) fid.dbg_name = newname;
}
};
Virtio9p.prototype.Reset = function() {
this.fids = [];
}
};
Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
dbg_assert(payloadsize >= 0, "9P: Negative payload size");
marshall.Marshall(["w", "b", "h"], [payloadsize+7, id+1, tag], this.replybuffer, 0);
if ((payloadsize+7) >= this.replybuffer.length) {
message.Debug("Error in 9p: payloadsize exceeds maximum length");
@ -123,16 +235,28 @@ Virtio9p.prototype.BuildReply = function(id, tag, payloadsize) {
// this.replybuffer[7+i] = payload[i];
this.replybuffersize = payloadsize+7;
return;
}
};
Virtio9p.prototype.SendError = function (tag, errormsg, errorcode) {
//var size = marshall.Marshall(["s", "w"], [errormsg, errorcode], this.replybuffer, 7);
var size = marshall.Marshall(["w"], [errorcode], this.replybuffer, 7);
this.BuildReply(6, tag, size);
}
};
Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
var header = marshall.Unmarshall2(["w", "b", "h"], GetByte);
Virtio9p.prototype.SendReply = function (bufchain) {
dbg_assert(this.replybuffersize >= 0, "9P: Negative replybuffersize");
bufchain.set_next_blob(this.replybuffer.subarray(0, this.replybuffersize));
this.virtqueue.push_reply(bufchain);
this.virtqueue.flush_replies();
};
Virtio9p.prototype.ReceiveRequest = async function (bufchain) {
// TODO: split into header + data blobs to avoid unnecessary copying.
const buffer = new Uint8Array(bufchain.length_readable);
bufchain.get_next_blob(buffer);
const state = { offset : 0 };
var header = marshall.Unmarshall(["w", "b", "h"], buffer, state);
var size = header[0];
var id = header[1];
var tag = header[2];
@ -149,86 +273,86 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
req[2] = Math.floor(space/req[1]); // free blocks
req[3] = req[2] - Math.floor(size/req[1]); // free blocks in fs
req[4] = req[2] - Math.floor(size/req[1]); // free blocks avail to non-superuser
req[5] = this.fs.inodes.length; // total number of inodes
req[6] = 1024*1024;
req[5] = this.fs.CountUsedInodes(); // total number of inodes
req[6] = this.fs.CountFreeInodes();
req[7] = 0; // file system id?
req[8] = 256; // maximum length of filenames
size = marshall.Marshall(["w", "w", "d", "d", "d", "d", "d", "d", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 112: // topen
case 12: // tlopen
var req = marshall.Unmarshall2(["w", "w"], GetByte);
var req = marshall.Unmarshall(["w", "w"], buffer, state);
var fid = req[0];
var mode = req[1];
message.Debug("[open] fid=" + fid + ", mode=" + mode);
var idx = this.fids[fid].inodeid;
var inode = this.fs.GetInode(idx);
message.Debug("file open " + inode.name);
message.Debug("file open " + this.fids[fid].dbg_name);
//if (inode.status == STATUS_LOADING) return;
var ret = this.fs.OpenInode(idx, mode);
this.fs.AddEvent(this.fids[fid].inodeid,
function() {
message.Debug("file opened " + inode.name + " tag:"+tag);
message.Debug("file opened " + this.fids[fid].dbg_name + " tag:"+tag);
var req = [];
req[0] = inode.qid;
req[1] = this.msize - 24;
marshall.Marshall(["Q", "w"], req, this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
this.SendReply(0, index);
this.SendReply(bufchain);
}.bind(this)
);
break;
case 70: // link (just copying)
var req = marshall.Unmarshall2(["w", "w", "s"], GetByte);
case 70: // link
var req = marshall.Unmarshall(["w", "w", "s"], buffer, state);
var dfid = req[0];
var fid = req[1];
var name = req[2];
message.Debug("[link] dfid=" + dfid + ", name=" + name);
var inode = this.fs.CreateInode();
var inodetarget = this.fs.GetInode(this.fids[fid].inodeid);
var targetdata = this.fs.inodedata[this.fids[fid].inodeid];
//inode = inodetarget;
inode.mode = inodetarget.mode;
inode.size = inodetarget.size;
inode.symlink = inodetarget.symlink;
var data = this.fs.inodedata[this.fs.inodes.length] = new Uint8Array(inode.size);
for(var i=0; i<inode.size; i++) {
data[i] = targetdata[i];
var ret = this.fs.Link(this.fids[dfid].inodeid, this.fids[fid].inodeid, name);
if(ret < 0)
{
let error_message = "";
if(ret === -EPERM) error_message = "Operation not permitted";
else
{
error_message = "Unknown error: " + (-ret);
dbg_assert(false, "[link]: Unexpected error code: " + (-ret));
}
this.SendError(tag, error_message, -ret);
this.SendReply(bufchain);
break;
}
inode.name = name;
inode.parentid = this.fids[dfid].inodeid;
this.fs.PushInode(inode);
//inode.uid = inodetarget.uid;
//inode.gid = inodetarget.gid;
//inode.mode = inodetarget.mode | S_IFLNK;
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 16: // symlink
var req = marshall.Unmarshall2(["w", "s", "s", "w"], GetByte);
var req = marshall.Unmarshall(["w", "s", "s", "w"], buffer, state);
var fid = req[0];
var name = req[1];
var symgt = req[2];
var gid = req[3];
message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
message.Debug("[symlink] fid=" + fid + ", name=" + name + ", symgt=" + symgt + ", gid=" + gid);
var idx = this.fs.CreateSymlink(name, this.fids[fid].inodeid, symgt);
var inode = this.fs.GetInode(idx);
inode.uid = this.fids[fid].uid;
inode.gid = gid;
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 18: // mknod
var req = marshall.Unmarshall2(["w", "s", "w", "w", "w", "w"], GetByte);
var req = marshall.Unmarshall(["w", "s", "w", "w", "w", "w"], buffer, state);
var fid = req[0];
var name = req[1];
var mode = req[2];
@ -243,28 +367,28 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
inode.gid = gid;
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 22: // TREADLINK
var req = marshall.Unmarshall2(["w"], GetByte);
var req = marshall.Unmarshall(["w"], buffer, state);
var fid = req[0];
message.Debug("[readlink] fid=" + fid);
var inode = this.fs.GetInode(this.fids[fid].inodeid);
message.Debug("[readlink] fid=" + fid + " name=" + this.fids[fid].dbg_name + " target=" + inode.symlink);
size = marshall.Marshall(["s"], [inode.symlink], this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 72: // tmkdir
var req = marshall.Unmarshall2(["w", "s", "w", "w"], GetByte);
var req = marshall.Unmarshall(["w", "s", "w", "w"], buffer, state);
var fid = req[0];
var name = req[1];
var mode = req[2];
var gid = req[3];
message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
message.Debug("[mkdir] fid=" + fid + ", name=" + name + ", mode=" + mode + ", gid=" + gid);
var idx = this.fs.CreateDirectory(name, this.fids[fid].inodeid);
var inode = this.fs.GetInode(idx);
inode.mode = mode | S_IFDIR;
@ -272,51 +396,85 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
inode.gid = gid;
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 14: // tlcreate
var req = marshall.Unmarshall2(["w", "s", "w", "w", "w"], GetByte);
var req = marshall.Unmarshall(["w", "s", "w", "w", "w"], buffer, state);
var fid = req[0];
var name = req[1];
var flags = req[2];
var mode = req[3];
var gid = req[4];
message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
this.bus.send("9p-create", [name, this.fids[fid].inodeid]);
message.Debug("[create] fid=" + fid + ", name=" + name + ", flags=" + flags + ", mode=" + mode + ", gid=" + gid);
var idx = this.fs.CreateFile(name, this.fids[fid].inodeid);
this.fids[fid].inodeid = idx;
this.fids[fid].type = FID_INODE;
this.fids[fid].dbg_name = name;
var inode = this.fs.GetInode(idx);
inode.uid = this.fids[fid].uid;
inode.gid = gid;
inode.mode = mode;
marshall.Marshall(["Q", "w"], [inode.qid, this.msize - 24], this.replybuffer, 7);
this.BuildReply(id, tag, 13+4);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 52: // lock always suceed
message.Debug("lock file\n");
marshall.Marshall(["w"], [0], this.replybuffer, 7);
case 52: // lock
var req = marshall.Unmarshall(["w", "b", "w", "d", "d", "w", "s"], buffer, state);
var fid = req[0];
var flags = req[2];
var lock_length = req[4] === 0 ? Infinity : req[4];
var lock_request = this.fs.DescribeLock(req[1], req[3], lock_length, req[5], req[6]);
message.Debug("[lock] fid=" + fid +
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
var ret = this.fs.Lock(this.fids[fid].inodeid, lock_request, flags);
marshall.Marshall(["b"], [ret], this.replybuffer, 7);
this.BuildReply(id, tag, 1);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
/*
case 54: // getlock
break;
*/
var req = marshall.Unmarshall(["w", "b", "d", "d", "w", "s"], buffer, state);
var fid = req[0];
var lock_length = req[3] === 0 ? Infinity : req[3];
var lock_request = this.fs.DescribeLock(req[1], req[2], lock_length, req[4], req[5]);
message.Debug("[getlock] fid=" + fid +
", type=" + P9_LOCK_TYPES[lock_request.type] + ", start=" + lock_request.start +
", length=" + lock_request.length + ", proc_id=" + lock_request.proc_id);
var ret = this.fs.GetLock(this.fids[fid].inodeid, lock_request);
if(!ret)
{
ret = lock_request;
ret.type = P9_LOCK_TYPE_UNLCK;
}
var ret_length = ret.length === Infinity ? 0 : ret.length;
size = marshall.Marshall(["b", "d", "d", "w", "s"],
[ret.type, ret.start, ret_length, ret.proc_id, ret.client_id],
this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(bufchain);
break;
case 24: // getattr
var req = marshall.Unmarshall2(["w", "d"], GetByte);
var req = marshall.Unmarshall(["w", "d"], buffer, state);
var fid = req[0];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
message.Debug("[getattr]: fid=" + fid + " name=" + inode.name + " request mask=" + req[1]);
message.Debug("[getattr]: fid=" + fid + " name=" + this.fids[fid].dbg_name + " request mask=" + req[1]);
if(!inode || inode.status === STATUS_UNLINKED)
{
message.Debug("getattr: unlinked");
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
}
req[0] |= 0x1000; // P9_STATS_GEN
@ -324,15 +482,15 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
req[0] = req[1]; // request mask
req[1] = inode.qid;
req[2] = inode.mode;
req[2] = inode.mode;
req[3] = inode.uid; // user id
req[4] = inode.gid; // group id
req[5] = inode.nlinks; // number of hard links
req[6] = (inode.major<<8) | (inode.minor); // device id low
req[7] = inode.size; // size low
req[8] = this.BLOCKSIZE;
req[9] = Math.floor(inode.size/512+1);; // blk size low
req[9] = Math.floor(inode.size/512+1); // blk size low
req[10] = inode.atime; // atime
req[11] = 0x0;
req[12] = inode.mtime; // mtime
@ -340,14 +498,14 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
req[14] = inode.ctime; // ctime
req[15] = 0x0;
req[16] = 0x0; // btime
req[17] = 0x0;
req[17] = 0x0;
req[18] = 0x0; // st_gen
req[19] = 0x0; // data_version
marshall.Marshall([
"d", "Q",
"w",
"w", "w",
"d", "d",
"d", "Q",
"w",
"w", "w",
"d", "d",
"d", "d", "d",
"d", "d", // atime
"d", "d", // mtime
@ -356,20 +514,20 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
"d", "d",
], req, this.replybuffer, 7);
this.BuildReply(id, tag, 8 + 13 + 4 + 4+ 4 + 8*15);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 26: // setattr
var req = marshall.Unmarshall2(["w", "w",
"w", // mode
var req = marshall.Unmarshall(["w", "w",
"w", // mode
"w", "w", // uid, gid
"d", // size
"d", "d", // atime
"d", "d"] // mtime
, GetByte);
"d", "d", // mtime
], buffer, state);
var fid = req[0];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" +inode.name);
message.Debug("[setattr]: fid=" + fid + " request mask=" + req[1] + " name=" + this.fids[fid].dbg_name);
if (req[1] & P9_SETATTR_MODE) {
inode.mode = req[2];
}
@ -395,33 +553,33 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
inode.mtime = req[8];
}
if (req[1] & P9_SETATTR_SIZE) {
this.fs.ChangeSize(this.fids[fid].inodeid, req[5]);
await this.fs.ChangeSize(this.fids[fid].inodeid, req[5]);
}
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 50: // fsync
var req = marshall.Unmarshall2(["w", "d"], GetByte);
var req = marshall.Unmarshall(["w", "d"], buffer, state);
var fid = req[0];
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 40: // TREADDIR
case 116: // read
var req = marshall.Unmarshall2(["w", "d", "w"], GetByte);
var req = marshall.Unmarshall(["w", "d", "w"], buffer, state);
var fid = req[0];
var offset = req[1];
var count = req[2];
var inode = this.fs.GetInode(this.fids[fid].inodeid);
if (id == 40) message.Debug("[treaddir]: fid=" + fid + " offset=" + offset + " count=" + count);
if (id == 116) message.Debug("[read]: fid=" + fid + " (" + inode.name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
if (id == 116) message.Debug("[read]: fid=" + fid + " (" + this.fids[fid].dbg_name + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
if(!inode || inode.status === STATUS_UNLINKED)
{
message.Debug("read/treaddir: unlinked");
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
}
if (this.fids[fid].type == FID_XATTR) {
@ -430,65 +588,101 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
this.replybuffer[7+4+i] = inode.caps[offset+i];
marshall.Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4 + count);
this.SendReply(0, index);
this.SendReply(bufchain);
} else {
var file = this.fs.inodes[this.fids[fid].inodeid];
this.bus.send("9p-read-start");
this.fs.OpenInode(this.fids[fid].inodeid, undefined);
this.fs.AddEvent(this.fids[fid].inodeid,
function() {
this.bus.send("9p-read-end", [file.name, count]);
const inodeid = this.fids[fid].inodeid;
if (inode.size < offset+count) count = inode.size - offset;
var data = this.fs.inodedata[this.fids[fid].inodeid];
if(data) {
for(var i=0; i<count; i++)
this.replybuffer[7+4+i] = data[offset+i];
}
marshall.Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4 + count);
this.SendReply(0, index);
}.bind(this)
);
if (inode.size < offset+count) count = inode.size - offset;
else if(id == 40)
{
// for directories, return whole number of dir-entries.
count = this.fs.RoundToDirentry(inodeid, offset + count) - offset;
}
if(offset > inode.size)
{
// offset can be greater than available - should return count of zero.
// See http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor30
count = 0;
}
this.bus.send("9p-read-start", [this.fids[fid].dbg_name]);
const data = await this.fs.Read(inodeid, offset, count);
this.bus.send("9p-read-end", [this.fids[fid].dbg_name, count]);
if(data) {
this.replybuffer.set(data, 7 + 4);
}
marshall.Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4 + count);
this.SendReply(bufchain);
}
break;
case 118: // write
var req = marshall.Unmarshall2(["w", "d", "w"], GetByte);
var req = marshall.Unmarshall(["w", "d", "w"], buffer, state);
var fid = req[0];
var offset = req[1];
var count = req[2];
message.Debug("[write]: fid=" + fid + " (" + this.fs.inodes[this.fids[fid].inodeid].name + ") offset=" + offset + " count=" + count);
this.fs.Write(this.fids[fid].inodeid, offset, count, GetByte);
var file = this.fs.inodes[this.fids[fid].inodeid];
this.bus.send("9p-write-end", [file.name, count]);
const filename = this.fids[fid].dbg_name;
message.Debug("[write]: fid=" + fid + " (" + filename + ") offset=" + offset + " count=" + count + " fidtype=" + this.fids[fid].type);
if(this.fids[fid].type === FID_XATTR)
{
// XXX: xattr not supported yet. Ignore write.
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
this.SendReply(bufchain);
break;
}
else
{
// XXX: Size of the subarray is unchecked
await this.fs.Write(this.fids[fid].inodeid, offset, count, buffer.subarray(state.offset));
}
this.bus.send("9p-write-end", [filename, count]);
marshall.Marshall(["w"], [count], this.replybuffer, 7);
this.BuildReply(id, tag, 4);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 74: // RENAMEAT
var req = marshall.Unmarshall2(["w", "s", "w", "s"], GetByte);
var req = marshall.Unmarshall(["w", "s", "w", "s"], buffer, state);
var olddirfid = req[0];
var oldname = req[1];
var newdirfid = req[2];
var newname = req[3];
message.Debug("[renameat]: oldname=" + oldname + " newname=" + newname);
var ret = this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
if (ret == false) {
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(0, index);
var ret = await this.fs.Rename(this.fids[olddirfid].inodeid, oldname, this.fids[newdirfid].inodeid, newname);
if (ret < 0) {
let error_message = "";
if(ret === -ENOENT) error_message = "No such file or directory";
else if(ret === -EPERM) error_message = "Operation not permitted";
else if(ret === -ENOTEMPTY) error_message = "Directory not empty";
else
{
error_message = "Unknown error: " + (-ret);
dbg_assert(false, "[renameat]: Unexpected error code: " + (-ret));
}
this.SendError(tag, error_message, -ret);
this.SendReply(bufchain);
break;
}
if(TRACK_FILENAMES)
{
const newidx = this.fs.Search(this.fids[newdirfid].inodeid, newname);
this.update_dbg_name(newidx, newname);
}
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 76: // TUNLINKAT
var req = marshall.Unmarshall2(["w", "s", "w"], GetByte);
var req = marshall.Unmarshall(["w", "s", "w"], buffer, state);
var dirfd = req[0];
var name = req[1];
var flags = req[2];
@ -496,75 +690,83 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
var fid = this.fs.Search(this.fids[dirfd].inodeid, name);
if (fid == -1) {
this.SendError(tag, "No such file or directory", ENOENT);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
}
var ret = this.fs.Unlink(fid);
if (!ret) {
this.SendError(tag, "Directory not empty", ENOTEMPTY);
this.SendReply(0, index);
var ret = this.fs.Unlink(this.fids[dirfd].inodeid, name);
if (ret < 0) {
let error_message = "";
if(ret === -ENOTEMPTY) error_message = "Directory not empty";
else if(ret === -EPERM) error_message = "Operation not permitted";
else
{
error_message = "Unknown error: " + (-ret);
dbg_assert(false, "[unlink]: Unexpected error code: " + (-ret));
}
this.SendError(tag, error_message, -ret);
this.SendReply(bufchain);
break;
}
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 100: // version
var version = marshall.Unmarshall2(["w", "s"], GetByte);
var version = marshall.Unmarshall(["w", "s"], buffer, state);
message.Debug("[version]: msize=" + version[0] + " version=" + version[1]);
this.msize = version[0];
size = marshall.Marshall(["w", "s"], [this.msize, this.VERSION], this.replybuffer, 7);
this.BuildReply(id, tag, size);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 104: // attach
// return root directorie's QID
var req = marshall.Unmarshall2(["w", "w", "s", "s", "w"], GetByte);
var req = marshall.Unmarshall(["w", "w", "s", "s", "w"], buffer, state);
var fid = req[0];
var uid = req[4];
message.Debug("[attach]: fid=" + fid + " afid=" + hex8(req[1]) + " uname=" + req[2] + " aname=" + req[3]);
this.fids[fid] = this.Createfid(0, FID_INODE, uid);
this.fids[fid] = this.Createfid(0, FID_INODE, uid, "");
var inode = this.fs.GetInode(this.fids[fid].inodeid);
marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 13);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 108: // tflush
var req = marshall.Unmarshall2(["h"], GetByte);
var req = marshall.Unmarshall(["h"], buffer, state);
var oldtag = req[0];
message.Debug("[flush] " + tag);
//marshall.Marshall(["Q"], [inode.qid], this.replybuffer, 7);
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 110: // walk
var req = marshall.Unmarshall2(["w", "w", "h"], GetByte);
var req = marshall.Unmarshall(["w", "w", "h"], buffer, state);
var fid = req[0];
var nwfid = req[1];
var nwname = req[2];
message.Debug("[walk]: fid=" + req[0] + " nwfid=" + req[1] + " nwname=" + nwname);
if (nwname == 0) {
this.fids[nwfid] = this.Createfid(this.fids[fid].inodeid, FID_INODE, this.fids[fid].uid);
this.fids[nwfid] = this.Createfid(this.fids[fid].inodeid, FID_INODE, this.fids[fid].uid, this.fids[fid].dbg_name);
//this.fids[nwfid].inodeid = this.fids[fid].inodeid;
marshall.Marshall(["h"], [0], this.replybuffer, 7);
this.BuildReply(id, tag, 2);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
}
var wnames = [];
for(var i=0; i<nwname; i++) {
wnames.push("s");
}
var walk = marshall.Unmarshall2(wnames, GetByte);
var walk = marshall.Unmarshall(wnames, buffer, state);
var idx = this.fids[fid].inodeid;
var offset = 7+2;
var nwidx = 0;
//console.log(idx, this.fs.inodes[idx]);
message.Debug("walk in dir " + this.fs.inodes[idx].name + " to: " + walk.toString());
//console.log(idx, this.fs.GetInode(idx));
message.Debug("walk in dir " + this.fids[fid].dbg_name + " to: " + walk.toString());
for(var i=0; i<nwname; i++) {
idx = this.fs.Search(idx, walk[i]);
@ -572,50 +774,59 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
message.Debug("Could not find: " + walk[i]);
break;
}
offset += marshall.Marshall(["Q"], [this.fs.inodes[idx].qid], this.replybuffer, offset);
offset += marshall.Marshall(["Q"], [this.fs.GetInode(idx).qid], this.replybuffer, offset);
nwidx++;
//message.Debug(this.fids[nwfid].inodeid);
//this.fids[nwfid].inodeid = idx;
//this.fids[nwfid].type = FID_INODE;
this.fids[nwfid] = this.Createfid(idx, FID_INODE, this.fids[fid].uid);
this.fids[nwfid] = this.Createfid(idx, FID_INODE, this.fids[fid].uid, walk[i]);
}
marshall.Marshall(["h"], [nwidx], this.replybuffer, 7);
this.BuildReply(id, tag, offset-7);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 120: // clunk
var req = marshall.Unmarshall2(["w"], GetByte);
var req = marshall.Unmarshall(["w"], buffer, state);
message.Debug("[clunk]: fid=" + req[0]);
if (this.fids[req[0]] && this.fids[req[0]].inodeid >= 0) {
this.fs.CloseInode(this.fids[req[0]].inodeid);
await this.fs.CloseInode(this.fids[req[0]].inodeid);
this.fids[req[0]].inodeid = -1;
this.fids[req[0]].type = FID_NONE;
}
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
break;
case 32: // txattrcreate
var req = marshall.Unmarshall2(["w", "s", "d", "w"], GetByte);
var req = marshall.Unmarshall(["w", "s", "d", "w"], buffer, state);
var fid = req[0];
var name = req[1];
var attr_size = req[2];
var flags = req[3];
message.Debug("[txattrcreate]: fid=" + fid + " name=" + name + " attr_size=" + attr_size + " flags=" + flags);
// XXX: xattr not supported yet. E.g. checks corresponding to the flags needed.
this.fids[fid].type = FID_XATTR;
this.BuildReply(id, tag, 0);
this.SendReply(0, index);
this.SendReply(bufchain);
//this.SendError(tag, "Operation i not supported", EINVAL);
//this.SendReply(0, index);
//this.SendReply(bufchain);
break;
case 30: // xattrwalk
var req = marshall.Unmarshall2(["w", "w", "s"], GetByte);
var req = marshall.Unmarshall(["w", "w", "s"], buffer, state);
var fid = req[0];
var newfid = req[1];
var name = req[2];
message.Debug("[xattrwalk]: fid=" + req[0] + " newfid=" + req[1] + " name=" + req[2]);
this.fids[newfid] = this.Createfid(this.fids[fid].inodeid, FID_NONE, this.fids[fid].uid);
// Workaround for Linux restarts writes until full blocksize
this.SendError(tag, "Setxattr not supported", EOPNOTSUPP);
this.SendReply(bufchain);
/*
this.fids[newfid] = this.Createfid(this.fids[fid].inodeid, FID_NONE, this.fids[fid].uid, this.fids[fid].dbg_name);
//this.fids[newfid].inodeid = this.fids[fid].inodeid;
//this.fids[newfid].type = FID_NONE;
var length = 0;
@ -625,17 +836,18 @@ Virtio9p.prototype.ReceiveRequest = function (index, GetByte) {
}
marshall.Marshall(["d"], [length], this.replybuffer, 7);
this.BuildReply(id, tag, 8);
this.SendReply(0, index);
this.SendReply(bufchain);
*/
break;
default:
message.Debug("Error in Virtio9p: Unknown id " + id + " received");
message.Abort();
//this.SendError(tag, "Operation i not supported", ENOTSUPP);
//this.SendReply(0, index);
//this.SendError(tag, "Operation i not supported", EOPNOTSUPP);
//this.SendReply(bufchain);
break;
}
//consistency checks if there are problems with the filesystem
//this.fs.Check();
}
};

2
lib/esprima-min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -40,15 +40,15 @@ var message = {};
message.Debug = function(log)
{
dbg_log([].slice.apply(arguments).join(" "), LOG_9P);
}
};
message.Abort = function()
{
{
if(DEBUG)
{
throw "abort";
throw new Error("message.Abort()");
}
}
};
// XXX: Should go through emulator interface
@ -85,7 +85,7 @@ if(typeof XMLHttpRequest !== "undefined")
};
*/
req.send(null);
}
};
}
else
{
@ -100,8 +100,8 @@ else
}
else
{
OnSuccess(new Uint8Array(data).buffer);
OnSuccess(data.buffer);
}
});
}
};
}

View file

@ -3,6 +3,8 @@
// -------------------------------------------------
// helper functions for virtio and 9p.
"use strict";
var marshall = {};
@ -46,8 +48,8 @@ marshall.Marshall = function(typelist, input, struct, offset) {
struct[offset++] = 0; // set the length later
struct[offset++] = 0;
size += 2;
for (var j in item) {
var utf8 = UnicodeToUTF8Stream(item.charCodeAt(j));
for (var j of item) {
var utf8 = UnicodeToUTF8Stream(j.charCodeAt(0));
utf8.forEach( function(c) {
struct[offset++] = c;
size += 1;
@ -58,7 +60,7 @@ marshall.Marshall = function(typelist, input, struct, offset) {
struct[lengthoffset+1] = (length >> 8) & 0xFF;
break;
case "Q":
marshall.Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset)
marshall.Marshall(["b", "w", "d"], [item.type, item.version, item.path], struct, offset);
offset += 13;
size += 13;
break;
@ -72,7 +74,8 @@ marshall.Marshall = function(typelist, input, struct, offset) {
// Extracts data from a byte aligned struct in memory to an array
marshall.Unmarshall = function(typelist, struct, offset) {
marshall.Unmarshall = function(typelist, struct, state) {
let offset = state.offset;
var output = [];
for (var i=0; i < typelist.length; i++) {
switch (typelist[i]) {
@ -104,17 +107,28 @@ marshall.Unmarshall = function(typelist, struct, offset) {
var str = '';
var utf8converter = new UTF8StreamToUnicode();
for (var j=0; j < len; j++) {
var c = utf8converter.Put(struct[offset++])
var c = utf8converter.Put(struct[offset++]);
if (c == -1) continue;
str += String.fromCharCode(c);
}
output.push(str);
break;
case "Q":
state.offset = offset;
const qid = marshall.Unmarshall(["b", "w", "d"], struct, state);
offset = state.offset;
output.push({
type: qid[0],
version: qid[1],
path: qid[2],
});
break;
default:
message.Debug("Error in Unmarshall: Unknown type=" + typelist[i]);
break;
}
}
state.offset = offset;
return output;
};
@ -152,7 +166,7 @@ marshall.Unmarshall2 = function(typelist, GetByte) {
var str = '';
var utf8converter = new UTF8StreamToUnicode();
for (var j=0; j < len; j++) {
var c = utf8converter.Put(GetByte())
var c = utf8converter.Put(GetByte());
if (c == -1) continue;
str += String.fromCharCode(c);
}
@ -165,4 +179,3 @@ marshall.Unmarshall2 = function(typelist, GetByte) {
}
return output;
};

32501
lib/softfloat/softfloat.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
"use strict";
var UTF8 = {}
var UTF8 = {};
/** @constructor */
function UTF8StreamToUnicode() {
@ -43,7 +43,7 @@ function UTF8StreamToUnicode() {
//break;
}
return -1;
}
};
}
function UnicodeToUTF8Stream(key)
@ -60,4 +60,4 @@ UTF8.UTF8Length = function(s)
length += c<128?1:2;
}
return length;
}
};

1
lib/walk-min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,330 +0,0 @@
// AST walker module for Mozilla Parser API compatible trees
(function(mod) {
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
mod((this.acorn || (this.acorn = {})).walk = {}); // Plain browser env
})(function(exports) {
"use strict";
// A simple walk is one where you simply specify callbacks to be
// called on specific nodes. The last two arguments are optional. A
// simple use would be
//
// walk.simple(myTree, {
// Expression: function(node) { ... }
// });
//
// to do something with all expressions. All Parser API node types
// can be used to identify node types, as well as Expression,
// Statement, and ScopeBody, which denote categories of nodes.
//
// The base argument can be used to pass a custom (recursive)
// walker, and state can be used to give this walked an initial
// state.
exports.simple = function(node, visitors, base, state) {
if (!base) base = exports.base;
function c(node, st, override) {
var type = override || node.type, found = visitors[type];
base[type](node, st, c);
if (found) found(node, st);
}
c(node, state);
};
// An ancestor walk builds up an array of ancestor nodes (including
// the current node) and passes them to the callback as the state parameter.
exports.ancestor = function(node, visitors, base, state) {
if (!base) base = exports.base;
if (!state) state = [];
function c(node, st, override) {
var type = override || node.type, found = visitors[type];
if (node != st[st.length - 1]) {
st = st.slice();
st.push(node);
}
base[type](node, st, c);
if (found) found(node, st);
}
c(node, state);
};
// A recursive walk is one where your functions override the default
// walkers. They can modify and replace the state parameter that's
// threaded through the walk, and can opt how and whether to walk
// their child nodes (by calling their third argument on these
// nodes).
exports.recursive = function(node, state, funcs, base) {
var visitor = funcs ? exports.make(funcs, base) : base;
function c(node, st, override) {
visitor[override || node.type](node, st, c);
}
c(node, state);
};
function makeTest(test) {
if (typeof test == "string")
return function(type) { return type == test; };
else if (!test)
return function() { return true; };
else
return test;
}
function Found(node, state) { this.node = node; this.state = state; }
// Find a node with a given start, end, and type (all are optional,
// null can be used as wildcard). Returns a {node, state} object, or
// undefined when it doesn't find a matching node.
exports.findNodeAt = function(node, start, end, test, base, state) {
test = makeTest(test);
try {
if (!base) base = exports.base;
var c = function(node, st, override) {
var type = override || node.type;
if ((start == null || node.start <= start) &&
(end == null || node.end >= end))
base[type](node, st, c);
if (test(type, node) &&
(start == null || node.start == start) &&
(end == null || node.end == end))
throw new Found(node, st);
};
c(node, state);
} catch (e) {
if (e instanceof Found) return e;
throw e;
}
};
// Find the innermost node of a given type that contains the given
// position. Interface similar to findNodeAt.
exports.findNodeAround = function(node, pos, test, base, state) {
test = makeTest(test);
try {
if (!base) base = exports.base;
var c = function(node, st, override) {
var type = override || node.type;
if (node.start > pos || node.end < pos) return;
base[type](node, st, c);
if (test(type, node)) throw new Found(node, st);
};
c(node, state);
} catch (e) {
if (e instanceof Found) return e;
throw e;
}
};
// Find the outermost matching node after a given position.
exports.findNodeAfter = function(node, pos, test, base, state) {
test = makeTest(test);
try {
if (!base) base = exports.base;
var c = function(node, st, override) {
if (node.end < pos) return;
var type = override || node.type;
if (node.start >= pos && test(type, node)) throw new Found(node, st);
base[type](node, st, c);
};
c(node, state);
} catch (e) {
if (e instanceof Found) return e;
throw e;
}
};
// Find the outermost matching node before a given position.
exports.findNodeBefore = function(node, pos, test, base, state) {
test = makeTest(test);
if (!base) base = exports.base;
var max;
var c = function(node, st, override) {
if (node.start > pos) return;
var type = override || node.type;
if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node))
max = new Found(node, st);
base[type](node, st, c);
};
c(node, state);
return max;
};
// Used to create a custom walker. Will fill in all missing node
// type properties with the defaults.
exports.make = function(funcs, base) {
if (!base) base = exports.base;
var visitor = {};
for (var type in base) visitor[type] = base[type];
for (var type in funcs) visitor[type] = funcs[type];
return visitor;
};
function skipThrough(node, st, c) { c(node, st); }
function ignore(_node, _st, _c) {}
// Node walkers.
var base = exports.base = {};
base.Program = base.BlockStatement = function(node, st, c) {
for (var i = 0; i < node.body.length; ++i)
c(node.body[i], st, "Statement");
};
base.Statement = skipThrough;
base.EmptyStatement = ignore;
base.ExpressionStatement = function(node, st, c) {
c(node.expression, st, "Expression");
};
base.IfStatement = function(node, st, c) {
c(node.test, st, "Expression");
c(node.consequent, st, "Statement");
if (node.alternate) c(node.alternate, st, "Statement");
};
base.LabeledStatement = function(node, st, c) {
c(node.body, st, "Statement");
};
base.BreakStatement = base.ContinueStatement = ignore;
base.WithStatement = function(node, st, c) {
c(node.object, st, "Expression");
c(node.body, st, "Statement");
};
base.SwitchStatement = function(node, st, c) {
c(node.discriminant, st, "Expression");
for (var i = 0; i < node.cases.length; ++i) {
var cs = node.cases[i];
if (cs.test) c(cs.test, st, "Expression");
for (var j = 0; j < cs.consequent.length; ++j)
c(cs.consequent[j], st, "Statement");
}
};
base.ReturnStatement = function(node, st, c) {
if (node.argument) c(node.argument, st, "Expression");
};
base.ThrowStatement = function(node, st, c) {
c(node.argument, st, "Expression");
};
base.TryStatement = function(node, st, c) {
c(node.block, st, "Statement");
if (node.handler) c(node.handler.body, st, "ScopeBody");
if (node.finalizer) c(node.finalizer, st, "Statement");
};
base.WhileStatement = function(node, st, c) {
c(node.test, st, "Expression");
c(node.body, st, "Statement");
};
base.DoWhileStatement = base.WhileStatement;
base.ForStatement = function(node, st, c) {
if (node.init) c(node.init, st, "ForInit");
if (node.test) c(node.test, st, "Expression");
if (node.update) c(node.update, st, "Expression");
c(node.body, st, "Statement");
};
base.ForInStatement = function(node, st, c) {
c(node.left, st, "ForInit");
c(node.right, st, "Expression");
c(node.body, st, "Statement");
};
base.ForInit = function(node, st, c) {
if (node.type == "VariableDeclaration") c(node, st);
else c(node, st, "Expression");
};
base.DebuggerStatement = ignore;
base.FunctionDeclaration = function(node, st, c) {
c(node, st, "Function");
};
base.VariableDeclaration = function(node, st, c) {
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i];
if (decl.init) c(decl.init, st, "Expression");
}
};
base.Function = function(node, st, c) {
c(node.body, st, "ScopeBody");
};
base.ScopeBody = function(node, st, c) {
c(node, st, "Statement");
};
base.Expression = skipThrough;
base.ThisExpression = ignore;
base.ArrayExpression = function(node, st, c) {
for (var i = 0; i < node.elements.length; ++i) {
var elt = node.elements[i];
if (elt) c(elt, st, "Expression");
}
};
base.ObjectExpression = function(node, st, c) {
for (var i = 0; i < node.properties.length; ++i)
c(node.properties[i].value, st, "Expression");
};
base.FunctionExpression = base.FunctionDeclaration;
base.SequenceExpression = function(node, st, c) {
for (var i = 0; i < node.expressions.length; ++i)
c(node.expressions[i], st, "Expression");
};
base.UnaryExpression = base.UpdateExpression = function(node, st, c) {
c(node.argument, st, "Expression");
};
base.BinaryExpression = base.AssignmentExpression = base.LogicalExpression = function(node, st, c) {
c(node.left, st, "Expression");
c(node.right, st, "Expression");
};
base.ConditionalExpression = function(node, st, c) {
c(node.test, st, "Expression");
c(node.consequent, st, "Expression");
c(node.alternate, st, "Expression");
};
base.NewExpression = base.CallExpression = function(node, st, c) {
c(node.callee, st, "Expression");
if (node.arguments) for (var i = 0; i < node.arguments.length; ++i)
c(node.arguments[i], st, "Expression");
};
base.MemberExpression = function(node, st, c) {
c(node.object, st, "Expression");
if (node.computed) c(node.property, st, "Expression");
};
base.Identifier = base.Literal = ignore;
// A custom walker that keeps track of the scope chain and the
// variables defined in it.
function makeScope(prev, isCatch) {
return {vars: Object.create(null), prev: prev, isCatch: isCatch};
}
function normalScope(scope) {
while (scope.isCatch) scope = scope.prev;
return scope;
}
exports.scopeVisitor = exports.make({
Function: function(node, scope, c) {
var inner = makeScope(scope);
for (var i = 0; i < node.params.length; ++i)
inner.vars[node.params[i].name] = {type: "argument", node: node.params[i]};
if (node.id) {
var decl = node.type == "FunctionDeclaration";
(decl ? normalScope(scope) : inner).vars[node.id.name] =
{type: decl ? "function" : "function name", node: node.id};
}
c(node.body, inner, "ScopeBody");
},
TryStatement: function(node, scope, c) {
c(node.block, scope, "Statement");
if (node.handler) {
var inner = makeScope(scope, true);
inner.vars[node.handler.param.name] = {type: "catch clause", node: node.handler.param};
c(node.handler.body, inner, "ScopeBody");
}
if (node.finalizer) c(node.finalizer, scope, "Statement");
},
VariableDeclaration: function(node, scope, c) {
var target = normalScope(scope);
for (var i = 0; i < node.declarations.length; ++i) {
var decl = node.declarations[i];
target.vars[decl.id.name] = {type: "var", node: decl.id};
if (decl.init) c(decl.init, scope, "Expression");
}
}
});
});

13526
lib/zstd/zstddeclib.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -5,23 +5,25 @@
"use strict";
var CORE_FILES =
"const.js config.js log.js cpu.js debug.js translate.js modrm.js string.js arith.js misc_instr.js instructions.js " +
"io.js main.js lib.js ide.js fpu.js pci.js floppy.js " +
"const.js config.js log.js lib.js cpu.js debug.js " +
"io.js main.js ide.js pci.js floppy.js " +
"memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js apic.js ioapic.js hpet.js sb16.js " +
"ne2k.js state.js virtio.js bus.js elf.js";
"ne2k.js state.js virtio.js bus.js elf.js kernel.js";
var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js speaker.js serial.js lib.js network.js starter.js worker_bus.js";
//var LIB_FILES = "esprima.js walk.js";
var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js speaker.js serial.js lib.js network.js starter.js worker_bus.js print_stats.js filestorage.js";
var LIB_FILES = "";
// jor1k stuff
LIB_FILES += " jor1k.js 9p.js filesystem.js marshall.js utf8.js";
var BUILD_FILES = "capstone-x86.min.js libwabt.js";
var to_load = [];
load_scripts(CORE_FILES, "src/");
load_scripts(BROWSER_FILES, "src/browser/");
load_scripts(LIB_FILES, "lib/");
load_scripts(BUILD_FILES, "build/");
function load_scripts(resp, path)
{
@ -42,7 +44,7 @@
function load_next()
{
var s = to_load.shift();
let s = to_load.shift();
if(!s)
{
@ -52,6 +54,11 @@
var script = document.createElement("script");
script.src = s;
script.onload = load_next;
script.onerror = function() {
dbg_log("Warning: trying to ignore script " + s + ", which failed to load");
load_next();
};
document.head.appendChild(script);
}
})();

View file

@ -1,11 +0,0 @@
{
"name": "v86",
"repository": {
"type": "git",
"url": "https://github.com/copy/v86"
},
"license": "BSD-2-Clause",
"scripts": {
"test": "node tests/full/run.js"
}
}

View file

@ -3,7 +3,7 @@
// http://www.uefi.org/sites/default/files/resources/ACPI_6_1.pdf
/** @const */
var PMTIMER_FREQ = 3579545;
var PMTIMER_FREQ_SECONDS = 3579545;
/**
* @constructor
@ -31,6 +31,9 @@ function ACPI(cpu)
// 00:07.0 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 08)
cpu.devices.pci.register_device(acpi);
this.timer_last_value = 0;
this.timer_imprecision_offset = 0;
this.status = 1;
this.pm1_status = 0;
this.pm1_enable = 0;
@ -145,7 +148,43 @@ ACPI.prototype.timer = function(now)
ACPI.prototype.get_timer = function(now)
{
return now * (PMTIMER_FREQ / 1000) | 0;
const t = Math.round(now * (PMTIMER_FREQ_SECONDS / 1000));
// Due to the low precision of JavaScript's time functions we increment the
// returned timer value every time it is read
if(t === this.timer_last_value)
{
// don't go past 1ms
if(this.timer_imprecision_offset < PMTIMER_FREQ_SECONDS / 1000)
{
this.timer_imprecision_offset++;
}
}
else
{
dbg_assert(t > this.timer_last_value);
const previous_timer = this.timer_last_value + this.timer_imprecision_offset;
// don't go back in time
if(previous_timer <= t)
{
this.timer_imprecision_offset = 0;
this.timer_last_value = t;
}
else
{
dbg_log("Warning: Overshot pmtimer, waiting;" +
" current=" + t +
" last=" + this.timer_last_value +
" offset=" + this.timer_imprecision_offset, LOG_ACPI);
}
}
return this.timer_last_value + this.timer_imprecision_offset;
};
ACPI.prototype.get_state = function()
@ -154,6 +193,7 @@ ACPI.prototype.get_state = function()
state[0] = this.status;
state[1] = this.pm1_status;
state[2] = this.pm1_enable;
state[3] = this.gpe;
return state;
};
@ -162,4 +202,5 @@ ACPI.prototype.set_state = function(state)
this.status = state[0];
this.pm1_status = state[1];
this.pm1_enable = state[2];
this.gpe = state[3];
};

View file

@ -1,15 +0,0 @@
/** @constructor */
function ScreenAdapter()
{
this.put_pixel_linear = function() {};
this.put_pixel_linear32 = function() {};
this.put_char = function() {};
this.set_mode = function() {};
this.set_size_graphical = function() {};
this.set_size_text = function() {};
this.update_cursor = function() {};
this.update_cursor_scanline = function() {};
this.timer = function() {};
}

View file

@ -394,9 +394,13 @@ APIC.prototype.timer = function(now)
if(mode === APIC_TIMER_MODE_PERIODIC)
{
// This isn't exact, because timer_current_count might already be
// negative at this point since timer() fires late
this.timer_current_count = this.timer_initial_count;
this.timer_current_count = this.timer_current_count % this.timer_initial_count;
if(this.timer_current_count <= 0)
{
this.timer_current_count += this.timer_initial_count;
}
dbg_assert(this.timer_current_count !== 0);
if((this.lvt_timer & IOAPIC_CONFIG_MASKED) === 0)
{

File diff suppressed because it is too large Load diff

160
src/browser/filestorage.js Normal file
View file

@ -0,0 +1,160 @@
"use strict";
/** @interface */
function FileStorageInterface() {}
/**
* Read a portion of a file.
* @param {string} sha256sum
* @param {number} offset
* @param {number} count
* @return {!Promise<Uint8Array>} null if file does not exist.
*/
FileStorageInterface.prototype.read = function(sha256sum, offset, count) {};
/**
* Add a read-only file to the filestorage.
* @param {string} sha256sum
* @param {!Uint8Array} data
* @return {!Promise}
*/
FileStorageInterface.prototype.cache = function(sha256sum, data) {};
/**
* Call this when the file won't be used soon, e.g. when a file closes or when this immutable
* version is already out of date. It is used to help prevent accumulation of unused files in
* memory in the long run for some FileStorage mediums.
*/
FileStorageInterface.prototype.uncache = function(sha256sum) {};
/**
* @constructor
* @implements {FileStorageInterface}
*/
function MemoryFileStorage()
{
/**
* From sha256sum to file data.
* @type {Map<string,Uint8Array>}
*/
this.filedata = new Map();
}
/**
* @param {string} sha256sum
* @param {number} offset
* @param {number} count
* @return {!Promise<Uint8Array>} null if file does not exist.
*/
MemoryFileStorage.prototype.read = async function(sha256sum, offset, count)
{
dbg_assert(sha256sum, "MemoryFileStorage read: sha256sum should be a non-empty string");
const data = this.filedata.get(sha256sum);
if(!data)
{
return null;
}
return data.subarray(offset, offset + count);
};
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
*/
MemoryFileStorage.prototype.cache = async function(sha256sum, data)
{
dbg_assert(sha256sum, "MemoryFileStorage cache: sha256sum should be a non-empty string");
this.filedata.set(sha256sum, data);
};
/**
* @param {string} sha256sum
*/
MemoryFileStorage.prototype.uncache = function(sha256sum)
{
this.filedata.delete(sha256sum);
};
/**
* @constructor
* @implements {FileStorageInterface}
* @param {FileStorageInterface} file_storage
* @param {string} baseurl
*/
function ServerFileStorageWrapper(file_storage, baseurl)
{
dbg_assert(baseurl, "ServerMemoryFileStorage: baseurl should not be empty");
this.storage = file_storage;
this.baseurl = baseurl;
}
/**
* @param {string} sha256sum
* @return {!Promise<Uint8Array>}
*/
ServerFileStorageWrapper.prototype.load_from_server = function(sha256sum)
{
return new Promise((resolve, reject) =>
{
v86util.load_file(this.baseurl + sha256sum, { done: buffer =>
{
const data = new Uint8Array(buffer);
this.cache(sha256sum, data).then(() => resolve(data));
}});
});
};
/**
* @param {string} sha256sum
* @param {number} offset
* @param {number} count
* @return {!Promise<Uint8Array>}
*/
ServerFileStorageWrapper.prototype.read = async function(sha256sum, offset, count)
{
const data = await this.storage.read(sha256sum, offset, count);
if(!data)
{
const full_file = await this.load_from_server(sha256sum);
return full_file.subarray(offset, offset + count);
}
return data;
};
/**
* @param {string} sha256sum
* @param {!Uint8Array} data
*/
ServerFileStorageWrapper.prototype.cache = async function(sha256sum, data)
{
return await this.storage.cache(sha256sum, data);
};
/**
* @param {string} sha256sum
*/
ServerFileStorageWrapper.prototype.uncache = function(sha256sum)
{
this.storage.uncache(sha256sum);
};
// Closure Compiler's way of exporting
if(typeof window !== "undefined")
{
window["MemoryFileStorage"] = MemoryFileStorage;
window["ServerFileStorageWrapper"] = ServerFileStorageWrapper;
}
else if(typeof module !== "undefined" && typeof module.exports !== "undefined")
{
module.exports["MemoryFileStorage"] = MemoryFileStorage;
module.exports["ServerFileStorageWrapper"] = ServerFileStorageWrapper;
}
else if(typeof importScripts === "function")
{
// web worker
self["MemoryFileStorage"] = MemoryFileStorage;
self["ServerFileStorageWrapper"] = ServerFileStorageWrapper;
}

View file

@ -111,7 +111,7 @@ function KeyboardAdapter(bus)
* ascii -> javascript event code (US layout)
* @const
*/
var asciimap = {10: 13, 32: 32, 39: 222, 44: 188, 45: 189, 46: 190, 47: 191, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 97: 65, 98: 66, 99: 67, 100: 68, 101: 69, 102: 70, 103: 71, 104: 72, 105: 73, 106: 74, 107: 75, 108: 76, 109: 77, 110: 78, 111: 79, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 122: 90};
var asciimap = {8: 8, 10: 13, 32: 32, 39: 222, 44: 188, 45: 189, 46: 190, 47: 191, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 97: 65, 98: 66, 99: 67, 100: 68, 101: 69, 102: 70, 103: 71, 104: 72, 105: 73, 106: 74, 107: 75, 108: 76, 109: 77, 110: 78, 111: 79, 112: 80, 113: 81, 114: 82, 115: 83, 116: 84, 117: 85, 118: 86, 119: 87, 120: 88, 121: 89, 122: 90};
var asciimap_shift = {33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 42: 56, 43: 187, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 94: 54, 95: 189, 123: 219, 124: 220, 125: 221, 126: 192};
// From:
@ -284,7 +284,7 @@ function KeyboardAdapter(bus)
function may_handle(e)
{
if(e.shiftKey && e.ctrlKey && (e.keyCode === 74 || e.keyCode === 75))
if(e.shiftKey && e.ctrlKey && (e.keyCode === 73 || e.keyCode === 74 || e.keyCode === 75))
{
// don't prevent opening chromium dev tools
// maybe add other important combinations here, too
@ -383,7 +383,7 @@ function KeyboardAdapter(bus)
if(!code)
{
console.log("Missing char in map: " + e.keyCode.toString(16));
console.log("Missing char in map: keyCode=" + (e.keyCode || -1).toString(16) + " code=" + e.code);
return;
}

View file

@ -15,9 +15,18 @@ var ASYNC_SAFE = false;
}
v86util.AsyncXHRBuffer = AsyncXHRBuffer;
v86util.AsyncXHRPartfileBuffer = AsyncXHRPartfileBuffer;
v86util.AsyncFileBuffer = AsyncFileBuffer;
v86util.SyncFileBuffer = SyncFileBuffer;
// Reads len characters at offset from Memory object mem as a JS string
v86util.read_sized_string_from_mem = function read_sized_string_from_mem(mem, offset, len)
{
offset >>>= 0;
len >>>= 0;
return String.fromCharCode(...new Uint8Array(mem.buffer, offset, len));
};
/**
* @param {string} filename
* @param {Object} options
@ -28,7 +37,11 @@ var ASYNC_SAFE = false;
http.open(options.method || "get", filename, true);
if(!options.as_text)
if(options.as_json)
{
http.responseType = "json";
}
else
{
http.responseType = "arraybuffer";
}
@ -49,6 +62,16 @@ var ASYNC_SAFE = false;
let start = options.range.start;
let end = start + options.range.length - 1;
http.setRequestHeader("Range", "bytes=" + start + "-" + end);
// Abort if server responds with complete file in response to range
// request, to prevent downloading large files from broken http servers
http.onreadystatechange = function()
{
if(http.status === 200)
{
http.abort();
}
};
}
http.onload = function(e)
@ -83,14 +106,14 @@ var ASYNC_SAFE = false;
if(options.range)
{
dbg_assert(!options.as_text);
dbg_assert(!options.as_json);
fs["open"](filename, "r", (err, fd) =>
{
if(err) throw err;
let length = options.range.length;
var buffer = new global["Buffer"](length);
var buffer = Buffer.allocUnsafe(length);
fs["read"](fd, buffer, 0, length, options.range.start, (err, bytes_read) =>
{
@ -108,7 +131,7 @@ var ASYNC_SAFE = false;
else
{
var o = {
encoding: options.as_text ? "utf-8" : null,
encoding: options.as_json ? "utf-8" : null,
};
fs["readFile"](filename, o, function(err, data)
@ -121,7 +144,11 @@ var ASYNC_SAFE = false;
{
var result = data;
if(!options.as_text)
if(options.as_json)
{
result = JSON.parse(result);
}
else
{
result = new Uint8Array(result).buffer;
}
@ -165,18 +192,12 @@ var ASYNC_SAFE = false;
}
else
{
cb({ header });
const error = "`Range: bytes=...` header not supported (Got `" + header + "`)";
cb(error);
}
},
headers: {
Range: "bytes=0-0",
//"Accept-Encoding": "",
// Added by Chromium, but can cause the whole file to be sent
// Settings this to empty also causes problems and Chromium
// doesn't seem to create this header any more
//"If-Range": "",
}
});
};
@ -198,7 +219,7 @@ var ASYNC_SAFE = false;
this.block_size = 256;
this.byteLength = size;
this.loaded_blocks = {};
this.loaded_blocks = Object.create(null);
this.onload = undefined;
this.onprogress = undefined;
@ -208,7 +229,7 @@ var ASYNC_SAFE = false;
{
if(this.byteLength !== undefined)
{
this.onload && this.onload({});
this.onload && this.onload(Object.create(null));
return;
}
@ -218,15 +239,13 @@ var ASYNC_SAFE = false;
{
if(error)
{
console.assert(false,
"Cannot use: " + this.filename + ". " +
"`Range: bytes=...` header not supported (Got `" + error.header + "`)");
console.assert(false, "Cannot use: " + this.filename + ". " + error);
}
else
{
dbg_assert(size >= 0);
this.byteLength = size;
this.onload && this.onload({});
this.onload && this.onload(Object.create(null));
}
});
};
@ -306,7 +325,7 @@ var ASYNC_SAFE = false;
/**
* Relies on this.byteLength, this.loaded_blocks and this.block_size
*
* @this {AsyncFileBuffer|AsyncXHRBuffer}
* @this {AsyncFileBuffer|AsyncXHRBuffer|AsyncXHRPartfileBuffer}
*
* @param {number} start
* @param {!Uint8Array} data
@ -344,7 +363,7 @@ var ASYNC_SAFE = false;
};
/**
* @this {AsyncFileBuffer|AsyncXHRBuffer}
* @this {AsyncFileBuffer|AsyncXHRBuffer|AsyncXHRPartfileBuffer}
* @param {number} offset
* @param {number} len
* @param {!Uint8Array} block
@ -381,17 +400,13 @@ var ASYNC_SAFE = false;
AsyncXHRBuffer.prototype.get_written_blocks = function()
{
var count = 0;
for(var _ in this.loaded_blocks)
{
count++;
}
var count = Object.keys(this.loaded_blocks).length;
var buffer = new Uint8Array(count * this.block_size);
var indices = [];
var i = 0;
for(var index in this.loaded_blocks)
for(var index of Object.keys(this.loaded_blocks))
{
var block = this.loaded_blocks[index];
dbg_assert(block.length === this.block_size);
@ -411,6 +426,122 @@ var ASYNC_SAFE = false;
};
};
AsyncXHRBuffer.prototype.get_state = function()
{
const state = [];
const loaded_blocks = [];
for(let [index, block] of Object.entries(this.loaded_blocks))
{
dbg_assert(isFinite(+index));
loaded_blocks.push([+index, block]);
}
state[0] = loaded_blocks;
return state;
};
AsyncXHRBuffer.prototype.set_state = function(state)
{
const loaded_blocks = state[0];
this.loaded_blocks = Object.create(null);
for(let [index, block] of Object.values(loaded_blocks))
{
this.loaded_blocks[index] = block;
}
};
/**
* Asynchronous access to ArrayBuffer, loading blocks lazily as needed,
* downloading files named filename-\d-\d.ext.
*
* @constructor
* @param {string} filename Name of the file to download
* @param {number|undefined} size
*/
function AsyncXHRPartfileBuffer(filename, size)
{
const parts = filename.match(/(.*)(\..*)/);
if(parts)
{
this.basename = parts[1];
this.extension = parts[2];
}
else
{
this.basename = filename;
this.extension = "";
}
/** @const */
this.block_size = 256;
this.byteLength = size;
this.loaded_blocks = Object.create(null);
this.onload = undefined;
this.onprogress = undefined;
}
AsyncXHRPartfileBuffer.prototype.load = function()
{
if(this.byteLength !== undefined)
{
this.onload && this.onload(Object.create(null));
return;
}
dbg_assert(false);
this.onload && this.onload(Object.create(null));
};
AsyncXHRPartfileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache;
/**
* @param {number} offset
* @param {number} len
* @param {function(!Uint8Array)} fn
*/
AsyncXHRPartfileBuffer.prototype.get = function(offset, len, fn)
{
console.assert(offset + len <= this.byteLength);
console.assert(offset % this.block_size === 0);
console.assert(len % this.block_size === 0);
console.assert(len);
var block = this.get_from_cache(offset, len, fn);
if(block)
{
if(ASYNC_SAFE)
{
setTimeout(fn.bind(this, block), 0);
}
else
{
fn(block);
}
return;
}
const part_filename = this.basename + "-" + offset + "-" + (offset + len) + this.extension;
v86util.load_file(part_filename, {
done: function done(buffer)
{
dbg_assert(buffer.byteLength === len);
var block = new Uint8Array(buffer);
this.handle_read(offset, len, block);
fn(block);
}.bind(this),
});
};
AsyncXHRPartfileBuffer.prototype.set = AsyncXHRBuffer.prototype.set;
AsyncXHRPartfileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read;
AsyncXHRPartfileBuffer.prototype.get_written_blocks = AsyncXHRBuffer.prototype.get_written_blocks;
AsyncXHRPartfileBuffer.prototype.get_state = AsyncXHRBuffer.prototype.get_state;
AsyncXHRPartfileBuffer.prototype.set_state = AsyncXHRBuffer.prototype.set_state;
/**
* Synchronous access to File, loading blocks from the input type=file
* The whole file is loaded into memory during initialisation
@ -505,6 +636,20 @@ var ASYNC_SAFE = false;
fn(this.buffer);
};
SyncFileBuffer.prototype.get_state = function()
{
const state = [];
state[0] = this.byteLength;
state[1] = new Uint8Array(this.buffer);
return state;
};
SyncFileBuffer.prototype.set_state = function(state)
{
this.byteLength = state[0];
this.buffer = state[1].slice().buffer;
};
/**
* Asynchronous access to File, loading blocks from the input type=file
*
@ -517,7 +662,7 @@ var ASYNC_SAFE = false;
/** @const */
this.block_size = 256;
this.loaded_blocks = {};
this.loaded_blocks = Object.create(null);
this.onload = undefined;
this.onprogress = undefined;
@ -525,7 +670,7 @@ var ASYNC_SAFE = false;
AsyncFileBuffer.prototype.load = function()
{
this.onload && this.onload({});
this.onload && this.onload(Object.create(null));
};
/**

View file

@ -6,7 +6,7 @@
var ON_LOCALHOST = !location.hostname.endsWith("copy.sh");
/** @const */
var HOST = ON_LOCALHOST ? "" : "//i.copy.sh/";
var HOST = ON_LOCALHOST ? "images/" : "//k.copy.sh/";
/**
* @return {Object.<string, string>}
@ -19,7 +19,7 @@
for(var i = 0; i < query.length; i++)
{
var param = query[i].split("=");
parameters[param[0]] = decodeURIComponent(param[1]);
parameters[param[0]] = decodeURIComponent(param.slice(1).join("="));
}
return parameters;
@ -28,6 +28,8 @@
function set_title(text)
{
document.title = text + " - Virtual x86" + (DEBUG ? " - debug" : "");
const description = document.querySelector("meta[name=description]");
description && (description.content = "Running " + text);
}
function format_timestamp(time)
@ -67,6 +69,13 @@
var el = $("loading");
el.style.display = "block";
if(e.file_name.endsWith(".wasm"))
{
const parts = e.file_name.split("/");
el.textContent = "Fetching " + parts[parts.length - 1] + " ...";
return;
}
if(e.file_index === e.file_count - 1 && e.loaded >= e.total - 2048)
{
// last file is (almost) loaded
@ -102,25 +111,22 @@
function $(id)
{
var el = document.getElementById(id);
if(!el)
{
dbg_log("Element with id `" + id + "` not found");
}
return el;
return document.getElementById(id);
}
function onload()
{
if(!("responseType" in new XMLHttpRequest))
if(!window.WebAssembly)
{
alert("Your browser is not supported " +
"because it doesn't have XMLHttpRequest.responseType");
alert("Your browser is not supported because it doesn't support WebAssembly");
return;
}
const script = document.createElement("script");
script.src = "build/xterm.js";
script.async = true;
document.body.appendChild(script);
var settings = {};
$("start_emulation").onclick = function()
@ -182,37 +188,40 @@
debug_onload(settings);
}
// Abandonware OS images are from https://winworldpc.com/library/operating-systems
var oses = [
{
id: "archlinux",
state: {
"url": HOST + "images/v86state.bin",
"size": 142770436,
},
name: "Arch Linux",
memory_size: 128 * 1024 * 1024,
memory_size: 512 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
// required for restoring state, should not be used when booted on 9p
hda: {
"url": HOST + "images/arch3.img",
"size": 16 * 1024 * 1024 * 1024,
"async": true,
state: {
"url": HOST + "arch_state.bin.zst",
},
filesystem: {
"basefs": {
"url": HOST + "images/fs.json",
"size": 10232633,
},
"baseurl": HOST + "arch/",
},
},
{
id: "haiku",
memory_size: 512 * 1024 * 1024,
hda: {
url: HOST + "haiku.img",
async: true,
use_parts: !ON_LOCALHOST,
size: 1 * 1024 * 1024 * 1024,
},
state: {
url: HOST + "haiku_state.bin.zst",
},
name: "Haiku",
},
{
id: "msdos",
hda: {
"url": HOST + "images/msdos.img",
"url": HOST + "msdos.img",
"size": 8 * 1024 * 1024,
"async": false,
},
boot_order: 0x132,
name: "MS-DOS",
@ -220,152 +229,372 @@
{
id: "freedos",
fda: {
"url": HOST + "images/freedos722.img",
"url": HOST + "freedos722.img",
"size": 737280,
"async": false,
},
name: "FreeDOS",
},
{
id: "oberon",
fda: {
"url": HOST + "images/oberon-boot.dsk",
"size": 1440 * 1024,
},
hda: {
"url": HOST + "images/oberon.dsk",
"size": 41943040,
"url": HOST + "oberon.img",
"size": 24 * 1024 * 1024,
"async": false,
},
name: "Oberon",
},
{
id: "windows1",
fda: {
"url": HOST + "images/windows101.img",
"url": HOST + "windows101.img",
"size": 1474560,
"async": false,
},
name: "Windows",
},
{
id: "linux26",
cdrom: {
"url": HOST + "images/linux.iso",
"size": 5666816,
"url": HOST + "linux.iso",
"size": 6547456,
"async": false,
},
name: "Linux",
},
{
id: "linux3",
cdrom: {
"url": HOST + "images/linux3.iso",
"url": HOST + "linux3.iso",
"size": 8624128,
"async": false,
},
name: "Linux",
},
{
id: "linux4",
cdrom: {
"url": HOST + "linux4.iso",
"size": 7731200,
"async": false,
},
name: "Linux",
filesystem: {},
},
{
id: "buildroot",
bzimage: {
url: HOST + "buildroot-bzimage.bin",
size: 5166352,
async: false,
},
name: "Buildroot Linux",
filesystem: {},
memory_size: 128 * 1024 * 1024,
cmdline: "tsc=reliable mitigations=off random.trust_cpu=on",
},
{
id: "dsl",
memory_size: 256 * 1024 * 1024,
cdrom: {
url: HOST + "dsl-4.11.rc2.iso",
size: 52824064,
async: false,
},
name: "Damn Small Linux",
homepage: "http://www.damnsmalllinux.org/",
},
{
id: "minix",
name: "Minix",
memory_size: 256 * 1024 * 1024,
cdrom: {
url: HOST + "minix-3.3.0.iso",
size: 605581312,
async: true,
use_parts: !ON_LOCALHOST,
},
},
{
id: "kolibrios",
fda: {
"url": ON_LOCALHOST ?
"images/kolibri.img" :
HOST + "kolibri.img" :
"//builds.kolibrios.org/eng/data/data/kolibri.img",
"size": 1474560,
"async": false,
},
name: "KolibriOS",
homepage: "https://kolibrios.org/en/",
},
{
id: "kolibrios-fallback",
fda: {
"url": HOST + "images/kolibri.img",
"url": HOST + "kolibri.img",
"size": 1474560,
"async": false,
},
name: "KolibriOS",
},
{
id: "openbsd",
fda: {
"url": HOST + "images/openbsd.img",
"size": 1474560,
hda: {
"url": HOST + "openbsd.img",
async: true,
use_parts: !ON_LOCALHOST,
size: 1073741824,
},
state: {
url: HOST + "openbsd_state.bin.zst",
},
memory_size: 256 * 1024 * 1024,
name: "OpenBSD",
},
{
id: "openbsd-boot",
hda: {
url: HOST + "openbsd.img",
async: true,
use_parts: !ON_LOCALHOST,
size: 1073741824,
},
memory_size: 256 * 1024 * 1024,
name: "OpenBSD",
//acpi: true, // doesn't seem to work
},
{
id: "solos",
fda: {
"url": HOST + "images/os8.dsk",
"url": HOST + "os8.img",
"async": false,
"size": 1474560,
},
name: "Sol OS",
},
{
id: "dexos",
cdrom: {
"url": HOST + "images/DexOSv6.iso",
"size": 1837056,
},
name: "DexOS",
homepage: "http://oby.ro/os/",
},
{
id: "bootchess",
fda: {
"url": HOST + "images/bootchess.img",
"url": HOST + "bootchess.img",
"async": false,
"size": 1474560,
},
name: "Bootchess",
homepage: "http://www.pouet.net/prod.php?which=64962",
},
{
id: "windows98",
memory_size: 64 * 1024 * 1024,
memory_size: 128 * 1024 * 1024,
hda: {
"url": HOST + "images/windows98.img",
"url": HOST + "windows98.img",
"async": true,
use_parts: !ON_LOCALHOST,
"size": 300 * 1024 * 1024,
},
name: "Windows 98",
state: {
"url": HOST + "images/windows98_state.bin",
"size": 75705744,
"url": HOST + "windows98_state.bin.zst",
},
preserve_mac_from_state_image: true,
},
{
id: "windows98-boot",
memory_size: 128 * 1024 * 1024,
hda: {
"url": HOST + "windows98.img",
"async": true,
use_parts: !ON_LOCALHOST,
"size": 300 * 1024 * 1024,
},
name: "Windows 98",
},
{
id: "windows95",
memory_size: 32 * 1024 * 1024,
hda: {
"url": HOST + "images/W95.IMG",
"url": HOST + "w95.img",
"size": 242049024,
"async": true,
use_parts: !ON_LOCALHOST,
},
name: "Windows 95",
state: {
"url": HOST + "images/windows95_state.bin",
"size": 42151316,
"url": HOST + "windows95_state.bin.zst",
},
},
{
id: "freebsd",
memory_size: 128 * 1024 * 1024,
state: {
"url": HOST + "images/freebsd_state.bin",
"size": 142815292,
},
id: "windows95-boot",
memory_size: 32 * 1024 * 1024,
hda: {
"url": HOST + "images/freebsd3.img",
"size": 17179869184,
"url": HOST + "w95.img",
"size": 242049024,
"async": true,
use_parts: !ON_LOCALHOST,
},
name: "Windows 95",
},
{
id: "windows30",
memory_size: 64 * 1024 * 1024,
cdrom: {
"url": HOST + "Win30.iso",
"async": false,
},
name: "Windows 3.0",
},
{
id: "freebsd",
memory_size: 256 * 1024 * 1024,
hda: {
"url": HOST + "freebsd.img",
"size": 2147483648,
"async": true,
use_parts: !ON_LOCALHOST,
},
state: {
"url": HOST + "freebsd_state.bin.zst",
},
name: "FreeBSD",
},
{
id: "reactos",
id: "freebsd-boot",
memory_size: 256 * 1024 * 1024,
cdrom: {
"url": HOST + "images/ReactOS-0.4.9-RC-Live.iso",
hda: {
"url": HOST + "freebsd.img",
"size": 2147483648,
"async": true,
use_parts: !ON_LOCALHOST,
},
state: {
"url": HOST + "images/reactos_state.bin",
"size": 276971224,
name: "FreeBSD",
},
{
id: "reactos-livecd",
memory_size: 256 * 1024 * 1024,
hda: {
"url": HOST + "reactos-livecd-0.4.15-dev-73-g03c09c9-x86-gcc-lin-dbg.iso",
"size": 250609664,
"async": true,
use_parts: !ON_LOCALHOST,
},
name: "ReactOS",
description: 'Running <a href="https://reactos.org/">ReactOS</a>',
homepage: "https://reactos.org/",
},
{
id: "reactos",
memory_size: 512 * 1024 * 1024,
hda: {
"url": HOST + "reactos.img",
"size": 500 * 1024 * 1024,
"async": true,
use_parts: !ON_LOCALHOST,
},
state: {
"url": HOST + "reactos_state.bin.zst",
},
preserve_mac_from_state_image: true,
name: "ReactOS",
homepage: "https://reactos.org/",
},
{
id: "reactos-boot",
memory_size: 512 * 1024 * 1024,
hda: {
"url": HOST + "reactos.img",
"size": 500 * 1024 * 1024,
"async": true,
use_parts: !ON_LOCALHOST,
},
name: "ReactOS",
homepage: "https://reactos.org/",
},
{
id: "skift",
memory_size: 128 * 1024 * 1024,
cdrom: {
"url": HOST + "skift-20200910.iso",
"size": 64452608,
"async": false,
},
name: "Skift",
homepage: "https://skiftos.org/",
},
{
id: "openwrt",
memory_size: 128 * 1024 * 1024,
hda: {
"url": HOST + "openwrt-18.06.1-x86-legacy-combined-squashfs.img",
"size": 19846474,
"async": false,
},
name: "OpenWrt",
},
{
id: "qnx",
memory_size: 128 * 1024 * 1024,
fda: {
url: HOST + "qnx-demo-network-4.05.img",
size: 1474560,
async: false
},
name: "QNX 4.05",
},
{
id: "9front",
memory_size: 128 * 1024 * 1024,
hda: {
url: HOST + "9front-7781.38dcaeaa222c.386.iso",
size: 496388096,
async: true,
use_parts: !ON_LOCALHOST,
},
state: {
"url": HOST + "9front_state.bin.zst",
},
acpi: true,
name: "9front",
},
{
id: "9front-boot",
memory_size: 128 * 1024 * 1024,
hda: {
url: HOST + "9front-7781.38dcaeaa222c.386.iso",
size: 496388096,
async: true,
use_parts: !ON_LOCALHOST,
},
acpi: true,
name: "9front",
},
{
id: "mobius",
memory_size: 64 * 1024 * 1024,
fda: {
"url": HOST + "mobius-fd-release5.img",
"size": 1474560,
"async": false,
},
name: "Mobius",
},
{
id: "android",
memory_size: 512 * 1024 * 1024,
cdrom: {
"url": HOST + "android-x86-1.6-r2.iso",
"size": 54661120,
"async": true,
use_parts: !ON_LOCALHOST,
},
name: "Android",
},
{
id: "tinycore",
memory_size: 256 * 1024 * 1024,
hda: {
"url": HOST + "TinyCore-11.0.iso",
"async": false,
},
name: "Tinycore",
homepage: "http://www.tinycorelinux.net/",
},
];
@ -408,6 +637,14 @@
var query_args = get_query_arguments();
var profile = query_args["profile"];
if(!profile && !DEBUG)
{
const link = document.createElement("link");
link.rel = "prefetch";
link.href = "build/v86.wasm";
document.head.appendChild(link);
}
if(query_args["use_bochs_bios"])
{
settings.use_bochs_bios = true;
@ -427,8 +664,9 @@
if(element)
{
element.onclick = function(infos, element)
element.onclick = function(infos, element, e)
{
e.preventDefault();
set_profile(infos.id);
element.blur();
@ -491,7 +729,13 @@
settings.cdrom = infos.cdrom;
settings.hda = infos.hda;
settings.multiboot = infos.multiboot;
settings.bzimage = infos.bzimage;
settings.initrd = infos.initrd;
settings.cmdline = infos.cmdline;
settings.bzimage_initrd_from_filesystem = infos.bzimage_initrd_from_filesystem;
settings.preserve_mac_from_state_image = infos.preserve_mac_from_state_image;
settings.acpi = infos.acpi;
settings.memory_size = infos.memory_size;
settings.vga_memory_size = infos.vga_memory_size;
@ -502,10 +746,15 @@
settings.boot_order = infos.boot_order;
}
if(!DEBUG && infos.description)
if(!DEBUG && infos.homepage)
{
$("description").style.display = "block";
$("description").innerHTML = "<br>" + infos.description;
const link = document.createElement("a");
link.href = infos.homepage;
link.textContent = infos.name;
link.target = "_blank";
$("description").appendChild(document.createTextNode("Running "));
$("description").appendChild(link);
}
start_emulation(settings, done);
@ -528,71 +777,58 @@
{
// called on window.onload, in debug mode
//settings.filesystem = {
// baseurl: "http://localhost/v86-images/arch/",
// basefs: "http://localhost/v86-images/fs.json",
//};
//$("restore_state").onchange = function()
//{
//};
//$("start_test").onclick = function()
//{
//};
var log_levels = $("log_levels");
for(var i = 0; i < LOG_NAMES.length; i++)
if(log_levels)
{
var mask = LOG_NAMES[i][0];
if(mask === 1)
continue;
var name = LOG_NAMES[i][1].toLowerCase(),
input = document.createElement("input"),
label = document.createElement("label");
input.type = "checkbox";
label.htmlFor = input.id = "log_" + name;
if(LOG_LEVEL & mask)
for(var i = 0; i < LOG_NAMES.length; i++)
{
input.checked = true;
var mask = LOG_NAMES[i][0];
if(mask === 1)
continue;
var name = LOG_NAMES[i][1].toLowerCase(),
input = document.createElement("input"),
label = document.createElement("label");
input.type = "checkbox";
label.htmlFor = input.id = "log_" + name;
if(LOG_LEVEL & mask)
{
input.checked = true;
}
input.mask = mask;
label.appendChild(input);
label.appendChild(document.createTextNode(v86util.pads(name, 4) + " "));
log_levels.appendChild(label);
if(i === Math.floor(LOG_NAMES.length / 2))
{
log_levels.appendChild(document.createTextNode("\n"));
}
}
input.mask = mask;
label.appendChild(input);
label.appendChild(document.createTextNode(v86util.pads(name, 4) + " "));
log_levels.appendChild(label);
if(i === Math.floor(LOG_NAMES.length / 2))
log_levels.onchange = function(e)
{
log_levels.appendChild(document.createTextNode("\n"));
}
var target = e.target,
mask = target.mask;
if(target.checked)
{
LOG_LEVEL |= mask;
}
else
{
LOG_LEVEL &= ~mask;
}
target.blur();
};
}
log_levels.onchange = function(e)
{
var target = e.target,
mask = target.mask;
if(target.checked)
{
LOG_LEVEL |= mask;
}
else
{
LOG_LEVEL &= ~mask;
}
target.blur();
};
var debug_infos = $("debug_infos");
debug_infos.textContent = "ACPI: " + (ENABLE_ACPI ? "enabled" : "disabled");
}
window.addEventListener("load", onload, false);
@ -654,6 +890,10 @@
}
}
const networking_proxy = $("networking_proxy").value;
const disable_audio = $("disable_audio").checked;
const enable_acpi = settings.acpi === undefined ? $("enable_acpi").checked : settings.acpi;
/** @const */
var BIOSPATH = "bios/";
@ -666,13 +906,8 @@
{
var biosfile = DEBUG ? "seabios-debug.bin" : "seabios.bin";
var vgabiosfile = DEBUG ? "vgabios-debug.bin" : "vgabios.bin";
//var biosfile = DEBUG ? "seabios-ultradebug.bin" : "seabios.bin";
//var vgabiosfile = DEBUG ? "vgabios-ultradebug.bin" : "vgabios.bin";
}
//var biosfile = "seabios-qemu.bin";
//var vgabiosfile = "vgabios-qemu.bin";
var bios;
var vga_bios;
@ -692,12 +927,11 @@
"vga_memory_size": vga_memory_size,
"screen_container": $("screen_container"),
"serial_container": $("serial"),
"serial_container_xtermjs": $("terminal"),
"boot_order": settings.boot_order || parseInt($("boot_order").value, 16) || 0,
"network_relay_url": "wss://relay.widgetry.org/",
//"network_relay_url": "ws://localhost:8001/",
"network_relay_url": ON_LOCALHOST ? "ws://localhost:8080/" : networking_proxy,
"bios": bios,
"vga_bios": vga_bios,
@ -708,9 +942,16 @@
"cdrom": settings.cdrom,
"multiboot": settings.multiboot,
"bzimage": settings.bzimage,
"initrd": settings.initrd,
"cmdline": settings.cmdline,
"bzimage_initrd_from_filesystem": settings.bzimage_initrd_from_filesystem,
"acpi": enable_acpi,
"initial_state": settings.initial_state,
"filesystem": settings.filesystem || {},
"disable_speaker": disable_audio,
"preserve_mac_from_state_image": settings.preserve_mac_from_state_image,
"autostart": true,
});
@ -724,6 +965,43 @@
debug_start(emulator);
}
if(emulator.v86.cpu.wm.exports["profiler_is_enabled"]())
{
const CLEAR_STATS = false;
var panel = document.createElement("pre");
document.body.appendChild(panel);
setInterval(function()
{
if(!emulator.is_running())
{
return;
}
const text = print_stats.stats_to_string(emulator.v86.cpu);
panel.textContent = text;
CLEAR_STATS && emulator.v86.cpu.clear_opstats();
}, CLEAR_STATS ? 5000 : 1000);
}
if(settings.id === "dsl")
{
setTimeout(() => {
// hack: Start automatically
emulator.keyboard_send_text("\n");
}, 3000);
}
else if(settings.id == "android")
{
setTimeout(() => {
// hack: select vesa mode and start automatically
emulator.keyboard_send_scancodes([0xe050, 0xe050 | 0x80]);
emulator.keyboard_send_text("\n");
}, 3000);
}
init_ui(settings, emulator);
done && done(emulator);
@ -808,24 +1086,32 @@
var last_tick = 0;
var running_time = 0;
var last_instr_counter = 0;
var interval;
var interval = null;
var os_uses_mouse = false;
var total_instructions = 0;
function update_info()
{
var now = Date.now();
var instruction_counter = emulator.get_instruction_counter();
var last_ips = instruction_counter - last_instr_counter;
if(instruction_counter < last_instr_counter)
{
// 32-bit wrap-around
last_instr_counter -= 0x100000000;
}
var last_ips = instruction_counter - last_instr_counter;
last_instr_counter = instruction_counter;
total_instructions += last_ips;
var delta_time = now - last_tick;
running_time += delta_time;
last_tick = now;
$("speed").textContent = last_ips / delta_time | 0;
$("avg_speed").textContent = instruction_counter / running_time | 0;
$("speed").textContent = (last_ips / 1000 / delta_time).toFixed(1);
$("avg_speed").textContent = (total_instructions / 1000 / running_time).toFixed(1);
$("running_time").textContent = format_timestamp(running_time / 1000 | 0);
}
@ -838,33 +1124,52 @@
emulator.add_listener("emulator-stopped", function()
{
update_info();
clearInterval(interval);
if(interval !== null)
{
clearInterval(interval);
}
});
var stats_9p = {
read: 0,
write: 0,
files: [],
};
emulator.add_listener("9p-read-start", function()
emulator.add_listener("9p-read-start", function(args)
{
const file = args[0];
stats_9p.files.push(file);
$("info_filesystem").style.display = "block";
$("info_filesystem_status").textContent = "Loading ...";
$("info_filesystem_last_file").textContent = file;
});
emulator.add_listener("9p-read-end", function(args)
{
stats_9p.read += args[1];
$("info_filesystem_status").textContent = "Idle";
$("info_filesystem_last_file").textContent = args[0];
$("info_filesystem_bytes_read").textContent = stats_9p.read;
const file = args[0];
stats_9p.files = stats_9p.files.filter(f => f !== file);
if(stats_9p.files[0])
{
$("info_filesystem_last_file").textContent = stats_9p.files[0];
}
else
{
$("info_filesystem_status").textContent = "Idle";
}
});
emulator.add_listener("9p-write-end", function(args)
{
stats_9p.write += args[1];
$("info_filesystem_last_file").textContent = args[0];
$("info_filesystem_bytes_written").textContent = stats_9p.write;
if(!stats_9p.files[0])
{
$("info_filesystem_last_file").textContent = args[0];
}
});
var stats_storage = {
@ -997,7 +1302,8 @@
$("memory_dump").onclick = function()
{
dump_file(emulator.v86.cpu.mem8, "v86memory.bin");
const mem8 = emulator.v86.cpu.mem8;
dump_file(new Uint8Array(mem8.buffer, mem8.byteOffset, mem8.length), "v86memory.bin");
$("memory_dump").blur();
};
@ -1172,6 +1478,7 @@
$("screen_container").addEventListener("mousedown", (e) =>
{
e.preventDefault();
phone_keyboard.focus();
}, false);
@ -1182,8 +1489,6 @@
$("take_screenshot").blur();
};
$("serial").style.display = "block";
window.addEventListener("keydown", ctrl_w_rescue, false);
window.addEventListener("keyup", ctrl_w_rescue, false);
window.addEventListener("blur", ctrl_w_rescue, false);
@ -1267,31 +1572,14 @@
// called as soon as soon as emulation is started, in debug mode
var debug = emulator.v86.cpu.debug;
var debug_infos = $("debug_infos");
debug_infos.textContent += " | logging ops: " +
(debug.step_mode || debug.trace_all ? "yes" : "no");
$("step").onclick = debug.step.bind(debug);
$("run_until").onclick = debug.run_until.bind(debug);
$("dump_gdt").onclick = debug.dump_gdt_ldt.bind(debug);
$("dump_idt").onclick = debug.dump_idt.bind(debug);
$("dump_regs").onclick = debug.dump_regs.bind(debug);
$("dump_pt").onclick = debug.dump_page_directory.bind(debug);
$("dump_instructions").onclick = debug.dump_instructions.bind(debug);
$("dump_log").onclick = function()
{
dump_file(log_data, "v86.log");
};
$("dump_instructions_file").onclick = function()
{
var ins = debug.get_instructions();
if(ins)
{
dump_file(ins, "trace.txt");
}
dump_file(log_data.join(""), "v86.log");
};
var cpu = emulator.v86.cpu;
@ -1301,6 +1589,8 @@
{
$("debug_panel").textContent =
cpu.debug.get_regs_short().join("\n") + "\n" + cpu.debug.get_state();
$("dump_log").value = "Dump log" + (log_data.length ? " (" + log_data.length + " lines)" : "");
}, 1000);
// helps debugging

View file

@ -13,7 +13,7 @@ function ModemAdapter()
this.enabled = true;
this.socket = new WebSocket("ws://localhost:2080");
this.socket.onopen = this.onopen.bind(this);;
this.socket.onopen = this.onopen.bind(this);
this.socket.onmessage = this.onmessage.bind(this);
this.socket.onclose = this.onclose.bind(this);
this.socket.onerror = this.onerror.bind(this);
@ -60,4 +60,4 @@ ModemAdapter.prototype.put_chr = function(chr)
{
this.socket.send(chr);
}
}
};

View file

@ -34,6 +34,10 @@ function MouseAdapter(bus, screen_container)
this.destroy = function()
{
if(typeof window === "undefined")
{
return;
}
window.removeEventListener("touchstart", touch_start_handler, false);
window.removeEventListener("touchend", touch_end_handler, false);
window.removeEventListener("touchmove", mousemove_handler, false);
@ -41,7 +45,7 @@ function MouseAdapter(bus, screen_container)
window.removeEventListener("mousedown", mousedown_handler, false);
window.removeEventListener("mouseup", mouseup_handler, false);
window.removeEventListener("DOMMouseScroll", mousewheel_handler, false);
window.removeEventListener("mousewheel", mousewheel_handler, false);
window.removeEventListener("mousewheel", mousewheel_handler, { passive: false });
};
this.init = function()
@ -59,7 +63,7 @@ function MouseAdapter(bus, screen_container)
window.addEventListener("mousedown", mousedown_handler, false);
window.addEventListener("mouseup", mouseup_handler, false);
window.addEventListener("DOMMouseScroll", mousewheel_handler, false);
window.addEventListener("mousewheel", mousewheel_handler, false);
window.addEventListener("mousewheel", mousewheel_handler, { passive: false });
};
this.init();
@ -84,18 +88,27 @@ function MouseAdapter(bus, screen_container)
return false;
}
if(e.type === "mousemove" || e.type === "touchmove")
{
return true;
}
const MOVE_MOUSE_WHEN_OVER_SCREEN_ONLY = true;
if(e.type === "mousewheel" || e.type === "DOMMouseScroll")
if(MOVE_MOUSE_WHEN_OVER_SCREEN_ONLY)
{
var parent = screen_container || document.body;
return is_child(e.target, parent);
return document.pointerLockElement || is_child(e.target, parent);
}
else
{
if(e.type === "mousemove" || e.type === "touchmove")
{
return true;
}
return !e.target || e.target.nodeName !== "INPUT" && e.target.nodeName !== "TEXTAREA";
if(e.type === "mousewheel" || e.type === "DOMMouseScroll")
{
return is_child(e.target, parent);
}
return !e.target || e.target.nodeName !== "INPUT" && e.target.nodeName !== "TEXTAREA";
}
}
function touch_start_handler(e)
@ -239,7 +252,7 @@ function MouseAdapter(bus, screen_container)
}
else
{
console.log("Unknown event.which: " + e.which);
dbg_log("Unknown event.which: " + e.which);
}
mouse.bus.send("mouse-click", [left_down, middle_down, right_down]);
}

View file

@ -13,8 +13,6 @@
*/
function NetworkAdapter(url, bus)
{
this.send_data = function(x) {};
this.bus = bus;
this.socket = undefined;
@ -75,6 +73,11 @@ NetworkAdapter.prototype.destroy = function()
NetworkAdapter.prototype.connect = function()
{
if(typeof WebSocket === "undefined")
{
return;
}
if(this.socket)
{
var state = this.socket.readyState;
@ -95,19 +98,10 @@ NetworkAdapter.prototype.connect = function()
this.last_connect_attempt = Date.now();
try
{
this.socket = new WebSocket(this.url);
}
catch(e)
{
this.handle_close(undefined);
return;
}
this.socket = new WebSocket(this.url);
this.socket.binaryType = "arraybuffer";
this.socket.onopen = this.handle_open.bind(this);;
this.socket.onopen = this.handle_open.bind(this);
this.socket.onmessage = this.handle_message.bind(this);
this.socket.onclose = this.handle_close.bind(this);
this.socket.onerror = this.handle_error.bind(this);
@ -132,4 +126,17 @@ NetworkAdapter.prototype.send = function(data)
{
this.socket.send(data);
}
}
};
NetworkAdapter.prototype.change_proxy = function(url)
{
this.url = url;
if(this.socket)
{
this.socket.onclose = function() {};
this.socket.onerror = function() {};
this.socket.close();
this.socket = undefined;
}
};

280
src/browser/print_stats.js Normal file
View file

@ -0,0 +1,280 @@
"use strict";
const print_stats = {
stats_to_string: function(cpu)
{
return print_stats.print_misc_stats(cpu) +
print_stats.print_instruction_counts(cpu);
},
print_misc_stats: function(cpu)
{
let text = "";
const stat_names = [
"COMPILE",
"COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS",
"COMPILE_SUCCESS",
"COMPILE_WRONG_ADDRESS_SPACE",
"COMPILE_CUT_OFF_AT_END_OF_PAGE",
"COMPILE_WITH_LOOP_SAFETY",
"COMPILE_PAGE",
"COMPILE_PAGE/COMPILE_SUCCESS",
"COMPILE_PAGE_SKIPPED_NO_NEW_ENTRY_POINTS",
"COMPILE_BASIC_BLOCK",
"COMPILE_DUPLICATED_BASIC_BLOCK",
"COMPILE_WASM_BLOCK",
"COMPILE_WASM_LOOP",
"COMPILE_DISPATCHER",
"COMPILE_ENTRY_POINT",
"COMPILE_WASM_TOTAL_BYTES",
"COMPILE_WASM_TOTAL_BYTES/COMPILE_PAGE",
"JIT_CACHE_OVERRIDE",
"JIT_CACHE_OVERRIDE_DIFFERENT_STATE_FLAGS",
"RUN_INTERPRETED",
"RUN_INTERPRETED_PENDING",
"RUN_INTERPRETED_NEAR_END_OF_PAGE",
"RUN_INTERPRETED_DIFFERENT_STATE",
"RUN_INTERPRETED_MISSED_COMPILED_ENTRY_RUN_INTERPRETED",
"RUN_INTERPRETED_MISSED_COMPILED_ENTRY_LOOKUP",
"RUN_INTERPRETED_STEPS",
"RUN_FROM_CACHE",
"RUN_FROM_CACHE_STEPS",
"RUN_FROM_CACHE_STEPS/RUN_FROM_CACHE",
"RUN_FROM_CACHE_STEPS/RUN_INTERPRETED_STEPS",
"DIRECT_EXIT",
"INDIRECT_JUMP",
"INDIRECT_JUMP_NO_ENTRY",
"NORMAL_PAGE_CHANGE",
"NORMAL_FALLTHRU",
"NORMAL_FALLTHRU_WITH_TARGET_BLOCK",
"NORMAL_BRANCH",
"NORMAL_BRANCH_WITH_TARGET_BLOCK",
"CONDITIONAL_JUMP",
"CONDITIONAL_JUMP_PAGE_CHANGE",
"CONDITIONAL_JUMP_EXIT",
"CONDITIONAL_JUMP_FALLTHRU",
"CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK",
"CONDITIONAL_JUMP_BRANCH",
"CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK",
"DISPATCHER_SMALL",
"DISPATCHER_LARGE",
"LOOP",
"LOOP_SAFETY",
"CONDITION_OPTIMISED",
"CONDITION_UNOPTIMISED",
"FAILED_PAGE_CHANGE",
"SAFE_READ_FAST",
"SAFE_READ_SLOW_PAGE_CROSSED",
"SAFE_READ_SLOW_NOT_VALID",
"SAFE_READ_SLOW_NOT_USER",
"SAFE_READ_SLOW_IN_MAPPED_RANGE",
"SAFE_WRITE_FAST",
"SAFE_WRITE_SLOW_PAGE_CROSSED",
"SAFE_WRITE_SLOW_NOT_VALID",
"SAFE_WRITE_SLOW_NOT_USER",
"SAFE_WRITE_SLOW_IN_MAPPED_RANGE",
"SAFE_WRITE_SLOW_READ_ONLY",
"SAFE_WRITE_SLOW_HAS_CODE",
"SAFE_READ_WRITE_FAST",
"SAFE_READ_WRITE_SLOW_PAGE_CROSSED",
"SAFE_READ_WRITE_SLOW_NOT_VALID",
"SAFE_READ_WRITE_SLOW_NOT_USER",
"SAFE_READ_WRITE_SLOW_IN_MAPPED_RANGE",
"SAFE_READ_WRITE_SLOW_READ_ONLY",
"SAFE_READ_WRITE_SLOW_HAS_CODE",
"PAGE_FAULT",
"TLB_MISS",
"DO_RUN",
"DO_MANY_CYCLES",
"CYCLE_INTERNAL",
"INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES",
"INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED",
"INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE",
"INVALIDATE_MODULE_DIRTY_PAGE",
"INVALIDATE_PAGE_HAD_CODE",
"INVALIDATE_PAGE_HAD_ENTRY_POINTS",
"DIRTY_PAGE_DID_NOT_HAVE_CODE",
"RUN_FROM_CACHE_EXIT_SAME_PAGE",
"RUN_FROM_CACHE_EXIT_NEAR_END_OF_PAGE",
"RUN_FROM_CACHE_EXIT_DIFFERENT_PAGE",
"CLEAR_TLB",
"FULL_CLEAR_TLB",
"TLB_FULL",
"TLB_GLOBAL_FULL",
"MODRM_SIMPLE_REG",
"MODRM_SIMPLE_REG_WITH_OFFSET",
"MODRM_SIMPLE_CONST_OFFSET",
"MODRM_COMPLEX",
"SEG_OFFSET_OPTIMISED",
"SEG_OFFSET_NOT_OPTIMISED",
];
let j = 0;
const stat_values = {};
for(let i = 0; i < stat_names.length; i++)
{
const name = stat_names[i];
let value;
if(name.includes("/"))
{
j++; // skip profiler_stat_get
const [left, right] = name.split("/");
value = stat_values[left] / stat_values[right];
}
else
{
let stat = stat_values[name] = cpu.wm.exports["profiler_stat_get"](i - j);
value = stat >= 100e6 ? Math.round(stat / 1e6) + "m" : stat >= 100e3 ? Math.round(stat / 1e3) + "k" : stat;
}
text += name + "=" + value + "\n";
}
text += "\n";
const tlb_entries = cpu.wm.exports["get_valid_tlb_entries_count"]();
const global_tlb_entries = cpu.wm.exports["get_valid_global_tlb_entries_count"]();
const nonglobal_tlb_entries = tlb_entries - global_tlb_entries;
text += "TLB_ENTRIES=" + tlb_entries + " (" + global_tlb_entries + " global, " + nonglobal_tlb_entries + " non-global)\n";
text += "WASM_TABLE_FREE=" + cpu.wm.exports["jit_get_wasm_table_index_free_list_count"]() + "\n";
text += "JIT_CACHE_SIZE=" + cpu.wm.exports["jit_get_cache_size"]() + "\n";
text += "FLAT_SEGMENTS=" + cpu.wm.exports["has_flat_segmentation"]() + "\n";
text += "do_many_cycles avg: " + (do_many_cycles_total / do_many_cycles_count || 0) + "\n";
text += "wasm memory size: " + (cpu.wasm_memory.buffer.byteLength >> 20) + "m\n";
text += "Config:\n";
text += "MAX_PAGES=" + cpu.wm.exports["get_config"](0) + "\n";
text += "JIT_USE_LOOP_SAFETY=" + cpu.wm.exports["get_config"](1) + "\n";
text += "MAX_EXTRA_BASIC_BLOCKS=" + cpu.wm.exports["get_config"](2) + "\n";
return text;
},
print_instruction_counts: function(cpu)
{
return [
print_stats.print_instruction_counts_offset(cpu, false, false, false, false),
print_stats.print_instruction_counts_offset(cpu, true, false, false, false),
print_stats.print_instruction_counts_offset(cpu, false, true, false, false),
print_stats.print_instruction_counts_offset(cpu, false, false, true, false),
print_stats.print_instruction_counts_offset(cpu, false, false, false, true),
].join("\n\n");
},
print_instruction_counts_offset: function(cpu, compiled, jit_exit, unguarded_register, wasm_size)
{
let text = "";
const counts = [];
const label =
compiled ? "compiled" :
jit_exit ? "jit exit" :
unguarded_register ? "unguarded register" :
wasm_size ? "wasm size" :
"executed";
for(let opcode = 0; opcode < 0x100; opcode++)
{
for(let fixed_g = 0; fixed_g < 8; fixed_g++)
{
for(let is_mem of [false, true])
{
const count = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, false, is_mem, fixed_g);
counts.push({ opcode, count, is_mem, fixed_g });
const count_0f = cpu.wm.exports["get_opstats_buffer"](compiled, jit_exit, unguarded_register, wasm_size, opcode, true, is_mem, fixed_g);
counts.push({ opcode: 0x0f00 | opcode, count: count_0f, is_mem, fixed_g });
}
}
}
let total = 0;
const prefixes = new Set([
0x26, 0x2E, 0x36, 0x3E,
0x64, 0x65, 0x66, 0x67,
0xF0, 0xF2, 0xF3,
]);
for(let { count, opcode } of counts)
{
if(!prefixes.has(opcode))
{
total += count;
}
}
if(total === 0)
{
return "";
}
const per_opcode = new Uint32Array(0x100);
const per_opcode0f = new Uint32Array(0x100);
for(let { opcode, count } of counts)
{
if((opcode & 0xFF00) == 0x0F00)
{
per_opcode0f[opcode & 0xFF] += count;
}
else
{
per_opcode[opcode & 0xFF] += count;
}
}
text += "------------------\n";
text += "Total: " + total + "\n";
const factor = total > 1e7 ? 1000 : 1;
const max_count = Math.max.apply(Math,
counts.map(({ count }) => Math.round(count / factor))
);
const pad_length = String(max_count).length;
text += `Instruction counts ${label} (in ${factor}):\n`;
for(let i = 0; i < 0x100; i++)
{
text += h(i, 2).slice(2) + ":" + v86util.pads(Math.round(per_opcode[i] / factor), pad_length);
if(i % 16 == 15)
text += "\n";
else
text += " ";
}
text += "\n";
text += `Instruction counts ${label} (0f, in ${factor}):\n`;
for(let i = 0; i < 0x100; i++)
{
text += h(i & 0xFF, 2).slice(2) + ":" + v86util.pads(Math.round(per_opcode0f[i] / factor), pad_length);
if(i % 16 == 15)
text += "\n";
else
text += " ";
}
text += "\n";
const top_counts = counts.filter(({ count }) => count).sort(({ count: count1 }, { count: count2 }) => count2 - count1);
for(let { opcode, is_mem, fixed_g, count } of top_counts.slice(0, 200))
{
let opcode_description = opcode.toString(16) + "_" + fixed_g + (is_mem ? "_m" : "_r");
text += opcode_description + ":" + (count / total * 100).toFixed(2) + " ";
}
text += "\n";
return text;
},
};
if(typeof module !== "undefined" && typeof module.exports !== "undefined")
{
module.exports["print_stats"] = print_stats;
}

View file

@ -1,13 +1,5 @@
"use strict";
if(typeof window !== "undefined" && !window.requestAnimationFrame)
{
window.requestAnimationFrame =
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame;
}
/**
* Adapter to use visual screen in browsers (in contrast to node)
* @constructor
@ -20,7 +12,7 @@ function ScreenAdapter(screen_container, bus)
var
graphic_screen = screen_container.getElementsByTagName("canvas")[0],
graphic_context = graphic_screen.getContext("2d"),
graphic_context = graphic_screen.getContext("2d", { alpha: false }),
text_screen = screen_container.getElementsByTagName("div")[0],
cursor_element = document.createElement("div");
@ -42,6 +34,8 @@ function ScreenAdapter(screen_container, bus)
/** @type {number} */
scale_y = 1,
base_scale = 1,
graphical_mode_width,
graphical_mode_height,
@ -193,7 +187,10 @@ function ScreenAdapter(screen_container, bus)
this.make_screenshot = function()
{
try {
window.open(graphic_screen.toDataURL());
const image = new Image();
image.src = graphic_screen.toDataURL("image/png");
const w = window.open("");
w.document.write(image.outerHTML);
}
catch(e) {}
};
@ -331,6 +328,16 @@ function ScreenAdapter(screen_container, bus)
graphical_mode_width = width;
graphical_mode_height = height;
// add some scaling to tiny resolutions
if(graphical_mode_width <= 640)
{
base_scale = 2;
}
else
{
base_scale = 1;
}
this.bus.send("screen-tell-buffer", [graphic_buffer32], [graphic_buffer32.buffer]);
update_scale_graphic();
};
@ -352,7 +359,7 @@ function ScreenAdapter(screen_container, bus)
function update_scale_graphic()
{
elem_set_scale(graphic_screen, scale_x, scale_y, false);
elem_set_scale(graphic_screen, scale_x * base_scale, scale_y * base_scale, false);
}
function elem_set_scale(elem, scale_x, scale_y, use_scale)
@ -362,7 +369,7 @@ function ScreenAdapter(screen_container, bus)
if(use_scale)
{
elem.style.transform = elem.style.webkitTransform = elem.style.MozTransform = "";
elem.style.transform = "";
}
var rectangle = elem.getBoundingClientRect();
@ -374,18 +381,15 @@ function ScreenAdapter(screen_container, bus)
scale_str += scale_x === 1 ? "" : " scaleX(" + scale_x + ")";
scale_str += scale_y === 1 ? "" : " scaleY(" + scale_y + ")";
elem.style.transform = elem.style.webkitTransform = elem.style.MozTransform = scale_str;
elem.style.transform = scale_str;
}
else
{
// unblur non-fractional scales
if(scale_x % 1 === 0 && scale_y % 1 === 0)
{
graphic_screen.style.imageRendering = "-moz-crisp-edges";
graphic_screen.style.imageRendering = "moz-crisp-edges";
graphic_screen.style.imageRendering = "webkit-optimize-contrast";
graphic_screen.style.imageRendering = "o-crisp-edges";
graphic_screen.style.imageRendering = "pixelated";
graphic_screen.style["imageRendering"] = "crisp-edges"; // firefox
graphic_screen.style["imageRendering"] = "pixelated";
graphic_screen.style["-ms-interpolation-mode"] = "nearest-neighbor";
}
else
@ -547,5 +551,3 @@ function ScreenAdapter(screen_container, bus)
this.init();
}

View file

@ -35,6 +35,7 @@ function SerialAdapter(element, bus)
{
this.destroy();
element.style.display = "block";
element.addEventListener("keypress", keypress_handler, false);
element.addEventListener("keydown", keydown_handler, false);
element.addEventListener("paste", paste_handler, false);
@ -79,7 +80,7 @@ function SerialAdapter(element, bus)
this.update_timer = setTimeout(() => {
this.update_timer = undefined;
var now = Date.now();
dbg_assert(now - this.last_update >= 16);
dbg_assert(now - this.last_update >= 15);
this.last_update = now;
this.render();
}, 16 - delta);
@ -107,7 +108,7 @@ function SerialAdapter(element, bus)
this.text_new_line = false;
element.scrollTop = 1e9;
}
}
};
/**
* @param {number} chr_code
@ -192,3 +193,51 @@ function SerialAdapter(element, bus)
}
}
}
/**
* @constructor
*
* @param {BusConnector} bus
*/
function SerialRecordingAdapter(bus)
{
var serial = this;
this.text = "";
bus.register("serial0-output-char", function(chr)
{
this.text += chr;
}, this);
}
/**
* @constructor
* @param {BusConnector} bus
*/
function SerialAdapterXtermJS(element, bus)
{
this.element = element;
if(!window["Terminal"])
{
return;
}
var term = this.term = new window["Terminal"]();
term["setOption"]("logLevel", "off");
term.write("This is the serial console. Whatever you type or paste here will be sent to COM1");
term["onData"](function(data) {
bus.send("serial0-input", data.charCodeAt(0));
});
bus.register("serial0-output-char", function(chr)
{
term.write(chr);
}, this);
}
SerialAdapterXtermJS.prototype.show = function()
{
this.term && this.term.open(this.element);
};

View file

@ -28,7 +28,7 @@ function SpeakerAdapter(bus)
this.bus = bus;
/** @const */
this.audio_context = new (window.AudioContext || window["webkitAudioContext"])();
this.audio_context = new AudioContext();
/** @const */
this.mixer = new SpeakerMixer(bus, this.audio_context);
@ -765,9 +765,11 @@ function SpeakerWorkletDAC(bus, audio_context, mixer)
this.node_processor = new AudioWorkletNode(this.audio_context, "dac-processor",
{
"numberOfInputs": 0,
"numberOfOutputs": 1,
"outputChannelCount": [2],
numberOfInputs: 0,
numberOfOutputs: 1,
outputChannelCount: [2],
parameterData: {},
processorOptions: {},
});
this.node_processor.port.postMessage(

View file

@ -27,6 +27,12 @@
* - `hda Object` (No hard drive) - First hard disk, see below.
* - `fda Object` (No floppy disk) - First floppy disk, see below.
* - `cdrom Object` (No CD) - See below.
*
* - `bzimage Object` - A Linux kernel image to boot (only bzimage format), see below.
* - `initrd Object` - A Linux ramdisk image, see below.
* - `bzimage_initrd_from_filesystem boolean` - Automatically fetch bzimage and
* initrd from the specified `filesystem`.
*
* - `initial_state Object` (Normal boot) - An initial state to load, see
* [`restore_state`](#restore_statearraybuffer-state) and below.
*
@ -89,11 +95,144 @@ function V86Starter(options)
this.cpu_is_running = false;
var bus = Bus.create();
var adapter_bus = this.bus = bus[0];
const bus = Bus.create();
const adapter_bus = this.bus = bus[0];
this.emulator_bus = bus[1];
var emulator = this.v86 = new v86(this.emulator_bus);
var cpu;
var wasm_memory;
const wasm_table = new WebAssembly.Table({ element: "anyfunc", "initial": WASM_TABLE_SIZE + WASM_TABLE_OFFSET });
const wasm_shared_funcs = {
"cpu_exception_hook": (n) => {
return this["cpu_exception_hook"] && this["cpu_exception_hook"](n);
},
"hlt_op": function() { return cpu.hlt_op(); },
"abort": function() { dbg_assert(false); },
"logop": function(eip, op) { return cpu.debug.logop(eip, op); },
"microtick": v86.microtick,
"get_rand_int": function() { return v86util.get_rand_int(); },
"pic_acknowledge": function() { cpu.pic_acknowledge(); },
"io_port_read8": function(addr) { return cpu.io.port_read8(addr); },
"io_port_read16": function(addr) { return cpu.io.port_read16(addr); },
"io_port_read32": function(addr) { return cpu.io.port_read32(addr); },
"io_port_write8": function(addr, value) { cpu.io.port_write8(addr, value); },
"io_port_write16": function(addr, value) { cpu.io.port_write16(addr, value); },
"io_port_write32": function(addr, value) { cpu.io.port_write32(addr, value); },
"mmap_read8": function(addr) { return cpu.mmap_read8(addr); },
"mmap_read16": function(addr) { return cpu.mmap_read16(addr); },
"mmap_read32": function(addr) { return cpu.mmap_read32(addr); },
"mmap_write8": function(addr, value) { cpu.mmap_write8(addr, value); },
"mmap_write16": function(addr, value) { cpu.mmap_write16(addr, value); },
"mmap_write32": function(addr, value) { cpu.mmap_write32(addr, value); },
"mmap_write64": function(addr, value0, value1) { cpu.mmap_write64(addr, value0, value1); },
"mmap_write128": function(addr, value0, value1, value2, value3) {
cpu.mmap_write128(addr, value0, value1, value2, value3);
},
"cpuid": function() { return cpu.cpuid(); },
"load_ldt": function() { return cpu.load_ldt.apply(cpu, arguments); },
"load_tr": function() { return cpu.load_tr.apply(cpu, arguments); },
"log_from_wasm": function(offset, len) {
const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
dbg_log(str, LOG_CPU);
},
"console_log_from_wasm": function(offset, len) {
const str = v86util.read_sized_string_from_mem(wasm_memory, offset, len);
console.error(str);
},
"dbg_trace_from_wasm": function() {
dbg_trace();
},
"codegen_finalize": (wasm_table_index, start, state_flags, ptr, len) => {
cpu.codegen_finalize(wasm_table_index, start, state_flags, ptr, len);
},
"jit_clear_func": (wasm_table_index) => cpu.jit_clear_func(wasm_table_index),
"jit_clear_all_funcs": () => cpu.jit_clear_all_funcs(),
"do_task_switch": (selector, has_error_code, error_code) => {
cpu.do_task_switch(selector, has_error_code, error_code);
},
"__indirect_function_table": wasm_table,
};
let v86_bin = DEBUG ? "v86-debug.wasm" : "v86.wasm";
let v86_bin_fallback = "v86-fallback.wasm";
if(options["wasm_path"])
{
v86_bin = options["wasm_path"];
}
else if(typeof window === "undefined" && typeof __dirname === "string")
{
v86_bin = __dirname + "/" + v86_bin;
v86_bin_fallback = __dirname + "/" + v86_bin_fallback;
}
else
{
v86_bin = "build/" + v86_bin;
v86_bin_fallback = "build/" + v86_bin_fallback;
}
v86util.load_file(v86_bin, {
done: bytes =>
{
WebAssembly
.instantiate(bytes, { "env": wasm_shared_funcs })
.then(({ instance }) => {
const imports = wasm_shared_funcs;
const exports = instance["exports"];
wasm_memory = exports.memory;
exports["rust_init"]();
const emulator = this.v86 = new v86(this.emulator_bus, { exports, wasm_table });
cpu = emulator.cpu;
this.continue_init(emulator, options);
}, err => {
v86util.load_file(v86_bin_fallback, {
done: bytes => {
WebAssembly
.instantiate(bytes, { "env": wasm_shared_funcs })
.then(({ instance }) => {
const imports = wasm_shared_funcs;
const exports = instance["exports"];
wasm_memory = exports.memory;
exports["rust_init"]();
const emulator = this.v86 = new v86(this.emulator_bus, { exports, wasm_table });
cpu = emulator.cpu;
this.continue_init(emulator, options);
});
},
});
});
},
progress: e =>
{
this.emulator_bus.send("download-progress", {
file_index: 0,
file_count: 1,
file_name: v86_bin,
lengthComputable: e.lengthComputable,
total: e.total,
loaded: e.loaded,
});
}
});
}
V86Starter.prototype.continue_init = async function(emulator, options)
{
this.bus.register("emulator-stopped", function()
{
this.cpu_is_running = false;
@ -114,49 +253,62 @@ function V86Starter(options)
"cdrom": undefined,
};
settings.acpi = options["acpi"];
settings.load_devices = true;
settings.log_level = options["log_level"];
settings.memory_size = options["memory_size"] || 64 * 1024 * 1024;
settings.vga_memory_size = options["vga_memory_size"] || 8 * 1024 * 1024;
settings.boot_order = options["boot_order"] || 0x213;
settings.fastboot = options["fastboot"] || false;
settings.fda = undefined;
settings.fdb = undefined;
settings.uart1 = options["uart1"] || false;
settings.uart2 = options["uart2"] || false;
settings.uart3 = options["uart3"] || false;
settings.uart1 = options["uart1"];
settings.uart2 = options["uart2"];
settings.uart3 = options["uart3"];
settings.cmdline = options["cmdline"];
settings.preserve_mac_from_state_image = options["preserve_mac_from_state_image"];
if(options["network_relay_url"])
{
this.network_adapter = new NetworkAdapter(options["network_relay_url"], adapter_bus);
settings.enable_ne2k = true;
this.network_adapter = new NetworkAdapter(options["network_relay_url"], this.bus);
}
// Enable unconditionally, so that state images don't miss hardware
// TODO: Should be properly fixed in restore_state
settings.enable_ne2k = true;
if(!options["disable_keyboard"])
{
this.keyboard_adapter = new KeyboardAdapter(adapter_bus);
this.keyboard_adapter = new KeyboardAdapter(this.bus);
}
if(!options["disable_mouse"])
{
this.mouse_adapter = new MouseAdapter(adapter_bus, options["screen_container"]);
this.mouse_adapter = new MouseAdapter(this.bus, options["screen_container"]);
}
if(options["screen_container"])
{
this.screen_adapter = new ScreenAdapter(options["screen_container"], adapter_bus);
this.screen_adapter = new ScreenAdapter(options["screen_container"], this.bus);
}
else if(options["screen_dummy"])
{
this.screen_adapter = new DummyScreenAdapter(adapter_bus);
this.screen_adapter = new DummyScreenAdapter(this.bus);
}
if(options["serial_container"])
{
this.serial_adapter = new SerialAdapter(options["serial_container"], adapter_bus);
this.serial_adapter = new SerialAdapter(options["serial_container"], this.bus);
//this.recording_adapter = new SerialRecordingAdapter(this.bus);
}
if(options["serial_container_xtermjs"])
{
this.serial_adapter = new SerialAdapterXtermJS(options["serial_container_xtermjs"], this.bus);
}
if(!options["disable_speaker"])
{
this.speaker_adapter = new SpeakerAdapter(adapter_bus);
this.speaker_adapter = new SpeakerAdapter(this.bus);
}
// ugly, but required for closure compiler compilation
@ -181,7 +333,13 @@ function V86Starter(options)
break;
case "multiboot":
settings.multiboot = this.disk_images["multiboot"] = buffer;
settings.multiboot = this.disk_images["multiboot"] = buffer.buffer;
break;
case "bzimage":
settings.bzimage = this.disk_images["bzimage"] = buffer.buffer;
break;
case "initrd":
settings.initrd = this.disk_images["initrd"] = buffer.buffer;
break;
case "bios":
@ -194,7 +352,7 @@ function V86Starter(options)
settings.initial_state = buffer.buffer;
break;
case "fs9p_json":
settings.fs9p_json = buffer.buffer;
settings.fs9p_json = buffer;
break;
default:
dbg_assert(false, name);
@ -226,10 +384,12 @@ function V86Starter(options)
async: file["async"],
url: file["url"],
size: file["size"],
use_parts: file.use_parts,
};
if(name === "bios" || name === "vga_bios" ||
name === "initial_state" || name === "multiboot")
name === "initial_state" || name === "multiboot" ||
name === "bzimage" || name === "initrd")
{
// Ignore async for these because they must be available before boot.
// This should make result.buffer available after the object is loaded
@ -279,7 +439,17 @@ function V86Starter(options)
{
if(file.async)
{
var buffer = new v86util.AsyncXHRBuffer(file.url, file.size);
let buffer;
if(file.use_parts)
{
buffer = new v86util.AsyncXHRPartfileBuffer(file.url, file.size);
}
else
{
buffer = new v86util.AsyncXHRBuffer(file.url, file.size);
}
files_to_load.push({
name: name,
loadable: buffer,
@ -300,10 +470,16 @@ function V86Starter(options)
}
}
if(options["state"])
{
console.warn("Warning: Unknown option 'state'. Did you mean 'initial_state'?");
}
var image_names = [
"bios", "vga_bios",
"cdrom", "hda", "hdb", "fda", "fdb",
"initial_state", "multiboot",
"bzimage", "initrd",
];
for(var i = 0; i < image_names.length; i++)
@ -316,8 +492,13 @@ function V86Starter(options)
var fs_url = options["filesystem"]["basefs"];
var base_url = options["filesystem"]["baseurl"];
this.fs9p = new FS(base_url);
settings.fs9p = this.fs9p;
let file_storage = new MemoryFileStorage();
if(base_url)
{
file_storage = new ServerFileStorageWrapper(file_storage, base_url);
}
settings.fs9p = this.fs9p = new FS(file_storage);
if(fs_url)
{
@ -336,7 +517,7 @@ function V86Starter(options)
name: "fs9p_json",
url: fs_url,
size: size,
as_text: true,
as_json: true,
});
}
}
@ -368,7 +549,7 @@ function V86Starter(options)
v86util.load_file(f.url, {
done: function(result)
{
put_on_settings.call(this, f.name, new SyncBuffer(result));
put_on_settings.call(this, f.name, f.as_json ? result : new SyncBuffer(result));
cont(index + 1);
}.bind(this),
progress: function progress(e)
@ -395,7 +576,7 @@ function V86Starter(options)
});
}
},
as_text: f.as_text,
as_json: f.as_json,
});
}
}.bind(this);
@ -403,36 +584,111 @@ function V86Starter(options)
function done()
{
if(settings.initial_state)
//if(settings.initial_state)
//{
// // avoid large allocation now, memory will be restored later anyway
// settings.memory_size = 0;
//}
if(settings.fs9p && settings.fs9p_json)
{
// avoid large allocation now, memory will be restored later anyway
settings.memory_size = 0;
if(!settings.initial_state)
{
settings.fs9p.load_from_json(settings.fs9p_json);
}
else
{
dbg_log("Filesystem basefs ignored: Overridden by state image");
}
if(options["bzimage_initrd_from_filesystem"])
{
const { bzimage, initrd } = this.get_bzimage_initrd_from_filesystem(settings.fs9p);
dbg_log("Found bzimage: " + bzimage + " and initrd: " + initrd);
Promise.all([
settings.fs9p.read_file(initrd),
settings.fs9p.read_file(bzimage),
]).then(([initrd, bzimage]) => {
put_on_settings.call(this, "initrd", new SyncBuffer(initrd.buffer));
put_on_settings.call(this, "bzimage", new SyncBuffer(bzimage.buffer));
finish.call(this);
});
}
else
{
finish.call(this);
}
}
else
{
console.assert(
!options["bzimage_initrd_from_filesystem"],
"bzimage_initrd_from_filesystem: Requires a filesystem");
finish.call(this);
}
this.bus.send("cpu-init", settings);
setTimeout(function()
function finish()
{
this.serial_adapter && this.serial_adapter.show && this.serial_adapter.show();
this.bus.send("cpu-init", settings);
if(settings.initial_state)
{
emulator.restore_state(settings.initial_state);
// The GC can't free settings, since it is referenced from
// several closures. This isn't needed anymore, so we delete it
// here
settings.initial_state = undefined;
}
setTimeout(function()
if(options["autostart"])
{
if(settings.fs9p && settings.fs9p_json)
{
settings.fs9p.OnJSONLoaded(settings.fs9p_json);
}
this.bus.send("cpu-run");
}
if(options["autostart"])
{
this.bus.send("cpu-run");
}
}.bind(this), 0);
}.bind(this), 0);
this.emulator_bus.send("emulator-loaded");
}
}
}
};
V86Starter.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
{
const root = (filesystem.read_dir("/") || []).map(x => "/" + x);
const boot = (filesystem.read_dir("/boot/") || []).map(x => "/boot/" + x);
let initrd;
let bzimage;
for(let f of [].concat(root, boot))
{
const old = /old/i.test(f) || /fallback/i.test(f);
const is_bzimage = /vmlinuz/i.test(f) || /bzimage/i.test(f);
const is_initrd = /initrd/i.test(f) || /initramfs/i.test(f);
if(is_bzimage && (!bzimage || !old))
{
bzimage = f;
}
if(is_initrd && (!initrd || !old))
{
initrd = f;
}
}
if(!initrd || !bzimage)
{
console.log("Failed to find bzimage or initrd in filesystem. Files:");
console.log(root.join(" "));
console.log(boot.join(" "));
}
return { initrd, bzimage };
};
/**
* Start emulation. Do nothing if emulator is running already. Can be
@ -643,7 +899,7 @@ V86Starter.prototype.get_instruction_counter = function()
{
if(this.v86)
{
return this.v86.cpu.timestamp_counter;
return this.v86.cpu.instruction_counter[0] >>> 0;
}
else
{
@ -851,6 +1107,59 @@ V86Starter.prototype.serial_send_bytes = function(serial, data)
}
};
/**
* Mount another filesystem to the current filesystem.
* @param {string} path Path for the mount point
* @param {string|undefined} baseurl
* @param {string|undefined} basefs As a JSON string
* @param {function(Object)=} callback
* @export
*/
V86Starter.prototype.mount_fs = async function(path, baseurl, basefs, callback)
{
let file_storage = new MemoryFileStorage();
if(baseurl)
{
file_storage = new ServerFileStorageWrapper(file_storage, baseurl);
}
const newfs = new FS(file_storage, this.fs9p.qidcounter);
const mount = () =>
{
const idx = this.fs9p.Mount(path, newfs);
if(!callback)
{
return;
}
if(idx === -ENOENT)
{
callback(new FileNotFoundError());
}
else if(idx === -EEXIST)
{
callback(new FileExistsError());
}
else if(idx < 0)
{
dbg_assert(false, "Unexpected error code: " + (-idx));
callback(new Error("Failed to mount. Error number: " + (-idx)));
}
else
{
callback(null);
}
};
if(baseurl)
{
dbg_assert(typeof basefs === "object", "Filesystem: basefs must be a JSON object");
newfs.load_from_json(basefs, () => mount());
}
else
{
mount();
}
};
/**
* Write to a file in the 9p filesystem. Nothing happens if no filesystem has
* been initialized. First argument to the callback is an error object if
@ -863,6 +1172,8 @@ V86Starter.prototype.serial_send_bytes = function(serial, data)
*/
V86Starter.prototype.create_file = function(file, data, callback)
{
callback = callback || function() {};
var fs = this.fs9p;
if(!fs)
@ -879,21 +1190,14 @@ V86Starter.prototype.create_file = function(file, data, callback)
if(!not_found)
{
fs.CreateBinaryFile(filename, parent_id, data);
fs.CreateBinaryFile(filename, parent_id, data)
.then(() => callback(null));
}
if(callback)
else
{
setTimeout(function()
{
if(not_found)
{
callback(new FileNotFoundError());
}
else
{
callback(null);
}
callback(new FileNotFoundError());
}, 0);
}
};
@ -915,35 +1219,96 @@ V86Starter.prototype.read_file = function(file, callback)
return;
}
var path_infos = fs.SearchPath(file);
var id = path_infos.id;
fs.read_file(file).then((result) => {
if(result)
{
callback(null, result);
}
else
{
callback(new FileNotFoundError(), null);
}
});
};
if(id === -1)
V86Starter.prototype.automatically = function(steps)
{
const run = (steps) =>
{
callback(new FileNotFoundError(), null);
}
else
{
fs.OpenInode(id, undefined);
fs.AddEvent(
id,
function()
const step = steps[0];
if(!step)
{
return;
}
const remaining_steps = steps.slice(1);
if(step.sleep)
{
setTimeout(() => run(remaining_steps), step.sleep * 1000);
return;
}
if(step.vga_text)
{
const screen = this.screen_adapter.get_text_screen();
for(let line of screen)
{
var data = fs.inodedata[id];
if(data)
if(line.includes(step.vga_text))
{
callback(null, data.subarray(0, fs.inodes[id].size));
}
else
{
callback(new FileNotFoundError(), null);
run(remaining_steps);
return;
}
}
);
}
setTimeout(() => run(steps), 1000);
return;
}
if(step.keyboard_send)
{
if(step.keyboard_send instanceof Array)
{
this.keyboard_send_scancodes(step.keyboard_send);
}
else
{
dbg_assert(typeof step.keyboard_send === "string");
this.keyboard_send_text(step.keyboard_send);
}
run(remaining_steps);
return;
}
if(step.call)
{
step.call();
run(remaining_steps);
return;
}
console.assert(false, step);
};
run(steps);
};
/**
* @ignore
* @constructor
*
* @param {string=} message
*/
function FileExistsError(message)
{
this.message = message || "File already exists";
}
FileExistsError.prototype = Error.prototype;
/**
* @ignore
* @constructor

View file

@ -20,13 +20,22 @@ var LOG_ALL_IO = false;
/**
* @const
* Enables logging of page faults, quite verbose
*/
var LOG_PAGE_FAULTS = false;
var DUMP_GENERATED_WASM = false;
/**
* @const
*/
var DUMP_UNCOMPILED_ASSEMBLY = false;
/**
* @const
* More accurate filenames in 9p debug messages at the cost of performance.
*/
var TRACK_FILENAMES = false;
var LOG_LEVEL = LOG_ALL & ~LOG_PS2 & ~LOG_PIT & ~LOG_VIRTIO & ~LOG_9P & ~LOG_PIC &
~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK;
~LOG_DMA & ~LOG_SERIAL & ~LOG_NET & ~LOG_FLOPPY & ~LOG_DISK & ~LOG_VGA;
/**
* @const
@ -38,19 +47,6 @@ var DEBUG_SCREEN_LAYERS = DEBUG && false;
/** @const */
var ENABLE_HPET = DEBUG && false;
/**
* @define {boolean}
* Overridden by closure compiler
*/
var ENABLE_ACPI = false;
/**
* @const
* How many cycles the CPU does at a time before running hardware timers
*/
var LOOP_COUNTER = 11001;
/**
* @const
* How often, in milliseconds, to yield to the browser for rendering and
@ -62,7 +58,7 @@ var TIME_PER_FRAME = 1;
* @const
* How many ticks the TSC does per millisecond
*/
var TSC_RATE = 8 * 1024;
var TSC_RATE = 1 * 1000 * 1000;
/** @const */

View file

@ -61,13 +61,6 @@ var LOG_NAMES = [
[LOG_SB16, "SB16"]
];
var
/** @const */ TLB_SYSTEM_READ = 1,
/** @const */ TLB_SYSTEM_WRITE = 2,
/** @const */ TLB_USER_READ = 4,
/** @const */ TLB_USER_WRITE = 8;
var
// flags register bitflags
@ -95,36 +88,6 @@ var
*/
flags_default = 1 << 1,
/**
* bitmask to select non-reserved flags bits
* @const
*/
flags_mask =
flag_carry | flag_parity | flag_adjust | flag_zero | flag_sign | flag_trap | flag_interrupt |
flag_direction | flag_overflow | flag_iopl | flag_nt | flag_rf | flag_vm | flag_ac |
flag_vif | flag_vip | flag_id,
/**
* all arithmetic flags
* @const
*/
flags_all = flag_carry | flag_parity | flag_adjust | flag_zero | flag_sign | flag_overflow,
/**
* opsizes used by get flag functions
*
* @const
*/
OPSIZE_8 = 7,
/** @const */
OPSIZE_16 = 15,
/** @const */
OPSIZE_32 = 31,
/** @const */
PSE_ENABLED = 128,
/** @const */ reg_eax = 0,
/** @const */ reg_ecx = 1,
@ -135,25 +98,6 @@ PSE_ENABLED = 128,
/** @const */ reg_esi = 6,
/** @const */ reg_edi = 7,
/** @const */ reg_ax = 0,
/** @const */ reg_cx = 2,
/** @const */ reg_dx = 4,
/** @const */ reg_bx = 6,
/** @const */ reg_sp = 8,
/** @const */ reg_bp = 10,
/** @const */ reg_si = 12,
/** @const */ reg_di = 14,
/** @const */ reg_al = 0,
/** @const */ reg_cl = 4,
/** @const */ reg_dl = 8,
/** @const */ reg_bl = 12,
/** @const */ reg_ah = 1,
/** @const */ reg_ch = 5,
/** @const */ reg_dh = 9,
/** @const */ reg_bh = 13,
/** @const */ reg_es = 0,
/** @const */ reg_cs = 1,
/** @const */ reg_ss = 2,
@ -176,15 +120,6 @@ var
/** @const */
MMAP_BLOCK_SIZE = 1 << MMAP_BLOCK_BITS;
/** @const */
var MEM_PAGE_WRITTEN = 1;
/** @const */
var MAGIC_CPU_EXCEPTION = 0xDEADBEE;
var
/** @const */
REPEAT_STRING_PREFIX_NONE = 0,
@ -207,6 +142,8 @@ var
/** @const */
CR0_WP = 1 << 16,
/** @const */
CR0_AM = 1 << 18,
/** @const */
CR0_NW = 1 << 29,
/** @const */
CR0_CD = 1 << 30,
@ -320,36 +257,32 @@ var IA32_APIC_BASE_EN = 1 << 11;
/** @const */ var TSR_LDT = 0x60;
// https://github.com/qemu/seabios/blob/14221cd86eadba82255fdc55ed174d401c7a0a04/src/fw/paravirt.c#L205-L219
/** @const */ var FW_CFG_SIGNATURE = 0x00;
/** @const */ var FW_CFG_ID = 0x01;
/** @const */ var FW_CFG_RAM_SIZE = 0x03;
/** @const */ var FW_CFG_NB_CPUS = 0x05;
/** @const */ var FW_CFG_MAX_CPUS = 0x0F;
/** @const */ var FW_CFG_NUMA = 0x0D;
/** @const */ var FW_CFG_FILE_DIR = 0x19;
/** @const */ var FW_CFG_CUSTOM_START = 0x8000;
// This value is specific to v86, choosen to hopefully not collide with other indexes
/** @const */ var FW_CFG_FILE_START = 0xC000;
/** @const */
var PREFIX_MASK_REP = 0b11000;
/** @const */
var PREFIX_REPZ = 0b01000;
/** @const */
var PREFIX_REPNZ = 0b10000;
/** @const */
var PREFIX_MASK_SEGMENT = 0b111;
/** @const */
var PREFIX_MASK_OPSIZE = 0b100000;
/** @const */
var PREFIX_MASK_ADDRSIZE = 0b1000000;
/** @const */
var PREFIX_F2 = PREFIX_REPNZ; // alias
/** @const */
var PREFIX_F3 = PREFIX_REPZ; // alias
/** @const */
var PREFIX_66 = PREFIX_MASK_OPSIZE; // alias
/** @const */ var FW_CFG_SIGNATURE_QEMU = 0x554D4551;
/** @const */
var MXCSR_MASK = (0xFFFF & ~(1 << 6));
// See same constant in jit.rs
/** @const */
var WASM_TABLE_SIZE = 900;
/** @const */
var WASM_TABLE_OFFSET = 1024;
/** @const */
var MIXER_CHANNEL_LEFT = 0;

4353
src/cpu.js

File diff suppressed because it is too large Load diff

View file

@ -130,7 +130,7 @@ CPU.prototype.debug_init = function()
cpu.running = false;
var a = parseInt(prompt("input hex", ""), 16);
if(a) while(cpu.instruction_pointer != a) step();
if(a) while(cpu.instruction_pointer[0] != a) step();
}
// http://ref.x86asm.net/x86reference.xml
@ -177,6 +177,8 @@ CPU.prototype.debug_init = function()
return;
}
_ip = _ip >>> 0;
if(debug.trace_all && debug.all_ops)
{
debug.all_ops.push(_ip, op);
@ -215,15 +217,17 @@ CPU.prototype.debug_init = function()
function get_state(where)
{
var vm = (cpu.flags & flag_vm) ? 1 : 0;
var mode = cpu.protected_mode ? vm ? "vm86" : "prot" : "real";
if(!DEBUG) return;
var mode = cpu.protected_mode[0] ? "prot" : "real";
var vm = (cpu.flags[0] & flag_vm) ? 1 : 0;
var flags = cpu.get_eflags();
var iopl = cpu.getiopl();
var cpl = cpu.cpl;
var cpl = cpu.cpl[0];
var cs_eip = h(cpu.sreg[reg_cs], 4) + ":" + h(cpu.get_real_eip() >>> 0, 8);
var ss_esp = h(cpu.sreg[reg_ss], 4) + ":" + h(cpu.get_stack_reg() >>> 0, 8);
var op_size = cpu.is_32 ? "32" : "16";
var if_ = (cpu.flags & flag_interrupt) ? 1 : 0;
var ss_esp = h(cpu.sreg[reg_ss], 4) + ":" + h(cpu.reg32[reg_es] >>> 0, 8);
var op_size = cpu.is_32[0] ? "32" : "16";
var if_ = (cpu.flags[0] & flag_interrupt) ? 1 : 0;
var flag_names = {
[flag_carry]: "c",
@ -253,12 +257,12 @@ CPU.prototype.debug_init = function()
}
}
return ("mode=" + mode + "/" + op_size + " paging=" + (+cpu.paging) +
return ("mode=" + mode + "/" + op_size + " paging=" + (+((cpu.cr[0] & CR0_PG) !== 0)) +
" iopl=" + iopl + " cpl=" + cpl + " if=" + if_ + " cs:eip=" + cs_eip +
" cs_off=" + h(cpu.get_seg(reg_cs) >>> 0, 8) +
" cs_off=" + h(cpu.get_seg_cs() >>> 0, 8) +
" flgs=" + h(cpu.get_eflags() >>> 0, 6) + " (" + flag_string + ")" +
" ss:esp=" + ss_esp +
" ssize=" + (+cpu.stack_size_32) +
" ssize=" + (+cpu.stack_size_32[0]) +
(where ? " in " + where : ""));
}
@ -283,8 +287,8 @@ CPU.prototype.debug_init = function()
for(var i = 0; i < 4; i++)
{
line1 += r32_names[i] + "=" + h(cpu.reg32[r32[r32_names[i]]], 8) + " ";
line2 += r32_names[i+4] + "=" + h(cpu.reg32[r32[r32_names[i+4]]], 8) + " ";
line1 += r32_names[i] + "=" + h(cpu.reg32[r32[r32_names[i]]] >>> 0, 8) + " ";
line2 += r32_names[i+4] + "=" + h(cpu.reg32[r32[r32_names[i+4]]] >>> 0, 8) + " ";
}
//line1 += " eip=" + h(cpu.get_real_eip() >>> 0, 8);
@ -357,8 +361,8 @@ CPU.prototype.debug_init = function()
{
if(!DEBUG) return;
dbg_log("gdt: (len = " + h(cpu.gdtr_size) + ")");
dump_table(cpu.translate_address_system_read(cpu.gdtr_offset), cpu.gdtr_size);
dbg_log("gdt: (len = " + h(cpu.gdtr_size[0]) + ")");
dump_table(cpu.translate_address_system_read(cpu.gdtr_offset[0]), cpu.gdtr_size[0]);
dbg_log("\nldt: (len = " + h(cpu.segment_limits[reg_ldtr]) + ")");
dump_table(cpu.translate_address_system_read(cpu.segment_offsets[reg_ldtr]), cpu.segment_limits[reg_ldtr]);
@ -439,9 +443,9 @@ CPU.prototype.debug_init = function()
{
if(!DEBUG) return;
for(var i = 0; i < cpu.idtr_size; i += 8)
for(var i = 0; i < cpu.idtr_size[0]; i += 8)
{
var addr = cpu.translate_address_system_read(cpu.idtr_offset + i),
var addr = cpu.translate_address_system_read(cpu.idtr_offset[0] + i),
base = cpu.read16(addr) | cpu.read16(addr + 6) << 16,
selector = cpu.read16(addr + 2),
type = cpu.read8(addr + 5),
@ -529,6 +533,7 @@ CPU.prototype.debug_init = function()
if(!entry)
{
dbg_log("Not present: " + h((i << 22) >>> 0, 8));
continue;
}
@ -583,7 +588,7 @@ CPU.prototype.debug_init = function()
if(start === undefined)
{
start = 0;
count = cpu.memory_size;
count = cpu.memory_size[0];
}
else if(count === undefined)
{
@ -630,7 +635,7 @@ CPU.prototype.debug_init = function()
var width = 0x80,
height = 0x10,
block_size = cpu.memory_size / width / height | 0,
block_size = cpu.memory_size[0] / width / height | 0,
row;
for(var i = 0; i < height; i++)
@ -707,4 +712,113 @@ CPU.prototype.debug_init = function()
// this.debug.dump_regs_short();
//}
};
let cs;
let capstone_decoder;
debug.dump_code = function(is_32, buffer, start)
{
if(!capstone_decoder)
{
if(cs === undefined)
{
if(typeof require === "function")
{
cs = require("./capstone-x86.min.js");
}
else
{
cs = window.cs;
}
if(cs === undefined)
{
dbg_log("Warning: Missing capstone library, disassembly not available");
return;
}
}
capstone_decoder = [
new cs.Capstone(cs.ARCH_X86, cs.MODE_16),
new cs.Capstone(cs.ARCH_X86, cs.MODE_32),
];
}
try
{
const instructions = capstone_decoder[is_32].disasm(buffer, start);
instructions.forEach(function (instr) {
dbg_log(h(instr.address >>> 0) + ": " +
v86util.pads(instr.bytes.map(x => h(x, 2).slice(-2)).join(" "), 20) + " " +
instr.mnemonic + " " + instr.op_str);
});
dbg_log("");
}
catch(e)
{
dbg_log("Could not disassemble: " + Array.from(buffer).map(x => h(x, 2)).join(" "));
}
};
function dump_file(ab, name)
{
var blob = new Blob([ab]);
var a = document.createElement("a");
a["download"] = name;
a.href = window.URL.createObjectURL(blob);
a.dataset["downloadurl"] = ["application/octet-stream", a["download"], a.href].join(":");
a.click();
window.URL.revokeObjectURL(a.src);
}
let wabt;
debug.dump_wasm = function(buffer)
{
if(wabt === undefined)
{
if(typeof require === "function")
{
wabt = require("./libwabt.js");
}
else
{
wabt = new window.WabtModule;
}
if(wabt === undefined)
{
dbg_log("Warning: Missing libwabt, wasm dump not available");
return;
}
}
// Need to make a small copy otherwise libwabt goes nuts trying to copy
// the whole underlying buffer
buffer = buffer.slice();
try
{
var module = wabt.readWasm(buffer, { readDebugNames: false });
module.generateNames();
module.applyNames();
const result = module.toText({ foldExprs: true, inlineExport: true });
dbg_log(result);
}
catch(e)
{
dump_file(buffer, "failed.wasm");
console.log(e.toString());
}
finally
{
if(module)
{
module.destroy();
}
}
};
};

View file

@ -104,11 +104,11 @@ function read_elf(buffer)
if(DEBUG)
{
for(let key in header)
for(let key of Object.keys(header))
{
console.log(key + ": 0x" + header[key].toString(16));
dbg_log(key + ": 0x" + header[key].toString(16));
}
console.log(header);
dbg_log(header);
}
console.assert(header.magic === ELF_MAGIC, "Bad magic");
@ -137,7 +137,7 @@ function read_elf(buffer)
SectionHeader,
header.shnum);
if(DEBUG)
if(DEBUG && LOG_LEVEL)
{
console.log("%d program headers:", program_headers.length);
for(let program of program_headers)

View file

@ -1,69 +1,56 @@
"use strict";
var performance = {};
var global = {};
var require = function(module) {};
var process = {};
var process = { hrtime: function() {} };
var __dirname = "";
var esprima = { tokenize: {}, parse: {} };
var acorn = { walk: { simple: {} } };
var exports = {};
var define = {};
var module = {};
// New Web Audio API
/**
* @constructor
* @extends {AudioNode}
* @param {Object=} options
*/
var AudioWorkletNode = function(context, name, options)
{
this.port =
{
/**
* @param {Object} data
* @param {Object=} transfer
*/
postMessage: function(data, transfer) {}
};
};
/**
* @constructor
*/
var AudioWorkletProcessor = function()
{
this.port =
{
/**
* @param {Object} data
* @param {Object=} transfer
*/
postMessage: function(data, transfer) {}
};
}
var AudioWorklet = function() {};
AudioContext.prototype.audioWorklet =
{
/** @return {Promise} */
addModule: function(file) {}
};
/**
* @param {string} name
* @param {function()} processor
*/
var registerProcessor = function(name, processor) {}
var registerProcessor = function(name, processor) {};
/** @const */
var currentTime = 0;
/** @const */
var sampleRate = 0;
var WebAssembly = {
Memory() {},
Table() {},
instantiate() { return { instance: null, module: null }; },
compile() {},
Instance() {},
Module() {},
};
WebAssembly.Module.customSections = function(module, section) {};
var WabtModule = {
readWasm: function(buf, opt) {},
generateNames: function() {},
applyNames: function() {},
toText: function() {},
};
var cs = {
Capstone: function() {},
ARCH_X86: 0,
MODE_16: 0,
MODE_32: 0,
disasm: { bytes: "", mnemonic: "", op_str: "", },
};
const Buffer = {
allocUnsafe : function(length) {},
from : function(arrayBuffer, byteOffset, length) {},
};

View file

@ -189,7 +189,7 @@ FloppyController.prototype.port3F7_read = function()
{
dbg_log("3F7 read", LOG_FLOPPY);
return 0x00;
}
};
FloppyController.prototype.port3F5_read = function()
{
@ -294,7 +294,7 @@ FloppyController.prototype.port3F2_read = function()
{
dbg_log("read 3F2: DOR", LOG_FLOPPY);
return this.dor;
}
};
FloppyController.prototype.port3F2_write = function(value)
{
@ -311,7 +311,7 @@ FloppyController.prototype.port3F2_write = function(value)
dbg_log("DOR = " + h(value), LOG_FLOPPY);
this.dor = value;
}
};
FloppyController.prototype.check_drive_status = function(args)
{
@ -320,7 +320,7 @@ FloppyController.prototype.check_drive_status = function(args)
this.response_index = 0;
this.response_length = 1;
this.response_data[0] = 1 << 5;
}
};
FloppyController.prototype.seek = function(args)
{
@ -331,14 +331,14 @@ FloppyController.prototype.seek = function(args)
this.last_head = args[0] >> 2 & 1;
this.raise_irq();
}
};
FloppyController.prototype.calibrate = function(args)
{
dbg_log("floppy calibrate", LOG_FLOPPY);
this.raise_irq();
}
};
FloppyController.prototype.check_interrupt_status = function()
{
@ -350,7 +350,7 @@ FloppyController.prototype.check_interrupt_status = function()
this.response_data[0] = 1 << 5;
this.response_data[1] = this.last_cylinder;
}
};
FloppyController.prototype.do_sector = function(is_write, args)
{
@ -424,12 +424,12 @@ FloppyController.prototype.done = function(args, cylinder, head, sector, error)
this.response_data[6] = args[4];
this.raise_irq();
}
};
FloppyController.prototype.fix_drive_data = function(args)
{
dbg_log("floppy fix drive data " + args, LOG_FLOPPY);
}
};
FloppyController.prototype.read_sector_id = function(args)
{
@ -447,7 +447,7 @@ FloppyController.prototype.read_sector_id = function(args)
this.response_data[6] = 0;
this.raise_irq();
}
};
FloppyController.prototype.raise_irq = function()
{

1628
src/fpu.js

File diff suppressed because it is too large Load diff

View file

@ -47,7 +47,7 @@ var HD_SECTOR_SIZE = 512;
function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
{
this.master = new IDEInterface(this, cpu, master_buffer, is_cd, nr, 0, bus);
this.slave = new IDEInterface(this, cpu, slave_buffer, is_cd, nr, 1, bus);
this.slave = new IDEInterface(this, cpu, slave_buffer, false, nr, 1, bus);
this.current_interface = this.master;
@ -83,8 +83,8 @@ function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
this.pci_space = [
0x86, 0x80, 0x10, 0x70, 0x05, 0x00, 0xA0, 0x02,
0x00, 0x80, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
this.ata_port & 0xFF | 1, this.ata_port >> 8, 0x00, 0x00,
this.ata_port_high & 0xFF | 1, this.ata_port_high >> 8, 0x00, 0x00,
0 | 1, 0, 0x00, 0x00,
0 | 1, 0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // second device
0x00, 0x00, 0x00, 0x00, // second device
this.master_port & 0xFF | 1, this.master_port >> 8, 0x00, 0x00,
@ -151,7 +151,7 @@ function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
{
dbg_log("Read error: " + h(this.current_interface.error & 0xFF) +
" slave=" + (this.current_interface === this.slave), LOG_DISK);
return this.current_interface.error;
return this.current_interface.error & 0xFF;
});
cpu.io.register_read(this.ata_port | 2, this, function()
{
@ -177,7 +177,7 @@ function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
cpu.io.register_read(this.ata_port | 6, this, function()
{
dbg_log("Read 1F6", LOG_DISK);
return this.current_interface.drive_head;
return this.current_interface.drive_head & 0xFF;
});
cpu.io.register_write(this.ata_port | 0, this, function(data)
@ -433,8 +433,8 @@ IDEDevice.prototype.get_state = function()
IDEDevice.prototype.set_state = function(state)
{
this.master = state[0];
this.slave = state[1];
this.master.set_state(state[0]);
this.slave.set_state(state[1]);
this.ata_port = state[2];
this.irq = state[3];
this.pci_id = state[4];
@ -617,6 +617,11 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus)
/** @type {number} */
this.write_dest = 0;
// cancellation support
this.last_io_id = 0;
this.in_progress_io_ids = new Set();
this.cancelled_io_ids = new Set();
Object.seal(this);
}
@ -642,6 +647,8 @@ IDEInterface.prototype.device_reset = function()
this.cylinder_low = 0; // lba_mid
this.cylinder_high = 0; // lba_high
}
this.cancel_io_operations();
};
IDEInterface.prototype.push_irq = function()
@ -1183,7 +1190,7 @@ IDEInterface.prototype.atapi_read = function(cmd)
this.status = 0x50 | 0x80;
this.report_read_start();
this.buffer.get(start, byte_count, (data) =>
this.read_buffer(start, byte_count, (data) =>
{
//setTimeout(() => {
dbg_log("cd read: data arrived", LOG_DISK);
@ -1236,7 +1243,7 @@ IDEInterface.prototype.atapi_read_dma = function(cmd)
this.status = 0x50 | 0x80;
this.report_read_start();
this.buffer.get(start, byte_count, (data) =>
this.read_buffer(start, byte_count, (data) =>
{
dbg_log("atapi_read_dma: Data arrived");
this.report_read_end(byte_count);
@ -1281,8 +1288,7 @@ IDEInterface.prototype.do_atapi_dma = function()
}
dbg_log("dma read dest=" + h(addr) + " count=" + h(count) + " datalen=" + h(this.data_length), LOG_DISK);
this.cpu.write_blob(data.subarray(offset,
Math.min(offset + count, this.data_length)), addr);
this.cpu.write_blob(data.subarray(offset, Math.min(offset + count, this.data_length)), addr);
offset += count;
prdt_start += 8;
@ -1487,7 +1493,11 @@ IDEInterface.prototype.write_end = function()
}
else
{
dbg_assert(this.current_command === 0x30 || this.current_command === 0x34);
dbg_assert(this.current_command === 0x30 ||
this.current_command === 0x34 ||
this.current_command === 0xC5,
"Unexpected command: " + h(this.current_command));
// XXX: Should advance here, but do_write does all the advancing
//this.ata_advance(this.current_command, 1);
this.status = 0x58;
@ -1561,7 +1571,7 @@ IDEInterface.prototype.ata_read_sectors = function(cmd)
this.status = 0x80 | 0x40;
this.report_read_start();
this.buffer.get(start, byte_count, (data) =>
this.read_buffer(start, byte_count, (data) =>
{
//setTimeout(() => {
dbg_log("ata_read: Data arrived", LOG_DISK);
@ -1621,7 +1631,7 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function()
var orig_prdt_start = this.device.prdt_addr;
this.buffer.get(start, byte_count, (data) =>
this.read_buffer(start, byte_count, (data) =>
{
//setTimeout(function() {
dbg_log("do_ata_read_sectors_dma: Data arrived", LOG_DISK);
@ -1734,12 +1744,12 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function()
var start = lba * this.sector_size;
var prdt_start = this.device.prdt_addr;
var prdt_count = 0;
var prdt_write_count = 0;
var offset = 0;
dbg_log("prdt addr: " + h(prdt_start, 8), LOG_DISK);
const buffer = new Uint8Array(byte_count);
do {
var prd_addr = this.cpu.read32s(prdt_start);
var prd_count = this.cpu.read16(prdt_start + 4);
@ -1756,39 +1766,29 @@ IDEInterface.prototype.do_ata_write_sectors_dma = function()
var slice = this.cpu.mem8.subarray(prd_addr, prd_addr + prd_count);
dbg_assert(slice.length === prd_count);
buffer.set(slice, offset);
//if(DEBUG)
//{
// dbg_log(hex_dump(slice), LOG_DISK);
//}
this.buffer.set(start + offset, slice, function()
{
prdt_write_count++;
});
offset += prd_count;
prdt_start += 8;
prdt_count++;
}
while(!end);
if(prdt_write_count === prdt_count)
dbg_assert(offset === buffer.length);
this.buffer.set(start, buffer, () =>
{
//setTimeout(function() {
dbg_log("dma write completed", LOG_DISK);
this.ata_advance(this.current_command, count);
this.status = 0x50;
this.push_irq();
this.device.dma_status &= ~1;
this.current_command = -1;
//}, 10);
}
else
{
// fails when writes don't happen synchronously, which isn't currently
// the case, but might be in the future
dbg_assert(false, "dma write not completed", LOG_DISK);
}
});
this.report_write(byte_count);
};
@ -2010,6 +2010,35 @@ IDEInterface.prototype.report_write = function(byte_count)
this.bus.send("ide-write-end", [this.nr, byte_count, sector_count]);
};
IDEInterface.prototype.read_buffer = function(start, length, callback)
{
const id = this.last_io_id++;
this.in_progress_io_ids.add(id);
this.buffer.get(start, length, data =>
{
if(this.cancelled_io_ids.delete(id))
{
dbg_assert(!this.in_progress_io_ids.has(id));
return;
}
const removed = this.in_progress_io_ids.delete(id);
dbg_assert(removed);
callback(data);
});
};
IDEInterface.prototype.cancel_io_operations = function()
{
for(const id of this.in_progress_io_ids)
{
this.cancelled_io_ids.add(id);
}
this.in_progress_io_ids.clear();
};
IDEInterface.prototype.get_state = function()
{
var state = [];
@ -2041,6 +2070,7 @@ IDEInterface.prototype.get_state = function()
state[25] = this.current_command;
state[26] = this.data_end;
state[27] = this.current_atapi_command;
state[28] = this.buffer;
return state;
};
@ -2075,4 +2105,6 @@ IDEInterface.prototype.set_state = function(state)
this.data16 = new Uint16Array(this.data.buffer);
this.data32 = new Int32Array(this.data.buffer);
this.buffer && this.buffer.set_state(state[28]);
};

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@ function IO(cpu)
this.ports[i] = this.create_empty_entry();
}
var memory_size = cpu.memory_size;
var memory_size = cpu.memory_size[0];
for(var i = 0; (i << MMAP_BLOCK_BITS) < memory_size; i++)
{
@ -246,34 +246,6 @@ IO.prototype.register_write_consecutive = function(port_addr, device, w8_1, w8_2
}
};
IO.prototype.in_mmap_range = function(start, count)
{
start >>>= 0;
count >>>= 0;
var end = start + count;
if(end >= this.cpu.memory_size)
{
return true;
}
//dbg_log("in_mmap_range start=" + start + " count=" + count);
start &= ~(MMAP_BLOCK_SIZE - 1);
while(start < end)
{
if(this.cpu.in_mapped_range(start))
{
return true;
}
start += MMAP_BLOCK_SIZE;
}
return false;
};
IO.prototype.mmap_read32_shim = function(addr)
{
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
@ -487,5 +459,3 @@ IO.prototype.get_port_description = function(addr)
return "";
}
};

View file

@ -76,12 +76,24 @@ function IOAPIC(cpu)
cpu.io.mmap_register(IOAPIC_ADDRESS, MMAP_BLOCK_SIZE,
(addr) =>
{
dbg_assert(false, "unsupported read8 from ioapic: " + h(addr));
return 0;
addr = addr - IOAPIC_ADDRESS | 0;
if(addr >= IOWIN && addr < IOWIN + 4)
{
const byte = addr - IOWIN;
dbg_log("ioapic read8 byte " + byte + " " + h(this.ioregsel), LOG_APIC);
return this.read(this.ioregsel) >> (8 * byte) & 0xFF;
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
dbg_assert(false);
return 0;
}
},
(addr, value) =>
{
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr));
dbg_assert(false, "unsupported write8 from ioapic: " + h(addr >>> 0));
},
(addr) =>
{
@ -97,7 +109,7 @@ function IOAPIC(cpu)
}
else
{
dbg_log("Unexpected IOAPIC register read: " + h(addr), LOG_APIC);
dbg_log("Unexpected IOAPIC register read: " + h(addr >>> 0), LOG_APIC);
dbg_assert(false);
return 0;
}
@ -116,7 +128,7 @@ function IOAPIC(cpu)
}
else
{
dbg_log("Unexpected IOAPIC register write: " + h(addr) + " <- " + h(value >>> 0, 8), LOG_APIC);
dbg_log("Unexpected IOAPIC register write: " + h(addr >>> 0) + " <- " + h(value >>> 0, 8), LOG_APIC);
dbg_assert(false);
}
});

235
src/kernel.js Normal file
View file

@ -0,0 +1,235 @@
"use strict";
// https://www.kernel.org/doc/Documentation/x86/boot.txt
const LINUX_BOOT_HDR_SETUP_SECTS = 0x1F1;
const LINUX_BOOT_HDR_SYSSIZE = 0x1F4;
const LINUX_BOOT_HDR_VIDMODE = 0x1FA;
const LINUX_BOOT_HDR_BOOT_FLAG = 0x1FE;
const LINUX_BOOT_HDR_HEADER = 0x202;
const LINUX_BOOT_HDR_VERSION = 0x206;
const LINUX_BOOT_HDR_TYPE_OF_LOADER = 0x210;
const LINUX_BOOT_HDR_LOADFLAGS = 0x211;
const LINUX_BOOT_HDR_CODE32_START = 0x214;
const LINUX_BOOT_HDR_RAMDISK_IMAGE = 0x218;
const LINUX_BOOT_HDR_RAMDISK_SIZE = 0x21C;
const LINUX_BOOT_HDR_HEAP_END_PTR = 0x224;
const LINUX_BOOT_HDR_CMD_LINE_PTR = 0x228;
const LINUX_BOOT_HDR_INITRD_ADDR_MAX = 0x22C;
const LINUX_BOOT_HDR_KERNEL_ALIGNMENT = 0x230;
const LINUX_BOOT_HDR_RELOCATABLE_KERNEL = 0x234;
const LINUX_BOOT_HDR_MIN_ALIGNMENT = 0x235;
const LINUX_BOOT_HDR_XLOADFLAGS = 0x236;
const LINUX_BOOT_HDR_CMDLINE_SIZE = 0x238;
const LINUX_BOOT_HDR_PAYLOAD_OFFSET = 0x248;
const LINUX_BOOT_HDR_PAYLOAD_LENGTH = 0x24C;
const LINUX_BOOT_HDR_PREF_ADDRESS = 0x258;
const LINUX_BOOT_HDR_INIT_SIZE = 0x260;
const LINUX_BOOT_HDR_CHECKSUM1 = 0xAA55;
const LINUX_BOOT_HDR_CHECKSUM2 = 0x53726448;
const LINUX_BOOT_HDR_TYPE_OF_LOADER_NOT_ASSIGNED = 0xFF;
const LINUX_BOOT_HDR_LOADFLAGS_LOADED_HIGH = 1 << 0;
const LINUX_BOOT_HDR_LOADFLAGS_QUIET_FLAG = 1 << 5;
const LINUX_BOOT_HDR_LOADFLAGS_KEEP_SEGMENTS = 1 << 6;
const LINUX_BOOT_HDR_LOADFLAGS_CAN_USE_HEAPS = 1 << 7;
function load_kernel(mem8, bzimage, initrd, cmdline)
{
dbg_log("Trying to load kernel of size " + bzimage.byteLength);
const KERNEL_HIGH_ADDRESS = 0x100000;
// Put the initrd at the 64 MB boundary. This means the minimum memory size
// is 64 MB plus the size of the initrd.
// Note: If set too low, kernel may fail to load the initrd with "invalid magic at start of compressed archive"
const INITRD_ADDRESS = 64 << 20;
const quiet = false;
const bzimage8 = new Uint8Array(bzimage);
const bzimage16 = new Uint16Array(bzimage);
const bzimage32 = new Uint32Array(bzimage);
const setup_sects = bzimage8[LINUX_BOOT_HDR_SETUP_SECTS] || 4;
const syssize = bzimage32[LINUX_BOOT_HDR_SYSSIZE >> 2] << 4;
const vidmode = bzimage16[LINUX_BOOT_HDR_VIDMODE >> 1];
const checksum1 = bzimage16[LINUX_BOOT_HDR_BOOT_FLAG >> 1];
if(checksum1 !== LINUX_BOOT_HDR_CHECKSUM1)
{
dbg_log("Bad checksum1: " + h(checksum1));
return;
}
// Not aligned, so split into two 16-bit reads
const checksum2 =
bzimage16[LINUX_BOOT_HDR_HEADER >> 1] |
bzimage16[LINUX_BOOT_HDR_HEADER + 2 >> 1] << 16;
if(checksum2 !== LINUX_BOOT_HDR_CHECKSUM2)
{
dbg_log("Bad checksum2: " + h(checksum2));
return;
}
const protocol = bzimage16[LINUX_BOOT_HDR_VERSION >> 1];
dbg_assert(protocol >= 0x202); // older not supported by us
const flags = bzimage8[LINUX_BOOT_HDR_LOADFLAGS];
dbg_assert(flags & LINUX_BOOT_HDR_LOADFLAGS_LOADED_HIGH); // low kernels not supported by us
// we don't relocate the kernel, so we don't care much about most of these
const flags2 = bzimage16[LINUX_BOOT_HDR_XLOADFLAGS >> 1];
const initrd_addr_max = bzimage32[LINUX_BOOT_HDR_INITRD_ADDR_MAX >> 2];
const kernel_alignment = bzimage32[LINUX_BOOT_HDR_KERNEL_ALIGNMENT >> 2];
const relocatable_kernel = bzimage8[LINUX_BOOT_HDR_RELOCATABLE_KERNEL];
const min_alignment = bzimage8[LINUX_BOOT_HDR_MIN_ALIGNMENT];
const cmdline_size = bzimage32[LINUX_BOOT_HDR_CMDLINE_SIZE >> 2];
const payload_offset = bzimage32[LINUX_BOOT_HDR_PAYLOAD_OFFSET >> 2];
const payload_length = bzimage32[LINUX_BOOT_HDR_PAYLOAD_LENGTH >> 2];
const pref_address = bzimage32[LINUX_BOOT_HDR_PREF_ADDRESS >> 2];
const pref_address_high = bzimage32[LINUX_BOOT_HDR_PREF_ADDRESS + 4 >> 2];
const init_size = bzimage32[LINUX_BOOT_HDR_INIT_SIZE >> 2];
dbg_log("kernel boot protocol version: " + h(protocol));
dbg_log("flags=" + h(flags) + " xflags=" + h(flags2));
dbg_log("code32_start=" + h(bzimage32[LINUX_BOOT_HDR_CODE32_START >> 2]));
dbg_log("initrd_addr_max=" + h(initrd_addr_max));
dbg_log("kernel_alignment=" + h(kernel_alignment));
dbg_log("relocatable=" + relocatable_kernel);
dbg_log("min_alignment=" + h(min_alignment));
dbg_log("cmdline max=" + h(cmdline_size));
dbg_log("payload offset=" + h(payload_offset) + " size=" + h(payload_length));
dbg_log("pref_address=" + h(pref_address_high) + ":" + h(pref_address));
dbg_log("init_size=" + h(init_size));
const real_mode_segment = 0x8000;
const base_ptr = real_mode_segment << 4;
const heap_end = 0xE000;
const heap_end_ptr = heap_end - 0x200;
// fill in the kernel boot header with infos the kernel needs to know
bzimage8[LINUX_BOOT_HDR_TYPE_OF_LOADER] = LINUX_BOOT_HDR_TYPE_OF_LOADER_NOT_ASSIGNED;
const new_flags =
(quiet ? flags | LINUX_BOOT_HDR_LOADFLAGS_QUIET_FLAG : flags & ~LINUX_BOOT_HDR_LOADFLAGS_QUIET_FLAG)
& ~LINUX_BOOT_HDR_LOADFLAGS_KEEP_SEGMENTS
| LINUX_BOOT_HDR_LOADFLAGS_CAN_USE_HEAPS;
bzimage8[LINUX_BOOT_HDR_LOADFLAGS] = new_flags;
bzimage16[LINUX_BOOT_HDR_HEAP_END_PTR >> 1] = heap_end_ptr;
// should parse the vga=... paramter from cmdline here, but we don't really care
bzimage16[LINUX_BOOT_HDR_VIDMODE >> 1] = 0xFFFF; // normal
dbg_log("heap_end_ptr=" + h(heap_end_ptr));
cmdline += "\x00";
dbg_assert(cmdline.length < cmdline_size);
const cmd_line_ptr = base_ptr + heap_end;
dbg_log("cmd_line_ptr=" + h(cmd_line_ptr));
bzimage32[LINUX_BOOT_HDR_CMD_LINE_PTR >> 2] = cmd_line_ptr;
for(let i = 0; i < cmdline.length; i++)
{
mem8[cmd_line_ptr + i] = cmdline.charCodeAt(i);
}
const prot_mode_kernel_start = (setup_sects + 1) * 512;
dbg_log("prot_mode_kernel_start=" + h(prot_mode_kernel_start));
const real_mode_kernel = new Uint8Array(bzimage, 0, prot_mode_kernel_start);
const protected_mode_kernel = new Uint8Array(bzimage, prot_mode_kernel_start);
let ramdisk_address = 0;
let ramdisk_size = 0;
if(initrd)
{
ramdisk_address = INITRD_ADDRESS;
ramdisk_size = initrd.byteLength;
dbg_assert(KERNEL_HIGH_ADDRESS + protected_mode_kernel.length < ramdisk_address);
mem8.set(new Uint8Array(initrd), ramdisk_address);
}
bzimage32[LINUX_BOOT_HDR_RAMDISK_IMAGE >> 2] = ramdisk_address;
bzimage32[LINUX_BOOT_HDR_RAMDISK_SIZE >> 2] = ramdisk_size;
dbg_assert(base_ptr + real_mode_kernel.length < 0xA0000);
mem8.set(real_mode_kernel, base_ptr);
mem8.set(protected_mode_kernel, KERNEL_HIGH_ADDRESS);
return {
option_rom:
{
name: "genroms/kernel.bin",
data: make_linux_boot_rom(real_mode_segment, heap_end),
}
};
}
function make_linux_boot_rom(real_mode_segment, heap_end)
{
// This rom will be executed by seabios after its initialisation
// It sets up segment registers, the stack and calls the kernel real mode entry point
const SIZE = 0x200;
const data8 = new Uint8Array(0x100);
const data16 = new Uint16Array(data8.buffer);
data16[0] = 0xAA55;
data8[2] = SIZE / 0x200;
let i = 3;
data8[i++] = 0xFA; // cli
data8[i++] = 0xB8; // mov ax, real_mode_segment
data8[i++] = real_mode_segment >> 0;
data8[i++] = real_mode_segment >> 8;
data8[i++] = 0x8E; // mov es, ax
data8[i++] = 0xC0;
data8[i++] = 0x8E; // mov ds, ax
data8[i++] = 0xD8;
data8[i++] = 0x8E; // mov fs, ax
data8[i++] = 0xE0;
data8[i++] = 0x8E; // mov gs, ax
data8[i++] = 0xE8;
data8[i++] = 0x8E; // mov ss, ax
data8[i++] = 0xD0;
data8[i++] = 0xBC; // mov sp, heap_end
data8[i++] = heap_end >> 0;
data8[i++] = heap_end >> 8;
data8[i++] = 0xEA; // jmp (real_mode_segment+0x20):0x0
data8[i++] = 0x00;
data8[i++] = 0x00;
data8[i++] = real_mode_segment + 0x20 >> 0;
data8[i++] = real_mode_segment + 0x20 >> 8;
dbg_assert(i < SIZE);
const checksum_index = i;
data8[checksum_index] = 0;
let checksum = 0;
for(let i = 0; i < data8.length; i++)
{
checksum += data8[i];
}
data8[checksum_index] = -checksum;
return data8;
}

View file

@ -1,31 +1,60 @@
"use strict";
var goog = goog || {};
goog.exportSymbol = function() {};
goog.exportProperty = function() {};
var v86util = v86util || {};
// pad string with spaces on the right
v86util.pads = function(str, len)
{
str = str ? str + "" : "";
while(str.length < len)
{
str = str + " ";
}
return str;
str = (str || str === 0) ? str + "" : "";
return str.padEnd(len, " ");
};
// pad string with zeros on the left
v86util.pad0 = function(str, len)
{
str = str ? str + "" : "";
str = (str || str === 0) ? str + "" : "";
return str.padStart(len, "0");
};
while(str.length < len)
{
str = "0" + str;
}
// generates array given size with zeros
v86util.zeros = function(size)
{
return Array(size).fill(0);
};
return str;
// generates [0, 1, 2, ..., size-1]
v86util.range = function(size)
{
return Array.from(Array(size).keys());
};
v86util.view = function(constructor, memory, offset, length)
{
return new Proxy({},
{
get: function(target, property, receiver)
{
const b = new constructor(memory.buffer, offset, length);
const x = b[property];
if(typeof x === "function")
{
return x.bind(b);
}
dbg_assert(/^\d+$/.test(property) || property === "buffer" || property === "length" ||
property === "BYTES_PER_ELEMENT" || property === "byteOffset");
return x;
},
set: function(target, property, value, receiver)
{
dbg_assert(/^\d+$/.test(property));
new constructor(memory.buffer, offset, length)[property] = value;
return true;
},
});
};
/**
@ -53,29 +82,26 @@ if(typeof window !== "undefined" && window.crypto && window.crypto.getRandomValu
{
let rand_data = new Int32Array(1);
v86util.has_rand_int = function()
{
return true;
};
v86util.get_rand_int = function()
{
window.crypto.getRandomValues(rand_data);
return rand_data[0];
};
}
else
else if(typeof require !== "undefined")
{
v86util.has_rand_int = function()
{
return false;
};
/** @type {{ randomBytes: Function }} */
const crypto = require("crypto");
v86util.get_rand_int = function()
{
console.assert(false);
return crypto.randomBytes(4)["readInt32LE"](0);
};
}
else
{
dbg_assert(false, "Unsupported platform: No cryptographic random values");
}
/**
@ -84,6 +110,8 @@ else
*/
function SyncBuffer(buffer)
{
dbg_assert(buffer instanceof ArrayBuffer);
this.buffer = buffer;
this.byteLength = buffer.byteLength;
this.onload = undefined;
@ -127,7 +155,19 @@ SyncBuffer.prototype.get_buffer = function(fn)
fn(this.buffer);
};
SyncBuffer.prototype.get_state = function()
{
const state = [];
state[0] = this.byteLength;
state[1] = new Uint8Array(this.buffer);
return state;
};
SyncBuffer.prototype.set_state = function(state)
{
this.byteLength = state[0];
this.buffer = state[1].slice().buffer;
};
(function()
{
@ -192,6 +232,7 @@ SyncBuffer.prototype.get_buffer = function(fn)
*/
v86util.int_log2 = function(x)
{
x >>>= 0;
dbg_assert(x > 0);
// http://jsperf.com/integer-log2/6
@ -225,22 +266,6 @@ SyncBuffer.prototype.get_buffer = function(fn)
})();
v86util.mul_low = v86util.imul_low =
typeof Math.imul === "function" &&
Math.imul(0x01234567, 0x89abcdef) === -0x36b1b9d7 ? Math.imul : function(a, b) {
b |= 0;
return (a & 0x003fffff) * b + ((a & 0xffc00000) * b | 0) | 0;
};
v86util.imul_high = function(a, b) {
return Math.floor(a * b / 0x100000000) | 0;
};
v86util.mul_high = function(a, b) {
return Math.floor((a >>> 0) * (b >>> 0) / 0x100000000) | 0;
};
/**
* @constructor
*
@ -478,3 +503,46 @@ function download(file_or_blob, name)
window.URL.revokeObjectURL(a.href);
}
/**
* A simple 1d bitmap
* @constructor
*/
v86util.Bitmap = function(length_or_buffer)
{
if(typeof length_or_buffer === "number")
{
this.view = new Uint8Array(length_or_buffer + 7 >> 3);
}
else if(length_or_buffer instanceof ArrayBuffer)
{
this.view = new Uint8Array(length_or_buffer);
}
else
{
console.assert(false);
}
};
v86util.Bitmap.prototype.set = function(index, value)
{
const bit_index = index & 7;
const byte_index = index >> 3;
const bit_mask = 1 << bit_index;
this.view[byte_index] =
value ? this.view[byte_index] | bit_mask : this.view[byte_index] & ~bit_mask;
};
v86util.Bitmap.prototype.get = function(index)
{
const bit_index = index & 7;
const byte_index = index >> 3;
return this.view[byte_index] >> bit_index & 1;
};
v86util.Bitmap.prototype.get_buffer = function()
{
return this.view.buffer;
};

View file

@ -94,7 +94,7 @@ function dbg_trace(level)
{
if(!DEBUG) return;
dbg_log(Error().stack.replace(/(?:(?:t|t16|t32)\.\(anonymous function\)\.)+/g, "t.(anonymous function)."), level);
dbg_log(Error().stack, level);
}
/**
@ -110,7 +110,7 @@ function dbg_assert(cond, msg, level)
{
dbg_assert_failed(msg);
}
};
}
function dbg_assert_failed(msg)

View file

@ -1,7 +1,10 @@
"use strict";
/** @constructor */
function v86(bus)
/**
* @constructor
* @param {Object=} wasm
*/
function v86(bus, wasm)
{
/** @type {boolean} */
this.running = false;
@ -10,7 +13,7 @@ function v86(bus)
this.stopped = false;
/** @type {CPU} */
this.cpu = new CPU(bus);
this.cpu = new CPU(bus, wasm);
this.bus = bus;
bus.register("cpu-init", this.init, this);
@ -23,6 +26,8 @@ function v86(bus)
v86.prototype.run = function()
{
this.stopped = false;
if(!this.running)
{
this.bus.send("emulator-started");
@ -192,20 +197,22 @@ v86.prototype.restore_state = function(state)
if(typeof performance === "object" && performance.now)
{
v86.microtick = performance.now.bind(performance);
}
else if(typeof require === "function")
{
const { performance } = require("perf_hooks");
v86.microtick = performance.now.bind(performance);
}
else if(typeof process === "object" && process.hrtime)
{
v86.microtick = function()
{
return performance.now();
var t = process.hrtime();
return t[0] * 1000 + t[1] / 1e6;
};
}
//else if(typeof process === "object" && process.hrtime)
//{
// v86.microtick = function()
// {
// var t = process.hrtime();
// return t[0] * 1000 + t[1] / 1e6;
// };
//}
else
{
v86.microtick = Date.now;

View file

@ -1,44 +1,5 @@
"use strict";
/** @const */
var A20_MASK = ~(1 << 20);
/** @const */
var A20_MASK16 = ~(1 << 20 - 1);
/** @const */
var A20_MASK32 = ~(1 << 20 - 2);
/** @const */
var USE_A20 = false;
// called by all memory writes
CPU.prototype.debug_write = function(addr, size, value)
{
if(!DEBUG)
{
return;
}
dbg_assert(typeof value === "number" && !isNaN(value));
dbg_assert(value >= -0x80000000 && addr < 0x80000000);
this.debug_read(addr, size, true);
}
/**
* @param {boolean=} is_write
*/
CPU.prototype.debug_read = function(addr, size, is_write)
{
if(!DEBUG)
{
return;
}
dbg_assert(typeof addr === "number");
dbg_assert(!isNaN(addr));
};
CPU.prototype.mmap_read8 = function(addr)
{
@ -70,208 +31,37 @@ CPU.prototype.mmap_read32 = function(addr)
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
return this.memory_map_read32[aligned_addr](addr);
}
};
CPU.prototype.mmap_write32 = function(addr, value)
{
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
this.memory_map_write32[aligned_addr](addr, value);
}
CPU.prototype.in_mapped_range = function(addr)
{
return (addr | 0) >= 0xA0000 && (addr | 0) < 0xC0000 || (addr >>> 0) >= (this.memory_size >>> 0);
};
/**
* @param {number} addr
*/
CPU.prototype.read8 = function(addr)
CPU.prototype.mmap_write64 = function(addr, value0, value1)
{
this.debug_read(addr, 1);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK;
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
// This should hold since writes across pages are split up
dbg_assert(aligned_addr === (addr + 7) >>> MMAP_BLOCK_BITS);
if(this.in_mapped_range(addr))
{
return this.mmap_read8(addr);
}
else
{
return this.mem8[addr];
}
var write_func32 = this.memory_map_write32[aligned_addr];
write_func32(addr, value0);
write_func32(addr + 4, value1);
};
/**
* @param {number} addr
*/
CPU.prototype.read16 = function(addr)
CPU.prototype.mmap_write128 = function(addr, value0, value1, value2, value3)
{
this.debug_read(addr, 2);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK;
var aligned_addr = addr >>> MMAP_BLOCK_BITS;
// This should hold since writes across pages are split up
dbg_assert(aligned_addr === (addr + 12) >>> MMAP_BLOCK_BITS);
if(this.in_mapped_range(addr))
{
return this.mmap_read16(addr);
}
else
{
return this.mem8[addr] | this.mem8[addr + 1 | 0] << 8;
}
};
/**
* @param {number} addr
*/
CPU.prototype.read_aligned16 = function(addr)
{
dbg_assert(addr >= 0 && addr < 0x80000000);
this.debug_read(addr << 1, 2);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK16;
if(this.in_mapped_range(addr << 1))
{
return this.mmap_read16(addr << 1);
}
else
{
return this.mem16[addr];
}
};
/**
* @param {number} addr
*/
CPU.prototype.read32s = function(addr)
{
this.debug_read(addr, 4);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK;
if(this.in_mapped_range(addr))
{
return this.mmap_read32(addr);
}
else
{
return this.mem8[addr] | this.mem8[addr + 1 | 0] << 8 |
this.mem8[addr + 2 | 0] << 16 | this.mem8[addr + 3 | 0] << 24;
}
};
/**
* @param {number} addr
*/
CPU.prototype.read_aligned32 = function(addr)
{
dbg_assert(addr >= 0 && addr < 0x40000000);
this.debug_read(addr << 2, 4);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK32;
if(this.in_mapped_range(addr << 2))
{
return this.mmap_read32(addr << 2);
}
else
{
return this.mem32s[addr];
}
};
/**
* @param {number} addr
* @param {number} value
*/
CPU.prototype.write8 = function(addr, value)
{
this.debug_write(addr, 1, value);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK;
if(this.in_mapped_range(addr))
{
this.mmap_write8(addr, value);
}
else
{
this.mem8[addr] = value;
}
};
/**
* @param {number} addr
* @param {number} value
*/
CPU.prototype.write16 = function(addr, value)
{
this.debug_write(addr, 2, value);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK;
if(this.in_mapped_range(addr))
{
this.mmap_write16(addr, value);
}
else
{
this.mem8[addr] = value;
this.mem8[addr + 1 | 0] = value >> 8;
}
};
/**
* @param {number} addr
* @param {number} value
*/
CPU.prototype.write_aligned16 = function(addr, value)
{
dbg_assert(addr >= 0 && addr < 0x80000000);
this.debug_write(addr << 1, 2, value);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK16;
if(this.in_mapped_range(addr << 1))
{
this.mmap_write16(addr << 1, value);
}
else
{
this.mem16[addr] = value;
}
};
/**
* @param {number} addr
* @param {number} value
*/
CPU.prototype.write32 = function(addr, value)
{
this.debug_write(addr, 4, value);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK;
if(this.in_mapped_range(addr))
{
this.mmap_write32(addr, value);
}
else
{
this.mem8[addr] = value;
this.mem8[addr + 1 | 0] = value >> 8;
this.mem8[addr + 2 | 0] = value >> 16;
this.mem8[addr + 3 | 0] = value >> 24;
}
};
CPU.prototype.write_aligned32 = function(addr, value)
{
dbg_assert(addr >= 0 && addr < 0x40000000);
this.debug_write(addr << 2, 4, value);
if(USE_A20 && !this.a20_enabled) addr &= A20_MASK32;
if(this.in_mapped_range(addr << 2))
{
this.mmap_write32(addr << 2, value);
}
else
{
this.mem32s[addr] = value;
}
var write_func32 = this.memory_map_write32[aligned_addr];
write_func32(addr, value0);
write_func32(addr + 4, value1);
write_func32(addr + 8, value2);
write_func32(addr + 12, value3);
};
/**
@ -280,19 +70,24 @@ CPU.prototype.write_aligned32 = function(addr, value)
*/
CPU.prototype.write_blob = function(blob, offset)
{
this.debug_write(offset, blob.length, 0)
dbg_assert(blob && blob.length >= 0);
this.mem8.set(blob, offset);
if(blob.length)
{
dbg_assert(!this.in_mapped_range(offset));
dbg_assert(!this.in_mapped_range(offset + blob.length - 1));
this.jit_dirty_cache(offset, offset + blob.length);
this.mem8.set(blob, offset);
}
};
/**
* @param {Array.<number>|Int32Array} blob
* @param {number} offset
*/
CPU.prototype.write_blob32 = function(blob, offset)
CPU.prototype.read_blob = function(offset, length)
{
dbg_assert(blob && blob.length);
this.debug_write(offset, blob.length << 2, 0);
this.mem32s.set(blob, offset);
if(length)
{
dbg_assert(!this.in_mapped_range(offset));
dbg_assert(!this.in_mapped_range(offset + length - 1));
}
return this.mem8.subarray(offset, offset + length);
};

View file

@ -1,558 +0,0 @@
/*
* Some miscellaneous instructions:
*
* jmpcc16, jmpcc32, jmp16
* loop, loope, loopne, jcxz
* test_cc
*
* mov, push, pop
* pusha, popa
* xchg, lss
* lea
* enter
* bswap
* fxsave, fxrstor
*/
"use strict";
CPU.prototype.jmpcc8 = function(condition)
{
var imm8 = this.read_op8s();
if(condition)
{
this.instruction_pointer = this.instruction_pointer + imm8 | 0;
this.branch_taken();
}
else
{
this.branch_not_taken();
}
};
CPU.prototype.jmp_rel16 = function(rel16)
{
var current_cs = this.get_seg(reg_cs);
// limit ip to 16 bit
// ugly
this.instruction_pointer -= current_cs;
this.instruction_pointer = (this.instruction_pointer + rel16) & 0xFFFF;
this.instruction_pointer = this.instruction_pointer + current_cs | 0;
};
CPU.prototype.jmpcc16 = function(condition)
{
var imm16 = this.read_op16();
if(condition)
{
this.jmp_rel16(imm16);
this.branch_taken();
}
else
{
this.branch_not_taken();
}
}
CPU.prototype.jmpcc32 = function(condition)
{
var imm32s = this.read_op32s();
if(condition)
{
// don't change to `this.instruction_pointer += this.read_op32s()`,
// since read_op32s modifies instruction_pointer
this.instruction_pointer = this.instruction_pointer + imm32s | 0;
this.branch_taken();
}
else
{
this.branch_not_taken();
}
};
CPU.prototype.cmovcc16 = function(condition)
{
var data = this.read_e16();
if(condition)
{
this.write_g16(data);
}
};
CPU.prototype.cmovcc32 = function(condition)
{
var data = this.read_e32s();
if(condition)
{
this.write_g32(data);
}
};
CPU.prototype.setcc = function(condition)
{
this.set_e8(condition ? 1 : 0)
};
CPU.prototype.loopne = function(imm8s)
{
if(this.decr_ecx_asize() && !this.getzf())
{
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
this.branch_taken();
}
else
{
this.branch_not_taken();
}
}
CPU.prototype.loope = function(imm8s)
{
if(this.decr_ecx_asize() && this.getzf())
{
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
this.branch_taken();
}
else
{
this.branch_not_taken();
}
}
CPU.prototype.loop = function(imm8s)
{
if(this.decr_ecx_asize())
{
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
this.branch_taken();
}
else
{
this.branch_not_taken();
}
}
CPU.prototype.jcxz = function(imm8s)
{
if(this.get_reg_asize(reg_ecx) === 0)
{
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
this.branch_taken();
}
else
{
this.branch_not_taken();
}
};
/**
* @return {number}
* @const
*/
CPU.prototype.getcf = function()
{
if(this.flags_changed & 1)
{
return (this.last_op1 ^ (this.last_op1 ^ this.last_op2) & (this.last_op2 ^ this.last_add_result)) >>> this.last_op_size & 1;
}
else
{
return this.flags & 1;
}
};
/** @return {number} */
CPU.prototype.getpf = function()
{
if(this.flags_changed & flag_parity)
{
// inverted lookup table
return 0x9669 << 2 >> ((this.last_result ^ this.last_result >> 4) & 0xF) & flag_parity;
}
else
{
return this.flags & flag_parity;
}
};
/** @return {number} */
CPU.prototype.getaf = function()
{
if(this.flags_changed & flag_adjust)
{
return (this.last_op1 ^ this.last_op2 ^ this.last_add_result) & flag_adjust;
}
else
{
return this.flags & flag_adjust;
}
};
/** @return {number} */
CPU.prototype.getzf = function()
{
if(this.flags_changed & flag_zero)
{
return (~this.last_result & this.last_result - 1) >>> this.last_op_size & 1;
}
else
{
return this.flags & flag_zero;
}
};
/** @return {number} */
CPU.prototype.getsf = function()
{
if(this.flags_changed & flag_sign)
{
return this.last_result >>> this.last_op_size & 1;
}
else
{
return this.flags & flag_sign;
}
};
/** @return {number} */
CPU.prototype.getof = function()
{
if(this.flags_changed & flag_overflow)
{
return ((this.last_op1 ^ this.last_add_result) & (this.last_op2 ^ this.last_add_result)) >>> this.last_op_size & 1;
}
else
{
return this.flags & flag_overflow;
}
};
CPU.prototype.test_o = CPU.prototype.getof;
CPU.prototype.test_b = CPU.prototype.getcf;
CPU.prototype.test_z = CPU.prototype.getzf;
CPU.prototype.test_s = CPU.prototype.getsf;
CPU.prototype.test_p = CPU.prototype.getpf;
CPU.prototype.test_be = function()
{
// Idea:
// return this.last_op1 <= this.last_op2;
return this.getcf() || this.getzf();
}
CPU.prototype.test_l = function()
{
// Idea:
// return this.last_add_result < this.last_op2;
return !this.getsf() !== !this.getof();
}
CPU.prototype.test_le = function()
{
// Idea:
// return this.last_add_result <= this.last_op2;
return this.getzf() || !this.getsf() !== !this.getof();
}
CPU.prototype.push16 = function(imm16)
{
var sp = this.get_stack_pointer(-2);
this.safe_write16(sp, imm16);
this.adjust_stack_reg(-2);
}
CPU.prototype.push32 = function(imm32)
{
var sp = this.get_stack_pointer(-4);
this.safe_write32(sp, imm32);
this.adjust_stack_reg(-4);
}
CPU.prototype.pop16 = function()
{
var sp = this.get_seg(reg_ss) + this.get_stack_reg() | 0,
result = this.safe_read16(sp);
this.adjust_stack_reg(2);
return result;
}
CPU.prototype.pop32s = function()
{
var sp = this.get_seg(reg_ss) + this.get_stack_reg() | 0,
result = this.safe_read32s(sp);
this.adjust_stack_reg(4);
return result;
}
CPU.prototype.pusha16 = function()
{
var temp = this.reg16[reg_sp];
// make sure we don't get a pagefault after having
// pushed several registers already
this.writable_or_pagefault(this.get_stack_pointer(-16), 16);
this.push16(this.reg16[reg_ax]);
this.push16(this.reg16[reg_cx]);
this.push16(this.reg16[reg_dx]);
this.push16(this.reg16[reg_bx]);
this.push16(temp);
this.push16(this.reg16[reg_bp]);
this.push16(this.reg16[reg_si]);
this.push16(this.reg16[reg_di]);
}
CPU.prototype.pusha32 = function()
{
var temp = this.reg32s[reg_esp];
this.writable_or_pagefault(this.get_stack_pointer(-32), 32);
this.push32(this.reg32s[reg_eax]);
this.push32(this.reg32s[reg_ecx]);
this.push32(this.reg32s[reg_edx]);
this.push32(this.reg32s[reg_ebx]);
this.push32(temp);
this.push32(this.reg32s[reg_ebp]);
this.push32(this.reg32s[reg_esi]);
this.push32(this.reg32s[reg_edi]);
}
CPU.prototype.popa16 = function()
{
this.translate_address_read(this.get_stack_pointer(0));
this.translate_address_read(this.get_stack_pointer(15));
this.reg16[reg_di] = this.pop16();
this.reg16[reg_si] = this.pop16();
this.reg16[reg_bp] = this.pop16();
this.adjust_stack_reg(2);
this.reg16[reg_bx] = this.pop16();
this.reg16[reg_dx] = this.pop16();
this.reg16[reg_cx] = this.pop16();
this.reg16[reg_ax] = this.pop16();
}
CPU.prototype.popa32 = function()
{
this.translate_address_read(this.get_stack_pointer(0));
this.translate_address_read(this.get_stack_pointer(31));
this.reg32s[reg_edi] = this.pop32s();
this.reg32s[reg_esi] = this.pop32s();
this.reg32s[reg_ebp] = this.pop32s();
this.adjust_stack_reg(4);
this.reg32s[reg_ebx] = this.pop32s();
this.reg32s[reg_edx] = this.pop32s();
this.reg32s[reg_ecx] = this.pop32s();
this.reg32s[reg_eax] = this.pop32s();
}
CPU.prototype.xchg8 = function(memory_data, modrm_byte)
{
var mod = modrm_byte >> 1 & 0xC | modrm_byte >> 5 & 1,
tmp = this.reg8[mod];
this.reg8[mod] = memory_data;
return tmp;
}
CPU.prototype.xchg16 = function(memory_data, modrm_byte)
{
var mod = modrm_byte >> 2 & 14,
tmp = this.reg16[mod];
this.reg16[mod] = memory_data;
return tmp;
}
CPU.prototype.xchg16r = function(operand)
{
var temp = this.reg16[reg_ax];
this.reg16[reg_ax] = this.reg16[operand];
this.reg16[operand] = temp;
}
CPU.prototype.xchg32 = function(memory_data, modrm_byte)
{
var mod = modrm_byte >> 3 & 7,
tmp = this.reg32s[mod];
this.reg32s[mod] = memory_data;
return tmp;
}
CPU.prototype.xchg32r = function(operand)
{
var temp = this.reg32s[reg_eax];
this.reg32s[reg_eax] = this.reg32s[operand];
this.reg32s[operand] = temp;
}
CPU.prototype.lss16 = function(seg)
{
if(this.modrm_byte >= 0xC0)
{
// 0xc4c4 #ud (EMULATOR_BOP) is used by reactos and windows to exit vm86 mode
this.trigger_ud();
}
var addr = this.modrm_resolve(this.modrm_byte);
var new_reg = this.safe_read16(addr),
new_seg = this.safe_read16(addr + 2 | 0);
this.switch_seg(seg, new_seg);
this.reg16[this.modrm_byte >> 2 & 14] = new_reg;
}
CPU.prototype.lss32 = function(seg)
{
if(this.modrm_byte >= 0xC0)
{
this.trigger_ud();
}
var addr = this.modrm_resolve(this.modrm_byte);
var new_reg = this.safe_read32s(addr),
new_seg = this.safe_read16(addr + 4 | 0);
this.switch_seg(seg, new_seg);
this.reg32s[this.modrm_byte >> 3 & 7] = new_reg;
}
CPU.prototype.enter16 = function(size, nesting_level)
{
nesting_level &= 31;
if(nesting_level) dbg_log("enter16 stack=" + (this.stack_size_32 ? 32 : 16) + " size=" + size + " nest=" + nesting_level, LOG_CPU);
this.push16(this.reg16[reg_bp]);
var frame_temp = this.reg16[reg_sp];
if(nesting_level > 0)
{
var tmp_ebp = this.reg16[reg_ebp];
for(var i = 1; i < nesting_level; i++)
{
tmp_ebp -= 2;
this.push16(this.safe_read16(this.get_seg(reg_ss) + tmp_ebp | 0));
}
this.push16(frame_temp);
}
this.reg16[reg_bp] = frame_temp;
this.adjust_stack_reg(-size);
};
CPU.prototype.enter32 = function(size, nesting_level)
{
nesting_level &= 31;
if(nesting_level) dbg_log("enter32 stack=" + (this.stack_size_32 ? 32 : 16) + " size=" + size + " nest=" + nesting_level, LOG_CPU);
this.push32(this.reg32s[reg_ebp]);
var frame_temp = this.reg32s[reg_esp];
if(nesting_level > 0)
{
var tmp_ebp = this.reg32s[reg_ebp];
for(var i = 1; i < nesting_level; i++)
{
tmp_ebp -= 4;
this.push32(this.safe_read32s(this.get_seg(reg_ss) + tmp_ebp | 0));
}
this.push32(frame_temp);
}
this.reg32s[reg_ebp] = frame_temp;
this.adjust_stack_reg(-size);
};
CPU.prototype.bswap = function(reg)
{
var temp = this.reg32s[reg];
this.reg32s[reg] = temp >>> 24 | temp << 24 | (temp >> 8 & 0xFF00) | (temp << 8 & 0xFF0000);
}
CPU.prototype.fxsave = function(addr)
{
this.writable_or_pagefault(addr, 512);
this.safe_write16(addr + 0 | 0, this.fpu.control_word);
this.safe_write16(addr + 2 | 0, this.fpu.load_status_word());
this.safe_write8( addr + 4 | 0, ~this.fpu.stack_empty & 0xFF);
this.safe_write16(addr + 6 | 0, this.fpu.fpu_opcode);
this.safe_write32(addr + 8 | 0, this.fpu.fpu_ip);
this.safe_write16(addr + 12 | 0, this.fpu.fpu_ip_selector);
this.safe_write32(addr + 16 | 0, this.fpu.fpu_dp);
this.safe_write16(addr + 20 | 0, this.fpu.fpu_dp_selector);
this.safe_write32(addr + 24 | 0, this.mxcsr);
this.safe_write32(addr + 28 | 0, MXCSR_MASK);
for(let i = 0; i < 8; i++)
{
this.fpu.store_m80(addr + 32 + (i << 4) | 0, this.fpu.st[this.fpu.stack_ptr + i & 7]);
}
// If the OSFXSR bit in control register CR4 is not set, the FXSAVE
// instruction may not save these registers. This behavior is
// implementation dependent.
for(let i = 0; i < 8; i++)
{
this.safe_write32(addr + 160 + (i << 4) + 0 | 0, this.reg_xmm32s[i << 2 | 0]);
this.safe_write32(addr + 160 + (i << 4) + 4 | 0, this.reg_xmm32s[i << 2 | 1]);
this.safe_write32(addr + 160 + (i << 4) + 8 | 0, this.reg_xmm32s[i << 2 | 2]);
this.safe_write32(addr + 160 + (i << 4) + 12 | 0, this.reg_xmm32s[i << 2 | 3]);
}
};
CPU.prototype.fxrstor = function(addr)
{
this.translate_address_read(addr | 0);
this.translate_address_read(addr + 511 | 0);
var new_mxcsr = this.safe_read32s(addr + 24 | 0);
if(new_mxcsr & ~MXCSR_MASK)
{
dbg_log("Invalid mxcsr bits: " + h((new_mxcsr & ~MXCSR_MASK) >>> 0, 8));
this.trigger_gp(0);
}
this.fpu.control_word = this.safe_read16(addr + 0 | 0);
this.fpu.set_status_word(this.safe_read16(addr + 2 | 0));
this.fpu.stack_empty = ~this.safe_read8(addr + 4 | 0) & 0xFF;
this.fpu.fpu_opcode = this.safe_read16(addr + 6 | 0);
this.fpu.fpu_ip = this.safe_read32s(addr + 8 | 0);
this.fpu.fpu_ip = this.safe_read16(addr + 12 | 0);
this.fpu.fpu_dp = this.safe_read32s(addr + 16 | 0);
this.fpu.fpu_dp_selector = this.safe_read16(addr + 20 | 0);
this.mxcsr = new_mxcsr;
for(let i = 0; i < 8; i++)
{
this.fpu.st[this.fpu.stack_ptr + i & 7] = this.fpu.load_m80(addr + 32 + (i << 4) | 0);
}
for(let i = 0; i < 8; i++)
{
this.reg_xmm32s[i << 2 | 0] = this.safe_read32s(addr + 160 + (i << 4) + 0 | 0);
this.reg_xmm32s[i << 2 | 1] = this.safe_read32s(addr + 160 + (i << 4) + 4 | 0);
this.reg_xmm32s[i << 2 | 2] = this.safe_read32s(addr + 160 + (i << 4) + 8 | 0);
this.reg_xmm32s[i << 2 | 3] = this.safe_read32s(addr + 160 + (i << 4) + 12 | 0);
}
};

File diff suppressed because it is too large Load diff

View file

@ -1,51 +1,54 @@
"use strict";
// http://www.ethernut.de/pdf/8019asds.pdf
/** @const */ var E8390_CMD = 0x00 /* The command register (for all pages) */
const NE2K_LOG_VERBOSE = false;
/** @const */ var E8390_CMD = 0x00; /* The command register (for all pages) */
/* Page 0 register offsets. */
/** @const */ var EN0_CLDALO = 0x01 /* Low byte of current local dma addr RD */
/** @const */ var EN0_STARTPG = 0x01 /* Starting page of ring bfr WR */
/** @const */ var EN0_CLDAHI = 0x02 /* High byte of current local dma addr RD */
/** @const */ var EN0_STOPPG = 0x02 /* Ending page +1 of ring bfr WR */
/** @const */ var EN0_BOUNDARY = 0x03 /* Boundary page of ring bfr RD WR */
/** @const */ var EN0_TSR = 0x04 /* Transmit status reg RD */
/** @const */ var EN0_TPSR = 0x04 /* Transmit starting page WR */
/** @const */ var EN0_NCR = 0x05 /* Number of collision reg RD */
/** @const */ var EN0_TCNTLO = 0x05 /* Low byte of tx byte count WR */
/** @const */ var EN0_FIFO = 0x06 /* FIFO RD */
/** @const */ var EN0_TCNTHI = 0x06 /* High byte of tx byte count WR */
/** @const */ var EN0_ISR = 0x07 /* Interrupt status reg RD WR */
/** @const */ var EN0_CRDALO = 0x08 /* low byte of current remote dma address RD */
/** @const */ var EN0_RSARLO = 0x08 /* Remote start address reg 0 */
/** @const */ var EN0_CRDAHI = 0x09 /* high byte, current remote dma address RD */
/** @const */ var EN0_RSARHI = 0x09 /* Remote start address reg 1 */
/** @const */ var EN0_RCNTLO = 0x0a /* Remote byte count reg WR */
/** @const */ var EN0_RCNTHI = 0x0b /* Remote byte count reg WR */
/** @const */ var EN0_RSR = 0x0c /* rx status reg RD */
/** @const */ var EN0_RXCR = 0x0c /* RX configuration reg WR */
/** @const */ var EN0_TXCR = 0x0d /* TX configuration reg WR */
/** @const */ var EN0_COUNTER0 = 0x0d /* Rcv alignment error counter RD */
/** @const */ var EN0_DCFG = 0x0e /* Data configuration reg WR */
/** @const */ var EN0_COUNTER1 = 0x0e /* Rcv CRC error counter RD */
/** @const */ var EN0_IMR = 0x0f /* Interrupt mask reg WR */
/** @const */ var EN0_COUNTER2 = 0x0f /* Rcv missed frame error counter RD */
/** @const */ var EN0_CLDALO = 0x01; /* Low byte of current local dma addr RD */
/** @const */ var EN0_STARTPG = 0x01; /* Starting page of ring bfr WR */
/** @const */ var EN0_CLDAHI = 0x02; /* High byte of current local dma addr RD */
/** @const */ var EN0_STOPPG = 0x02; /* Ending page +1 of ring bfr WR */
/** @const */ var EN0_BOUNDARY = 0x03; /* Boundary page of ring bfr RD WR */
/** @const */ var EN0_TSR = 0x04; /* Transmit status reg RD */
/** @const */ var EN0_TPSR = 0x04; /* Transmit starting page WR */
/** @const */ var EN0_NCR = 0x05; /* Number of collision reg RD */
/** @const */ var EN0_TCNTLO = 0x05; /* Low byte of tx byte count WR */
/** @const */ var EN0_FIFO = 0x06; /* FIFO RD */
/** @const */ var EN0_TCNTHI = 0x06; /* High byte of tx byte count WR */
/** @const */ var EN0_ISR = 0x07; /* Interrupt status reg RD WR */
/** @const */ var EN0_CRDALO = 0x08; /* low byte of current remote dma address RD */
/** @const */ var EN0_RSARLO = 0x08; /* Remote start address reg 0 */
/** @const */ var EN0_CRDAHI = 0x09; /* high byte, current remote dma address RD */
/** @const */ var EN0_RSARHI = 0x09; /* Remote start address reg 1 */
/** @const */ var EN0_RCNTLO = 0x0a; /* Remote byte count reg WR */
/** @const */ var EN0_RCNTHI = 0x0b; /* Remote byte count reg WR */
/** @const */ var EN0_RSR = 0x0c; /* rx status reg RD */
/** @const */ var EN0_RXCR = 0x0c; /* RX configuration reg WR */
/** @const */ var EN0_TXCR = 0x0d; /* TX configuration reg WR */
/** @const */ var EN0_COUNTER0 = 0x0d; /* Rcv alignment error counter RD */
/** @const */ var EN0_DCFG = 0x0e; /* Data configuration reg WR */
/** @const */ var EN0_COUNTER1 = 0x0e; /* Rcv CRC error counter RD */
/** @const */ var EN0_IMR = 0x0f; /* Interrupt mask reg WR */
/** @const */ var EN0_COUNTER2 = 0x0f; /* Rcv missed frame error counter RD */
/** @const */ var NE_DATAPORT = 0x10 /* NatSemi-defined port window offset. */
/** @const */ var NE_RESET = 0x1f /* Issue a read to reset, a write to clear. */
/** @const */ var NE_DATAPORT = 0x10; /* NatSemi-defined port window offset. */
/** @const */ var NE_RESET = 0x1f; /* Issue a read to reset, a write to clear. */
/* Bits in EN0_ISR - Interrupt status register */
/** @const */ var ENISR_RX = 0x01 /* Receiver, no error */
/** @const */ var ENISR_TX = 0x02 /* Transmitter, no error */
/** @const */ var ENISR_RX_ERR = 0x04 /* Receiver, with error */
/** @const */ var ENISR_TX_ERR = 0x08 /* Transmitter, with error */
/** @const */ var ENISR_OVER = 0x10 /* Receiver overwrote the ring */
/** @const */ var ENISR_COUNTERS = 0x20 /* Counters need emptying */
/** @const */ var ENISR_RDC = 0x40 /* remote dma complete */
/** @const */ var ENISR_RESET = 0x80 /* Reset completed */
/** @const */ var ENISR_ALL = 0x3f /* Interrupts we will enable */
/** @const */ var ENISR_RX = 0x01; /* Receiver, no error */
/** @const */ var ENISR_TX = 0x02; /* Transmitter, no error */
/** @const */ var ENISR_RX_ERR = 0x04; /* Receiver, with error */
/** @const */ var ENISR_TX_ERR = 0x08; /* Transmitter, with error */
/** @const */ var ENISR_OVER = 0x10; /* Receiver overwrote the ring */
/** @const */ var ENISR_COUNTERS = 0x20; /* Counters need emptying */
/** @const */ var ENISR_RDC = 0x40; /* remote dma complete */
/** @const */ var ENISR_RESET = 0x80; /* Reset completed */
/** @const */ var ENISR_ALL = 0x3f; /* Interrupts we will enable */
/** @const */ var ENRSR_RXOK = 0x01 /* Received a good packet */
/** @const */ var ENRSR_RXOK = 0x01; /* Received a good packet */
/** @const */ var START_PAGE = 0x40;
/** @const */ var START_RX_PAGE = 0x40 + 12;
@ -56,8 +59,9 @@
* @constructor
* @param {CPU} cpu
* @param {BusConnector} bus
* @param {Boolean} preserve_mac_from_state_image
*/
function Ne2k(cpu, bus)
function Ne2k(cpu, bus, preserve_mac_from_state_image)
{
/** @const @type {CPU} */
this.cpu = cpu;
@ -65,6 +69,8 @@ function Ne2k(cpu, bus)
/** @const @type {PCI} */
this.pci = cpu.devices.pci;
this.preserve_mac_from_state_image = preserve_mac_from_state_image;
/** @const @type {BusConnector} */
this.bus = bus;
this.bus.register("net0-receive", function(data)
@ -113,16 +119,16 @@ function Ne2k(cpu, bus)
this.tsr = 1;
// mac address
var mac = [
this.mac = new Uint8Array([
0x00, 0x22, 0x15,
Math.random() * 255 | 0,
Math.random() * 255 | 0,
Math.random() * 255 | 0,
];
]);
for(var i = 0; i < 6; i++)
{
this.memory[i << 1] = this.memory[i << 1 | 1] = mac[i];
this.memory[i << 1] = this.memory[i << 1 | 1] = this.mac[i];
}
// the PROM signature of 0x57, 0x57 is also doubled
@ -130,12 +136,12 @@ function Ne2k(cpu, bus)
this.memory[14 << 1] = this.memory[14 << 1 | 1] = 0x57;
this.memory[15 << 1] = this.memory[15 << 1 | 1] = 0x57;
dbg_log("Mac: " + h(mac[0], 2) + ":" +
h(mac[1], 2) + ":" +
h(mac[2], 2) + ":" +
h(mac[3], 2) + ":" +
h(mac[4], 2) + ":" +
h(mac[5], 2), LOG_NET);
dbg_log("Mac: " + h(this.mac[0], 2) + ":" +
h(this.mac[1], 2) + ":" +
h(this.mac[2], 2) + ":" +
h(this.mac[3], 2) + ":" +
h(this.mac[4], 2) + ":" +
h(this.mac[5], 2), LOG_NET);
this.rsar = 0;
@ -209,7 +215,8 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Read pg1/1f", LOG_NET);
dbg_log("Read pg" + pg + "/1f", LOG_NET);
dbg_assert(false);
}
return 0;
});
@ -224,7 +231,32 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/1f: " + h(data_byte), LOG_NET);
dbg_log("Write pg" + pg + "/1f: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
io.register_read(this.port | EN0_STARTPG, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
return this.pstart;
}
else if(pg === 1)
{
dbg_log("Read pg1/01 (mac[0])", LOG_NET);
return this.mac[0];
}
else if(pg === 2)
{
return this.pstart;
}
else
{
dbg_log("Read pg" + pg + "/01");
dbg_assert(false);
return 0;
}
});
@ -236,9 +268,44 @@ function Ne2k(cpu, bus)
dbg_log("start page: " + h(data_byte, 2), LOG_NET);
this.pstart = data_byte;
}
else if(pg === 1)
{
dbg_log("mac[0] = " + h(data_byte), LOG_NET);
this.mac[0] = data_byte;
}
else if(pg === 3)
{
dbg_log("Unimplemented: Write pg3/01 (9346CR): " + h(data_byte), LOG_NET);
}
else
{
dbg_log("pg1/1: " + h(data_byte, 2), LOG_NET);
dbg_log("Write pg" + pg + "/01: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
io.register_read(this.port | EN0_STOPPG, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
return this.pstop;
}
else if(pg === 1)
{
dbg_log("Read pg1/02 (mac[1])", LOG_NET);
return this.mac[1];
}
else if(pg === 2)
{
return this.pstop;
}
else
{
dbg_log("Read pg" + pg + "/02", LOG_NET);
dbg_assert(false);
return 0;
}
});
@ -248,11 +315,22 @@ function Ne2k(cpu, bus)
if(pg === 0)
{
dbg_log("stop page: " + h(data_byte, 2), LOG_NET);
if(data_byte > (this.memory.length >> 8))
{
data_byte = this.memory.length >> 8;
dbg_log("XXX: Adjusting stop page to " + h(data_byte), LOG_NET);
}
this.pstop = data_byte;
}
else if(pg === 1)
{
dbg_log("mac[1] = " + h(data_byte), LOG_NET);
this.mac[1] = data_byte;
}
else
{
dbg_log("pg1/2: " + h(data_byte, 2), LOG_NET);
dbg_log("Write pg" + pg + "/02: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
@ -264,11 +342,15 @@ function Ne2k(cpu, bus)
dbg_log("Read isr: " + h(this.isr, 2), LOG_NET);
return this.isr;
}
else
else if(pg === 1)
{
dbg_log("Read curpg: " + h(this.curpg, 2), LOG_NET);
return this.curpg;
}
else
{
dbg_assert(false);
}
});
io.register_write(this.port | EN0_ISR, this, function(data_byte)
@ -278,13 +360,17 @@ function Ne2k(cpu, bus)
{
// acknowledge interrupts where bit is set
dbg_log("Write isr: " + h(data_byte, 2), LOG_NET);
this.isr &= ~data_byte
this.isr &= ~data_byte;
this.update_irq();
}
else if(pg === 1)
{
dbg_log("Write curpg: " + h(data_byte, 2), LOG_NET);
this.curpg = data_byte;
}
else
{
dbg_log("Write curpg: " + h(data_byte, 2), LOG_NET);
this.curpg = data_byte
dbg_assert(false);
}
});
@ -298,7 +384,7 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/0x0d " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/0d " + h(data_byte, 2), LOG_NET);
}
});
@ -312,7 +398,22 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/0x0e " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/0e " + h(data_byte, 2), LOG_NET);
}
});
io.register_read(this.port | EN0_RCNTLO, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
dbg_log("Read pg0/0a", LOG_NET);
return 0x50;
}
else
{
dbg_assert(false, "TODO");
return 0;
}
});
@ -326,7 +427,22 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/0x0a " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/0a " + h(data_byte, 2), LOG_NET);
}
});
io.register_read(this.port | EN0_RCNTHI, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
dbg_log("Read pg0/0b", LOG_NET);
return 0x43;
}
else
{
dbg_assert(false, "TODO");
return 0;
}
});
@ -340,7 +456,22 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/0x0b " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/0b " + h(data_byte, 2), LOG_NET);
}
});
io.register_read(this.port | EN0_RSARLO, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
dbg_log("Read remote start address low", LOG_NET);
return this.rsar & 0xFF;
}
else
{
dbg_log("Unimplemented: Read pg" + pg + "/08", LOG_NET);
dbg_assert(false);
}
});
@ -354,7 +485,22 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/0x08 " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/08 " + h(data_byte, 2), LOG_NET);
}
});
io.register_read(this.port | EN0_RSARHI, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
dbg_log("Read remote start address high", LOG_NET);
return this.rsar >> 8 & 0xFF;
}
else
{
dbg_log("Unimplemented: Read pg" + pg + "/09", LOG_NET);
dbg_assert(false);
}
});
@ -363,12 +509,12 @@ function Ne2k(cpu, bus)
var pg = this.get_page();
if(pg === 0)
{
dbg_log("Write start address count high: " + h(data_byte, 2), LOG_NET);
dbg_log("Write remote start address low: " + h(data_byte, 2), LOG_NET);
this.rsar = this.rsar & 0xFF | data_byte << 8 & 0xFF00;
}
else
{
dbg_log("Write pg1/0x09 " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/09 " + h(data_byte, 2), LOG_NET);
}
});
@ -383,7 +529,7 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Write pg1/0x0f " + h(data_byte, 2), LOG_NET);
dbg_log("Unimplemented: Write pg" + pg + "/0f " + h(data_byte, 2), LOG_NET);
}
});
@ -395,9 +541,20 @@ function Ne2k(cpu, bus)
dbg_log("Read boundary: " + h(this.boundary, 2), LOG_NET);
return this.boundary;
}
else if(pg === 1)
{
dbg_log("Read pg1/03 (mac[2])", LOG_NET);
return this.mac[2];
}
else if(pg === 3)
{
dbg_log("Unimplemented: Read pg3/03 (CONFIG0)", LOG_NET);
return 0;
}
else
{
dbg_log("Read pg1/0x03", LOG_NET);
dbg_log("Read pg" + pg + "/03", LOG_NET);
dbg_assert(false);
return 0;
}
});
@ -410,9 +567,15 @@ function Ne2k(cpu, bus)
dbg_log("Write boundary: " + h(data_byte, 2), LOG_NET);
this.boundary = data_byte;
}
else if(pg === 1)
{
dbg_log("mac[2] = " + h(data_byte), LOG_NET);
this.mac[2] = data_byte;
}
else
{
dbg_log("Write pg1/0x03 " + h(data_byte, 2), LOG_NET);
dbg_log("Write pg" + pg + "/03: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
@ -423,9 +586,15 @@ function Ne2k(cpu, bus)
{
return this.tsr;
}
else if(pg === 1)
{
dbg_log("Read pg1/04 (mac[3])", LOG_NET);
return this.mac[3];
}
else
{
dbg_log("Read pg1/0x04", LOG_NET);
dbg_log("Read pg" + pg + "/04", LOG_NET);
dbg_assert(false);
return 0;
}
});
@ -438,9 +607,41 @@ function Ne2k(cpu, bus)
dbg_log("Write tpsr: " + h(data_byte, 2), LOG_NET);
this.tpsr = data_byte;
}
else if(pg === 1)
{
dbg_log("mac[3] = " + h(data_byte), LOG_NET);
this.mac[3] = data_byte;
}
else
{
dbg_log("Write pg1/0x04 " + h(data_byte, 2), LOG_NET);
dbg_log("Write pg" + pg + "/04: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
io.register_read(this.port | EN0_TCNTLO, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
dbg_log("Unimplemented: Read pg0/05 (NCR: Number of Collisions Register)", LOG_NET);
return 0;
}
else if(pg === 1)
{
dbg_log("Read pg1/05 (mac[4])", LOG_NET);
return this.mac[4];
}
else if(pg === 3)
{
dbg_log("Unimplemented: Read pg3/05 (CONFIG2)", LOG_NET);
return 0;
}
else
{
dbg_log("Read pg" + pg + "/05", LOG_NET);
dbg_assert(false);
return 0;
}
});
@ -452,9 +653,45 @@ function Ne2k(cpu, bus)
dbg_log("Write tcnt low: " + h(data_byte, 2), LOG_NET);
this.tcnt = this.tcnt & ~0xFF | data_byte;
}
else if(pg === 1)
{
dbg_log("mac[4] = " + h(data_byte), LOG_NET);
this.mac[4] = data_byte;
}
else if(pg === 3)
{
dbg_log("Unimplemented: Write pg3/05 (CONFIG2): " + h(data_byte), LOG_NET);
}
else
{
dbg_log("Write pg1/0x05 " + h(data_byte, 2), LOG_NET);
dbg_log("Write pg" + pg + "/05: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
io.register_read(this.port | EN0_TCNTHI, this, function()
{
var pg = this.get_page();
if(pg === 0)
{
dbg_assert(false, "TODO");
return 0;
}
else if(pg === 1)
{
dbg_log("Read pg1/06 (mac[5])", LOG_NET);
return this.mac[5];
}
else if(pg === 3)
{
dbg_log("Unimplemented: Read pg3/06 (CONFIG3)", LOG_NET);
return 0;
}
else
{
dbg_log("Read pg" + pg + "/06", LOG_NET);
dbg_assert(false);
return 0;
}
});
@ -466,9 +703,19 @@ function Ne2k(cpu, bus)
dbg_log("Write tcnt high: " + h(data_byte, 2), LOG_NET);
this.tcnt = this.tcnt & 0xFF | data_byte << 8;
}
else if(pg === 1)
{
dbg_log("mac[5] = " + h(data_byte), LOG_NET);
this.mac[5] = data_byte;
}
else if(pg === 3)
{
dbg_log("Unimplemented: Write pg3/06 (CONFIG3): " + h(data_byte), LOG_NET);
}
else
{
dbg_log("Write pg1/0x06 " + h(data_byte, 2), LOG_NET);
dbg_log("Write pg" + pg + "/06: " + h(data_byte), LOG_NET);
dbg_assert(false);
}
});
@ -481,15 +728,24 @@ function Ne2k(cpu, bus)
}
else
{
dbg_log("Read pg1/0x0c", LOG_NET);
dbg_log("Unimplemented: Read pg" + pg + "/0c", LOG_NET);
dbg_assert(false);
return 0;
}
});
io.register_write(this.port | EN0_RXCR, this, function(data_byte)
{
dbg_log("RX configuration reg write: " + h(data_byte, 2), LOG_NET);
this.rxcr = data_byte;
var pg = this.get_page();
if(pg === 0)
{
dbg_log("RX configuration reg write: " + h(data_byte, 2), LOG_NET);
this.rxcr = data_byte;
}
else
{
dbg_log("Unimplemented: Write pg" + pg + "/0c: " + h(data_byte), LOG_NET);
}
});
io.register_read(this.port | NE_DATAPORT | 0, this,
@ -522,6 +778,12 @@ Ne2k.prototype.get_state = function()
state[8] = this.pstart;
state[9] = this.curpg;
state[10] = this.boundary;
state[11] = this.pstop;
state[12] = this.rxcr;
state[13] = this.txcr;
state[14] = this.tsr;
state[15] = this.mac;
state[16] = this.memory;
return state;
};
@ -539,6 +801,16 @@ Ne2k.prototype.set_state = function(state)
this.pstart = state[8];
this.curpg = state[9];
this.boundary = state[10];
this.pstop = state[11];
this.rxcr = state[12];
this.txcr = state[13];
this.tsr = state[14];
if(this.preserve_mac_from_state_image)
{
this.mac = state[15];
this.memory = state[16];
}
};
Ne2k.prototype.do_interrupt = function(ir_mask)
@ -562,18 +834,20 @@ Ne2k.prototype.update_irq = function()
Ne2k.prototype.data_port_write = function(data_byte)
{
dbg_log("Write data port: data=" + h(data_byte & 0xFF, 2) +
" rsar=" + h(this.rsar, 4) +
" rcnt=" + h(this.rcnt, 4), LOG_NET);
if(this.rsar > 0x10 && this.rsar < (START_PAGE << 8))
if(NE2K_LOG_VERBOSE)
{
// unmapped
return;
dbg_log("Write data port: data=" + h(data_byte & 0xFF, 2) +
" rsar=" + h(this.rsar, 4) +
" rcnt=" + h(this.rcnt, 4), LOG_NET);
}
if(this.rsar <= 0x10 || this.rsar >= (START_PAGE << 8) && this.rsar < (STOP_PAGE << 8))
{
this.memory[this.rsar] = data_byte;
}
this.rsar++;
this.rcnt--;
this.memory[this.rsar++] = data_byte;
if(this.rsar >= (this.pstop << 8))
{
@ -606,11 +880,21 @@ Ne2k.prototype.data_port_write32 = function(data)
Ne2k.prototype.data_port_read = function()
{
var data = this.memory[this.rsar++];
let data = 0;
dbg_log("Read data port: data=" + h(data, 2) +
" rsar=" + h(this.rsar - 1, 4) +
" rcnt=" + h(this.rcnt, 4), LOG_NET);
if(this.rsar < (STOP_PAGE << 8))
{
data = this.memory[this.rsar];
}
if(NE2K_LOG_VERBOSE)
{
dbg_log("Read data port: data=" + h(data, 2) +
" rsar=" + h(this.rsar, 4) +
" rcnt=" + h(this.rcnt, 4), LOG_NET);
}
this.rsar++;
this.rcnt--;
if(this.rsar >= (this.pstop << 8))
@ -677,9 +961,9 @@ Ne2k.prototype.receive = function(data)
// XXX
return;
}
else if(data[0] === this.memory[0] && data[1] === this.memory[2] &&
data[2] === this.memory[4] && data[3] === this.memory[6] &&
data[4] === this.memory[8] && data[5] === this.memory[10])
else if(data[0] === this.mac[0] && data[1] === this.mac[1] &&
data[2] === this.mac[2] && data[3] === this.mac[3] &&
data[4] === this.mac[4] && data[5] === this.mac[5])
{
}
else
@ -696,14 +980,33 @@ Ne2k.prototype.receive = function(data)
var end = offset + total_length;
if(end > this.memory.length)
const needed = 1 + (total_length >> 8);
// boundary == curpg interpreted as ringbuffer empty
const available = this.boundary > this.curpg ?
this.boundary - this.curpg :
this.pstop - this.curpg + this.boundary - this.pstart;
if(available < needed &&
this.boundary !== 0 // XXX: ReactOS sets this to 0 initially and never updates it unless it receives a packet
)
{
// shouldn't happen because at this size it can't cross a page
dbg_log("Buffer full, dropping packet pstart=" + h(this.pstart) + " pstop=" + h(this.pstop) +
" curpg=" + h(this.curpg) + " needed=" + h(needed) + " boundary=" + h(this.boundary) + " available=" + h(available), LOG_NET);
return;
}
if(end > (this.pstop << 8))
{
// Shouldn't happen because at this size it can't cross a page,
// so we can skip filling with zeroes
dbg_assert(data.length >= 60);
var cut = this.memory.length - data_start;
var cut = (this.pstop << 8) - data_start;
dbg_assert(cut >= 0);
this.memory.set(data.subarray(0, cut), data_start);
this.memory.set(data.subarray(cut), START_RX_PAGE);
this.memory.set(data.subarray(cut), this.pstart << 8);
dbg_log("rcv cut=" + h(cut), LOG_NET);
}
else
@ -712,10 +1015,7 @@ Ne2k.prototype.receive = function(data)
if(data.length < 60)
{
for(var i = data.length; i < 60; i++)
{
this.memory[data_start + i] = 0;
}
this.memory.fill(0, data_start + data.length, data_start + 60);
}
}
@ -739,5 +1039,5 @@ Ne2k.prototype.receive = function(data)
Ne2k.prototype.get_page = function()
{
return this.cr & 0xC0;
return this.cr >> 6 & 3;
};

View file

@ -117,6 +117,13 @@ function PCI(cpu)
},
function(out_byte)
{
if((this.pci_addr[1] & 0x06) === 0x02 && (out_byte & 0x06) === 0x06)
{
dbg_log("CPU reboot via PCI");
cpu.reboot_internal();
return;
}
this.pci_addr[1] = out_byte;
},
function(out_byte)
@ -145,14 +152,22 @@ function PCI(cpu)
// pci_bars: [],
//};
// This needs to be set in order for seabios to not execute code outside of
// mapped memory. While we map the BIOS into high memory, we don't allow
// executing code there, which enables optimisations in read_imm8.
// See [make_bios_writable_intel] in src/fw/shadow.c in seabios for details
const PAM0 = 0x10;
var host_bridge = {
pci_id: 0,
pci_space: [
// 00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
0x86, 0x80, 0x37, 0x12, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x86, 0x80, 0x37, 0x12, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, PAM0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
pci_bars: [],
name: "82441FX PMC",
@ -316,7 +331,7 @@ PCI.prototype.pci_write8 = function(address, written)
}
dbg_assert(!(addr >= 0x10 && addr < 0x2C || addr >= 0x30 && addr < 0x34),
"PCI: Expected 32-bit write");
"PCI: Expected 32-bit write, got 8-bit (addr: " + h(addr) + ")");
dbg_log("PCI write8 dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
" value=" + h(written, 2), LOG_PCI);
@ -339,8 +354,15 @@ PCI.prototype.pci_write16 = function(address, written)
return;
}
dbg_assert(!(addr >= 0x10 && addr < 0x2C || addr >= 0x30 && addr < 0x34),
"PCI: Expected 32-bit write");
if(addr >= 0x10 && addr < 0x2C)
{
// Bochs bios
dbg_log("Warning: PCI: Expected 32-bit write, got 16-bit (addr: " + h(addr) + ")");
return;
}
dbg_assert(!(addr >= 0x30 && addr < 0x34),
"PCI: Expected 32-bit write, got 16-bit (addr: " + h(addr) + ")");
dbg_log("PCI writ16 dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
" value=" + h(written, 4), LOG_PCI);
@ -446,6 +468,11 @@ PCI.prototype.pci_write32 = function(address, written)
space[addr >> 2] = 0;
}
}
else if(addr === 0x04)
{
dbg_log("PCI write dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
" value=" + h(written >>> 0, 8), LOG_PCI);
}
else
{
dbg_log("PCI write dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
@ -529,7 +556,7 @@ PCI.prototype.set_io_bars = function(bar, from, to)
old_entry.write16 === this.io.empty_port_write &&
old_entry.write32 === this.io.empty_port_write)
{
dbg_log("Move IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI);
dbg_log("Warning: Bad IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI);
}
var entry = bar.entries[i];
@ -538,13 +565,17 @@ PCI.prototype.set_io_bars = function(bar, from, to)
ports[to + i] = entry;
// these can fail if the os maps an io port in multiple bars (indicating a bug)
dbg_assert(empty_entry.read8 === this.io.empty_port_read8, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.read16 === this.io.empty_port_read16, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.read32 === this.io.empty_port_read32, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.write8 === this.io.empty_port_write, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.write16 === this.io.empty_port_write, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.write32 === this.io.empty_port_write, "Bad IO bar: Target already mapped");
if(empty_entry.read8 === this.io.empty_port_read8 ||
empty_entry.read16 === this.io.empty_port_read16 ||
empty_entry.read32 === this.io.empty_port_read32 ||
empty_entry.write8 === this.io.empty_port_write ||
empty_entry.write16 === this.io.empty_port_write ||
empty_entry.write32 === this.io.empty_port_write)
{
// These can fail if the os maps an io port in multiple bars (indicating a bug)
// XXX: Fails during restore_state
dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4), LOG_PCI);
}
}
};

View file

@ -129,7 +129,7 @@ function PIC(cpu, master)
{
PIC_LOG_VERBOSE && dbg_log("master> spurious requested=" + this.requested_irq, LOG_PIC);
this.requested_irq = -1;
this.cpu.pic_call_irq(this.irq_map | 7);
//this.cpu.pic_call_irq(this.irq_map | 7);
return;
}
dbg_assert(this.irr); // spurious
@ -292,18 +292,25 @@ function PIC(cpu, master)
return;
}
if(PIC_LOG_VERBOSE)
{
dbg_log("master> set irq " + irq_number, LOG_PIC);
}
var irq_mask = 1 << irq_number;
if((this.irq_value & irq_mask) === 0)
{
if(PIC_LOG_VERBOSE)
{
dbg_log("master> set irq " + irq_number, LOG_PIC);
}
this.irr |= irq_mask;
this.irq_value |= irq_mask;
this.check_irqs();
}
else
{
if(PIC_LOG_VERBOSE)
{
dbg_log("master> set irq " + irq_number + ": already set!", LOG_PIC);
}
}
};
this.clear_irq = function(irq_number)
@ -334,18 +341,26 @@ function PIC(cpu, master)
this.set_irq = function(irq_number)
{
dbg_assert(irq_number >= 0 && irq_number < 8);
if(PIC_LOG_VERBOSE)
{
dbg_log("slave > set irq " + irq_number, LOG_PIC);
}
var irq_mask = 1 << irq_number;
if((this.irq_value & irq_mask) === 0)
{
if(PIC_LOG_VERBOSE)
{
dbg_log("slave > set irq " + irq_number, LOG_PIC);
}
this.irr |= irq_mask;
this.irq_value |= irq_mask;
this.check_irqs();
}
else
{
if(PIC_LOG_VERBOSE)
{
dbg_log("slave > set irq " + irq_number + ": already set!", LOG_PIC);
}
}
};
this.clear_irq = function(irq_number)
@ -398,7 +413,7 @@ PIC.prototype.set_state = function(state)
this.isr = state[2];
this.irr = state[3];
this.is_master = state[4];
this.slave = state[5];
this.slave && this.slave.set_state(state[5]);
this.expect_icw4 = state[6];
this.state = state[7];
this.read_isr = state[8];

View file

@ -113,6 +113,11 @@ PIT.prototype.timer = function(now, no_irq)
dbg_log("pit interrupt. new value: " + this.counter_start_value[0], LOG_PIT);
// This isn't strictly correct, but it's necessary since browsers
// may sleep longer than necessary to trigger the else branch below
// and clear the irq
this.cpu.device_lower_irq(0);
this.cpu.device_raise_irq(0);
var mode = this.counter_mode[0];
@ -277,7 +282,7 @@ PIT.prototype.port43_write = function(reg_byte)
this.counter_latch[i] = 2;
var value = this.get_counter_value(i, v86.microtick());
dbg_log("latch: " + value, LOG_PIT);
this.counter_latch_value[i] = value ? value - 1 : 0
this.counter_latch_value[i] = value ? value - 1 : 0;
return;
}
@ -329,3 +334,10 @@ PIT.prototype.port43_write = function(reg_byte)
this.bus.send("pcspeaker-update", [this.counter_mode[2], this.counter_reload[2]]);
};
PIT.prototype.dump = function()
{
const reload = this.counter_reload[0];
const time = (reload || 0x10000) / OSCILLATOR_FREQ;
dbg_log("counter0 ticks every " + time + "ms (reload=" + reload + ")");
};

View file

@ -1,5 +1,8 @@
"use strict";
/** @const */
let PS2_LOG_VERBOSE = false;
/**
* @constructor
* @param {CPU} cpu
@ -322,7 +325,10 @@ PS2.prototype.send_mouse_packet = function(dx, dy)
this.mouse_buffer.push(delta_x);
this.mouse_buffer.push(delta_y);
dbg_log("adding mouse packets: " + [info_byte, dx, dy], LOG_PS2);
if(PS2_LOG_VERBOSE)
{
dbg_log("adding mouse packets: " + [info_byte, dx, dy], LOG_PS2);
}
this.raise_irq();
};

Some files were not shown because too many files have changed in this diff Show more